|
|

: ]9 l! m$ d. ?7 t+ p本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
5 P* F9 {4 a% {$ x7 `
, W( z* w2 ], ?4 a. q# b- 键值设计, l/ T9 K& p: V# u8 B! ?
- 命令使用2 x+ N$ [8 S c+ p+ d
- 客户端使用. X! [$ _5 ~4 _& A
- 相关工具
0 C/ [2 X E# O& ~7 X 通过本文的介绍可以减少使用Redis过程带来的问题。3 N9 R h) Z K4 y2 k# p( N
一、键值设计& E* ~* e5 M3 ] X4 ?/ [& y( P
0 Q) Y3 z% o+ w& R0 \
1、key名设计' g+ B1 i1 o8 \7 _
( x" H( ^- N" H4 f6 C% Q
可读性和可管理性: B$ a8 c0 X* D* N
* A3 o: C: y- @* Z8 z2 U: n8 g以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
8 ^1 y8 B# b& D7 `" m0 G
8 G& Z. b8 X) R& L& S ?- ugc:video:1$ \" T% A; [, t! F+ `) v
简洁性
8 R+ z7 ^# j& T6 `- T* o* W, U; f8 N8 ]0 L' a4 `" |7 o# w( @0 d
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
. Z9 e" _* B7 c3 f* O, J: i' J7 V/ r1 J
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
1 P8 _1 r; p: J h% n 不要包含特殊字符; X. u. o* v; V( K2 L: O" t( ^
! D O2 T! O- E* o$ ^7 e
反例:包含空格、换行、单双引号以及其他转义字符
2 ^3 P. E5 r3 N5 r: }2、value设计9 `& M! S. w! A3 c8 |8 g5 {
0 m! }$ T+ o+ u: A6 O0 x! X/ L拒绝bigkey
" w, p) s5 E; d' j' q4 c8 G0 _8 o) C
7 u$ x1 k' _% u) h# s0 }& i防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
/ A8 W% N1 F5 d! K5 Z& P! L9 [反例:一个包含200万个元素的list。; u3 v% }' x, q/ p
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
0 U3 R$ K4 o7 b# u/ A选择适合的数据类型
& e$ @. m9 s# P; {& d8 ]+ s6 l( m0 |$ z
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
9 n+ V }0 w7 Y4 P$ b2 ~, B% ~反例:
' w1 z* y* k( O' N6 I5 {# k; z4 w- y6 b5 C9 [
- set user:1:name tom! i8 g' V9 ^$ d
- set user:1:age 19
7 j7 S8 M8 ^9 A5 k8 U% o' N/ E - set user:1:favor football: A9 H$ Y4 Z+ d
正例:- e: ~0 ~( f- e
3 t! D8 N& X8 p% S8 {- hmset user:1 name tom age 19 favor football( l( `! ^1 s# O4 r! O* u+ ?2 @
控制key的生命周期
8 X) L5 |3 A! y' w3 ]( Z& U
% c! Z A3 b- ^: D: W3 R/ X' Tredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。# T8 z1 l+ K$ D* p3 F" Y! q" R) v1 M
二、命令使用
4 X1 Y+ r* T0 T. R: K! `# U1 O" \$ J0 |7 D$ s
1、O(N)命令关注N的数量0 q. W! m! O: E3 ~
& G' u* M9 ` y8 h
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。, H; Z1 ^+ m: T7 F9 V
2、禁用命令- R/ s7 c. k" Z7 G
% D3 N) J1 q( H( \
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。, [' V2 K1 }7 n& d
3、合理使用select
$ f* |& @- u: O+ S3 |0 f% p. |, l) ]' D
5 L: F4 V! C2 L6 Z
( P1 k, b7 o% j* }redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
! @9 V4 |9 T7 v4 [: V% X+ e2 g4、使用批量操作提高效率2 D3 G3 T- x: J) L! G7 p
, M6 p2 e8 z, ~$ q' N1 i7 F" e! h
0 P9 R3 B0 H a! U% L8 ~6 v5 `- 原生命令:例如mget、mset。
7 v" Z4 m& A" z5 \2 Q! q. _0 k - 非原生命令:可以使用pipeline提高效率。, T- N' v2 j8 Y/ I3 v
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
% R3 q1 L4 V# @7 H! |, M* V注意两者不同:
H g6 L/ b0 o+ }) A2 z8 w4 T" w* m
- 原生是原子操作,pipeline是非原子操作。
6 m& F7 l- O/ N% Q7 u - pipeline可以打包不同的命令,原生做不到
2 _# L3 N* S; @3 N* b: m3 b" V - pipeline需要客户端和服务端同时支持。
+ g" k) P! q! U 5、不建议过多使用Redis事务功能1 n7 [ V2 ~. a! u( O' X7 @, m
5 Y% F! b r, A5 T+ R' V: a8 W# t& A
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!3 C& f0 V3 c- o* M% n1 c
6、Redis集群版本在使用Lua上有特殊要求
: B+ z2 D2 K5 W% r4 ^5 M% ]* G, Q' c# r
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"
# L- L- z$ k+ `3 [7、monitor命令. X% M+ a. Q) _0 ^9 d1 [1 O
4 l' p) d9 i1 f- K) \
必要情况下使用monitor命令时,要注意不要长时间使用。 U& W* B9 p7 d! T9 f9 ~4 i' U
三、客户端使用
7 q }: [7 H2 ^. O8 Z1 o6 S/ y: ?. @5 c3 B$ S% E0 v
1、避免多个应用使用一个Redis实例4 T/ g% R3 q* y* Z8 ^4 Z% a% _
, f5 a" C9 b I4 ?不相干的业务拆分,公共数据做服务化。
: e \% Y0 H3 p; [6 \2、使用连接池$ k, A8 y- l% J& b! Y! ]' d
* ~* }( ~# L1 t2 P8 x2 k
可以有效控制连接,同时提高效率,标准使用方式:
5 V+ g2 U* Z: YJedis jedis = null;; X A1 j5 n, m+ ?6 C0 R* o
try {
0 S' c) L L1 l; W7 w9 c3 `7 x' h jedis = jedisPool.getResource;
0 q) l7 |( @% A6 E3 M2 V& P3 S# s //具体的命令3 _) G. @- d1 v- `, Y( L
jedis.executeCommand0 u8 x9 {1 P6 M6 R$ Q/ f
} catch (Exception e) {
. | h' k; I" h: m logger.error("op key {} error: " + e.getMessage, key, e);/ [, r" m& U( K& f; V! Y1 Y
} finally {
, D$ d* q: W) J( a2 l4 @ //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
; V! J: ^6 C9 {# ?( @( [: p3 B if (jedis != null) & z$ \; ?2 Y& V. b6 f
jedis.close;
# O0 m, {4 M5 U2 v+ Y4 Z: q/ `$ V}
J. }! f2 i: D$ L/ M! i3、熔断功能2 W- V* \2 p0 l( x; d& g8 t1 Y$ @: x
9 D3 F. x: k. g% Z- T& @高并发下建议客户端添加熔断功能(例如netflix hystrix)
* _5 v2 _: l" N; I# p4、合理的加密, L( N# S( e) c% u3 {4 c, j( h0 s8 v/ ?
- u1 t# y5 v/ Z; `* Q设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)4 q. C' v! I0 j4 Y D2 g( A
5、淘汰策略3 f# [+ v- {& h# s$ ~8 h" H. o9 ?) q
3 r# f% k7 v/ C+ O2 \ l. z根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
' ]" o v; J) i3 ?1 y3 y默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
/ U1 J8 M- f2 D7 `. p! P其他策略如下:
3 m" ^+ p7 w9 S3 q, u8 |' g( H; l! q, X2 V3 L3 j! C
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。, g+ n8 h& H* o$ ?+ ?) x" s* r( O
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
* o3 @7 h5 C# f6 [' w - volatile-random:随机删除过期键,直到腾出足够空间为止。
9 x+ U) i9 r# g! m: g - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。+ e/ O* F$ t' k, v. ^: W
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。6 `' D9 j) L# V+ Y. r
四、相关工具
# N$ C0 q- g$ l( l! c% _$ Y0 ]9 @$ {
1、数据同步
: g& t1 W; g; S7 x8 o8 Q3 J& w
7 x" U# D/ {# q0 N3 n. \' vredis间数据同步可以使用:redis-port
6 ^# t3 J) ?4 [. Y2、big key搜索
% ~' T' X2 U. a. l0 O4 r! ]( T/ `% m q4 I! e
redis大key搜索工具: F$ k+ H9 t( T+ m1 V. T
3、热点key寻找 s n3 h4 \9 [# D A# H
7 e, L% @4 W6 K ?$ Q
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题4 s2 L+ ]8 @. v4 o: d* a. c( W0 ^: I
五、删除bigkey
, [- G! P% i1 F- n# b$ x
! M$ k; }. D* _3 n; N
% ^2 G9 Y( P, `7 V( D- 下面操作可以使用pipeline加速。
' z+ h3 {" V& m' q q( G - redis 4.0已经支持key的异步删除,欢迎使用。
# W4 K o4 q6 D" h6 H- J) T 1、Hash删除: hscan + hdel+ c+ Y7 R) L, k" g' L: o$ B9 o) U
1 t8 }7 N$ r3 Y6 p5 p2 w2 ~
public void delBigHash(String host, int port, String password, String bigHashKey) {
' G8 a: o1 w( Z; J0 n Jedis jedis = new Jedis(host, port);, O; M* w/ E% @$ S" V+ r% k8 e3 f
if (password != null && !"".equals(password)) {" H6 k1 `5 ?, d+ ^: d k* G
jedis.auth(password);$ \" L* q* {! |9 J; z" G
}
3 K" i0 Z& v. y" U. } ScanParams scanParams = new ScanParams.count(100);0 R8 W9 c! r* K; I9 N$ L' ?
String cursor = "0";" |) e4 B9 K; R3 e2 j {
do {
/ k/ C( I! P g6 M$ K: N ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
# R* V, ?% W# w, K y List entryList = scanResult.getResult;
4 U3 q4 n$ a5 j0 {. t& R if (entryList != null && !entryList.isEmpty) {
: S+ Z" L4 g$ \* \1 N" w) R for (Entry entry : entryList) {
& l1 P4 e8 Y5 G; w jedis.hdel(bigHashKey, entry.getKey);9 p4 x; m. j4 Q
}+ G- X0 x# `/ a" ` B/ V! g/ r4 S
}+ O9 p. j* Q, C3 J2 S
cursor = scanResult.getStringCursor;( ?* Q& V" W) \( ^. C7 T' A9 D
} while (!"0".equals(cursor));
# [5 a, e, p# U3 D- H* S# q; {& O& @- h4 }
//删除bigkey
& N" }8 _. ^6 _3 I1 ` [/ s jedis.del(bigHashKey);
}& R" a& V8 r* L$ X- d}# h! h5 `* N& ?3 M) T
2、List删除: ltrim
! C f8 B' B1 Y* ^9 [) m" {* r( h# U4 U8 J8 C
public void delBigList(String host, int port, String password, String bigListKey) {
9 M W1 y1 O. k Jedis jedis = new Jedis(host, port);
; p8 c8 Y/ V; [: c7 r4 u* e if (password != null && !"".equals(password)) {% L8 o- O4 C$ k i. X
jedis.auth(password);$ H, f/ E" c# ]+ t4 _4 o+ j$ c
}, W; S( |4 h: c
long llen = jedis.llen(bigListKey);
) t& c+ ~* y1 Q0 a+ c& f int counter = 0;
" t( ^- K# |: x int left = 100;. u) q& A3 {' o8 l4 ]' K' V7 {
while (counter < llen) {9 K1 c$ a. T2 u0 q4 V; q
//每次从左侧截掉100个
+ F b$ b& O$ J$ k. {3 H! D jedis.ltrim(bigListKey, left, llen);
4 ]/ Q- N- y& C counter += left;
) G7 D$ H* _, P' [& q, |- U9 q }
+ o$ M3 e d1 T- G //最终删除key/ C& R* ^9 A3 ?7 Q6 o
jedis.del(bigListKey);
0 y& i- l3 Y' c/ ]' H7 J}3、Set删除: sscan + srem
1 G. [& h+ O; m2 c% g" _( g0 J: O% w9 k2 \
public void delBigSet(String host, int port, String password, String bigSetKey) {
& @4 |: X0 G2 p7 v! u+ Z Jedis jedis = new Jedis(host, port);5 |) I8 r5 v2 B* o* S" m3 i5 C
if (password != null && !"".equals(password)) {
5 I" V; [# j. z jedis.auth(password);
2 t7 m. @$ f0 O6 H A }: f: G0 f. P1 a5 a3 ^/ h. p
ScanParams scanParams = new ScanParams.count(100);4 E2 I( C! L4 b2 X( M
String cursor = "0";/ b/ X, V' A' g d- O# e! f8 b8 d
do {
, Z7 m" u( Z0 v3 }8 G ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);5 l$ ?. j4 O# T) y m
List memberList = scanResult.getResult;
4 Q1 l1 V6 M. o% s W1 l if (memberList != null && !memberList.isEmpty) {! O1 F6 D+ T# D/ c2 k7 B9 a
for (String member : memberList) {: c7 Z: t3 Z/ m0 ?1 U9 i
jedis.srem(bigSetKey, member);6 B* p! r0 {: t: U( S# u' f8 x; J
}
0 ?7 I. Y; B Q' D) b }& Y" d- G% Q; ^( H
cursor = scanResult.getStringCursor;
0 S; |% |5 g" N. z h0 I } while (!"0".equals(cursor)); J) U( s& M( B
$ h' r, c z' p3 E2 H//删除bigkey! A3 v) l5 d. c, F# ?8 n
jedis.del(bigSetKey);
5 T2 H8 H" l# x$ S}3 W" w) R1 |) y6 R
4、SortedSet删除: zscan + zrem' Q, \; `3 S8 P4 f5 ? a, k
( G% k5 H. r- `
public void delBigZset(String host, int port, String password, String bigZsetKey) {
3 {* Z% z9 p7 L+ B* x' z Jedis jedis = new Jedis(host, port);
$ n0 ?2 w* j6 ] if (password != null && !"".equals(password)) { + e$ c& ^' p; h; C' A4 v' \2 g
jedis.auth(password);
1 y! A: \5 r( W! }8 { } # ]! d* G+ l- \0 d/ h, u
ScanParams scanParams = new ScanParams.count(100);
% | R7 d/ ]! W: Y8 _. k* m String cursor = "0";
. Q! }. \2 h& G. M0 ~& d# u do { 9 O5 `1 }: m) K, }7 P1 f x
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); . [- N" p( |8 i. F) X
ListtupleList = scanResult.getResult;
- y0 ]# h6 u! i if (tupleList != null && !tupleList.isEmpty) {
) \1 V) x) k2 O for (Tuple tuple : tupleList) {
5 W6 n( U+ g# Q5 W5 ]" C jedis.zrem(bigZsetKey, tuple.getElement); x8 q& U; \; |, D4 a6 @0 j
} 5 D6 A8 F! Q' ~1 ]6 l0 |
} 4 \4 a8 M- t3 h& u5 P$ h9 B
cursor = scanResult.getStringCursor;
2 L# R1 b2 Y3 { } while (!"0".equals(cursor));# h. J* D9 ]$ @ E% q" x1 U
9 u$ ~8 [+ K0 T: Z* V//删除bigkey
' v5 I6 M& A+ | jedis.del(bigZsetKey); - Y r1 G. }: W. C" Z
} 公众号内回复“1”带你进粉丝群
' W& q4 X1 \& y0 m来源:http://www.yidianzixun.com/article/0LevQm7t
% i. Z- \% X) A6 y免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|