|
|

/ o: O. g r# X2 V本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。/ f; G7 N( m" q6 v7 \8 Y( o$ w
% X% s( X6 {( g; c* |- 键值设计
4 B l# {+ W$ I7 A - 命令使用( I& k' \2 X+ J) |6 B7 p5 K
- 客户端使用+ y4 x4 ]3 C. [7 q6 U6 {- g
- 相关工具6 f" L, k! s% _
通过本文的介绍可以减少使用Redis过程带来的问题。
5 C, |, Z" X o( ?4 H一、键值设计# ]- Q- O% N8 k1 q S0 C4 G/ U
7 _! n. g, x$ V' g+ f( d1、key名设计
; T) @ j8 i8 j' b! v0 W. F; C6 ?& N+ Z7 C8 J, S2 @% c
可读性和可管理性
- f6 q3 l3 d6 w: O: r
1 o) Z7 B+ |8 m# O3 K O8 N以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
: @; ]( N2 R# _% @1 C& t8 x, S9 M, z$ |" U5 ]
- ugc:video:1 {3 \+ V% {4 G& v9 s
简洁性
' H0 Z! Y- B7 M) s' H6 o4 e c- {: d( O5 n2 G0 h8 ~
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:' Y, P6 a: f* A% l3 c; U
8 d4 M3 S2 M7 S4 J
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。) f1 H/ B) [4 h7 A0 A- o: I
不要包含特殊字符
; I4 ?4 L* K) M/ @ ]& J2 A/ M
+ w9 a* S+ u( B7 i3 N$ Y& W反例:包含空格、换行、单双引号以及其他转义字符
# `6 U2 M1 G! \2、value设计8 d- {. H o% E+ T( z9 b
, r0 t) {! t2 N4 y) B拒绝bigkey+ q7 N7 v j! }* H# Q' P
1 `3 }& P+ }1 i& y4 z$ l/ v- `9 u4 }防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。% i0 k7 @1 B5 Q# N4 C9 t
反例:一个包含200万个元素的list。" Y2 F5 O+ p; H
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法7 \ f$ S1 d4 M* |% v% b9 L# B
选择适合的数据类型4 c5 s$ j; y8 j: z3 P. j. q3 e- Z
# w$ t+ X# t+ A8 L例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
7 ?4 }( Y6 ~( {1 ~反例:
/ \* Z7 l; u2 u/ c+ `/ X* Y' j" [5 ~7 `
- set user:1:name tom" K/ a' f7 r O) h! Q
- set user:1:age 19
6 v0 g: A. N+ m! @% v" u2 w - set user:1:favor football
8 _0 \; r# S( @. f! a 正例:; k( Q) {* z5 @ r3 `/ j
& p5 i# A/ D/ ]$ y- hmset user:1 name tom age 19 favor football# s& F, r: K$ h" K+ `) q% h% S9 q/ X/ _
控制key的生命周期" \5 C, S& T; }
1 [' L3 }8 O* ?5 `' s% h( oredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。, F1 A" P8 R" k: B
二、命令使用
/ l4 F9 e. }6 |" O- B* E2 w/ L9 u6 T7 L5 ~" F/ W3 i8 p
1、O(N)命令关注N的数量/ d2 X2 g- `0 G
* N; o4 g2 O" [% Y: k
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
0 ]! `* U S+ ?# K/ G' H2、禁用命令7 y! Y' M6 Q0 ~; O& n5 T0 ]. {
5 s; ?6 _ X* C# |6 R2 ~1 Z
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。8 c; F" x; s M) l, B6 N
3、合理使用select
1 L. k, N* w+ n; j$ F
% U2 F- o X8 k, a5 V, W. I3 D% x3 }, Q) b! Y
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。: S# l- C q$ y6 ~- {! ~) |3 n
4、使用批量操作提高效率
( ` Y7 ?; I i' E7 U# J p# T
# _! X% d: D- Y* n# H: T
6 q7 u8 ?7 ^8 r$ S2 E- 原生命令:例如mget、mset。5 f4 E% f; `: R+ p* p$ m2 e
- 非原生命令:可以使用pipeline提高效率。8 ]. x. K3 O+ [
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。" n& e% `+ c- n9 k
注意两者不同:
! b1 S* L0 c1 s1 n% S
& a* F& i% d& ?; A- 原生是原子操作,pipeline是非原子操作。
) X; s6 m% U% i2 z5 W" @9 G! D - pipeline可以打包不同的命令,原生做不到! @+ p0 ~: M# }% P0 w ~
- pipeline需要客户端和服务端同时支持。
0 g X2 I `3 R 5、不建议过多使用Redis事务功能
# G$ w: N3 k6 W* m& D4 U9 z2 @3 w& a
/ h: h$ N* t9 XRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
; Z3 ^2 l9 w0 l2 K% S6、Redis集群版本在使用Lua上有特殊要求' ^3 [+ ^9 Q' X8 U7 q- x, c/ z
. p5 q4 [; w- t1、所有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"
; C* F% ~9 Z1 [$ M7、monitor命令
! t1 [: K! W7 Y( b2 j% u4 g F8 \( k3 \, v2 c O
必要情况下使用monitor命令时,要注意不要长时间使用。& p9 m9 I1 e2 h1 u, R0 I/ [8 s
三、客户端使用
6 @3 }8 @$ \9 I* k6 O+ Y1 B w5 K. q8 [! q# l8 Z
1、避免多个应用使用一个Redis实例
r/ n9 l, d) `" `, E9 Y, _+ E/ Y( q
不相干的业务拆分,公共数据做服务化。! _ x0 ` W' W2 @. }* m3 s
2、使用连接池
# p: c( `+ H3 U) o' T
5 }. G! D9 k8 ?8 ]可以有效控制连接,同时提高效率,标准使用方式:
# ?6 w* h/ S/ \0 EJedis jedis = null;
# y" h3 u6 V6 L7 J3 _+ E- [$ d) Etry {
" z' y: C" @* X jedis = jedisPool.getResource;
: a- T+ U- u: ^5 `# Z2 Q4 T, B //具体的命令
! q7 A- O; w+ }$ q% D) P% a+ ^ T: Z jedis.executeCommand
# L! ^2 W- I0 o/ ~, }& T. r. Z0 q} catch (Exception e) {
6 a% V/ g; _4 n8 E% \$ a9 q3 g; f* _# ^ logger.error("op key {} error: " + e.getMessage, key, e);
' }9 r3 \5 }( \& t! A' g3 @} finally { V. s, `! w) q8 n) ~( p5 @
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
6 O5 s6 i8 C( |3 t- T if (jedis != null) 4 P3 l3 `& X; \5 W0 B
jedis.close;
( y1 n( _# U" f. T0 s' o}2 K7 L! [5 T9 a0 q
3、熔断功能
: G F5 a$ [% {
7 v5 a( z9 A0 }* A. g1 O) v' ^高并发下建议客户端添加熔断功能(例如netflix hystrix)( U$ C8 }8 ]" p4 ^' {( n4 A" F8 Q
4、合理的加密& y( r2 q5 k* }/ @
0 e! r3 E2 g% h6 }$ H设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)# i j) T6 w4 W5 J9 ^$ Z! ?
5、淘汰策略7 t* N2 x. I# l; Y
; y% B/ m7 l* s5 y+ J* O
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。% M2 B3 b: d! i" X* B' k
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。+ Q* ^! w4 a; ?- g9 @3 ^7 Z% s
其他策略如下:2 D1 v4 w% D0 |- A! M# \; B- E
1 @$ }3 h" B, t( l+ o" N
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
* V% f; j4 }9 g; ]( @+ K# L' I - allkeys-random:随机删除所有键,直到腾出足够空间为止。
1 h+ ~* ^2 K4 G6 C! H - volatile-random:随机删除过期键,直到腾出足够空间为止。
$ v8 a) |6 H" r* J - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
, n0 Y. n4 T1 T# ?& u - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。4 v% h5 e3 x4 e) k
四、相关工具
$ J* f! J8 f0 ~4 O4 R; Z: p$ `
. q$ O; |# t3 l W$ W+ V7 Q( o- \$ E1、数据同步5 m. a; j* j: j. L. t+ r
: c& ~1 x( F! F# R, N' \: _+ Qredis间数据同步可以使用:redis-port
) y4 P2 S5 R0 W# ?3 D: `3 V- b$ _5 w) O2、big key搜索
7 ]: Z, U2 R3 e3 N
5 x2 y5 `: F2 U! a' x$ r2 i9 lredis大key搜索工具8 {% |) ?; y1 V2 {: I! w7 F
3、热点key寻找
. Q* o* j# r+ X1 d8 {- N
" Q5 I+ Y; h- Z' G3 l内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
/ e/ x4 s; W8 x/ f五、删除bigkey9 I) ~6 Y8 I5 }( s
( ~+ p. y% E- [7 w2 f# y) G5 a6 _7 c0 I: M6 @) i
- 下面操作可以使用pipeline加速。
K$ b& j2 U- F$ J0 B. a - redis 4.0已经支持key的异步删除,欢迎使用。
& [/ r! ? m G" J3 B8 t 1、Hash删除: hscan + hdel
) A# x& [% ~( O
% k% N% @9 g& |5 Z1 spublic void delBigHash(String host, int port, String password, String bigHashKey) {6 Y% R: [" s6 P9 E+ B
Jedis jedis = new Jedis(host, port);2 L: \2 I" ~1 }8 X6 z& v
if (password != null && !"".equals(password)) {
/ b$ j o3 q' b, P6 @ jedis.auth(password);6 _6 h5 z/ c2 H+ f( h. Y o! t, c9 N
}0 F: h. U6 H7 i, ^5 D% M4 Q
ScanParams scanParams = new ScanParams.count(100);
; d' D* w9 O9 e& _4 [2 X* }" Y String cursor = "0";
+ D+ T2 q v" W7 B do {
* t3 P; R, R+ q ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);: U1 q4 t" l. F" o) J7 x: G
List entryList = scanResult.getResult;
# v5 H. S9 w5 m- ] r* n$ \ if (entryList != null && !entryList.isEmpty) {
+ W u, T( j# C5 q" E7 M1 r! V: F1 e for (Entry entry : entryList) {: G/ P$ J; f* c7 S2 e, }1 k Y
jedis.hdel(bigHashKey, entry.getKey);
( g: T4 Q$ V5 w# E* j }
$ i- y* b, e4 U }
9 v1 G; K& L' {4 x3 r+ r cursor = scanResult.getStringCursor;
7 H, R3 T$ C1 Y, _4 B0 a } while (!"0".equals(cursor));
+ r2 ^9 d' W: r" q, U
; c: D% b9 ~. E C//删除bigkey" A1 b( e/ d; I. i4 I" q' ^
jedis.del(bigHashKey);
5 n8 Q3 L- ], P. s! |2 J# Z5 y}
7 c+ Y6 o4 I1 V9 J6 a }2、List删除: ltrim% ?- @8 z. O7 H; p0 m
$ r1 M3 l A5 y$ o" }; ]1 Opublic void delBigList(String host, int port, String password, String bigListKey) {+ F- T* l% `: [
Jedis jedis = new Jedis(host, port);- O; r/ e1 B* ]3 w; d
if (password != null && !"".equals(password)) { W3 r9 B5 w- O: d6 z% h
jedis.auth(password); B* |0 @8 X, c, X. x' w$ j; j* P
}
; C3 ]- M, U- ?' r0 v3 z long llen = jedis.llen(bigListKey);
' x1 J z M4 ?! E int counter = 0;* I& @! s" U9 |' e* t o( a7 B
int left = 100;9 w. [, f0 A) w4 x9 F1 P
while (counter < llen) {
. a. c( V' \* Z8 Q //每次从左侧截掉100个- }% e% N/ e; w" `
jedis.ltrim(bigListKey, left, llen);
" _. E/ |6 ` I [( j( A counter += left;& _2 L% S. W4 d+ {5 v) o& d' W
}
% k1 ?$ A( ` v //最终删除key/ Q2 X4 ^$ i8 f5 n2 X; f! Z6 g
jedis.del(bigListKey);5 A7 f& ?2 I% r4 h
}3、Set删除: sscan + srem7 N/ F. X8 s- i- s
$ N: M3 x* W+ h( ppublic void delBigSet(String host, int port, String password, String bigSetKey) {
) ~6 e8 P$ M B1 h; w Jedis jedis = new Jedis(host, port);
3 \, S9 _" ^; n7 W if (password != null && !"".equals(password)) {
3 X5 P; c8 q& }3 g jedis.auth(password);
: i; `+ U3 ~' m. C5 u5 T! ?& P! p }
* ?- M9 `- X0 j- D; G+ l! f5 z, D ScanParams scanParams = new ScanParams.count(100);& [: h1 X7 O$ r
String cursor = "0";
6 W4 a6 W- G1 t9 { do {
( m$ `; s) i8 y7 S# B; {2 u ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
# a2 A( m4 P4 r! Q" W List memberList = scanResult.getResult;
. W5 a1 Q8 k% F% i6 k2 P if (memberList != null && !memberList.isEmpty) {3 t# i7 Q- F$ x( @7 r* f' m
for (String member : memberList) {
/ j E* M5 b" F5 Y6 h' x jedis.srem(bigSetKey, member);6 W+ ?" [, o, ^2 t. {
}
3 I* G% `% ^9 L" V2 o) c }; r; k& d9 F h/ a+ x
cursor = scanResult.getStringCursor; _4 U! \/ W# g f7 T& }; a
} while (!"0".equals(cursor));6 u' {1 k7 q/ }" L. M2 Q5 D
# Z* w; d! i( r3 u! Y$ |% e
//删除bigkey
$ z5 L$ @4 o. x: W* x jedis.del(bigSetKey);2 B% B* |+ |0 _' g g( R
}6 D9 h( Q% Z8 D) h
4、SortedSet删除: zscan + zrem
1 M! W: ]% U2 j' Z2 A; ?
3 t e8 } v! `0 R2 l0 F: S; o: tpublic void delBigZset(String host, int port, String password, String bigZsetKey) {
2 `& ^. Z" i, x* ^ Jedis jedis = new Jedis(host, port); 7 {% ^; _: \9 n: C6 V; L- r' |
if (password != null && !"".equals(password)) { " ?6 y! ]5 }: l% C5 ?1 ~
jedis.auth(password); & I1 ?& K6 @# m: H V
}
% u& ?2 B7 C9 E0 } ScanParams scanParams = new ScanParams.count(100);
3 o3 T8 F2 n+ t. [4 a5 X4 l String cursor = "0"; 7 I$ e6 R9 `" }4 ` z! h( D' S/ A
do { ; H5 u9 l, n. T
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
8 x$ G# k" o2 p2 e9 U: Y! i ListtupleList = scanResult.getResult; . M; b8 w1 i( z2 P# ^
if (tupleList != null && !tupleList.isEmpty) {
' O N1 Q' E v, d) c% r6 z for (Tuple tuple : tupleList) {
) O+ c, K9 @/ e9 l# T# B jedis.zrem(bigZsetKey, tuple.getElement); " f! q- O! V8 _; D9 F
} 5 x# D5 c, B/ O
}
/ K9 M$ y/ Z: [8 Y3 o- ? X cursor = scanResult.getStringCursor; & `4 \; T# @" S% \8 e W' f
} while (!"0".equals(cursor));% L' [ N1 b0 r6 |# a% N
8 ]% B- }3 B# k//删除bigkey 5 @. K) k7 ^2 t8 ?# T
jedis.del(bigZsetKey); % H1 y' j9 ^# C7 P; }" U b
} 公众号内回复“1”带你进粉丝群
% h1 R* o$ I8 O4 A; @3 \( N! y来源:http://www.yidianzixun.com/article/0LevQm7t2 w* m+ Q2 z: W. r
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|