|
|

: b/ N& ], M/ T本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。! s* n# W3 u4 P: d n
( R, ]6 ^" d# y4 A, z, u- Y
- 键值设计7 y7 X% y; w6 |: A9 B! Y
- 命令使用: N/ W; r4 P9 w+ h" r% P- N3 W
- 客户端使用
- T$ X( o, l4 ]8 J$ ^& i - 相关工具2 m: i( C. w4 ~: @6 m
通过本文的介绍可以减少使用Redis过程带来的问题。# |! @% i; Y; ], j
一、键值设计
) W% \3 \. d( @( |# b! v, \! x7 y. j, q- c
1、key名设计
" A3 s- S6 w0 A3 C) i0 n
]8 u& g4 O" }6 a7 x/ @可读性和可管理性, H7 q! J% Y- t, V8 R- m4 K
( ~/ \# z. x! P0 K, M! m- `( k以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id J1 q( Q. y2 h
8 S" _: B0 P6 z4 S ^5 M
- ugc:video:1
" M, F3 {! P8 S 简洁性
4 C0 l( B4 W. z9 k7 u1 G
B* o: H/ U( j9 q. w" C$ s保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:/ g0 v# j5 y5 C, o, L
/ r) d6 O/ `* q$ ?5 T- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。1 U j1 r x4 d6 O2 U3 @
不要包含特殊字符9 V& T/ h* G# \: V3 ?
& m- u) k/ {2 D* |7 J
反例:包含空格、换行、单双引号以及其他转义字符
$ `$ j2 |0 |+ r0 g. A% D4 p7 @2、value设计6 \4 v- z6 W5 v2 M; o# C
' @' X+ q, ]' n% F拒绝bigkey" r+ }5 V' t: U5 |
% ~3 [- g7 X0 A5 J/ O. o1 r3 |( ~
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
! j, S9 W9 W! X5 v$ \! `! X( Z反例:一个包含200万个元素的list。
: a Z3 _2 o F/ ]5 X非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
1 ^: g& ]) k: I选择适合的数据类型) C. Z' B% S- h& \6 q2 W+ a
* @5 ]2 w: \0 |例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?: T' N1 ^! O- V+ k
反例:' P1 K; ~/ [' z
) R8 c! _3 W" N) ^4 G- set user:1:name tom' i. z: s w z% d; s5 k" M* P$ r
- set user:1:age 19 I$ ]2 }9 E$ l) X: Z ?: `
- set user:1:favor football
; p7 Q( ]- Q1 U 正例:$ n8 j& s/ w4 V" D# a9 a
/ W3 G: f$ c1 e8 W8 c2 j
- hmset user:1 name tom age 19 favor football
/ |% P) h( r5 x- | 控制key的生命周期2 g! U0 m! o; Q. |
M C# u+ f9 B3 l
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
3 j# m3 V6 t3 o5 p) Q. T二、命令使用
. q: o. p8 s, i
' \6 t/ ^8 s6 W) j% x5 p: e6 d; G1、O(N)命令关注N的数量
) _* h) F- | M( X
+ s5 L/ I1 K* h' f1 z0 w; e例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
! n5 O! M: ~4 G1 s2、禁用命令! D/ p, D/ J1 R+ D. O. ?
$ p! @. }$ [7 m$ c3 F9 V
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。" i5 [/ l' l, g3 G, h6 I
3、合理使用select# q4 U% Y5 P/ x4 I) J! |
& g% m4 ]9 K- B, `) h9 g2 z+ h K
& b4 R) j9 B# x5 b* U9 B$ n; @, yredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
6 l) r. m$ r5 d i' s% H! W) E4、使用批量操作提高效率
8 V* I% e# B- ?2 V* \3 D+ k. x1 f+ ~4 l) u; I, P
0 o- _( e( K+ Z/ T
- 原生命令:例如mget、mset。8 ~4 \$ m9 [1 C% d' X6 O
- 非原生命令:可以使用pipeline提高效率。
, D& C* }3 U6 M7 U& \, ` N" @ 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
) i5 D5 j$ i( Y9 z注意两者不同:
2 _" `2 P0 ?# M8 o/ ]7 L/ e5 e
1 K! X+ S) E. T! T% L/ q- 原生是原子操作,pipeline是非原子操作。. S5 V; y6 N% x; J
- pipeline可以打包不同的命令,原生做不到
, A5 L8 y" T4 L* k# D/ o0 Z - pipeline需要客户端和服务端同时支持。) W/ o& e+ J# H: a7 w8 J
5、不建议过多使用Redis事务功能
9 e3 ]0 x2 V1 c; v: [, ], u( @5 o; f) i0 Z" _
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!" s5 V* [8 {# O8 G2 ^
6、Redis集群版本在使用Lua上有特殊要求/ e3 Y+ |% ~" c
' v; B$ i6 S8 Z* D5 j+ [- @
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"
6 \: s& X ^- V7 S( y2 c+ [7、monitor命令0 w$ x+ F$ G# A- f* r5 j4 j0 q
0 R* p3 c# P9 E. w$ ~( t3 w7 C! G
必要情况下使用monitor命令时,要注意不要长时间使用。& n, f# T1 q3 T. P9 `! P6 Z; p0 p. L
三、客户端使用2 ]. y* s- H5 p# u8 e& X
$ a* k( q% I, p" @$ |$ a1、避免多个应用使用一个Redis实例: Y6 W8 v1 ~7 l
1 r1 o: p( I- ~/ K3 |
不相干的业务拆分,公共数据做服务化。% H, L( z I8 d6 K: j
2、使用连接池0 {- N8 V( ]' o
$ `2 A4 |. e& Y8 Q- c; J, a/ N
可以有效控制连接,同时提高效率,标准使用方式:9 h6 `1 s5 n9 j+ b- q
Jedis jedis = null;
, U" C" w% y F4 k6 H; ~try {
1 A/ @3 M; P9 J& ~- J* f jedis = jedisPool.getResource;- x. v; x& q# m7 t! j6 B* L# T
//具体的命令( B% d( L' f; a. l0 e
jedis.executeCommand
' v* A, }, i* H} catch (Exception e) {
, m8 L# z4 T, h; @% d5 G logger.error("op key {} error: " + e.getMessage, key, e);
- S5 m% O) S8 p! O} finally {
# s2 T# c, \) Y, h4 A //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。# ^- K# z- G2 Z+ ]) b
if (jedis != null)
3 q" h P! ?' i7 [5 y8 h W' H jedis.close;4 V9 W2 a1 N; H6 n" l( c. g
}
6 v* \# b6 z1 V& C3、熔断功能7 W! q( f* l- ?! ?: c( i, R7 |1 {
4 v$ h) @& ]+ n$ n! h. ~- L
高并发下建议客户端添加熔断功能(例如netflix hystrix)# g, A6 a1 Z: y
4、合理的加密' p( s- B2 n0 J
; {9 u6 ?2 ^0 y) A
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
8 [. C8 }7 Q0 T' O% ]( q5、淘汰策略, L/ ]( y- Y6 Q! I8 a, U/ D, f/ R
6 C/ h, A- \, W" z' i根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。5 |/ v2 x, S3 J8 D i
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。3 s+ }3 Z# X1 k A) x* d2 [
其他策略如下:$ ?& m3 ^( Q- {, K, G
* ^7 D4 `( |% m- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
/ @" { H9 }, c6 s% F - allkeys-random:随机删除所有键,直到腾出足够空间为止。' q4 |$ p5 d" f7 X3 J: U, C5 K4 C
- volatile-random:随机删除过期键,直到腾出足够空间为止。
% n1 f' S1 s; }$ Y! d7 `. ] - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。4 s9 }2 v b0 R1 d0 y: b
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。2 } e* Z3 O$ B
四、相关工具( D4 e5 {9 ~2 s) N/ h
) v5 R h0 L$ i/ r1、数据同步
, d# b9 |* D) v3 |9 Q# f c5 b" o! u
redis间数据同步可以使用:redis-port$ M; L# S' c# M2 b
2、big key搜索 H( L w0 r. x
. M- `8 ?- s! Eredis大key搜索工具
( x9 g0 R9 p# `7 C3、热点key寻找
9 T1 m. ?0 G l& ~: g5 N/ X
, I) D% D# N4 h& }: w2 Y3 Y内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
; g4 ~- ^% `# R五、删除bigkey
: E" w( r' w; L2 O/ Q% `
$ ~# U: y" E/ e/ V8 c1 A( U. c% A0 d8 k* W$ ^
- 下面操作可以使用pipeline加速。1 }: n; V3 Q% `! y
- redis 4.0已经支持key的异步删除,欢迎使用。
7 V9 `! [9 H+ B C9 R3 z 1、Hash删除: hscan + hdel
( k8 s! i, s! J/ K& p/ s( c/ ~" f3 G0 e* t: Y/ Z' Q8 s
public void delBigHash(String host, int port, String password, String bigHashKey) {% _$ M F/ L3 j8 g" }$ ~6 r
Jedis jedis = new Jedis(host, port);
2 G, c7 j! U5 D' _) z7 Z! D if (password != null && !"".equals(password)) {
. n+ H0 b: M% e4 j5 J jedis.auth(password);
" ]' r! _; a* k+ K }0 [) g9 W( o/ v& J" L# E% R
ScanParams scanParams = new ScanParams.count(100);
2 x: o' t5 [* ^9 X/ ~6 X5 Q String cursor = "0";
" D2 s5 \" R( H0 }+ l) b: T$ P do {; [, ?/ T, j8 o: g
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
3 s/ f5 o6 d; R( ^& q: E2 ` List entryList = scanResult.getResult;7 Y% |; |8 i, `1 R3 |5 m* E
if (entryList != null && !entryList.isEmpty) {
+ `- D% {4 C2 j" c3 T% @ for (Entry entry : entryList) {
6 \0 V- ^. U, _ jedis.hdel(bigHashKey, entry.getKey);
: G+ n, U! e* |, o f }
; w6 v, i) Q! F0 { }
6 w l2 G2 K! {. e2 Y cursor = scanResult.getStringCursor;. Q" N& Q( d; k( C
} while (!"0".equals(cursor));
3 g. M" @$ a% |9 P/ Y. h- N$ Y1 k k' Z7 ?7 ^$ N+ @3 _
//删除bigkey: d z- f$ k& G
jedis.del(bigHashKey);: D6 ]% A2 W1 j9 G5 F' K: _
}% J+ |! L, L5 r& J
2、List删除: ltrim
G- f" L, I. x. H+ v- J) l) y$ [
5 K. |6 I& i5 B2 F4 `* r5 Xpublic void delBigList(String host, int port, String password, String bigListKey) {
2 Q9 R( ~4 x4 X7 `8 M Jedis jedis = new Jedis(host, port);
) F# a3 i9 R( K0 x1 l- P) `; s if (password != null && !"".equals(password)) {
7 x) s2 S7 V. W6 ?# ^- v' M( p jedis.auth(password);1 q; _$ w. v: q, f' a9 u
}
) W# O0 [1 P% H" o. j long llen = jedis.llen(bigListKey);
# }7 t, `) O1 y/ A$ H6 q4 t4 ^. f( @ int counter = 0;
Z- F3 { }- r4 {4 ]* V int left = 100;- a0 w% b( w% T x0 q4 W6 v
while (counter < llen) {" d9 c0 i' R1 a! s. m2 W' X0 Y
//每次从左侧截掉100个2 n9 o2 l, S- _
jedis.ltrim(bigListKey, left, llen);& N3 c( D' M% d8 w5 \
counter += left;5 i( W8 w! B9 `) ~1 @) w
}% d" U+ H8 g9 z+ r
//最终删除key
# I* Y1 m; ^% ^2 C4 @2 `6 t% k3 J, N jedis.del(bigListKey);9 e" o1 ^0 I$ Z
}3、Set删除: sscan + srem
1 x$ v6 o$ Z7 ~9 N/ G1 e
, ^# d6 h& x" v# Bpublic void delBigSet(String host, int port, String password, String bigSetKey) { y3 `4 n' U+ ^
Jedis jedis = new Jedis(host, port);4 J) i; P& O* L( c* S& V# n
if (password != null && !"".equals(password)) {
4 o E% W; ~& f jedis.auth(password);( T+ t# f# ^, G1 v( Z( Q
}
! q0 J+ V5 f' y9 R) S0 y2 r ScanParams scanParams = new ScanParams.count(100);1 i6 P! O' x- e2 @3 l; f' u( l
String cursor = "0";
9 s( d8 {3 E* Y# d do {, x( I- O! \0 }* Q9 }- v; W; [7 F
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);5 I7 H, x1 ~& ]
List memberList = scanResult.getResult;; }# Q9 i, r6 _# B
if (memberList != null && !memberList.isEmpty) {
( h+ S4 I" n& _& q2 d, S for (String member : memberList) {
2 i7 ^' T8 @5 M7 d jedis.srem(bigSetKey, member);. w3 r9 W c+ I4 f
}
, G- q# Q; S' @ @8 e7 e }: l, F' m3 L6 G4 q( ^# Q
cursor = scanResult.getStringCursor;
2 n; a2 g& m, G% ]1 D } while (!"0".equals(cursor));1 M7 }5 K7 d7 c# q, y% P" A
4 O/ D5 ^# y! P; ~/ s7 Z7 k, O
//删除bigkey
' O9 B9 @ c7 X2 h7 n, B jedis.del(bigSetKey);8 O8 l& }/ C0 S, r
}0 V' C( D/ ]5 L
4、SortedSet删除: zscan + zrem: s/ F6 o8 S: ]# @
4 S0 ^" O! Z4 ], A9 J+ Z
public void delBigZset(String host, int port, String password, String bigZsetKey) {
( h8 O' ^) S z) c2 z( U8 W Jedis jedis = new Jedis(host, port);
; Y8 S j" E. l* m4 M' O2 M, A if (password != null && !"".equals(password)) {
7 o! `' Q& F |: R% H C jedis.auth(password); & I8 F% y+ W J: b
} $ m6 {: N8 f: }* P' x" ^/ K& ]
ScanParams scanParams = new ScanParams.count(100);
4 G$ o, R( }& N# T( C; V String cursor = "0"; . L! i0 U5 ?3 C8 y t6 @3 D2 E
do { + c; T) c: q4 s6 J8 p! c
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 3 \3 l! k2 c# e! n* F$ Q/ F1 {3 D
ListtupleList = scanResult.getResult;
9 N' o- b6 Q5 v: p2 F6 M( p- [, w if (tupleList != null && !tupleList.isEmpty) {
; F; @ g& `" O/ Q for (Tuple tuple : tupleList) {
; F& z1 j) e$ I5 F/ K9 u jedis.zrem(bigZsetKey, tuple.getElement); $ i# m& b- q1 ^, U4 ^) ^
} ( Q; s+ A9 `$ ]6 S1 M1 d
} 2 M6 l, k9 `. u
cursor = scanResult.getStringCursor;
0 W- L8 W+ r" s. o: G& d } while (!"0".equals(cursor));3 A, J5 i0 j, N/ K' Q9 V8 ^
+ M# V) x- ^/ U; d
//删除bigkey . @- U# c' _- o, s1 h
jedis.del(bigZsetKey);
" h5 Y2 D- Y0 P- J5 a3 E3 z% v6 ^} 公众号内回复“1”带你进粉丝群
0 ?! g0 M2 o+ R( J3 V来源:http://www.yidianzixun.com/article/0LevQm7t+ L2 _6 L1 @0 d4 T/ K
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|