|
|
4 V' J" M& w9 w2 ?& g- C" @5 q
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。 [9 l$ C2 v' h
) S* ]& o0 C1 H
- 键值设计+ h/ D# w2 D3 T5 }0 f4 F
- 命令使用
# Z' \7 c, c- `6 a - 客户端使用
0 @7 q9 u0 ~7 o% n# m - 相关工具
* c/ ^: |! `4 P7 V" R3 R$ i# b 通过本文的介绍可以减少使用Redis过程带来的问题。3 U7 v+ m0 ?: I) f) d+ j% K
一、键值设计) A. R& w0 {( D0 }
+ V: _1 m- z7 R7 J o1、key名设计. ^1 ~ m. n( J' H! c% A6 R
5 H# r! O5 G7 j! l; U可读性和可管理性+ x6 Y% d5 f1 i) ?. u5 t
9 ^# X3 c3 r* E v
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id, F0 x+ b( h8 j+ y% Z0 Q
3 }' J5 _' `/ J5 x+ t* e0 L- ugc:video:1
2 s7 ]+ d; c4 i0 L9 ]. }3 E 简洁性
& U" W4 P$ B; k( u3 \, O
: a: D' Z& `+ b6 C7 }* d' e保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
( t6 w& _ P" ^# D1 }6 P0 o5 w, h1 h: h; Q- g
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。* W8 q1 I$ ^, i! H% B0 v
不要包含特殊字符
0 |( G& R r/ V# Q6 |' b. f. d. C3 P R
反例:包含空格、换行、单双引号以及其他转义字符
+ i% C- z" q' I* l2 {, H2、value设计6 i6 \6 p/ X. s( @
+ `7 S" }' @/ P2 z& v
拒绝bigkey
$ W, U5 ~+ v2 L* {: l1 B& e
0 f" ?. F. z l& @/ E& Y防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
, _1 r: i- o: W' x3 j2 E# o# |, s# \反例:一个包含200万个元素的list。" A8 O8 P9 E; u( J* G2 k5 ~2 `; Y
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
- T6 z7 K ^& f9 U4 k; G选择适合的数据类型
# k0 W% N/ S$ H! w# A, c. l' @5 ?' Q) h6 i6 K: A
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
& R, T- i6 |2 \7 ^+ r反例:
3 R& E; P, Z" R0 E- }$ M. l
1 I- F: L9 p9 m* m. d2 A3 \+ e- set user:1:name tom
- e8 y) M" Y$ u' i3 Y - set user:1:age 19( C) m% o0 h& \3 S% G. g! Q
- set user:1:favor football
) E" ?; J: Y. S3 l p1 f 正例:
( Z* g" m& f6 w6 E& k* R0 E% k, F2 g
- hmset user:1 name tom age 19 favor football
2 s; i8 r: R6 X7 _" W2 V3 X5 ? 控制key的生命周期
" V5 J& f$ { ^
* w: V1 W; a. o! W8 t3 iredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。5 s' _- ~2 E" M" a X
二、命令使用
$ Y* r' P0 q' n7 f) y
5 K% ~6 a( H& ]0 _6 l* Z1、O(N)命令关注N的数量
; D/ Z+ {8 F1 C# K/ m+ I7 d* O/ l$ i' s9 k* Y% ?
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
7 O+ V4 g1 W* `6 G5 P2、禁用命令3 k7 v: i7 R# O: N+ v
: I0 d- l' ?( a4 C4 S" u禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。5 h s% J$ ~; W' E
3、合理使用select6 E6 m: p1 c" X9 J0 \
, }( A% w$ E3 o1 J N0 R4 C t; l! _0 {2 |/ }/ Z* B
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
' j4 A# ]8 Z& h) Y* ^( R4、使用批量操作提高效率
. Z5 h* Q. e. T# T
2 ^6 S* s8 R( m; Z6 B' R' K9 K$ |2 H0 S/ F8 i
- 原生命令:例如mget、mset。/ [' ~9 z( `- t5 ]! E) C
- 非原生命令:可以使用pipeline提高效率。
6 R( X6 H( S+ G5 }9 Q, P" C) ? p 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。6 G7 a4 @8 i. B% F2 X
注意两者不同:; g& R0 S' s' | K
, V5 P8 |$ O# w/ u1 A
- 原生是原子操作,pipeline是非原子操作。5 V" f ~# V' C+ o5 r6 S7 k9 P
- pipeline可以打包不同的命令,原生做不到7 t& f, z! k1 o) ` P/ F
- pipeline需要客户端和服务端同时支持。8 i) j8 K$ I) t7 k
5、不建议过多使用Redis事务功能' Y, ~ n( h3 l1 t
" g7 y X) O0 P! p8 P" N
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!0 j" m$ j8 B7 q. W2 P* K4 P: J
6、Redis集群版本在使用Lua上有特殊要求 [! O* C* z. W0 r" z
, Z9 o8 M( h4 s( @& i# h
1、所有key都应该由 KEYS 数组来传递,redis.call/pcall 里面调用的redis命令,key的位置,必须是KEYS array, 否则直接返回error,"-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS arrayrn" 2、所有key,必须在1个slot上,否则直接返回error, "-ERR eval/evalsha command keys must in same slotrn"
% O5 V1 _2 O" B& f- c/ q7、monitor命令; u( x7 ]4 K7 h! i; y/ \
! o7 `7 s& q' f. |7 Y- Z+ f: z3 r
必要情况下使用monitor命令时,要注意不要长时间使用。
% I4 v# K$ y/ M! F; X5 K三、客户端使用6 }& D: c, a" w1 O, _9 ]0 @4 o
8 N1 s R2 {/ {1 z) B5 r4 T1、避免多个应用使用一个Redis实例% M$ c* E% }( X% P
5 |$ \' v! u8 T不相干的业务拆分,公共数据做服务化。3 h) C& X. T9 M" _5 h
2、使用连接池( i+ w+ A6 N# m! m3 W2 O W9 \
" G/ H( l+ T1 f3 }: u- T可以有效控制连接,同时提高效率,标准使用方式:
( B' H! Z) p5 e# Q5 j* [Jedis jedis = null;( G1 Z6 h, m: p- O* G
try {0 c* V3 S% J/ F9 G8 c
jedis = jedisPool.getResource;9 {; B2 }0 Z( p& O9 H* K
//具体的命令$ m; m7 P, b9 e
jedis.executeCommand
/ X" t& r* _9 F0 c' D6 L7 V0 y} catch (Exception e) {! M1 Y8 z ^+ Z& o, q
logger.error("op key {} error: " + e.getMessage, key, e);/ w$ h$ J$ S% x: o% H! Y3 a0 S
} finally {
: q# O* F" O* B2 } //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。, a" E2 k, S0 t g% q |
if (jedis != null) + d. w1 m; T# W" e i+ A2 U( H
jedis.close;
9 @% s, w) {( ]7 {" h}1 k; D; o" W+ C4 G; X! c t: d
3、熔断功能
7 @4 h o( y8 u. S! w" c' T+ m, [0 |7 z$ x1 g
高并发下建议客户端添加熔断功能(例如netflix hystrix)3 n1 Z, z+ F2 y0 B1 I
4、合理的加密4 e4 _- c: _' ^# O% z
8 m6 L4 K( i1 d1 k+ a" j% Z$ P+ ~6 }1 m" W设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
% B X; Z I! N9 v3 U5、淘汰策略
2 x5 k! g& }, V: ?# w, v6 t2 R' q0 R- O
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。0 Y, T) ~! u& W Z3 ^/ h& R
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
0 W1 ~# y' y. V$ ?& O8 b: P0 D其他策略如下:
: E5 E* g$ t4 l [1 B/ d$ S
# @; s0 r% L$ u: x- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。" Q8 d/ l- Q3 F( W. F* ?
- allkeys-random:随机删除所有键,直到腾出足够空间为止。3 z- w" \3 W0 r' p2 O4 S
- volatile-random:随机删除过期键,直到腾出足够空间为止。& A7 n) |$ @9 j) ~) Y
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
6 g i' Q' }& z/ y H - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
! r* f& A# K/ d+ l 四、相关工具
5 j' m7 F0 r, B! m6 g( n+ X8 I5 Q) h. V
1、数据同步3 P, b: d- v/ q" k- ^
# o) x0 R2 r; ?6 }0 |redis间数据同步可以使用:redis-port
; X& C. m2 ]9 i; E7 ]' ~) ]2、big key搜索
9 C1 i9 C2 e* }; A7 b1 G6 J3 |) p- p* p# }9 O4 k
redis大key搜索工具
! [9 X. A) U, g {+ h3、热点key寻找+ s' ]' D( J. u) I7 t) q
& J0 ^ k$ ?. B, I' \6 K N7 e内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
M: X( ]: E, z! S五、删除bigkey
; F$ E$ }# N4 q" y9 [; }
0 U* G6 ?! ^. V' E
( e4 ?* \6 I, \8 g- 下面操作可以使用pipeline加速。+ c: }2 R5 L/ w9 T# T) f9 w
- redis 4.0已经支持key的异步删除,欢迎使用。
7 b! Q8 s3 u# u B) f 1、Hash删除: hscan + hdel
6 K3 O0 e# A& P r0 b! i8 v& W6 O5 g1 q0 h
public void delBigHash(String host, int port, String password, String bigHashKey) {; D4 U9 S' P% ^# Z. W: r2 ~. a9 x- C6 ?
Jedis jedis = new Jedis(host, port);
8 K) Y8 ?! f7 |, x if (password != null && !"".equals(password)) {
9 k; g+ J6 t) d! T0 V j1 {( u jedis.auth(password);/ \: Y- k! R( V' d- h
}/ |1 K: t7 d. @4 ~# i
ScanParams scanParams = new ScanParams.count(100);
- M( @! \7 o9 h" N3 p* Z! z String cursor = "0";, V; A. r& ^0 E: @2 L* b
do {& M, ~ I) D5 x" K% n0 V4 d/ D
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
% v, N* h: y! a2 \& u List entryList = scanResult.getResult;5 E" c* z8 e1 {$ @
if (entryList != null && !entryList.isEmpty) {6 f1 O+ j- I$ ~3 [
for (Entry entry : entryList) { P1 [; Y: j/ l/ d2 O
jedis.hdel(bigHashKey, entry.getKey);
1 u9 t; V. |+ ?4 F }
& g4 b. \% f! a9 ^ }
( q- q" h. P. k3 Y cursor = scanResult.getStringCursor;
4 c! N5 H) V. v } while (!"0".equals(cursor));
0 a ?6 Q0 X8 g) L& L; m8 h2 F5 o0 d. C# ]5 U$ L
//删除bigkey
7 B# H9 l0 O: X1 r jedis.del(bigHashKey);: R) n+ @+ w/ a3 N! t* e
}
/ u8 S- a! |9 K& S P4 `. |, H8 H2、List删除: ltrim/ o; }, o/ B! y
0 j1 H( }0 c O @
public void delBigList(String host, int port, String password, String bigListKey) {4 w, p# `9 L0 ?. c* I
Jedis jedis = new Jedis(host, port);
. X/ J" x; [: h) O7 I) {0 m if (password != null && !"".equals(password)) {
" }! D2 y4 H" m) E$ J1 h( U9 E; B3 K# l jedis.auth(password);/ `) R& c6 O) }2 V& t+ {& u/ [9 I9 E
}
' c9 ^! {- Z6 v) R& j long llen = jedis.llen(bigListKey);# }0 ?9 \0 H1 t9 M$ X. I
int counter = 0;
! {# I) I+ }" |* |2 C int left = 100;
$ N0 e2 x5 W& v/ }+ ?' Q while (counter < llen) {
2 m9 w2 I$ o7 N: a" d" x5 S. s' F //每次从左侧截掉100个$ g8 l1 H- ?0 v3 [
jedis.ltrim(bigListKey, left, llen); S7 U* _' m4 ?: H4 r v
counter += left;) B0 T4 \0 H# Z0 q+ c& D' r
}
! a& P3 N0 ?7 S: y2 o8 R //最终删除key
9 o0 X" Z# z3 f D jedis.del(bigListKey);9 b' n- W; ^- P/ g0 X# d# _" q3 C
}3、Set删除: sscan + srem" ~: ]$ l) g" ^
, `9 b+ G/ }$ h5 k9 \3 v& jpublic void delBigSet(String host, int port, String password, String bigSetKey) {: ^) `$ |7 K. \
Jedis jedis = new Jedis(host, port);( i2 J. y7 O- D4 B1 D5 r
if (password != null && !"".equals(password)) {( g3 ~! x7 N4 h. ?. b/ ]3 Q
jedis.auth(password);
2 e& ?' m8 W* k/ M }$ x6 Y; c8 R6 e5 p. S8 k$ _
ScanParams scanParams = new ScanParams.count(100);- O; O# x4 X1 E9 ~" R
String cursor = "0";1 L0 x: x# ?- C, P+ ~( R
do {
* S/ K% j# `: H& X5 r ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
$ @9 N1 a3 c0 T& | List memberList = scanResult.getResult;- p7 `1 i7 Q7 Q2 s1 h' P# K
if (memberList != null && !memberList.isEmpty) {
9 Y$ B+ f# z; l: Z for (String member : memberList) {) {3 O) ]; q! A+ i0 }( p
jedis.srem(bigSetKey, member);) r( t4 C3 |- x2 I% [( k
}
. _; y% h/ z4 t" [ m1 J6 i7 m }
9 M z5 b% V- _; t4 @6 P5 {' _$ l cursor = scanResult.getStringCursor;
) G. X) m0 g u5 Q& p } while (!"0".equals(cursor));7 o/ F B' F. E) d# X
" ~) n2 q2 |; c& t. D$ a//删除bigkey: d" K. \3 o( O, Z. h
jedis.del(bigSetKey);( R) W0 y i q0 [* f3 g
}7 t% a' s Y r) u3 o
4、SortedSet删除: zscan + zrem
2 t- [" }" G& m- M1 N
6 m' L$ L% @: ?5 s5 ]. y; fpublic void delBigZset(String host, int port, String password, String bigZsetKey) { . N8 y0 d7 Q S' s
Jedis jedis = new Jedis(host, port);
$ V l' z1 {, B% V if (password != null && !"".equals(password)) {
! b* N8 v; O& B& h H jedis.auth(password); 0 m2 H; @0 C }6 a4 ^, ?
}
6 w3 |$ n% A7 s ScanParams scanParams = new ScanParams.count(100);
" ]5 P0 ~; K$ p" l5 V* V, {5 s0 ^7 y; ^ String cursor = "0"; 9 L0 a$ E1 o+ \8 w
do { " U0 h, z1 ?5 N/ f1 B9 N
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 0 f5 A. L7 v% h {; g# ?
ListtupleList = scanResult.getResult;
* F8 z" m) z, i* Q, n7 {2 | if (tupleList != null && !tupleList.isEmpty) {
: v" D: y; g! c! N for (Tuple tuple : tupleList) { 9 e8 P( }( K! x, N
jedis.zrem(bigZsetKey, tuple.getElement); 9 A8 ^! ~$ i, E
} $ y; N7 c ^. x( a
}
# p+ v' k. ^8 b; m cursor = scanResult.getStringCursor; # |# V2 q `, ~$ L& u8 H1 `
} while (!"0".equals(cursor));
) _: S# r; T! q% F T; _+ _4 s5 S6 h( p: S1 i
//删除bigkey
% a' x$ k, K6 T x4 l1 b jedis.del(bigZsetKey);
' w7 v5 ~( h/ J+ _5 _. Y+ k% c} 公众号内回复“1”带你进粉丝群
" p4 B |% p* c来源:http://www.yidianzixun.com/article/0LevQm7t
7 F3 E) K$ w; ^: z& \) n# b. Z7 j" K免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|