|
|

! M1 B5 B/ S L本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
& W" _( x- G: g+ K+ W2 J2 U
, Q% c: K; x: w! C0 f3 r- 键值设计 u9 o) Y P- a4 D
- 命令使用9 ~ ^' x+ M5 U* h; @4 {
- 客户端使用! j6 [% F8 z4 j1 v
- 相关工具
7 }% Z2 X7 c, c$ u2 W8 G! R 通过本文的介绍可以减少使用Redis过程带来的问题。
m- x3 [2 I- v8 B; [* i4 B一、键值设计
* i) O7 Y$ l3 I4 k! K0 `2 L+ W+ N+ r, _1 y, B
1、key名设计
+ D" I; n* ^$ f) G2 p4 ]6 \
3 E9 v# P' }" y( d1 J8 }! C可读性和可管理性
+ j1 p- n$ p) ?, e9 [8 R$ q$ o8 J: y0 R+ ]% m, k3 e* @ }6 z
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id4 U& M1 ~6 P k0 s/ N
* ?& W# y4 \6 g$ s- ugc:video:11 R" ^3 q2 J) |4 i. R3 C
简洁性
/ o% J' H+ U4 V6 w. \0 a5 T1 _2 b9 j: C" C1 A" V
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:/ T- |* o6 ~4 X8 z
$ T( P) [3 z2 C" _. ?- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
. s7 m5 ]+ G; m- E, ]( B; L: ~4 \ 不要包含特殊字符
! ^2 }9 E, `& |! S: O
0 D$ F B2 e+ T; y反例:包含空格、换行、单双引号以及其他转义字符
. G% \: e) B9 P. Y9 C+ h2、value设计' @. E9 W! `, ~$ d, ~7 w8 Z
- D: k% e c$ x7 J" r7 q" n( ^拒绝bigkey/ y* c+ l: ~2 G* F2 k- v5 |+ ]- c
. `8 W6 X& @ _/ a2 F
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
0 I; r; {& b) _% H% H反例:一个包含200万个元素的list。
/ Z/ ^" I- r4 H7 n( m9 A9 u非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
7 L" w2 \$ b, ]' s$ R3 m选择适合的数据类型. i5 v4 S" k% K1 O# I
# i, i( F+ ~2 I3 J' a
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
7 [( i9 s; b8 S) q0 ~) f9 e+ p反例:4 b( |9 e2 J. p- F' f
" S9 t7 T4 w5 U3 D
- set user:1:name tom
% E$ [3 l/ ~$ \2 ` - set user:1:age 19& t, f4 K% q0 y" D% |' C' ?6 U
- set user:1:favor football' |% s4 N# Y$ Q* \1 {/ t: y2 T8 T
正例:- h `/ g! W! U2 D. U% a* Q
7 x4 G5 w1 Z$ m% Z- hmset user:1 name tom age 19 favor football6 B( i- D7 {8 l. f# {% x( d
控制key的生命周期# X/ d. }3 }& ~+ S5 f& d4 r
. E9 _0 \$ `8 \8 S; S) C# L
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。! ~. @$ l# l( a6 M M: `+ O
二、命令使用# D: T I, q! d( Y, o+ J( y. U8 [
' i( E% B+ N: H5 A, w z8 [
1、O(N)命令关注N的数量: Q- p1 K& V; D$ S1 b' v( ^$ K
# }: T6 L: d/ t" Z: R- u% \例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。$ c/ S0 l+ P5 G- @
2、禁用命令7 T; T5 t, |. C$ @$ \
. [; z- e& z6 y禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。8 `8 c) V& e3 p; S. v) x' _
3、合理使用select2 H2 Q6 _2 I7 q3 V$ }3 [
# q' l. e9 r% G) _5 Q v# a
9 T( A0 L7 h' }: Kredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
1 E Y) [0 l# ^, t2 F# R0 |5 L* a! x4、使用批量操作提高效率7 o; k0 G( N# W2 P- T0 r' ~! Q
- n$ J. O& d t$ K( W5 v% F
+ [$ u: Y0 A8 }3 t
- 原生命令:例如mget、mset。+ Y% L2 \3 I! O# N5 \
- 非原生命令:可以使用pipeline提高效率。
% M: U' e$ ]. U n+ \" V 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。/ w+ \/ ~; v$ {0 d$ J; w) h
注意两者不同:
; t" l: @& K$ S) Y
) k J, {3 s8 Z- 原生是原子操作,pipeline是非原子操作。& z! Z3 t# Y1 i
- pipeline可以打包不同的命令,原生做不到
f) V2 p9 O8 k- W - pipeline需要客户端和服务端同时支持。
. S/ e' q; [9 M 5、不建议过多使用Redis事务功能
! M& L" c+ j' j" u9 h# D. o# {7 ~' `2 z* y
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
5 S W3 q4 i$ Q) f, r) Z6、Redis集群版本在使用Lua上有特殊要求
7 v% M+ n9 i/ C4 g% f8 C( G+ w/ n, ?- ~
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"! G6 d% `" i0 _# p" R2 ]
7、monitor命令: m- h, l) u; N! j
& Y0 P% ~! y8 y. V" C6 W必要情况下使用monitor命令时,要注意不要长时间使用。
9 q7 Q) _) s \& W+ G三、客户端使用
# i9 P) ^: G. Y' I) W8 }$ ^
; E; ~& c% G, s7 x1、避免多个应用使用一个Redis实例
. l6 g% e) _7 ^) m8 j( b/ ^4 H) w+ N8 R& l5 Z& W
不相干的业务拆分,公共数据做服务化。+ d0 d' I" y/ _7 t( A8 n9 h% a
2、使用连接池
& d1 S$ @7 U: M" @
! M' K9 D& X; O" {可以有效控制连接,同时提高效率,标准使用方式:
/ y6 x6 a k7 |( Q% \, x& oJedis jedis = null;( d3 E3 i' |* G9 H g. U" P9 N
try {
; \! n6 \8 C: {2 P/ j1 z, D jedis = jedisPool.getResource;
( d2 g/ K. {' s. z) R: \ N //具体的命令
& s5 n( K$ l( ]* `# ^5 I0 L jedis.executeCommand
7 P3 Q4 @" V0 J p3 f, X" D. s} catch (Exception e) {
& `& J0 F( @+ ^+ c$ H. [0 |5 j3 y logger.error("op key {} error: " + e.getMessage, key, e);! J# n J& A* b% r7 q5 P9 G
} finally {
& _% ~* r" y1 S; F# J //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
' j. B9 P- {3 l if (jedis != null) % u* Y9 J l) e5 v) m0 m
jedis.close;3 L( |& R8 w* x( f+ ?6 v
}: ?" G1 B+ [, v4 F
3、熔断功能; D4 M; p7 |. u4 R/ ?
_0 h5 b7 O* L! |: F6 T3 f高并发下建议客户端添加熔断功能(例如netflix hystrix) f# g9 a# u0 F# z y6 M$ @
4、合理的加密
: t0 L$ p1 ?* m
- N k$ D+ ^7 E- i2 K2 q设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
8 s( O. a6 b% g8 I& t% M: u5、淘汰策略$ r7 t: Y6 h* t+ X4 z
; K9 H' u6 e% B$ E" Z
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。0 P) Q8 x$ s- {8 i4 k
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。6 q& i$ A+ h# N/ e7 z3 \* k
其他策略如下:
9 M" x* k- O( d! r3 \* a' A E
# ^3 O, L# T5 V2 w- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。1 \) a4 R% ]) F+ f
- allkeys-random:随机删除所有键,直到腾出足够空间为止。* `- J7 _9 y3 Y
- volatile-random:随机删除过期键,直到腾出足够空间为止。# U: m6 T. q. Z$ \ G& e! p+ I; G
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
) H' T) `* P; E* i - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
* B0 B* i7 @+ T6 T# _! E 四、相关工具
9 B( w, @( [" k3 a6 I2 }6 {% t
# a$ u2 H+ C9 I! u7 G5 O$ ^1、数据同步0 g; R L$ @; u; E
- e! a# T k$ ~$ D+ h: W$ Sredis间数据同步可以使用:redis-port
( P0 R3 t, \8 ]; l& O! L2、big key搜索
# A k* f- Z! j: Q4 ?! O
/ K5 D: p4 @4 c5 S1 Kredis大key搜索工具% D/ _3 j3 t0 C8 Q( w
3、热点key寻找
7 L* U; w- n; H$ U7 s( P0 B4 t3 H/ j! V; d% Y
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题/ X$ ?- `7 Y/ B5 X
五、删除bigkey
5 E( c2 M T9 e: x# x3 a% m* ~% e6 A; E, B9 W5 n2 q; H
" o3 p s# O* t- f4 S6 G- 下面操作可以使用pipeline加速。
1 Q7 j$ G- H! o9 p/ Z/ o - redis 4.0已经支持key的异步删除,欢迎使用。
7 ?4 j9 S, ]* C/ i' Y 1、Hash删除: hscan + hdel- p5 X- v. S8 `' v" k
* d% s2 M4 r: d& Y
public void delBigHash(String host, int port, String password, String bigHashKey) {+ S# t5 U; c, R. N5 ?! i& ^* D
Jedis jedis = new Jedis(host, port);
5 y7 E0 n. i6 `$ T' B* r if (password != null && !"".equals(password)) {# ~2 n5 F% J$ W
jedis.auth(password);, Z N. ^, ?5 r
}
5 C: `! x0 d- ` ScanParams scanParams = new ScanParams.count(100);
& F( T9 x9 _# ]; v$ D" R8 l/ P String cursor = "0";1 M& f6 X2 K4 V. D4 R. D
do {
! M" ~. q- [6 z( m3 j) e9 c. p% u ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
2 d e, N& c1 Q) E% T List entryList = scanResult.getResult;
& w" b4 m$ U9 ]7 I0 W if (entryList != null && !entryList.isEmpty) {9 [. \; Z; p0 Z; x8 i2 Y( d
for (Entry entry : entryList) {
& _9 Q) v5 ]( ?3 \8 Y1 c jedis.hdel(bigHashKey, entry.getKey);* o! e6 h7 O* E+ G1 y
}5 b) r0 e [/ q$ m9 H0 e4 u
}
, i( _/ a% R7 t" D3 C: G+ ^. P cursor = scanResult.getStringCursor;
. {3 s; D8 L' x: k9 u, f4 h' q } while (!"0".equals(cursor));
; E" w0 R. M6 J# G' s& i# \! u$ V. x# I4 \( t
//删除bigkey
0 Y Z/ c; c' Y' d jedis.del(bigHashKey);
! b, l9 j- y4 T* e}
/ q8 r, P; Q. }" }) ]. ?2、List删除: ltrim2 `" e2 u+ A$ [/ ^( h/ Y
1 E( ~0 c7 Q' b' `# J5 o/ K+ Ppublic void delBigList(String host, int port, String password, String bigListKey) {
0 [3 g* U# s$ i. J Jedis jedis = new Jedis(host, port);) C/ P; n- x! Q( Z; W' D9 p+ o, u
if (password != null && !"".equals(password)) {" Z% L' z6 X3 D
jedis.auth(password);" D D0 N, G. W
}: h, D% L; \/ N# B0 z3 {# j
long llen = jedis.llen(bigListKey);, g- S( B3 Y3 o- J- o( V
int counter = 0;
( R: y' H" v+ A, s* f! b int left = 100;. |7 G6 B9 k5 d
while (counter < llen) {
' g% j8 |5 S M* \9 g) S5 T //每次从左侧截掉100个2 y5 _7 L2 [" D, C3 K
jedis.ltrim(bigListKey, left, llen);4 u! Z% [) [0 v! f+ W
counter += left;7 n: ` [4 [! T5 m
}
. g* _' k- H8 Z$ Y) p //最终删除key/ |6 x# w* O' v7 n3 H& H
jedis.del(bigListKey);( `6 J4 r8 G% N$ Z# R
}3、Set删除: sscan + srem
: C) t' _' G: u* Y9 F+ J6 c. d
# d6 p. R/ @* \public void delBigSet(String host, int port, String password, String bigSetKey) {
! i E; G; _8 ?" d4 F! z; v1 W, K Jedis jedis = new Jedis(host, port);
; C: T2 o3 [' P3 o) X if (password != null && !"".equals(password)) {
2 M+ x+ i# h" F. B4 ~% ~) q jedis.auth(password);
/ O5 k3 o {& v( z% L1 C }
) ?1 [! w( V# C9 d. D ScanParams scanParams = new ScanParams.count(100);7 u# W1 P# D( ?( X8 S7 `
String cursor = "0";
, b5 C8 a# R, I' F do {( c2 @. G# u0 {. ? `1 Z
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
* X/ j' m: B* H* T" l List memberList = scanResult.getResult;1 E" D0 Q% f6 w/ u6 s+ D6 t
if (memberList != null && !memberList.isEmpty) {
. k" ]2 g* k! V! B0 _% ?3 R for (String member : memberList) {- F; |1 t2 N8 i& r% x" n3 ^ Q
jedis.srem(bigSetKey, member);0 s8 Y% \% K( d0 N" x
}
3 a/ @9 L. {' `3 q( I4 e }" y* H1 ^4 d* x) W% b/ K
cursor = scanResult.getStringCursor;
# X% {( r% _9 v' g } while (!"0".equals(cursor));
& P/ [% E# l* k N4 @0 Z8 r6 P9 Z; h8 ~. X$ K
//删除bigkey) U2 `$ I" a# m1 T0 B% P- n2 B! D
jedis.del(bigSetKey);/ D. A- \& N8 ~6 Y
}3 j: M4 L* K" `" x
4、SortedSet删除: zscan + zrem
$ R( S: F7 D& S* M5 a+ U, A" s R5 Q' a; O8 l% @9 o- o# n4 l- ?9 \% E2 U9 ^: ^
public void delBigZset(String host, int port, String password, String bigZsetKey) { 3 {# v3 @: a V5 j: \3 ]
Jedis jedis = new Jedis(host, port); 5 @8 F9 f' o4 P: P2 A( W
if (password != null && !"".equals(password)) {
5 l% n- I) E, J+ k2 A jedis.auth(password);
) O3 u/ {, Y* V8 Z1 ?9 W f }
' g, W6 h% y3 x* w' M ScanParams scanParams = new ScanParams.count(100);
0 X7 [/ N" q4 N2 P String cursor = "0"; 5 H# Q/ m8 N( _1 b
do {
7 y& X# q0 Q- X; {4 Q$ Y ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 3 @( z% T0 Z( c' K5 Z) h0 r
ListtupleList = scanResult.getResult; # [( N, {- o( P7 i
if (tupleList != null && !tupleList.isEmpty) {
& G# y2 {" n! b7 M for (Tuple tuple : tupleList) { ' u7 J% {4 E4 x$ m3 Y! q
jedis.zrem(bigZsetKey, tuple.getElement);
) ~5 e& K& l. W: A# e }
3 {% u/ W; P1 s3 s' d% P/ W } $ F0 Y& l% S- [7 f, W# q
cursor = scanResult.getStringCursor;
- a$ m3 r! V# {' U- L% T5 { } while (!"0".equals(cursor));" G8 r$ C) E# O4 Q/ R* W
1 b$ j, {& m" U4 M- H% |//删除bigkey
6 K; B4 B$ H D# p) b2 L- I& v jedis.del(bigZsetKey);
s' _# b% G( }1 M( I} 公众号内回复“1”带你进粉丝群
% G* V4 ~2 X r4 D* }3 ?来源:http://www.yidianzixun.com/article/0LevQm7t
3 o9 q: G. Z" f0 Q免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|