|
|
2 A( e, p E: U/ ^3 ?1 ]8 l
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
, |/ r/ H& m0 w+ x. x- L% i2 Q
- l) I4 ]& @0 H; A; x) ?% ~4 D- 键值设计
2 o5 W. B. g$ y. P D3 ?- W: J - 命令使用
+ x$ s7 \# ]8 c4 w: P3 O9 u - 客户端使用' G+ X/ F; ]4 @7 X
- 相关工具
# J& q$ o4 T3 M2 C 通过本文的介绍可以减少使用Redis过程带来的问题。
7 v% B. }0 _+ A9 y, W, Z一、键值设计
, `/ P8 T" ~2 U' X" R4 E% j+ K; K5 j2 }+ w( W) C; ?
1、key名设计5 ^- z/ M8 i H' q8 x. W" ?0 m# F1 f
( A5 X6 B: _2 q9 D: i2 U) G% I+ E可读性和可管理性
; }5 j4 `2 o U4 B
0 h9 |' v. X1 S7 k- \以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
5 ? T/ |! P) t6 j# E! S# t" p1 `
- ugc:video:1
/ N1 N9 f) S$ V' @ 简洁性
3 L9 n. i2 m0 b+ n3 q q, G6 T1 `
8 K, T3 o; j! d( J5 I保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
+ \0 U' X- U- X+ e
6 n: i; ~$ j' [" L; ]$ F- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。 e% D; D: `- ]6 \* Y
不要包含特殊字符
' a# W& Y3 j9 G7 o9 ]1 U1 `- R. \8 }. [& K6 B* [ v2 n7 F
反例:包含空格、换行、单双引号以及其他转义字符8 ^4 S% G% j# q
2、value设计
; U* {; k1 g( H1 |9 v; V+ t
' S' E& |0 _ R H1 u% X% @拒绝bigkey
( t; f2 W2 @; m$ S7 u2 N) n+ ]- r' ^8 W
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
. a; Y' {! I5 T/ C1 o D) o# M反例:一个包含200万个元素的list。
; C1 B0 k7 j$ G# j' l非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
- O. y- m) u( S# x5 w- S选择适合的数据类型
. L% M6 `6 ~: W) K, ]7 q
`/ Z7 v S4 i# d! L% h例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?: \1 S$ V$ J6 P5 i: \5 j
反例:
0 d; V: I6 I3 B+ e' ^8 @( w* f
2 a$ c$ Y$ C8 \2 o- set user:1:name tom
, k) D( Q9 e8 n2 O8 [/ i - set user:1:age 197 f0 F X/ c+ m" h" j; N
- set user:1:favor football
3 l: h4 U) v. [$ V 正例:
7 h2 I5 M. n2 I6 y5 a
( Y$ k4 H! D: u4 y( q- hmset user:1 name tom age 19 favor football
! ` A" }9 K$ |# j 控制key的生命周期+ F) T& A, K2 L l; F3 n y0 B: Q, F3 L
0 n4 P8 D/ Z# M9 b/ Iredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。/ b) l8 w# y" B& h+ e+ q) v
二、命令使用( x6 \5 J" M1 l4 E7 e9 n
* s$ w+ q$ B. F3 `6 X+ B' d1、O(N)命令关注N的数量+ U' @! Y9 }. X; m/ Y
T M2 ~; U+ N6 S! Y
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。+ Y& _ ?4 T1 l
2、禁用命令
: K. K) K9 ]7 v! }
9 E" G% H7 ^* W$ ?; D( ?禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
# i0 H. m: u* _4 i" M- ~( x3、合理使用select7 M3 B7 u9 L$ K5 d# b7 I3 z
' I. D- \& b2 _& N
) G; N% b, V4 V
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。% D6 p5 k- B* c
4、使用批量操作提高效率
1 o; ^% D% a R* q: C) O9 _0 W7 ~
' }2 j; [4 }6 d( f2 }8 I
7 _7 i& s u( O) U3 ~5 Z- 原生命令:例如mget、mset。0 x/ c/ Y+ ^6 e& m7 m
- 非原生命令:可以使用pipeline提高效率。% c0 ?7 \8 C9 a2 `+ U5 A3 ]& H
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
6 r8 B/ j$ a8 ~# x, m4 S0 M注意两者不同:
4 m4 D( U4 [7 M5 I7 q* ]8 |
! d5 P2 x6 M5 g4 A- P- 原生是原子操作,pipeline是非原子操作。
9 q, v0 d6 v: o - pipeline可以打包不同的命令,原生做不到
$ R1 T" ~, ]6 E5 e' p7 R5 z - pipeline需要客户端和服务端同时支持。) r9 |) I3 V& R: z7 t
5、不建议过多使用Redis事务功能
1 F* F: d* l2 @- y# E/ H8 @* }9 }3 v3 D) s/ { b M
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
; q8 g {* [( O {8 O. v' v9 w6、Redis集群版本在使用Lua上有特殊要求& t$ V- @1 f9 t, `& y% c
9 {) @9 I0 J! G' v$ A. O
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"
& o3 |* w. a% d; I; U( j4 m7、monitor命令% ]2 T8 s/ M# G. R. j; P
" f0 v: `. z4 Z: ^/ b
必要情况下使用monitor命令时,要注意不要长时间使用。
; x' {% a2 c2 W0 D2 z% V三、客户端使用" j; w" S3 I( ~# a W5 s
0 ^: j* r" C/ F( K1、避免多个应用使用一个Redis实例
/ }3 Y5 w- s/ w3 f/ v% X5 [; ^9 r( d6 |+ O' o
不相干的业务拆分,公共数据做服务化。
' [1 V- H( L' I' C2、使用连接池
) ?/ c% R' t3 h! k3 U
2 _% v7 j1 Q4 D可以有效控制连接,同时提高效率,标准使用方式:
T8 v; B/ a3 e) YJedis jedis = null;8 v% O2 H* e3 i7 x( ]$ p# H! B
try {
, A) o. ?8 J" I4 ~ jedis = jedisPool.getResource;
3 i0 h" y; ]3 K, Y8 Z1 v! _ //具体的命令
( v' h3 f! |8 l, O5 h. ]$ y jedis.executeCommand
/ Y1 |- |) Q- `$ j) n. d9 e} catch (Exception e) {
; E( D( i1 w0 o2 X6 F5 L) l' c logger.error("op key {} error: " + e.getMessage, key, e);: x$ O+ K5 y) x6 [
} finally {$ i1 z' w( ?6 Q( B( R
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。! w3 [$ a, E( k# N# i. ^ e
if (jedis != null)
. q) N: l6 C7 a8 l7 l2 I9 L" F jedis.close;
0 y" `1 T5 g9 X- ^( U D}) F+ u* X7 O4 x" N, k A
3、熔断功能
0 J: u7 ]) `3 @/ \3 w6 S- A$ P9 X7 Z7 ~5 n/ w
高并发下建议客户端添加熔断功能(例如netflix hystrix)
6 X' f% b1 J: P) V3 ]; Q3 m; ]4、合理的加密
# r. D8 k( S% |1 P( \% K8 k) `9 P3 M8 l
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
. \% g$ y, }7 P! z$ x/ o5、淘汰策略, l0 S+ [) w0 E$ g; o$ f% I, R
^1 ?+ l) p! t根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。* c2 ^" h |3 j Z x8 j7 J
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。( ]1 C! v8 b4 K. ~' d9 u
其他策略如下:
2 T/ R* {' x+ Q# D' ^ D. Z# r* V$ }) d% p7 p( R9 ~/ `
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。* z7 r4 p' P5 ?+ S0 A, q3 n+ D
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
" K& e# c0 ?7 O2 y) f& h - volatile-random:随机删除过期键,直到腾出足够空间为止。! c/ ]% S4 Y- q* u8 j# T
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
8 e8 m4 `, r& f. n, r1 ^* @ - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。9 N- m) s, L% l3 ~1 b( j9 H7 S
四、相关工具
0 o4 H ^7 ~& @0 _" g Q
/ i4 |: _1 H6 H+ }2 v1、数据同步
# g1 R2 f6 t# l
- c6 `) u& y5 s7 B) a5 Q* P' w$ k% Mredis间数据同步可以使用:redis-port
( m' L$ Y# |8 m% Q2、big key搜索. q- P% k u+ }1 N+ R2 `5 ]5 o3 D
( _; H9 J9 Z$ R1 F2 B
redis大key搜索工具" p/ d/ R, V. l0 {( E1 L
3、热点key寻找( c/ s+ X$ |# v& f9 ^/ [( U
/ E7 U8 \" [' d
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题5 o( s2 [8 y0 J
五、删除bigkey
: G; `. J3 s/ n$ A6 f q2 Y" H0 I9 O- W+ h' o7 e0 w
9 L7 U& T/ o2 U# v0 [
- 下面操作可以使用pipeline加速。. D- K6 d9 J$ V
- redis 4.0已经支持key的异步删除,欢迎使用。
4 t# h8 c6 Z% J9 v6 F& } 1、Hash删除: hscan + hdel5 N* K7 D$ y, {
y; ]: K, K( dpublic void delBigHash(String host, int port, String password, String bigHashKey) {6 i6 }3 j8 I l/ m( J/ L" ?5 M
Jedis jedis = new Jedis(host, port);8 p" C. U6 t1 N
if (password != null && !"".equals(password)) {
& C0 l3 z8 Y q( u" C9 t7 g) A jedis.auth(password);
6 [. J+ v) ]8 m6 h }, c5 \' }% v, a) V% F( h/ \* Q
ScanParams scanParams = new ScanParams.count(100);% ?. v3 F; ~) V' v# u6 k
String cursor = "0";+ T) d( t0 u& x @! k
do {
: R2 S1 I8 J" T ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
, g3 u* t, n$ _4 n( u* M List entryList = scanResult.getResult;. t4 w; l; y, K& ]
if (entryList != null && !entryList.isEmpty) { x: V3 U: D. Z0 Q
for (Entry entry : entryList) {! Z) Z T& t2 U0 R- r1 {* h8 ~! w8 @% q
jedis.hdel(bigHashKey, entry.getKey);: w' Y$ a% R7 j3 D T: c
}
8 j, n* t- U. H1 i; M9 B7 T/ J) M }& S" P' I: \9 W: m" L1 _3 C- K
cursor = scanResult.getStringCursor;
) {' l1 t4 g2 V- a6 P1 ~ } while (!"0".equals(cursor));
]# V1 m9 G+ @ W8 `! Y, t' c
; m) W) J0 X+ h//删除bigkey
& ~0 L: |4 C$ ^4 D jedis.del(bigHashKey);
. B9 R! U& e* O}, [2 {2 s& H" H5 R& |
2、List删除: ltrim F8 f k! S: z) L; D
) i& d4 v7 J) X+ x" Q! y
public void delBigList(String host, int port, String password, String bigListKey) {9 I1 U, q; o8 X$ d) p1 c
Jedis jedis = new Jedis(host, port);
( c; a& e( S5 W' M% y' W- ^ if (password != null && !"".equals(password)) {
. [( G d3 }; ~0 L/ _; N jedis.auth(password);
" d! A- E# u: p$ R }
( j: ]$ d8 R. b- n; n6 A long llen = jedis.llen(bigListKey);
( ~/ ]* P( e y- J9 A; h$ s9 I+ s int counter = 0;
$ G4 J$ @9 H: x" m0 k3 z% P5 U int left = 100;
0 I7 z' t3 ?7 ^: W6 S5 { while (counter < llen) {
4 S8 v' t* E+ U7 x //每次从左侧截掉100个% Q3 Q( w' c4 u! g8 F
jedis.ltrim(bigListKey, left, llen);! R! I; N. |4 l* ^
counter += left;
% U( t+ Q0 f* k$ f$ a5 h+ l }
3 b: r1 `% g8 ^, Q8 C( Q6 U //最终删除key
( J' `3 Y; { v4 x- f( ^ jedis.del(bigListKey);
( B8 p6 }8 Z M! m9 l0 L}3、Set删除: sscan + srem
~( |0 u* [% ^1 C z, Z) y/ d7 N, |7 e* {; k& u9 W
public void delBigSet(String host, int port, String password, String bigSetKey) {
% @$ E1 F v- E/ |2 n Jedis jedis = new Jedis(host, port);
: E* D+ y- ^* A3 z if (password != null && !"".equals(password)) {
* B. `# A9 \* e: K jedis.auth(password);9 m& `! s" M" q$ j0 |& g. I
}
1 Q2 u& P' O5 A+ ^( r3 x! Q ScanParams scanParams = new ScanParams.count(100);
3 _3 O) T) p$ [ String cursor = "0";
5 G% V3 y/ w. m% k do {! Q6 ?* o; e, x
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
3 I+ t5 G4 p$ B: d3 S) | List memberList = scanResult.getResult;
$ p1 v0 f3 B" j* @0 {% v* K$ K if (memberList != null && !memberList.isEmpty) {
0 h! L6 i3 I! }/ T- v for (String member : memberList) {
& n h+ n1 L9 f) m: M jedis.srem(bigSetKey, member);, e* Q( ]8 I$ Z& n# e
}! |; [8 u" p7 t) `- K- j0 a
}
; ?- T) K: w0 P; I# v cursor = scanResult.getStringCursor;
9 d) p' q C+ R } while (!"0".equals(cursor));
% c7 ?& T6 }$ L
% u% W' b2 i1 O8 q//删除bigkey! _( \; K$ b" X$ S& C1 Z1 E& y- X7 U: R
jedis.del(bigSetKey);( ]( h/ K* j* k: L% Y* D2 j
}
2 J6 m0 l1 V8 g2 d) m. G* {4、SortedSet删除: zscan + zrem; ~2 q) H+ x* l" K& S2 f
}9 h. V; e; |9 v g& Opublic void delBigZset(String host, int port, String password, String bigZsetKey) {
* m2 {4 t- V5 v2 [" O Jedis jedis = new Jedis(host, port); * Q& b4 f# ~( k$ D+ y
if (password != null && !"".equals(password)) { ; A) [, k# z( N5 L* d/ ] }
jedis.auth(password);
, j. L( ~* i9 m8 |) _/ Y7 v }
! B. \4 R% X" h+ o: T ScanParams scanParams = new ScanParams.count(100); ) G( z7 j' c% E6 k @
String cursor = "0";
9 p5 z' ~' v: O do {
7 P6 r7 @$ |/ r% q _7 n: A ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
; a, K! f4 n6 |( n4 _# Q' L' l/ h y ListtupleList = scanResult.getResult; i; {9 p: o- e0 G/ j! n- g) Q
if (tupleList != null && !tupleList.isEmpty) {
. V6 [3 s, h1 b1 g8 I for (Tuple tuple : tupleList) { , A% R D& Q0 K2 B% }6 H2 @
jedis.zrem(bigZsetKey, tuple.getElement); ) d$ n) h% A4 Y0 T. y- a; _
}
4 S1 v/ d2 Y- Y: r( w } 9 P* f. f' |& G% [9 y9 ~
cursor = scanResult.getStringCursor;
5 G3 e, K2 l- a } while (!"0".equals(cursor));) i6 J8 W: U1 {2 g1 _
) h2 n( V* V% Q, G/ J) E//删除bigkey
7 E4 f D6 o; I5 h% i+ i% v jedis.del(bigZsetKey);
6 O9 p+ U# R+ {0 q. K1 K} 公众号内回复“1”带你进粉丝群: ]; Q/ L& H8 }( v9 Z+ T' G4 I7 A
来源:http://www.yidianzixun.com/article/0LevQm7t8 D3 z/ i0 Z# N- \, Q v- [) u! n3 O
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|