|
|

& s" `1 B. Y% r本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。0 h |2 q2 z) D. j2 l X6 X
/ s. t5 y5 z4 \2 T$ g5 |4 x# [0 n& M
- 键值设计
|# M1 |( p9 m6 [: v - 命令使用
+ U2 F7 X" W! M6 E& A: K" N - 客户端使用
( Q; y" a5 p; |2 p* T/ A - 相关工具
. e+ O J Q: \8 m l% L! i4 o" V4 z 通过本文的介绍可以减少使用Redis过程带来的问题。# Z6 d7 L) k* n/ ~
一、键值设计$ l' J3 Q$ f9 h9 f, f* c
! S, s# {0 M" L5 ?& l& J
1、key名设计
q, }- _ h8 ], u, L& _/ p) `8 |
$ s: N7 u- W( ^) l, I" p% i" C; q可读性和可管理性
( Y$ |! C- {5 W9 P# K' s" K) ~* E5 c% G8 E
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id2 @% d6 A( @1 F4 @3 I6 h% Z
1 k1 d+ R. K! r9 Y7 K
- ugc:video:1$ h- p$ L5 h9 `1 q* L
简洁性$ E) P% N4 n2 `+ G: f
* s) j2 |; ~; A) a2 L: l/ r# w保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:6 B. d$ d( S1 f9 B
! Q/ b2 {( i* t4 b6 Z- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
+ s2 y% K9 Y# C8 m e; X( R; u 不要包含特殊字符
/ y( }4 Z7 C2 y" p5 W9 B6 [2 j2 ?( N( _# y" f2 T
反例:包含空格、换行、单双引号以及其他转义字符
6 _. |& Q9 b5 \$ e2、value设计
5 ~- V7 @1 T- l$ z. t" s2 J4 Q( ]) a6 o7 l# }8 m
拒绝bigkey4 r2 b3 }& J; A4 y: V" z5 O
" ^6 R z# m- T/ w9 A) l, q
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。) r3 F" O F a7 E7 S
反例:一个包含200万个元素的list。: Y4 x& H! S; I3 g& J1 I) L& Z
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
3 u, `4 [4 S; a z2 o! Q F选择适合的数据类型
R* ?+ a; Y+ _1 O+ [6 ~ M! Y! f- W- ~/ w# Z: @# O
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?& v' \) M! M. |- @
反例:
# u7 M8 w8 P9 d. {2 O+ F4 h! ]" @
% D& @ H! Y0 d/ O& c+ t- set user:1:name tom/ |: R& p. J. n* h, w
- set user:1:age 19
+ J- d' U( y3 V0 ], h# V - set user:1:favor football
8 L: |8 a1 |" [6 w) H 正例:% J) A2 O+ l$ `7 x; r
8 c; `, D; V$ s2 O; Y- hmset user:1 name tom age 19 favor football! T# M S' U4 ?! m; J9 _
控制key的生命周期/ t, k- i9 |0 p4 i e! o4 k4 y/ l+ M
& a8 x. q6 b) n) [% i4 Y i; a) Q4 K: oredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
% y4 n, R" v+ T) j+ l二、命令使用
3 J. G: r8 m( q2 H, d+ F; D1 a* g- Q1 _9 I3 B3 B. t% q
1、O(N)命令关注N的数量% C0 z; Z( r" {* C$ s: `
/ F) F3 C( h* f. s
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。+ L" j, O& g- g4 o9 \# w, W/ a9 X1 [
2、禁用命令% k& d; u0 L: ]
+ _5 l$ W0 [3 m1 F, K! `
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。" I' h2 b# L, ?* a7 C
3、合理使用select
d& K6 T# `0 c: M# h8 ?/ b! D" z3 J. c, B% H3 C. v/ J
4 ^' @% m. j* K* P& x
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。& g& ]0 i4 ^' A e/ \3 p9 n
4、使用批量操作提高效率
/ O, ~' i; O( I' M3 ~3 G5 c+ A2 H. o4 E
5 i# ^! _! n, O9 Q* _2 K
- 原生命令:例如mget、mset。7 B8 i6 h' }2 V/ R7 R2 g, w+ q) X
- 非原生命令:可以使用pipeline提高效率。3 k' {2 n7 L/ ]% P+ Z% m
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。0 n+ k5 C% z$ Y F
注意两者不同: B: _" s+ z4 E
2 Q$ Y6 {6 ?, `- 原生是原子操作,pipeline是非原子操作。
3 P5 ~+ J# ]. q! t A M* ~ - pipeline可以打包不同的命令,原生做不到: {( D1 n: C* G/ c3 i$ ~$ Y
- pipeline需要客户端和服务端同时支持。4 Y) e/ C' N/ ~7 h
5、不建议过多使用Redis事务功能* q5 a6 R7 Y; ~# P/ E9 \
/ d9 B( T- Y# TRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!8 W2 ~3 i3 Z6 b0 B
6、Redis集群版本在使用Lua上有特殊要求
" p3 k- V3 v( R2 v' }, \5 D* B3 V
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"" `, F2 Y" P# H2 }2 ?+ D6 [. n
7、monitor命令4 j( \. ~8 {4 @8 ?
1 G, m* P# m h/ Z
必要情况下使用monitor命令时,要注意不要长时间使用。' V& O- `4 R) x7 Y- L. r+ K% U
三、客户端使用
! g* \2 A6 g/ W" V2 e. \9 D) x. O& G, f( `' ^
1、避免多个应用使用一个Redis实例
: v6 O z6 @9 j4 c5 B
' Y2 |" a8 I, v6 m, |不相干的业务拆分,公共数据做服务化。9 v8 E! V* P9 p+ \1 ]/ _% X) t
2、使用连接池8 b* R% {/ ~: G3 c- ^. r
; e9 J0 i7 u+ D# O6 W5 }- h; P. a" D& ?
可以有效控制连接,同时提高效率,标准使用方式:8 t v+ z# i0 y8 d w( T
Jedis jedis = null;
]8 M0 C3 h2 w+ g; @5 ntry {
+ m1 z0 ^9 e* l* R jedis = jedisPool.getResource;, v* \3 h4 z; X1 d# Z
//具体的命令2 U: j1 K- Q0 U; G! j0 b# U
jedis.executeCommand
. B; s9 a3 j9 Q/ `8 ?} catch (Exception e) {
/ s3 m1 B4 v+ F: K1 ^ d: k logger.error("op key {} error: " + e.getMessage, key, e);
P/ L' W* {+ I" D& N- O} finally {
2 _+ A% h9 S$ y+ o //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。2 P- V' a8 f0 [: |: [) u
if (jedis != null)
/ V+ H6 a4 U& S4 x7 o$ |, _* B X jedis.close;
b, j$ B. q' D}# s4 i" j, L, o" a0 a
3、熔断功能2 @+ S! }; V9 s% G
, ^" U# K$ I0 a4 s高并发下建议客户端添加熔断功能(例如netflix hystrix)( s$ C" B- j5 R- z
4、合理的加密' a% k8 ?( H% J! R5 p$ p/ v, S g
& f/ F4 e2 M: g2 K; n+ }设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持) D( R7 W9 o, a1 {% R9 `, A
5、淘汰策略' E8 F$ {0 h' ~3 ]& q8 W/ d( \
$ S+ @ V8 V0 r* k2 j根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
C6 y4 j) q E- O7 C8 \9 u默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。! V8 y( Y( Z9 }8 _9 L
其他策略如下:
5 ~' c/ }* i- ~9 o7 I0 g5 G. ]* ?( A, b
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
9 N, D% B/ d$ x2 h6 Q" d# Y6 b - allkeys-random:随机删除所有键,直到腾出足够空间为止。
, G. K5 H7 ?; V4 }9 L% I& i8 x" r - volatile-random:随机删除过期键,直到腾出足够空间为止。: g; E" e+ o0 b4 L/ ^) G
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
6 L5 e* _1 z8 r" A# n - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。) X' q, e w% P% G
四、相关工具
; z) S! ?8 y v- z) [8 i5 e
2 N, h% d9 ?. G9 M$ a2 k' h0 x1、数据同步
: `7 L& ~; b( L+ [# ^- _+ D1 l' ]
6 v2 P$ m8 Z- Qredis间数据同步可以使用:redis-port+ U; F, T1 L! q0 D8 _* }$ E/ ~% V
2、big key搜索
0 m3 B4 A% v* r( s$ d3 b* n: q
# ^' u* Y. Y" k& P/ \0 B) Vredis大key搜索工具
4 g9 E4 f: o6 }" X3、热点key寻找
: O1 H) y& l# Z* _# o. {: N7 V
|) f6 F2 i9 Y0 y* @内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
; X' }( j, H5 x% T( x/ S五、删除bigkey
1 e3 P' C0 X X. D ~: T
4 t! z. w! i/ O
0 a2 R- w. Q' n- g/ U- 下面操作可以使用pipeline加速。0 Q3 c, v# V2 |1 e6 w
- redis 4.0已经支持key的异步删除,欢迎使用。: @8 K1 y0 @- J
1、Hash删除: hscan + hdel
; b$ P4 M6 D; A, w7 A6 [
+ p8 T# o! Q$ K: `public void delBigHash(String host, int port, String password, String bigHashKey) {3 ~3 D: I8 Z: C
Jedis jedis = new Jedis(host, port);' {" F, W" @/ o+ e4 N9 X
if (password != null && !"".equals(password)) {
$ a9 t4 G5 G1 N5 M jedis.auth(password);
1 Z( i1 B d3 w0 w' s6 g( T }
. y4 {5 [: X* X0 E u$ Y+ K* b# N ScanParams scanParams = new ScanParams.count(100);
4 A7 e# n+ ]6 @0 T: @6 i String cursor = "0";
) v8 s+ U7 P1 S7 h do {9 g4 s: M: i% O/ A* V7 {# h, \
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);* g8 C4 m: v- ~8 w, A
List entryList = scanResult.getResult;* Y; S$ i# o2 g5 Y: Y5 Y
if (entryList != null && !entryList.isEmpty) {
% Q8 f# F# m" }) @& O for (Entry entry : entryList) {
! j e. ^& u* c3 \* d. }- E jedis.hdel(bigHashKey, entry.getKey);
8 j* [$ V' k3 J+ @ }
7 b. b* J' j! O* l }0 Y$ [) q, N4 d2 E. k0 Q. R5 Y
cursor = scanResult.getStringCursor;
2 v7 \$ _! b ^2 r7 m } while (!"0".equals(cursor));
& w3 {7 R: U% B4 g" a7 ^, D) b6 q
//删除bigkey
6 v8 G# S# G( {! X8 x+ a& f" k: k2 ] jedis.del(bigHashKey);* f G3 x% b- w! e- H7 ^
}
7 Q, {: R# Q, @4 T2、List删除: ltrim5 F8 J* c# M- k7 l2 u, }
1 K0 |6 o$ F, @ J( Jpublic void delBigList(String host, int port, String password, String bigListKey) {. |: x( `# e+ n% k
Jedis jedis = new Jedis(host, port);) i. z$ j# S& x6 f4 k
if (password != null && !"".equals(password)) {
) j) N0 W5 V5 W X) ~& Q! k jedis.auth(password);( d+ B7 d3 i! _# j- c( X
}5 z# |( w" K' M0 J2 w. v' L
long llen = jedis.llen(bigListKey);
0 ]% U1 P5 E. } int counter = 0;
* q4 ~7 H+ p- p! y* C5 b& N8 b" K( u3 a int left = 100;1 o- E7 [' }5 r, J. t
while (counter < llen) {
$ f4 I$ J( _. m* @- [ //每次从左侧截掉100个4 U, _% }8 V0 y
jedis.ltrim(bigListKey, left, llen);* P) q" Y4 s6 T- Q0 Y0 s; l8 K; m
counter += left;
! ~" T/ | G& O0 x' L* v }
4 i; F. D$ `( q1 m7 T+ L) z, G6 p; m" t //最终删除key9 L5 V- M2 q E
jedis.del(bigListKey);
& [, _# E4 R9 d& J; s}3、Set删除: sscan + srem E# {9 G9 V j6 O
2 s- {/ T/ K( B
public void delBigSet(String host, int port, String password, String bigSetKey) {
2 O7 y: J- t- r Jedis jedis = new Jedis(host, port);
, r- Y0 E) E8 p4 s3 {, S if (password != null && !"".equals(password)) { E1 \# [& [( |% |" T
jedis.auth(password);5 @+ J/ i- E: Y2 r: P! K0 _
}) b( b; a9 j2 ~! x* _- i
ScanParams scanParams = new ScanParams.count(100);0 J* b) D: u- c) F. A8 Y7 h5 Y. ^
String cursor = "0";
2 j' [3 s; Z @5 B" @ do {
/ k ~1 M" t. }$ B ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
1 _6 g/ l' L9 H( q) @4 Q List memberList = scanResult.getResult;
6 V) Z7 d- x# i1 y2 |5 R; L if (memberList != null && !memberList.isEmpty) {& Y% e6 u# s: t0 q; n; R
for (String member : memberList) {
' ^2 X5 m* N$ m* B; a% r+ P/ v3 x jedis.srem(bigSetKey, member);, m% N% z# Z: A* c# [& O8 N* B
} R5 n9 P& ]6 _8 g: B/ ]
}6 G# ~4 P0 L- D8 h: M1 O
cursor = scanResult.getStringCursor;# L# y0 a* `, Q' n, p
} while (!"0".equals(cursor));
6 s- O$ @9 B# u6 ?6 e3 g# i: U5 }0 Y$ T$ Q7 L7 y3 ^$ |) n9 ?
//删除bigkey
& A' n- V$ W' ^; e# L: u2 @) h jedis.del(bigSetKey);
& w# [% _4 s5 O8 B ~' X}
# ], E9 h1 i; u4、SortedSet删除: zscan + zrem
, e: y. t, t5 t# e# B
/ ~7 I/ E2 q2 R) Qpublic void delBigZset(String host, int port, String password, String bigZsetKey) { ' L- u9 @% q* t
Jedis jedis = new Jedis(host, port); # Z- J7 Z: O) u2 f: G" r& q
if (password != null && !"".equals(password)) {
5 o3 J) B; `, F jedis.auth(password); 5 @" b( T; v, B. w* P% `- b; Y
} ! m: V) A9 x4 y7 ~% l+ Z8 c7 R6 i
ScanParams scanParams = new ScanParams.count(100);
2 y# W' }+ `' F) c/ V* w String cursor = "0"; 6 x1 i, b/ J/ t, R: c. s
do { . M6 s1 a5 f- A
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 6 R' E4 Y1 @- D% s) C }
ListtupleList = scanResult.getResult; / o& `' r/ f8 k
if (tupleList != null && !tupleList.isEmpty) { ( @. C4 v7 W& K2 U2 H& o; ^! l$ g
for (Tuple tuple : tupleList) {
! h% b4 m2 o* _1 @* k+ C jedis.zrem(bigZsetKey, tuple.getElement); % Q! b W9 y4 p; T0 w1 j4 g
}
4 d. W6 J4 c5 o2 M+ P+ h }
' T* A+ d J9 x cursor = scanResult.getStringCursor; " U( l- W: ^. y, k8 A2 u
} while (!"0".equals(cursor));
+ E. L" A" X0 }5 }8 w9 ^7 c0 |: R$ N) v! _6 J) p; y
//删除bigkey 2 s/ ]7 Y0 [/ s5 e, }# K
jedis.del(bigZsetKey);
: y- x7 y1 N3 x, p* J% U2 E} 公众号内回复“1”带你进粉丝群
U" `1 u& ?+ [+ a7 X) a1 o7 s来源:http://www.yidianzixun.com/article/0LevQm7t
3 w, G3 j9 G/ h, g, F" k$ Q3 a免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|