|
|

; S R1 M' P9 C0 \. h8 \本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。' Y4 J2 q3 e5 a6 G+ Y( Q
# [( z0 W4 B! G4 @% ^
- 键值设计6 F6 ~. X X/ Z' K! J0 S6 ~* ?
- 命令使用
; q2 z- n4 A! k, _ - 客户端使用0 G6 e8 g1 v% v& y+ n
- 相关工具$ q. b7 _, F) `( n2 n1 `+ ?/ p
通过本文的介绍可以减少使用Redis过程带来的问题。
0 K. h* F" m6 A& \& {7 o6 ]一、键值设计
, o2 z3 E! y1 v4 d5 f- ~) C1 {4 L) ?5 v' \
1、key名设计
, z+ k+ P( y+ p* D' p0 v
: B6 |" H X/ m" }. G' d3 O/ C可读性和可管理性
6 q" e* e. `6 g/ e' M% u/ W5 l, U0 u/ F" O, ?$ x. ]! Z V$ h
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
" r, T$ W2 C# G
* Y+ `( [; E( @8 `. \/ s$ U3 p- ugc:video:1& |. P' r" P) C ?4 a7 g( }; ^. @) p
简洁性
! t! o6 L* E" ~4 a" `; R6 z
0 K) G6 x( N" J1 | G- m' V保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
" L5 x5 \' V6 ?' r& N6 L6 e+ A3 L" Q
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
% D; u; z: E4 D" ` 不要包含特殊字符4 [/ C1 L6 A# O
& w8 e9 Z1 `9 I: W反例:包含空格、换行、单双引号以及其他转义字符
+ ^& M5 V% d2 v+ ^2、value设计8 d7 t2 K; L: D" ^
, ~( {2 {9 Y7 m( e
拒绝bigkey
6 N; {2 o3 ^0 l+ N, p- _/ P( n
' T( D F- h ]3 B7 E防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。8 t" r4 Q* [. m; J# Z N
反例:一个包含200万个元素的list。% Q1 }3 C" u) b: N2 x/ ~2 `
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法( Q) S0 c4 x& L( X6 C& j/ U- `
选择适合的数据类型
* I9 t3 y# X3 G- ~4 r8 s; ~ p' J |2 l5 b8 O2 m" u
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
5 [5 C* _, a- [/ g+ D) b5 m反例:8 c. ~' U# S! Z1 r. g4 [
) G z2 k" z$ _7 v9 O' M
- set user:1:name tom
& [8 n0 M$ W+ m. M! l2 G' M5 c - set user:1:age 190 k' w0 L. m# S1 u
- set user:1:favor football+ b/ j* k, A- ]6 W$ U' A4 F
正例:
' x5 O7 k! f5 J( n H, P- t! t" h: R* w0 ^
- hmset user:1 name tom age 19 favor football
! B* B+ \* S2 v" s$ O 控制key的生命周期" j; F+ x9 q! u3 h; \. h0 W0 X
. t5 N$ J; j1 d/ I9 g9 {redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
* T1 a3 D+ H- L' v0 \二、命令使用 ?1 N: B5 z: K3 ~3 q
9 `1 y' i% _7 G* ?
1、O(N)命令关注N的数量6 W, B7 N# r, E( {" N, p; X
' ~" U) U/ C, U例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
) a+ R; d7 y. y2、禁用命令
" B" [$ [4 ^' G {
t( W5 t4 U9 ^/ O/ Y c. R0 S禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。8 G9 r& Z- Q* i' g# U4 y
3、合理使用select! a' Z" }) @- J0 [/ g, s
9 ~2 F; G$ ^, s6 m
8 i1 D: E. u. S
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。& u) j$ z. I, g. a% H2 x! ~: B
4、使用批量操作提高效率
$ y' P F; d @4 J: t4 m/ \& w0 X$ ?3 G
$ }+ n; d0 R* M) X( D- 原生命令:例如mget、mset。 M6 e a' \- M# v q3 Q! w
- 非原生命令:可以使用pipeline提高效率。, W5 S. K$ q0 q- G
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。3 f+ p' C! j5 z2 D1 O
注意两者不同:
& N6 P; N/ Q" k( l8 k# l
( \, z1 V& T% A- 原生是原子操作,pipeline是非原子操作。; u" p v* S! q- S
- pipeline可以打包不同的命令,原生做不到
: _4 }: k* C, L0 h5 \# J) R6 m - pipeline需要客户端和服务端同时支持。
: k" F( n4 `) B4 j 5、不建议过多使用Redis事务功能
' P2 m% H7 _) a& E
3 y' J; @7 V1 _5 K; h1 n) P) vRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!+ I+ x w0 e* F8 S' z( Y8 K! \
6、Redis集群版本在使用Lua上有特殊要求- k& [! D/ A8 g R- r
* S- J, i n0 C" K8 |2 s) k! V1 L! G1、所有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"
# Q" e" B" {5 T9 R, _& [* B3 H7、monitor命令
0 S* q) I* W$ z H E+ Q2 n$ X) O0 \9 ?- \3 ^" p' a4 ?3 `
必要情况下使用monitor命令时,要注意不要长时间使用。
" g( D1 `, U) t i4 N; s# f% z, K9 u三、客户端使用
4 R" q, L, I$ o# G8 ?
5 j) x* C+ D( Y. {1、避免多个应用使用一个Redis实例7 F- P% U) p% {$ N2 k( R
: A4 [6 b# h6 I不相干的业务拆分,公共数据做服务化。
4 _ |* j5 C* ^8 k2、使用连接池6 `- X5 q4 T( r2 k1 E7 w
6 X; c- K! h1 ?6 u& |/ N
可以有效控制连接,同时提高效率,标准使用方式:; l6 c5 o( T; U* [; }2 C
Jedis jedis = null;
9 P2 Z4 I$ P4 y/ r- t; Mtry {4 X8 ^( I( u- `: n) X5 z
jedis = jedisPool.getResource;) b0 S3 Z! Q7 y
//具体的命令8 n% w# C5 n7 B2 \
jedis.executeCommand
' b3 B) F6 P% Y+ M% L6 H* `: U} catch (Exception e) {% p, R1 X8 l! R4 k3 ]% e% Z8 }
logger.error("op key {} error: " + e.getMessage, key, e);
8 V G! @+ h0 ?; c, R} finally {6 m; _9 k5 X/ O
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
1 H1 u% j; l! @( G' Y' H4 M9 @ if (jedis != null)
2 L2 z* @; f6 R8 ^; B' P- l# m/ ~ jedis.close;
2 M' O, z6 K% v' q1 o}6 ?/ o) `7 V8 ~& J& \$ s4 R
3、熔断功能
+ I$ Q) H, c3 ?, I {% N# ^1 `7 d1 O) G) ^1 w0 u `4 n6 k
高并发下建议客户端添加熔断功能(例如netflix hystrix)
( t7 t% O, l o( Z; y4、合理的加密
2 A' n; @$ v9 E7 _' h% \+ L1 b. g- ~, a3 ?4 `
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持); w5 O/ ^* Z' f' M
5、淘汰策略
' G5 h- W# G' e4 U" d9 m
3 r! F3 y3 _" a2 `根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
! e- Q( P: R$ h1 Q8 x默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。2 a7 s- B6 M& R8 r' S1 w
其他策略如下:! v/ U8 E" r( h8 K) `
5 o, n! n7 ]+ f+ B- {- g0 ]
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
" l, I( f) r9 ?& `& H0 g - allkeys-random:随机删除所有键,直到腾出足够空间为止。9 c$ w( \! X+ N# _( w$ R& i: [
- volatile-random:随机删除过期键,直到腾出足够空间为止。
8 k; u0 I4 L! {1 g; u* ^$ o4 i - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。 C; Y8 \6 X" |' p ? H; g- {
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
, u3 p) ]8 g/ d* x8 l' k$ s 四、相关工具+ I4 m) M B6 K
* B+ `6 ~7 j$ s$ \7 W1、数据同步
) B" Q: Z6 ^+ |( y' {1 w9 n3 ^5 n% U; n, k$ ~, \8 i
redis间数据同步可以使用:redis-port5 s0 u/ x6 H# R! f$ `6 s" x( Q W
2、big key搜索
# J8 W% \ I7 G4 B! m% r
8 m. Z* r, T& b6 C' c6 Fredis大key搜索工具
, X) v4 A+ h, o0 R5 Z( }3、热点key寻找5 O/ O( o2 Y5 @2 ?8 i f3 K |- w0 X
* c' i0 P) R& ]内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
6 Q) n2 T) q3 u: l0 W五、删除bigkey8 O+ A0 ?- B% c) Y, \5 b, Q8 ?
3 F$ B0 l0 F8 n0 l2 D: a" E8 i( s8 ?" [- e/ ~% {
- 下面操作可以使用pipeline加速。
+ e- S0 _+ R D! V - redis 4.0已经支持key的异步删除,欢迎使用。( Y1 Q, P1 H b8 C3 ]
1、Hash删除: hscan + hdel
+ o0 P4 v4 ]: @8 _/ R! y- y3 h1 N" f+ K! n" Q9 |* t8 f4 q, \
public void delBigHash(String host, int port, String password, String bigHashKey) {5 U$ u7 P0 v8 a# ^5 \
Jedis jedis = new Jedis(host, port);8 n1 @2 `7 [( d/ ] ~
if (password != null && !"".equals(password)) {- D4 @( m+ ]0 Z& A' a1 G
jedis.auth(password);1 y+ i! ]6 l- e' E6 E: I: y/ ~4 G
}9 x$ ^& V5 Y- w! w2 E H
ScanParams scanParams = new ScanParams.count(100);/ C# N- {$ g1 B3 h! A
String cursor = "0";
# d9 H+ c5 K- w/ q do {( Y/ _, M X- O, b! s4 P7 ~
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
V6 E6 ]+ o6 u, F List entryList = scanResult.getResult;2 z7 H0 R+ I" K
if (entryList != null && !entryList.isEmpty) {0 C1 c, l: A9 Q5 q& ^
for (Entry entry : entryList) {0 p9 g7 @6 u1 q( ?3 G
jedis.hdel(bigHashKey, entry.getKey);
# e1 ?+ G' z9 N: A0 ~ }; M0 j" y/ A* W9 u/ K) f( R! i
}+ I0 ?, Q) X2 ]& f0 W/ g/ M
cursor = scanResult.getStringCursor;& E) @5 p, I' `6 L: d4 A
} while (!"0".equals(cursor));/ R/ i/ R9 r3 o4 q& N. Q
! @+ s' J3 D5 o" }
//删除bigkey; D- ^& Q* C1 Z& H& m a
jedis.del(bigHashKey);
" h/ A: @2 E+ Q! D}
}5 I# _; h; f8 }; X2、List删除: ltrim
7 C- _! S( i$ O$ d# y& |) N+ c% a" X- n+ h7 f* a( }
public void delBigList(String host, int port, String password, String bigListKey) {# m/ R+ \: C0 |& R& B* \& K
Jedis jedis = new Jedis(host, port);& b* H5 J" O* ]+ E( h% N
if (password != null && !"".equals(password)) {% @0 K9 l& j9 [* m/ g) e. T
jedis.auth(password);( q' M# q% x# V `$ t) G( P9 o2 X
}
) V# W2 f# j4 }+ I8 k long llen = jedis.llen(bigListKey);3 V m: K+ O# J8 s A
int counter = 0;1 a. \) Z+ D* b5 _& }. h
int left = 100;5 A+ p- ]1 f P6 H- B! M6 E
while (counter < llen) {
7 _( R* w; P8 E2 E W- }4 Z3 _8 n //每次从左侧截掉100个
' m5 x$ e* k- Y2 c' r9 ] jedis.ltrim(bigListKey, left, llen);* Q$ Q( ]3 s$ D. ]4 h
counter += left;
; G! T7 }0 k( z. q2 `1 o" f& j, x }- Y: \( i. p$ ^9 w0 _
//最终删除key
1 C8 W& p2 h$ B jedis.del(bigListKey);: I7 e: L8 s: J/ @) b# s0 |
}3、Set删除: sscan + srem- l2 g: F J6 E6 }. K
3 `( h( b8 B. N: rpublic void delBigSet(String host, int port, String password, String bigSetKey) {
4 @. _* D2 o! U# ]) b+ E Jedis jedis = new Jedis(host, port);! X" V! M; A! m
if (password != null && !"".equals(password)) {
0 u: l" q' q9 l- |6 W jedis.auth(password);( h( G: {$ a. A1 W
}
( }5 P. L1 W1 d8 e/ a ScanParams scanParams = new ScanParams.count(100);
# T* ^% q% N0 N) o String cursor = "0";
+ d3 k- _* R6 O( C) a do {' S2 F6 p, s! Y5 A9 z
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);+ }5 e- a% _- a
List memberList = scanResult.getResult;
/ P- Q7 i9 @( D/ F0 p if (memberList != null && !memberList.isEmpty) {4 o& u3 t5 Y% K/ j- W% J* x2 o% F
for (String member : memberList) {
! H- F- L- B+ ~2 N- p jedis.srem(bigSetKey, member);
7 X, H9 c9 v: c/ a$ | }4 x! k+ E L5 l/ H! J
}& ^; h# _) f+ k1 B/ i! N h3 w
cursor = scanResult.getStringCursor;
+ k! u/ ~# I+ A* _: k; O } while (!"0".equals(cursor));
* T5 S- w# F" `# u* h" R( h% I# B& X/ \5 P# E/ E
//删除bigkey3 [* V% a) N8 p |* ~+ m
jedis.del(bigSetKey);0 T( } b; V3 d% c( o! T
}% u/ i$ E6 Y& l
4、SortedSet删除: zscan + zrem
7 m5 `# k% O4 f! ^( X$ H0 f' I5 S9 f* k
public void delBigZset(String host, int port, String password, String bigZsetKey) { " u* \6 g7 K8 g
Jedis jedis = new Jedis(host, port);
4 h; c4 w/ j" y6 k: k: R {+ g. X if (password != null && !"".equals(password)) {
% X& } N, J: z5 ^ jedis.auth(password); , R3 `' r: _ @) M
} 4 r& B- V/ O: F3 [
ScanParams scanParams = new ScanParams.count(100); * I2 n9 C* _- O* ]0 x
String cursor = "0";
4 A$ @- V8 L: ~3 o0 x7 h7 Y9 B do {
! f; t8 \. z" ^3 u ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
, R0 w% |2 X% y2 S8 p5 _ d0 s8 S ListtupleList = scanResult.getResult; $ O) e7 }2 h4 A4 v
if (tupleList != null && !tupleList.isEmpty) { : z- V% `/ r/ _+ D
for (Tuple tuple : tupleList) {
7 w- |1 K( ]# R jedis.zrem(bigZsetKey, tuple.getElement); * z& a3 }! T; \ C
}
1 M% L# s/ x& a) ?* F! X }
! B; A" [( X7 l9 S0 Z T* K cursor = scanResult.getStringCursor; & Y% O. M' t5 F! y2 X, T
} while (!"0".equals(cursor));1 _# _$ h- c+ c( O1 c
1 U" z# \8 U8 @+ ]
//删除bigkey
2 j) ~9 O; E _* M `' Q( \/ f jedis.del(bigZsetKey);
- G; L: m3 C7 a/ X/ L} 公众号内回复“1”带你进粉丝群# i+ C, F% }" _$ H" u4 u4 w
来源:http://www.yidianzixun.com/article/0LevQm7t
: L/ d9 l! ^! y0 b3 M: k, ^" ~免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|