|
|

$ X5 i* Z9 c% p T0 A) v本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
[( w* W3 m- c) [$ a/ k6 c0 P; K; X( C+ D
- 键值设计
0 L& i! O- _' W6 q) N - 命令使用4 Q6 |6 C, h9 Y! ^+ e
- 客户端使用- g+ c0 g2 P4 G' O6 s, K
- 相关工具
8 }# e. Z& J0 Q U6 c# D 通过本文的介绍可以减少使用Redis过程带来的问题。
2 c! a' {/ Y6 l一、键值设计
+ W: t7 I/ Q1 i- \0 K" j \0 [
1 [+ E2 N, B$ w; v1 |! f1、key名设计
8 k9 `' m. [. @# U% J7 o/ m' [, S' U4 p1 x, k9 o7 w
可读性和可管理性
) U+ u$ y3 S7 D& Y; e
% m/ }+ T: B* n" Z8 k; }以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id( @6 l H6 J# i* ?( f: U% U
! I# @. u" i9 h( Y0 N) ] i
- ugc:video:1
' R R/ O& n. J 简洁性: t3 H. Y7 ~5 t' H* q/ J
7 `% l* G+ i, Z. G* @
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
, V( E" c. X! F+ ?: l! H1 ^0 }6 u% p9 O. G% y" a/ @
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
+ q0 n4 C2 e& O; Y4 \# M 不要包含特殊字符
& U( |( w( s$ f+ d
' m! c" W( Z! T9 w8 C4 A. x反例:包含空格、换行、单双引号以及其他转义字符
! s) U% y4 f/ j* j3 M2、value设计0 D3 X5 f; E3 m) C* Q- C" }/ N/ g
& E3 m# Z6 x; y/ k% |: s) Q- r% \拒绝bigkey
8 P* k: F/ ?0 e. z2 k4 ~: c8 m3 x s2 M1 z4 U9 h
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。% F' ?7 I7 k5 M" P) f
反例:一个包含200万个元素的list。( b- q; P( R* a1 f" v, G3 D
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
_# H# s, |9 p8 q# ~' r! v选择适合的数据类型5 |/ c, |6 D. L' {0 W" `" ?
( R u6 H l9 P/ w* l% P2 m V
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
( P$ c b& A% V5 g! ]反例:
0 G$ W) X) d9 m' I7 W2 L5 i6 d) }0 y8 t% I: J
- set user:1:name tom
: {! j3 @+ N3 r: k - set user:1:age 19: B; Q# V; M; K# V
- set user:1:favor football
( j5 ~0 G" w4 u# D- e$ f 正例:/ f/ U3 [4 A8 A' b
: L7 A. I/ O/ L- hmset user:1 name tom age 19 favor football
7 N+ X% C; }0 J 控制key的生命周期
+ J7 e7 s" Y; Q+ Z5 d; ]
0 }' \8 S0 e* f% c$ M: W8 Hredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。! Y6 V8 w! x- w5 r: }8 r
二、命令使用
/ S2 y7 q$ r7 K- v& c( n8 L9 I1 n5 h
1、O(N)命令关注N的数量
) E. {8 Y2 _1 d1 k( Q7 U( ^$ a) C; p6 k+ g7 z3 _( s4 j
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
) n: D$ O6 j& K# F1 q% {2、禁用命令
8 q1 {. ?1 }2 ?% U( |. M7 A: b- v/ y) x
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
8 D/ _& U1 B# a+ c o: n; k# @3、合理使用select0 {" p3 f% o% l8 o4 f
0 U- I6 R- Y( ~
, V+ z. w; e% _! c# t/ dredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
; W$ Z* A7 k2 L" U( |5 D, |7 s4、使用批量操作提高效率- T! A( G0 x/ B# D# Z( {; E! l
* }5 i, H# F( \6 D0 H
2 ?1 f( w+ X4 \3 I! h# T# R- 原生命令:例如mget、mset。6 [. q7 @. k0 a2 }* y c
- 非原生命令:可以使用pipeline提高效率。
* Q+ `+ B& z8 Z2 q+ \ Q/ g% x0 Y 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。4 V+ Z& O! D9 c. o9 R
注意两者不同:2 |! P1 s7 P- E+ c+ [
! x/ l: o+ X* G. Y& }- 原生是原子操作,pipeline是非原子操作。
9 l, q4 i/ Q# m5 o S; A# `; d - pipeline可以打包不同的命令,原生做不到' K3 X2 W- N& [& t$ v
- pipeline需要客户端和服务端同时支持。
* S0 H* ~& U1 c4 N- [ Z# b 5、不建议过多使用Redis事务功能
5 u* t) B$ i8 @: ^. `. t4 N9 I V+ P6 n$ x
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
& B: W: Q+ _ O ]6、Redis集群版本在使用Lua上有特殊要求, m8 I: B; r6 A$ t7 ^
' M+ L; d+ }, h, o1、所有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"
) s! z8 U3 q% c8 e. d8 Y" `7、monitor命令
! Y. I8 A R' s8 {
( ?- ]# O/ r$ ]# g* j- D& K必要情况下使用monitor命令时,要注意不要长时间使用。
' R5 _3 x. b4 q |) J+ d w9 m三、客户端使用
/ f% n# s, Q ]% i9 ]& i5 ^
: ?) q. K1 a: f, q% l: ]4 H% m1、避免多个应用使用一个Redis实例
. |. Z, z4 T* I) e4 s: @( W
4 U$ u( ^% s# d6 a$ H不相干的业务拆分,公共数据做服务化。3 |+ c4 J$ V3 c
2、使用连接池; P1 A5 [/ x" W; N: ^
4 _$ _6 F% [% f x2 d8 k& Q9 ~可以有效控制连接,同时提高效率,标准使用方式:% n+ L7 Y: W8 A9 g
Jedis jedis = null; [5 z8 t* z, B, ~. i
try {. R' a3 q) i$ _1 S
jedis = jedisPool.getResource;
. {/ g) Y/ N( Y3 a2 S7 G //具体的命令( H+ r. b9 {, ^8 i6 p/ R1 {- x1 v2 t
jedis.executeCommand
. g2 o1 {/ @& l5 a* O} catch (Exception e) {" @* s7 i& S1 [* }' C
logger.error("op key {} error: " + e.getMessage, key, e);" M& ?( Y! h- {' p8 B) j3 A
} finally {9 z3 l4 o `6 {' N0 r5 {
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。: c9 v% A G% x6 c, `2 ^
if (jedis != null) ! S1 H \. Q( j) U9 X0 B
jedis.close;
2 j. t) N# m, Z) A* X1 ?# |" [}
2 T) u& c g; m# z. j- J3、熔断功能
' _7 ?# s! ^/ b' O3 _' M" M6 Q6 _7 U& I6 [) D+ R! [
高并发下建议客户端添加熔断功能(例如netflix hystrix)
: N4 A; I- u( N9 }# z4、合理的加密
& H: Q6 N2 n# a5 g" Y5 z1 F0 i. G, P" [6 \
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
0 ]4 `. M6 p% F- k; a% M6 \2 {: s5、淘汰策略; _2 R$ r# v7 w+ ^( X5 M9 g( T6 K
5 A6 ]/ E/ `; a2 G: P根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
% v4 p& `" y8 S2 J# `% [5 u. s默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。7 C6 @" s. C( q4 o
其他策略如下:
9 W$ R$ _8 W$ @5 C% v) l7 u2 h
8 I1 H. e1 \' g5 d3 p4 N% {- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。. g3 U! l5 Y3 }$ e, u/ L. h
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
. x7 ]! {% @% K+ F! d, H8 t$ c - volatile-random:随机删除过期键,直到腾出足够空间为止。
3 D0 t) H4 c: E - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
7 g- w: m1 ?; u! w: J - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。2 X9 C o: i: z0 p9 D! B# t; I
四、相关工具
3 z& l2 E/ ^1 m( E
+ d7 g* d2 [' m1、数据同步2 k4 i+ F T$ K J" }9 S' `) h% r
: I# T# o7 H7 H# yredis间数据同步可以使用:redis-port8 @2 C3 M Z1 r6 P0 v7 l
2、big key搜索8 T( R& f: ]5 G: c5 l
7 N/ v- E7 o, S0 {redis大key搜索工具
" _ N0 V$ a8 k/ l+ y3、热点key寻找
! B9 @. F1 A% z5 w
/ C# }4 ]8 }' e( x' S( u' Y内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题0 S8 d4 r' B9 ~( {$ r# o0 l
五、删除bigkey
9 S7 i- v. e$ ?6 E, V/ ~1 H
% ]8 M. z7 A9 e+ u E" [' f4 W- F2 _" U% f0 L0 e0 Z
- 下面操作可以使用pipeline加速。
6 t3 \! B; d( o$ @ - redis 4.0已经支持key的异步删除,欢迎使用。
- w4 w. R6 O, y% ~4 Y+ Z0 Y 1、Hash删除: hscan + hdel
C- }) F, [9 D- M0 P W) y1 l7 x! w" D
public void delBigHash(String host, int port, String password, String bigHashKey) {
3 o3 j/ f/ I6 [# `) D7 n3 W6 O; j Jedis jedis = new Jedis(host, port);
! X; O1 ^$ g+ W* k7 O if (password != null && !"".equals(password)) {
* j: e r: Y$ Z8 V jedis.auth(password);+ Y6 @+ O# h5 W2 @
}
H3 T4 s& |3 R) b9 ~0 _ ScanParams scanParams = new ScanParams.count(100);: Q2 H5 y$ s. e6 j+ j
String cursor = "0";/ z" A: ^5 \3 V
do {
+ R1 m1 j; G5 |7 j- p- R; d ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
5 S2 m6 y+ T- O- q List entryList = scanResult.getResult;
$ g, @) Z/ J* Q1 L8 }% [) B# T if (entryList != null && !entryList.isEmpty) {
( c) w* p2 `9 Y5 i, M for (Entry entry : entryList) {; y$ j0 Q: Y. M( t- F
jedis.hdel(bigHashKey, entry.getKey);' B6 j! u( F; M, D6 G% W
}, a1 K5 K" D; G/ I
}
9 }# N3 p9 ]' i cursor = scanResult.getStringCursor;# c9 |3 S$ @/ Q
} while (!"0".equals(cursor));
/ k) D( q) A" \4 [! a: E/ @
# t4 g8 i4 N/ h* g+ m//删除bigkey# |( E9 {9 N$ ^9 j4 ~4 J
jedis.del(bigHashKey);) c( U9 F' V( N# U2 W
}: G8 v8 V' ?9 p- E1 x |1 C
2、List删除: ltrim" y, Q6 D' f' h$ p8 I) n
- W2 p4 q- Q5 J) p8 J9 Z& z
public void delBigList(String host, int port, String password, String bigListKey) {. l9 g) M8 s+ Y) V& d
Jedis jedis = new Jedis(host, port);6 \# I: V# h# |! c" j6 Z
if (password != null && !"".equals(password)) {; \( ]% h; u: f( Y# o. U1 F& \
jedis.auth(password);
6 u0 C0 Z" g7 {2 t& y }
) n1 S: b# f% b+ w L$ S long llen = jedis.llen(bigListKey);
( `- b" x* l" W3 Y" m: u( C/ T/ l int counter = 0;- J O5 }. M4 Z& t% J8 v6 V5 g% D
int left = 100;
8 X/ l; ~7 Y4 o: w while (counter < llen) {
O4 g0 ~7 g( t/ T! `2 ~ //每次从左侧截掉100个
4 W9 N+ R3 a/ X$ M! L' z/ L jedis.ltrim(bigListKey, left, llen);/ h& I- [) H! u8 \
counter += left;0 E! U$ D( L9 o- K
}
; F' w: W) i: X# H$ u4 z //最终删除key5 P$ |- e' ^3 w+ o3 `/ i
jedis.del(bigListKey);; ?$ w0 Z4 l( u
}3、Set删除: sscan + srem' O' k3 D/ b7 m7 I. T
7 `5 K9 x8 b; S9 D- ?
public void delBigSet(String host, int port, String password, String bigSetKey) {3 N7 o3 V1 f2 h
Jedis jedis = new Jedis(host, port);7 R, O; n* L. @$ c1 `8 r4 R
if (password != null && !"".equals(password)) {
+ j: K0 B9 B2 k, A jedis.auth(password);* Y" B6 d, A9 b" y. W
}, U9 M) a! c3 D5 ]+ X# [
ScanParams scanParams = new ScanParams.count(100);
0 `7 t; S3 n: a0 d' S String cursor = "0";9 l* T U6 I4 Z. A2 K" v
do {
3 A) R( @0 f6 {: Z+ O* ~+ U ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
0 j; D" }1 f& j) a% Y: {6 C List memberList = scanResult.getResult;
a, g2 g, e! m5 b if (memberList != null && !memberList.isEmpty) {1 U7 u% z% ~7 `3 H, K; n% {6 o+ G
for (String member : memberList) {
# Q- C% l t, i) `1 N' }9 _& ] jedis.srem(bigSetKey, member);4 L6 n8 S3 A! @5 T0 K
}7 H- Z" a6 j& B: r' z! y; T) |
}
1 n1 j" B3 _) y cursor = scanResult.getStringCursor;
* w" @, p$ r- D3 d# ]4 Y" `" [0 U } while (!"0".equals(cursor));: q6 }& [' f& s
3 ` y1 p$ U/ K3 h6 A' f( a//删除bigkey, L4 i4 w5 B* [* q# y2 _
jedis.del(bigSetKey);) a+ ^2 L; o Y% x' j
}
- T$ G8 V/ `1 _% c0 s9 Z4、SortedSet删除: zscan + zrem
6 j0 u( b& k; ~* v e8 Z7 s1 M: c( \. v, ~5 X% G/ _
public void delBigZset(String host, int port, String password, String bigZsetKey) { \8 z# H& E( n0 R
Jedis jedis = new Jedis(host, port); * H0 v! l1 M' ~3 |% b
if (password != null && !"".equals(password)) { 1 Y! a& u) v0 g) D+ ]" U
jedis.auth(password);
* |4 a0 @3 z2 n2 K/ X }
7 n! G/ A. Z. ]: [" | ScanParams scanParams = new ScanParams.count(100);
. {& A- c, p, X3 u) ? String cursor = "0"; , `6 ~' {4 X* [- ?9 q
do { 8 M: U; X& a8 _8 n3 w! ~/ n& A
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
" g' c/ F& c- O* Q6 J$ T ListtupleList = scanResult.getResult;
. N* I8 u: W9 _ if (tupleList != null && !tupleList.isEmpty) { 1 i& e u; M, n: c- b* X0 Q
for (Tuple tuple : tupleList) { 5 _0 O) C9 T% y& U- X
jedis.zrem(bigZsetKey, tuple.getElement);
- A y- A3 R- Y9 S9 r0 F1 J } ' y% w# I, {3 O( W( E
} . I: f' K" q: n
cursor = scanResult.getStringCursor;
# j& n5 S! G9 K } while (!"0".equals(cursor));
7 K# _, Q4 [- e. B$ M3 W
# l1 h4 s" t ?0 t% r9 G//删除bigkey
: O" Q* j- G( \" h3 a jedis.del(bigZsetKey); 6 ^7 @5 Q* U( P# C0 C
} 公众号内回复“1”带你进粉丝群4 T1 }9 u# s* e( b8 V5 H. M. }
来源:http://www.yidianzixun.com/article/0LevQm7t
B8 b+ V5 `2 f免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|