|
|

. X) R. i7 M& o% R" i# G+ H本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
0 N7 E1 k) O, C9 G
o& c7 w- h* m- 键值设计
5 u4 ?- h- g1 D7 \, }0 F! ?: u - 命令使用2 C" N( @4 U" E+ }6 S
- 客户端使用
|( n8 k, F0 ?1 f% \/ o7 a0 e - 相关工具7 n! J8 B5 j' U9 p* J" ?3 e9 V
通过本文的介绍可以减少使用Redis过程带来的问题。
3 b, ?4 `! T1 X* y$ L& Z一、键值设计: p( K6 g" _8 P y* N, S
( A f' Y6 Q( W5 W
1、key名设计
: k( O( d' ?2 V* ?! P3 M& ~: g9 Y8 l3 ~4 j5 J7 W% o
可读性和可管理性
* @2 X. B2 Q) h9 ]+ K) X2 a7 ~0 S- `4 a: S: E
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
1 v+ }, |) Q, _4 ?4 z( F& Y, v0 D- F" q P
- ugc:video:1- N+ `, v2 z5 j! H
简洁性) M& h) L5 N, F; M' t. F7 y1 Z
' ]4 w5 r/ L- K) k保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:' R# S4 y7 a* ]8 u: q6 x- q4 m
8 I/ n% l; W' _6 P( b- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。* z( y" V [, C5 A9 f. }
不要包含特殊字符- M o; v/ f* E0 y1 p
: i# |1 i" M4 ?$ V$ d) A反例:包含空格、换行、单双引号以及其他转义字符
d; r% `; N, M) V1 ~, Q C2、value设计9 j$ a$ ~+ \# }0 X" W/ m
/ E8 L$ T9 `; X
拒绝bigkey% e5 B$ j+ c# f+ I C) T3 x- w
) R, R, ~ y1 Q( j( z, v3 W& a) c防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。3 N X: N0 f* W+ u6 ^1 e/ ^
反例:一个包含200万个元素的list。
; f5 \( q+ S2 E% O! z4 @非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法# t# o" Z7 C3 @7 q% q
选择适合的数据类型
) `/ M/ ?2 [; c1 a# ~) Z- _6 ~/ v2 }& K7 q
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
* [/ U, [" v2 i反例:, b) b, S9 A- S5 h4 D% I8 v2 Y
8 u0 ?0 e" O( H% S0 x- set user:1:name tom3 K6 D! Y; l) ~& a
- set user:1:age 19
n/ o6 X) Z6 C9 L - set user:1:favor football* ~) n/ x" S$ W4 R# V1 k4 A( F9 |" V
正例:( b, z U' n# p& w. Z0 I
* a' Z! c. V- a' c- hmset user:1 name tom age 19 favor football
- ?( L' m/ t, O4 }1 Z, z 控制key的生命周期$ S! d8 O `( r F* @; ]
$ O0 i4 u+ h F6 B# o* lredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
- h/ U2 c; r% e: Q. T& V二、命令使用: |" X2 c& D" r9 Z$ w1 ~
2 [& n& s9 }' h2 W: T0 V1、O(N)命令关注N的数量
6 O! {% i2 t3 k* F) M4 _$ S3 ^! s2 @6 h+ ?5 n
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
* y- E" x3 A+ U2、禁用命令& e9 x9 o, I3 c+ t. {3 J+ U( k% B
, x) z4 G# Y9 P- b禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。 _# m( x1 E8 ^% P5 F Y6 J3 S4 V. i
3、合理使用select
5 o' N- x: u, K3 W* j
: E/ i) v! J0 s. a y6 O' ?: J
5 j& G) N. q) iredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
& _! i- W) t( ^4、使用批量操作提高效率
% ~' u0 k& Q; g1 u: c( E; F+ a3 h
/ H0 b0 G/ ^8 r% T* ?4 B2 X: n& T( R' |4 `4 d1 L+ x
- 原生命令:例如mget、mset。6 s7 ~+ e* o J3 R+ a4 q
- 非原生命令:可以使用pipeline提高效率。
) U# R& q6 Z& _1 @. q 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。7 k, x' E8 G+ l$ _' n
注意两者不同:3 c( ~$ [- g7 l @( f1 t) J
, J: S- a# ]/ Y$ W1 S0 B- n- 原生是原子操作,pipeline是非原子操作。" Q7 p0 W5 l' }( S9 R8 i4 |
- pipeline可以打包不同的命令,原生做不到
: L' p& Q7 E7 d9 N# [ - pipeline需要客户端和服务端同时支持。
6 @, w; t& @; M! l* B 5、不建议过多使用Redis事务功能" C/ {0 A7 M% M: W+ F
; `& g4 c! `9 k" m8 W) y
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
7 [# _3 N4 D/ m% z3 R% U6、Redis集群版本在使用Lua上有特殊要求' ]9 e1 |$ w' L8 E7 U4 F8 |
: ^+ k& a0 i8 j) T2 Q0 l7 p1、所有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 m- ~& U/ X- m6 c
7、monitor命令
+ Z, b4 {& `5 G: y9 A0 A0 `9 F* T; `7 b% i3 w+ D2 |# h
必要情况下使用monitor命令时,要注意不要长时间使用。! u8 G" D& O2 C5 M3 |6 q+ p8 C: e
三、客户端使用
: V1 T' v6 |% s+ m& [ y* c3 U" j+ O& F0 I, o' N9 ]7 {# Q# _
1、避免多个应用使用一个Redis实例
k! A. F8 y0 w" Q7 m7 K. e
4 @' S0 B9 T4 B不相干的业务拆分,公共数据做服务化。
* L' d5 L d! N* Z/ ^: y2、使用连接池/ |' @" O( E) ]! i0 P
! n/ X- u1 _8 |, l: h( l- N
可以有效控制连接,同时提高效率,标准使用方式:2 q1 T# M, M7 x9 ]8 h
Jedis jedis = null;) [7 f+ {2 V0 I, ?1 g4 i: n7 k
try {& L) ^5 g# k; R
jedis = jedisPool.getResource;8 c% p6 X: {' v% I
//具体的命令0 \& S, e; K' Z- d
jedis.executeCommand# N4 z2 |1 I( N( b; u* X; _
} catch (Exception e) {
/ u* P9 h, T$ w$ R$ q logger.error("op key {} error: " + e.getMessage, key, e);
- i O# i0 {4 l. q5 {} finally {6 u+ p- K+ V+ K1 P o: f
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。6 J' Z# P1 W: P5 \7 U' F
if (jedis != null) # t5 c% }0 x6 J! d/ D( Q
jedis.close;! u& B9 F; t2 Q' |' F4 |4 O
}
6 W% B7 O$ F& p2 O3、熔断功能
- _4 k5 D2 {) \! [7 v I' F1 i$ F* I+ T
% `6 ]- V6 T6 _( X2 ~高并发下建议客户端添加熔断功能(例如netflix hystrix)5 [! }8 [) \4 n6 U0 V/ T
4、合理的加密; i: o. q: `: Z2 G O
/ Z+ T5 `& ^$ j4 z* B3 h# }设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
& k; {9 Y8 O2 I6 P# B, G5、淘汰策略
+ z7 z& p& T" L" Y) t6 Z" t7 y: t) d7 K3 Z$ X1 {' r
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。4 m0 W/ B% e7 q
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。4 v% n y, V# B0 A$ x% Z0 C
其他策略如下:: D) B# |* {1 Y) @8 t
# [3 I3 e1 C4 ]/ W2 m1 b& ]- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
$ y8 Z0 ]$ K0 e( b+ v! o5 t - allkeys-random:随机删除所有键,直到腾出足够空间为止。
- }# g' N5 c; ^) j) e4 U' f - volatile-random:随机删除过期键,直到腾出足够空间为止。
6 ~7 b" {6 Q3 v: N. f9 x4 c - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。 c: q$ w- a2 t+ `2 A
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。$ x7 w7 L k5 w ]8 \4 T
四、相关工具" w j3 `- B8 ]) h1 j
- s/ T' J5 u' S) i/ X0 X: j
1、数据同步
) n, T6 w$ V5 z+ I8 V- m2 U/ ~5 x3 Z7 ?" V; o& A( T! z3 N
redis间数据同步可以使用:redis-port8 ]0 z% M. l* w/ d& N
2、big key搜索
9 P+ ]/ S2 w" R& q* c! p& x) Y% y1 A2 M! A9 m
redis大key搜索工具
1 U; k0 H$ X1 ? g3、热点key寻找
8 P0 ?1 ^7 d+ J9 D6 H) {* k/ l
- G( Y# J9 }; b内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
* S f4 O% _6 K8 Y五、删除bigkey
{. x p7 K2 r8 o$ C" g" b T# Q s$ h% I5 f4 o" v& x, }! l
2 F) c, _$ \2 \% G& m* u) E- 下面操作可以使用pipeline加速。/ @7 e2 r' j u* a3 D
- redis 4.0已经支持key的异步删除,欢迎使用。* L V# p' `# y7 n7 W, _. S, V
1、Hash删除: hscan + hdel. P3 W7 C* X, A: q
" |2 L( Q) D/ _5 f) m% @) y' q+ e
public void delBigHash(String host, int port, String password, String bigHashKey) {9 p+ ]+ a8 [6 W; V
Jedis jedis = new Jedis(host, port);# H- {' R J( a# T
if (password != null && !"".equals(password)) {7 X- i7 M' i( t i* Q4 J+ f
jedis.auth(password);
) ^9 Q6 L5 ^$ i* v. e8 _% s }5 I1 F& l0 c$ h, b8 ]
ScanParams scanParams = new ScanParams.count(100);
( f/ m5 O% j6 g2 C String cursor = "0";
( K9 m2 t$ E- q+ y" _7 x do {
8 ^1 W7 O% w3 `' X' c ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
( P" b% k0 `% G1 M) d List entryList = scanResult.getResult;
; H# G( |! P7 S3 y7 i% N if (entryList != null && !entryList.isEmpty) {
3 F( Q+ v7 Q/ r for (Entry entry : entryList) {3 K8 {1 i( Z' N4 Y1 Q2 a% j
jedis.hdel(bigHashKey, entry.getKey);
9 _( y+ i$ `2 u7 Q4 m8 b- ? }
/ y& ]4 e( n( ` }
7 b6 ~$ |% K, U3 b1 D cursor = scanResult.getStringCursor;& c' R9 e: |& n, {
} while (!"0".equals(cursor));
& Y% i7 j. x: B2 `7 x9 r Y7 d0 d' c) V$ e5 m9 |1 a5 b+ |8 H
//删除bigkey
4 A9 a( g% `0 T& q1 } jedis.del(bigHashKey);
# }% o1 l/ o1 Q}1 @5 U i: h$ ?* h% ^/ N
2、List删除: ltrim) _% J7 `$ s" A
$ ^; r5 g& K0 V& H7 _+ N( z+ k, Lpublic void delBigList(String host, int port, String password, String bigListKey) {
* `2 {( F- F$ c. s+ H# ? Jedis jedis = new Jedis(host, port);
0 k5 J8 j6 S# _/ k; W/ t if (password != null && !"".equals(password)) {
( X! T! b) ~! C; @, E jedis.auth(password);
5 t% o; n' y4 y+ M+ m* \ }+ s4 d. f; j0 ^, X2 t: w3 B
long llen = jedis.llen(bigListKey);
4 t/ f0 o* E2 [- s int counter = 0;1 P. Y# N; }& G- M ?8 E9 e
int left = 100;# \4 w5 L" O: e0 e9 r
while (counter < llen) { ~6 Z7 Y/ L4 h, c7 ]
//每次从左侧截掉100个
2 | H0 o/ q0 K% ~/ T- A jedis.ltrim(bigListKey, left, llen);
+ |/ X8 ^2 Y, M7 l" j! D/ c counter += left;9 h( Y' d6 t- O4 h1 H p( \' X
}
3 \) v0 n: p4 V/ w; [9 | //最终删除key2 l* n' ~7 Q2 T7 D
jedis.del(bigListKey);
' k! a) W$ Q& s1 K* D# }}3、Set删除: sscan + srem4 @* R0 m6 y, O- X: C7 e
6 G+ U4 x, l3 ^, Z) opublic void delBigSet(String host, int port, String password, String bigSetKey) {
' W3 X5 s* f S" k5 f Jedis jedis = new Jedis(host, port);2 U L y4 i( j/ I: `: X
if (password != null && !"".equals(password)) {
5 A J! k2 u( \; A* Z jedis.auth(password);7 _1 G* E- n% V
}
' }; a- v' Z) q+ V* y; T ScanParams scanParams = new ScanParams.count(100);6 B7 x4 c6 V7 N K8 {
String cursor = "0";7 e) n# U y, Y% Y1 ]# z( M$ H" D
do {
4 Y0 X1 e8 [/ N: O% G! X8 k ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);/ @: w4 c9 w# S5 x
List memberList = scanResult.getResult;' H1 ]2 j2 k# W
if (memberList != null && !memberList.isEmpty) {
5 ]1 a0 t- t& p) C+ ] for (String member : memberList) {
y- T# d% |# ^" e w jedis.srem(bigSetKey, member);1 }( g: f0 P4 Q$ c9 R
}' Y+ O& A% C. k5 H0 O4 p* j
}
9 ~: w! }" g! s3 E/ s cursor = scanResult.getStringCursor;- S S- F4 H* ]& z! j
} while (!"0".equals(cursor));8 |6 h$ x- Z2 h; g' B
; [& L+ U; g, u//删除bigkey9 {+ X" W# l* ]& b
jedis.del(bigSetKey);
3 g( c; J- J+ V2 f2 E}
9 ]! U4 T: y/ w; r+ L' F, X4、SortedSet删除: zscan + zrem
& e3 c3 r' z9 o7 L/ p: h2 R, g& Q: t- }* ]. M( J3 P$ @# q n& }& E
public void delBigZset(String host, int port, String password, String bigZsetKey) {
* J! J4 x& [. ]2 b$ k! I7 I& D Jedis jedis = new Jedis(host, port);
& o9 ~# g4 }% F& N if (password != null && !"".equals(password)) {
_% C- s* o; ?. g, f. k jedis.auth(password);
* P" Z# X! g1 y }
1 Y1 h1 N* b, I1 k. Z0 a; L ScanParams scanParams = new ScanParams.count(100); & ^; x( o5 [0 V: K* O# |2 I+ b
String cursor = "0"; 7 c* u7 p4 L. o
do { . f: @" c# F9 F3 ?/ J
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 5 i! j2 ?, ]: \* \/ x% b
ListtupleList = scanResult.getResult;
$ X# P: f5 s+ v q2 M, ^ if (tupleList != null && !tupleList.isEmpty) { . S% R$ Q! `4 V/ @
for (Tuple tuple : tupleList) {
9 E4 H. y: i3 o1 ~ jedis.zrem(bigZsetKey, tuple.getElement);
$ I& g* ]$ P% s: R/ Y } , `+ O% J: r% ]$ D' ~( K1 Y
}
" O, O0 e" r" T' q& j9 i8 K cursor = scanResult.getStringCursor;
. k6 L+ u. \3 h. ~2 j } while (!"0".equals(cursor));
. G' V% W* T4 B: A, t* s" X# Y1 t3 ~# y0 R0 e( c
//删除bigkey $ z2 m$ s+ n+ x" U3 r
jedis.del(bigZsetKey); F: B. ]8 U& A
} 公众号内回复“1”带你进粉丝群
6 N, Z) p$ u3 y3 q来源:http://www.yidianzixun.com/article/0LevQm7t) @3 i) T; F, X9 O, S- ?
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|