|
|
% K( W% g: z& M" k/ K5 j8 \
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。5 _% F* ]: n$ C2 f8 t" t
9 ]* z* v/ x9 g) c1 V9 ~% J
- 键值设计$ [7 r- R* v4 B8 s) @
- 命令使用4 S& c' J6 C) Y" M3 u
- 客户端使用
8 l- P0 c( z# R# \* Q9 m - 相关工具
0 z! _8 r2 v2 D$ P 通过本文的介绍可以减少使用Redis过程带来的问题。
7 J3 a$ p( l% A一、键值设计
4 Y" A0 ~8 Y; i% {7 W4 \9 M) A0 S0 ?6 b) N) x8 q) p& O+ R4 u
1、key名设计
( O" X3 w4 [+ L2 E' }9 a' c1 }0 I/ _1 p
可读性和可管理性
Q6 N$ D% a1 G D5 v2 R
: o5 U* z. Q. i3 k以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id' \) r) C* K* Y' [* ^
4 P% v/ y1 J6 g- X# J* r) Q7 n
- ugc:video:1' Y" M8 M- N/ u4 O
简洁性. I+ L' x; p D+ z: W# p
0 C+ |1 M- f8 i6 ? J
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
1 o3 a& v6 u" M# j K+ T$ E; e0 t1 S" }7 e/ [0 T. |5 y, e. U
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
6 Z N( y! K/ [# K1 F* W" l- ] 不要包含特殊字符
O+ s7 U4 B1 U5 d% _7 ]! g1 |1 `. R, U$ f8 U( X* r
反例:包含空格、换行、单双引号以及其他转义字符
1 {# @+ q* S4 E, \" F4 D1 c2、value设计6 `. l/ [/ M z1 j* u2 y( ~$ l5 p* J6 S
+ }3 `3 p1 @7 s% ~2 \拒绝bigkey, x" B. W, Z. ~! X+ _
4 ]& I D0 t& V1 N1 G! a& n防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
2 M) M, ~0 O3 d* g; s0 _$ Q反例:一个包含200万个元素的list。
' P. z& B7 p/ u- n+ a) ?2 p非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
?8 a8 a' L t选择适合的数据类型6 G* q4 n3 _, L/ {
3 [" p9 b' F. J2 E" L8 z/ z
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
* k( R5 [& ]/ S) O- K反例:5 V, D" C% y" |( k
/ \- ^: R2 p. ?; q# Q
- set user:1:name tom
4 E. k/ W8 X% @4 u0 c, G+ Z - set user:1:age 19
$ s: C& J( v- R& H2 A - set user:1:favor football( T# p2 t1 b8 e/ Y) E. X
正例:
8 {* ? m. t% I) C% a2 P
6 D1 u& |8 S5 n- hmset user:1 name tom age 19 favor football( |( l7 U$ b% L( A2 |. V
控制key的生命周期
8 s2 Q" ~% h! y. z! w/ {6 f, a2 ]7 h. A+ i" @# }/ n* C- c
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
) v J; P7 J. R/ Y& E二、命令使用+ i6 s" h/ t7 V# O% O' B9 f
5 E2 j9 U1 ]" {3 C' k9 J3 h$ V1、O(N)命令关注N的数量
7 R& I5 g. T* r/ c5 q* ?
) {7 |* ]; V( h1 |! ^例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。0 C7 `0 A. y6 ?- T: ]
2、禁用命令- t7 o3 W( l% x9 x" Z& E: K% @' \
9 G. I* `7 Z) i禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
( k2 d9 s# C6 I( B+ I4 s3、合理使用select
1 D" t8 g$ D& X& E& u0 u5 y
, S' U7 @" X/ o
2 F% Y* j' W6 e( j1 _redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
7 o# k: W! {9 S7 T6 p2 h4 `5 i4、使用批量操作提高效率" f$ \) }, Y* T
; j6 j1 l* S/ I' g0 Y( V
2 }! F: g, b2 _! |; k
- 原生命令:例如mget、mset。* ?" B- q$ O' B( W% v @# o# n8 @
- 非原生命令:可以使用pipeline提高效率。
2 C# L6 | v0 S" o* V4 o: t 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。, _1 {2 P0 E# j0 O) [: ]* p
注意两者不同:; |# s& t( w. [6 ~ p
4 I# I3 U& {! s; \4 e: K& n* p9 m5 R
- 原生是原子操作,pipeline是非原子操作。- p& o' p0 @$ W5 h- _" w; }
- pipeline可以打包不同的命令,原生做不到; a. g) V! s( z4 _; d' o
- pipeline需要客户端和服务端同时支持。
& c+ v- Z0 {/ t5 d% _+ F 5、不建议过多使用Redis事务功能, _; E, y3 s& {% S$ ?3 e
! g! n$ ]3 H2 E' U" Z
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!( r" ^: m* v% Q
6、Redis集群版本在使用Lua上有特殊要求6 O' q f1 [$ p0 ]5 Z8 Q
* f+ y& ]1 {8 S5 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"
0 V+ I( t; R$ [7 N+ N6 c; Q7、monitor命令% y$ O, f7 ~* V6 Z! ~
0 y Y& m4 z3 P% F- K4 M Q& W% L必要情况下使用monitor命令时,要注意不要长时间使用。% x* R6 [; [& Z, @9 X) R) Q. b3 Q
三、客户端使用
N. k8 [1 K! M3 q7 M0 I P
' g8 I! |# S% c" U |4 o1、避免多个应用使用一个Redis实例+ {7 i; P6 J9 F( v' F ~
9 y' T: b/ H! c p不相干的业务拆分,公共数据做服务化。
8 U4 B1 X4 F" k1 R8 b2、使用连接池
& f! x: s: \% ~ W" j# W0 l4 {8 n# y; E4 O9 A
可以有效控制连接,同时提高效率,标准使用方式:
7 C& i% s2 d, l7 ]. WJedis jedis = null;" ~; w4 q0 m# X, H8 F, Q: z
try {
- V K# Y2 M1 W4 n9 } jedis = jedisPool.getResource;/ I1 H- |4 `$ B5 q( \+ J
//具体的命令
) |* C. u; ^8 W" ]9 C# y jedis.executeCommand/ X8 e& q, c0 \8 F* n* {4 C" u
} catch (Exception e) {
& B$ r$ C, A8 L: A# I logger.error("op key {} error: " + e.getMessage, key, e);
2 p2 D3 f8 l- t3 O} finally {$ c6 \" o% }# e% e" r M
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
; V5 O" `: c0 d if (jedis != null)
6 T- k6 R4 Y3 ?% X- P3 A7 ? jedis.close;5 m* S( U: Y. @0 h8 O
}
# {. ^5 o/ v. x/ L2 d* G: `8 Y3、熔断功能$ G. e1 }& ?4 y6 p/ X: b4 F
6 g: M6 I$ U) M# W5 J1 R3 j, ?高并发下建议客户端添加熔断功能(例如netflix hystrix)2 F- k, x! k8 z) R" z
4、合理的加密
% s/ R7 _6 m' D( N z4 D% J
# l3 y9 X0 ^2 \: E设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)7 x' g- O' O% f" ~
5、淘汰策略
9 J: K% f+ ^4 ~5 c. O3 y5 }+ }, C1 c, e* T
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。* Y; ^ O- g9 W/ X+ N
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。" }5 W J0 q6 d2 o% j
其他策略如下:
4 c( y$ s G% q" D5 n0 @. b- i/ C0 |
5 [. a2 E$ `( C6 p- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。- D6 }( S A$ L" l; f
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
( c' r: E2 O# S. [. \ D. Q - volatile-random:随机删除过期键,直到腾出足够空间为止。
, |& ?) [5 \, W: i2 P - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。 s$ |; G8 |! g2 ]6 @, [
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
7 e8 A: P, @$ p( @5 R6 O$ @$ [ 四、相关工具& x% W! Z W, A+ H
: i, X* x/ d+ l: {: [' |# S
1、数据同步
2 g5 D' }; Z5 M8 R+ ^# y" C" g K$ H: M- p$ i7 o5 D7 Z; d
redis间数据同步可以使用:redis-port
, x; H. S1 D+ E" r2、big key搜索
7 Q# D; l1 x; }7 j5 e6 I
6 v' B. _4 a6 P6 nredis大key搜索工具
! b) {) C/ M+ C; T" x3、热点key寻找
" `$ ?- T5 B4 ^. P0 l) H
0 {3 F" b& [9 ~: \! ^3 C+ i内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
& s# h8 `) J1 Q, T/ g" M: S- [五、删除bigkey
+ ] F- I8 y! V1 R2 n) k# l3 h( l) O, y
1 R7 O' m! p" W9 S- 下面操作可以使用pipeline加速。
8 }( v0 O! `7 v5 h& z. K- B3 [7 [ - redis 4.0已经支持key的异步删除,欢迎使用。
: X v) E A1 h5 n6 ~* u: C 1、Hash删除: hscan + hdel
" l% D* m& h* h
) ^8 e$ K% R$ E3 q3 d# \: qpublic void delBigHash(String host, int port, String password, String bigHashKey) {
: R; `$ \7 p5 T, E3 ~. \ Jedis jedis = new Jedis(host, port);) q3 H( ~2 } f0 J" R- u8 o
if (password != null && !"".equals(password)) {6 L( |& Y; Q: p, P. A7 v
jedis.auth(password);" K. G; k5 F& ^. x0 f
}) a0 X w( d1 L3 Q9 @
ScanParams scanParams = new ScanParams.count(100);
8 ^. L1 v. |0 r( \ B9 `- } String cursor = "0";* D" d Z; I5 r
do {$ \. i/ |$ V( N) u2 Q4 d4 O4 T4 U
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);. `$ J/ y. m& r1 R K( ^( A* r
List entryList = scanResult.getResult;
, ]! e; T* X& c if (entryList != null && !entryList.isEmpty) {
3 c" W+ k( X& U for (Entry entry : entryList) {
8 [. A {* ?" j* q% T' ? jedis.hdel(bigHashKey, entry.getKey);
, Q0 q! ~, \# ^4 y1 M6 v }
, O% a4 { Q# K8 z* Q! B }
6 \+ _7 [# H3 q0 @ cursor = scanResult.getStringCursor;& x% [0 ?/ P: |3 ^8 s9 |! h5 z
} while (!"0".equals(cursor));
4 i7 ~' c2 w0 Q0 u% r; T
7 k1 H2 H7 O1 ~* M//删除bigkey
2 G, k" B& E' v4 |* }6 S jedis.del(bigHashKey);0 f# o' M0 j* @' z
}
: I+ S9 r" U4 O. g0 s9 B2 ?2、List删除: ltrim
4 h1 U& _; `% m% M8 C/ H
: Y) |0 z; t/ G4 Ypublic void delBigList(String host, int port, String password, String bigListKey) {
I" L2 p" x' m6 r& a- k8 i) w6 T+ t Jedis jedis = new Jedis(host, port);; W( ?0 E! N# d+ d' q% a0 ]
if (password != null && !"".equals(password)) {# V% `' [9 g, I% n9 n. R$ ~- i
jedis.auth(password);
1 d: g9 c$ a2 ^! v7 P0 Q }
0 ^ L! }) | E# s* t( r; j long llen = jedis.llen(bigListKey);
4 o4 E# j. B S% d int counter = 0;
' ?: E) v2 { g* O int left = 100;
' s- g( S) O! ] while (counter < llen) {
5 \: s- T* B$ E t% C- z- ` //每次从左侧截掉100个
+ @( I. z, ~% ` jedis.ltrim(bigListKey, left, llen);
F+ Q! X9 ~6 A/ P Z5 x counter += left;+ J! L# y( |! s8 X5 S
}8 s0 W' _2 w ?8 n
//最终删除key. \2 H& M! k, c3 l; I8 k
jedis.del(bigListKey);
# i- ]! {1 {8 O}3、Set删除: sscan + srem
! ?! U/ B; f0 c6 x4 y7 `- J ^( }- M; ~& }4 k2 Y+ r- j- b
public void delBigSet(String host, int port, String password, String bigSetKey) {
: R- d# W) z! \9 P' S) N9 [7 N Jedis jedis = new Jedis(host, port);
" Z0 S5 P5 r! @# H V5 w" ` if (password != null && !"".equals(password)) {" S& w4 C; }5 m% f, {
jedis.auth(password);
9 Q: l( Y) ]. D: l' ~ }4 | z1 v4 r! G
ScanParams scanParams = new ScanParams.count(100);) }% D. F6 P2 P6 f. E* X
String cursor = "0";
4 T. _ }7 ?% g" p7 |5 @ do {) N5 { C3 @& |! P. ]
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);" d1 x7 S5 A) Y1 X. |$ a3 g( g
List memberList = scanResult.getResult;
& R5 h S9 R7 L8 B if (memberList != null && !memberList.isEmpty) {+ R8 B* t Z( }4 A* b2 Y
for (String member : memberList) {
% H, a# a, V1 }# U7 R R* Q jedis.srem(bigSetKey, member);& B6 h, n4 V5 J5 B! W
}
7 M) Z3 \3 G5 {7 e5 X' J }, s% ]# j3 a# S0 h$ h$ n
cursor = scanResult.getStringCursor;; v6 j7 F9 z# t( }* s
} while (!"0".equals(cursor));8 K8 ~; R/ p$ U; E
I2 I( a/ {; A# N ?. J1 g//删除bigkey3 i; L, V3 a3 a$ _
jedis.del(bigSetKey);. `$ S# ?5 C& x" r; z) L
}1 q* O M/ ?: c/ S* s6 i
4、SortedSet删除: zscan + zrem
H: G$ U! C, ]6 i7 J! z9 Y0 Z% ]
! o( E) e5 t* R5 \! n( \, z2 |public void delBigZset(String host, int port, String password, String bigZsetKey) { 4 A5 X2 k3 n7 O/ g2 i* B
Jedis jedis = new Jedis(host, port); * _' |9 q# @- G2 ^+ T1 Y, I& W" L# X9 {
if (password != null && !"".equals(password)) { . W# I" w6 Q* ?4 P: \7 @
jedis.auth(password); : M+ Y' E) k) e- a# c5 T
} 9 W! m/ n2 q' I0 ~5 K4 t6 I
ScanParams scanParams = new ScanParams.count(100);
4 }9 _1 {; d- F' W5 ?+ Y2 V4 u String cursor = "0";
' V. K/ B/ O6 T, j$ N2 C# x2 e W do { A6 y$ V* _3 F) e" |$ O
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
3 B3 H# _( W3 d9 k2 w, ~ ListtupleList = scanResult.getResult; " X# j4 u7 d9 B, J7 }; H, k w
if (tupleList != null && !tupleList.isEmpty) {
% x# @4 q: M1 g- ^- d for (Tuple tuple : tupleList) {
1 ^4 F( y( p' q& d jedis.zrem(bigZsetKey, tuple.getElement); 9 }. J" Y7 e/ J" ]2 m
}
+ J/ @ B. u9 v" M( M4 K }
9 x- P! ~! T% F cursor = scanResult.getStringCursor; 7 u4 T( ~* ?0 _3 y# U2 p4 ?+ M. N
} while (!"0".equals(cursor));
# B8 @5 ^6 i# w. u) @
- q: A. t. g. I6 x/ @. T% R/ q//删除bigkey * C# x% e, K3 R6 @
jedis.del(bigZsetKey);
& h' M+ M- R( v5 g} 公众号内回复“1”带你进粉丝群
* l/ k: }$ f, J# c) i6 O来源:http://www.yidianzixun.com/article/0LevQm7t
, G! l2 Q3 N* R/ z& L" v免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|