|
|
) r9 l; K: o* K% W
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
/ |- a( V0 N+ V7 p+ E+ m
0 r& {, b9 ` T! R7 q- 键值设计4 J2 c7 w/ Y, Q: _9 f
- 命令使用
* W4 G3 Z# G1 ^ - 客户端使用$ W! p6 e- T1 v' w
- 相关工具% k) h' f; K8 Y! w
通过本文的介绍可以减少使用Redis过程带来的问题。* K. ]) b4 D$ T& ^
一、键值设计2 ^1 S% ?8 F9 z8 ]
/ R/ {2 q; F R' d! P a+ \
1、key名设计
3 K5 ~; x u* L q3 ?" d& M; F! K# ~/ N7 h& _- @, }' h' q! `
可读性和可管理性! b7 G9 Z! w6 k2 L5 \3 c) D. e. W
~" A; E9 X( B- S2 |以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
" b3 J" V0 H9 U' t1 g {, n# x8 I& [) Z
- ugc:video:13 t: W; }) h# Q! O/ I
简洁性 \2 s" q0 i8 G: ?" q
# y6 j$ Z1 s' l4 L7 G3 F e* E保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
8 o2 f6 K% v8 x" ]& F9 V( ^2 @. e6 z4 v2 j2 D, z7 L
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
% z$ ?6 @3 H2 O( h# W) `3 v& q: x 不要包含特殊字符
9 ~8 K) b$ C8 h& ?; J. c* F# a7 x3 |+ h* r
( x5 f2 e$ b( A' ?反例:包含空格、换行、单双引号以及其他转义字符
; S" K5 d& t# Z/ L, r2、value设计: F, L0 ^+ G v4 q6 h+ a0 Z# n
2 c/ a z5 ]9 X" ^0 c4 [
拒绝bigkey" q: x0 B* x* M6 N+ D8 \ E ^: W( L
+ e) W6 ?8 T1 p" F' r+ T. x" k
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。) f; g$ {6 l' V' C A8 U2 R
反例:一个包含200万个元素的list。) u) S( W' L! o1 z& v5 c
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
4 X @. P8 g1 |$ g! w) f选择适合的数据类型
. A# i" X7 d1 ~* S0 {+ W- W7 v
4 d. t5 O( Z) H+ G. B1 Z' M* r例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
* f' {. c8 F) L/ r8 k, J反例:
! q, Y2 P% x/ v! J. p s m0 q1 o. U$ v6 z. T: W' l& V
- set user:1:name tom
( z0 O! T3 W) a; C1 e' \" n; c! g - set user:1:age 19" r& \+ K2 j; Y* @
- set user:1:favor football
1 R6 R& Y9 H) R1 D" P. ~6 ^ 正例:
5 {4 y5 V. G4 W6 ?7 f+ s5 A4 N
: u" Y7 J, T4 ~3 ^/ C- hmset user:1 name tom age 19 favor football5 [1 P6 o; Z# A/ [2 [( {
控制key的生命周期& C) ~- }4 d# X0 m( ]0 u
# R% `% {6 L" G/ v* X2 \/ Lredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
U. I- `$ F7 C8 F" o$ _二、命令使用2 X/ k# L$ D. l+ v7 m7 ?
8 p3 d" N! _. P3 S- }& ]0 W1、O(N)命令关注N的数量
2 g3 R2 e0 R/ B Z$ [ W: Z) C( C, e
2 ]+ `* g4 c! l- o# O9 Y5 v例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
, L c/ x: J- X2、禁用命令
# E* ^: ]( u3 B+ `7 u) Q o2 t; s; g. l! t' ^: W
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
- m9 H/ x! A! r3 Q5 X4 c3、合理使用select
' Y& [9 u4 f3 N8 V1 Q& V' K
5 ^; @9 |4 U7 _5 c# k7 _; N! j' T
( p. z: `. H; Wredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
7 A+ H- W$ f% e2 l! P# P4、使用批量操作提高效率 H! q& Y( \; c: o0 h
' x4 k. ?# x+ F% ^& d
W( X8 Y" o: q+ e) Q- 原生命令:例如mget、mset。# o3 t! j( T0 c Q4 d+ Q# h
- 非原生命令:可以使用pipeline提高效率。
4 W Y2 e. T1 f9 g" {& y m 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
2 D2 @" a I% a3 `2 H/ N* x% V注意两者不同:+ Q8 E2 e8 g: T/ v1 N) k( y
* T" x7 [+ X7 [0 j- b4 s- 原生是原子操作,pipeline是非原子操作。 V0 f* A( W* h
- pipeline可以打包不同的命令,原生做不到
E/ \+ m2 x/ c7 Y1 Z2 f - pipeline需要客户端和服务端同时支持。6 o" E. R6 a# p2 e6 p
5、不建议过多使用Redis事务功能
?! F x- a5 x7 a- F. Y4 |7 O
7 y2 V. @0 b% [! h9 C& @Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!: M7 g) z; Q T' i) N9 P4 j3 S
6、Redis集群版本在使用Lua上有特殊要求; E# y- C' [5 d+ s
$ I2 c9 M) X) P! R* w/ p5 q6 [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"4 m6 ~9 _ M/ s1 |5 u$ z
7、monitor命令/ q: P* E h7 y7 |6 \3 N
" }; x- |- Z# o$ L5 r( r
必要情况下使用monitor命令时,要注意不要长时间使用。: J$ U: O& A( V# D, o2 H: e+ \2 n! f
三、客户端使用# |; N( p2 g7 ?5 Z& a! n! w5 A6 \
% n: D1 e' e, d# `3 `$ q1 B* [# ~1 G1、避免多个应用使用一个Redis实例5 o- ^7 S2 L" d4 Q
1 V9 N) L6 [/ ]0 o2 M: g不相干的业务拆分,公共数据做服务化。: Z. K( R( ^' [* Y
2、使用连接池
, b$ j8 k0 J a5 e- H* E3 ~6 {# n0 g
% g9 A& M4 a9 i可以有效控制连接,同时提高效率,标准使用方式:- u) I8 p& Z( x
Jedis jedis = null;/ W" C5 \% f+ U5 o# B3 T
try {6 Q" z* m; a- Y
jedis = jedisPool.getResource;1 [) C( q- G& Q
//具体的命令
7 Z2 V$ x' ~8 J jedis.executeCommand
/ a5 l& M* q4 l. V. X} catch (Exception e) {/ A% c) X/ h0 e* e' t |
logger.error("op key {} error: " + e.getMessage, key, e); u) c) v, T" d `$ i C
} finally {- Y% e8 j9 V, M% f' H1 e
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。3 N* r- ]; Z/ j! e( X5 x- i
if (jedis != null) ) R+ c$ {' b* I
jedis.close;& a, ~2 R0 \* k" k
}
9 G* O3 j5 Y' C; C; N3、熔断功能: W2 ^7 X i2 E% j) F4 a
6 P% l" s0 c K& Z3 J$ I1 S; D! @$ m- E/ Y高并发下建议客户端添加熔断功能(例如netflix hystrix)
2 O4 |2 C! W2 t2 O6 J7 u1 I( H4、合理的加密- O7 o7 t9 a! o9 d% r& k) A
: D }4 H2 I# B$ v2 ^
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
2 `0 n2 W+ r1 t( ^5、淘汰策略5 O. A: R' u; ^" @2 t
3 W: C" V4 w1 n' b: K
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
; h6 T9 a9 `4 K2 ?0 U# E默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。. t! E' I" h# }& [$ c- R8 n
其他策略如下:
3 v" b4 m8 a2 W6 K% ?- Z+ E6 C. @3 @4 [5 d- s; {
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。0 A1 o- f- @4 X* {' H1 ^
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
$ [5 o% I7 u9 w J) o# I - volatile-random:随机删除过期键,直到腾出足够空间为止。
' H/ {: a& Y/ _# V. K4 L' J1 {2 G - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。+ z7 ?4 W* c: S
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。( \/ H8 Q5 Z9 P% J
四、相关工具% c0 e. f, {' l# i% a7 n; }) b- Q
1 J& \2 r* U- j! \# ?1、数据同步
5 j/ q" f( O; n- [
/ ^2 {% x/ b" Q9 [7 oredis间数据同步可以使用:redis-port2 @& P [" j7 ~
2、big key搜索" t, S$ r$ E3 @1 d' Z" k: t: P1 ^/ \
. U. _4 r& U* Nredis大key搜索工具
- ^; b2 t4 x. V) u' ?3、热点key寻找2 r/ ?; l2 H1 @. A
, I( S" e5 H2 |7 ~
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题6 Q: c5 X% m- t) v F0 U: j
五、删除bigkey# Y0 Y+ h2 @) O/ G; V7 w- f) w
# v% e4 K& F, P3 I9 r
7 B) s x& X) V. u5 ?
- 下面操作可以使用pipeline加速。# g: c4 n( M- j' [( i
- redis 4.0已经支持key的异步删除,欢迎使用。
6 Z! E4 M' ^( u% t 1、Hash删除: hscan + hdel: c4 O* W8 N+ u5 G t% S0 q
! e% n0 Z& C. {' vpublic void delBigHash(String host, int port, String password, String bigHashKey) {" t2 p+ E9 y' f' g( [/ a
Jedis jedis = new Jedis(host, port);
+ _' {+ j9 o$ ]' R/ K- t( _6 w if (password != null && !"".equals(password)) { z. ^4 K5 r& P0 z+ j
jedis.auth(password);2 f! V$ Y' w$ ?& F! u! G5 _/ z
}4 ?. E4 w) B9 ~7 i, z. V3 N; [
ScanParams scanParams = new ScanParams.count(100);* W1 Q ?% V8 A7 R: Q
String cursor = "0";
: E* T2 W* l7 W$ I& Z0 p3 C% K2 @( m* T do {2 c) A; E' b! S
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);; s9 Y3 u9 g3 v* [9 W! M. G% x
List entryList = scanResult.getResult;
' \6 q8 Z# Q+ t0 ^& |0 c0 P' U8 P if (entryList != null && !entryList.isEmpty) {
- M8 r- J: x* p3 r! l for (Entry entry : entryList) {5 s7 v# R3 M$ x7 H; h+ l! h7 B1 n
jedis.hdel(bigHashKey, entry.getKey);, B9 r3 l- P+ S; p2 C+ Q
}
% X# D1 l) w" a; `- Y7 W }4 L( k% g; y4 q" O9 v3 j
cursor = scanResult.getStringCursor;
" X# ^+ W/ }& }+ M } while (!"0".equals(cursor));
2 D+ s% H7 c+ S3 K% }2 W$ a0 w$ U! l$ c; F# ~) O4 g
//删除bigkey
. I! i- f4 q: I- t' Y' e$ n jedis.del(bigHashKey);2 j3 R1 ^3 ]% n% a
}
/ ]; D) f: q. C2、List删除: ltrim
$ P7 j8 j, h H: ~! s1 z2 V1 C Q+ z/ `0 I3 E( \# P1 h
public void delBigList(String host, int port, String password, String bigListKey) {; b' E) x0 d" s+ r( A
Jedis jedis = new Jedis(host, port);
( `3 P; m- n4 L' F# U if (password != null && !"".equals(password)) {
+ B) w$ Y" o! [5 c1 O$ g jedis.auth(password);/ U2 n) w: f* x. S3 x
}# C" Z! Q" [ j M
long llen = jedis.llen(bigListKey);1 J+ |6 `5 Y( ?+ s) p
int counter = 0;
$ N& M! g9 K% Q8 N) o- `: y! Z' ^" p2 k int left = 100;" Q. ]( Q( e% h3 P
while (counter < llen) {
" U( m) {5 Y3 o4 u //每次从左侧截掉100个. a$ \' `9 E D8 `* Y+ }6 Y: k
jedis.ltrim(bigListKey, left, llen);
* t: g. v( E, d: w9 W counter += left;# A: Z9 R& T9 l2 `4 e4 k9 U+ t! J
}( k" b: V4 A. E
//最终删除key
9 Q. ]0 q5 N- g9 Y jedis.del(bigListKey);
4 ^( B- T1 h- \}3、Set删除: sscan + srem
8 ], F4 n) T3 o( F7 m% n' U
/ L8 F' m% T5 U Opublic void delBigSet(String host, int port, String password, String bigSetKey) {6 w% Z6 @! W$ ~9 f
Jedis jedis = new Jedis(host, port);
" M$ Q X9 d5 S$ Y4 g- j1 Q if (password != null && !"".equals(password)) {* ]9 s4 E. @. R$ m6 i/ |
jedis.auth(password);
s& @' R4 @" C- i7 m }
( d; R) k: K, y7 Y3 {+ F ScanParams scanParams = new ScanParams.count(100);/ z) ?+ b( z; j8 w8 x& d& E
String cursor = "0";& A% n x4 e, ]. P F' A
do {
, \: \* n& Z$ M9 u ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);1 J* _ B! g6 p( I/ X( i4 ?
List memberList = scanResult.getResult;" _( h$ g- X/ {0 Q; ^2 U$ i0 I
if (memberList != null && !memberList.isEmpty) {9 y9 |: J Y5 u8 V
for (String member : memberList) {6 F- x& k1 `: {
jedis.srem(bigSetKey, member);9 c" k* A5 S7 D' w- w
}
+ O: d3 W- P8 ]3 w& p8 Q: X }- G/ r; F+ v) e$ Q/ D
cursor = scanResult.getStringCursor;2 a8 U9 @; `! }8 _- M5 U
} while (!"0".equals(cursor));3 Q' Z6 z! i8 V" F4 v5 A m
+ m/ D! c2 b4 H0 Y+ J+ Q//删除bigkey
' `( P4 E: H3 ^8 k6 j* b jedis.del(bigSetKey);* w5 N0 O. \* q+ H" j& h! n1 W
}
7 `7 ?( `2 B! Q" _4、SortedSet删除: zscan + zrem
6 |0 A& v$ S7 s
( c' M& T8 _! kpublic void delBigZset(String host, int port, String password, String bigZsetKey) { ' o5 \( [8 b4 \! B4 P( n6 A9 @
Jedis jedis = new Jedis(host, port); V5 t4 k3 f8 l) k3 o8 |4 {$ I' K
if (password != null && !"".equals(password)) { ) J; `3 w& Z9 e& X4 P
jedis.auth(password);
5 w5 O n' @2 C/ e {2 t } 6 A" c6 p( o* z+ W. l
ScanParams scanParams = new ScanParams.count(100);
% R4 U, X+ M# K& T& t9 r String cursor = "0"; 3 |( b. r' R* ?, Z. t1 d8 G2 b
do {
% c% Z7 a4 q! a6 D ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
6 ?7 r1 s* v' {6 T" Z* j7 f; x ListtupleList = scanResult.getResult; W% b' `3 v; b* ?3 H
if (tupleList != null && !tupleList.isEmpty) { * @. E9 \; w+ [7 V' L' R
for (Tuple tuple : tupleList) { 6 u7 m8 Z/ g- g
jedis.zrem(bigZsetKey, tuple.getElement); 3 w9 S) Y9 D% }- f1 A+ C" h3 _
} ) P( l& {' D" j+ c: w: I& q- t
} 3 A3 I3 x( S- o+ r0 s+ C
cursor = scanResult.getStringCursor;
" h3 M9 i7 B) T" q/ k% F$ t } while (!"0".equals(cursor));
7 V( J# f2 H7 i3 H( C/ c6 A0 \" G# u' `: A, E+ r" A
//删除bigkey / @# |7 {$ Q0 S& q+ [
jedis.del(bigZsetKey); , f, l; x7 s& D6 a7 C
} 公众号内回复“1”带你进粉丝群
8 b8 |1 o! P" }# Z" A来源:http://www.yidianzixun.com/article/0LevQm7t
5 x) i' A g8 U( O% ?* u免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|