|
|

0 h" F3 \) M S本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。" m4 ?1 G9 p! S9 r0 }: W! _1 o: i! M0 U
# I# Z% ^) f c& H$ B& S0 `) |
- 键值设计
- r$ {9 y$ C9 Z, Y3 P6 d; Z8 o- s" Z7 }: v - 命令使用
2 W6 }$ Q3 z+ N" [. [* X - 客户端使用5 o$ e! {: ]! T3 h! |4 [- A- e
- 相关工具2 N; h( G h& M
通过本文的介绍可以减少使用Redis过程带来的问题。3 ^3 P+ ]/ R9 B A) u+ C
一、键值设计6 W8 t+ ~& i- E) W [5 b- s
& K1 Y$ ~$ L6 F6 e: Y' X* @. `: j( i1、key名设计
! O; e! E/ }. Q5 Y. W8 r1 m. M! P; Q% C5 B, t3 i7 `, j
可读性和可管理性
9 ~( l* N; K1 [0 o U: B2 \+ n' b; _ @: s. B, c
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
* T( y# {; o. m
& P+ W- b2 C# j1 N" T9 V- ugc:video:1
% c% K. F- n' N* Z; P 简洁性
& V" A# V0 D' {" |7 e. m# g4 ]7 y5 L5 }
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:& ?% I# S% u8 M! B1 v
, V7 ?# w3 ~7 h; b( H) e( p, N. u- e- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。8 Z, d6 ]2 @+ K% e
不要包含特殊字符* f) G1 O3 [9 w5 M0 w* R
2 X- ?! f; Z! Z& ]! X& q6 _+ J5 I6 F! q0 G反例:包含空格、换行、单双引号以及其他转义字符. X9 {7 U, b2 f
2、value设计
- j/ z& E6 q9 U1 m& Q
8 c/ a* q+ c5 N2 C ?% J拒绝bigkey
* E8 {8 A7 E3 ~' F( |' n$ H* a
- ?% c* V5 r3 p: \# O防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
& F2 {9 w' `! y- f. u) L: [$ l8 e Z反例:一个包含200万个元素的list。/ F) ^# y- T+ ^. z0 F6 d
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
/ v" _& _, n5 Q4 ~选择适合的数据类型: A- y6 ?/ l3 _% ]! a
! Y% H5 D9 G+ S1 w# D3 B. Y3 A# V) |例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
( Y, R- |$ S" \8 B* e. `- h r q反例:: b& b" f$ f) i! w( ~* L3 g
+ @# o# {; O$ x: j8 Z- set user:1:name tom8 ~0 o6 x- a1 f/ F+ \/ ]# A. g
- set user:1:age 19+ I/ i# f P4 X+ n) n
- set user:1:favor football
1 Z" m$ ]; Y: n( M- k; x: t 正例:0 E! P: i( } g, j- z, g
: W2 l% d* U9 A- O7 d
- hmset user:1 name tom age 19 favor football4 g4 L. ], i& r
控制key的生命周期6 b" N1 ]. r' ?1 o0 F
" q" w! Z# `9 p( u, s, H' F2 Tredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。, }8 `# ?9 _7 ~
二、命令使用# X( J: d- ]: h0 C
0 w2 _7 X4 F ^" K+ i9 m6 z4 x. M% y1、O(N)命令关注N的数量, [. C# x9 ^0 W- N! U
8 L f+ H- \6 b例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。3 n. J$ L& T8 r
2、禁用命令: ~5 j& B7 T$ p' o; j; x
3 A+ R2 i+ b$ P7 {! u* K
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
0 H( g* k1 l; ]. i" \3、合理使用select
" R9 a/ k# K) b8 W7 c! w( W: g' S
% D7 L& |6 G2 e- Q* n! v8 a$ Y' t/ J5 i; N
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
" ?7 }- F, E; |! D0 L( s8 K1 e7 k4、使用批量操作提高效率% z3 m" h- i# L$ s
1 {4 f% [( T0 L" `% z) T0 h
" }7 u, B* M' c# F* |, S% ^( g% g8 U
- 原生命令:例如mget、mset。
# x6 |* s% f2 K6 q! `2 G - 非原生命令:可以使用pipeline提高效率。
e+ L$ @) l6 ]5 l( u 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。) ]9 g. u- m$ S4 `8 J& e: V" L7 _7 ?
注意两者不同:6 V5 O4 w2 T5 V' ]6 J: W
6 }. s! w% t9 }6 | j) b- 原生是原子操作,pipeline是非原子操作。$ t- w/ Y* k+ F
- pipeline可以打包不同的命令,原生做不到5 g( H8 n& Q* O1 h5 J( }- t
- pipeline需要客户端和服务端同时支持。
: ?) B9 w: I$ `2 ]8 L/ ^ ^ 5、不建议过多使用Redis事务功能1 j, ?7 q( m- t! |) r. K
( E6 ~8 F$ }2 F- n. q; I
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
: z" G: ]8 u/ [9 \% j6 Q6 V6、Redis集群版本在使用Lua上有特殊要求
$ l" f. G. H9 n& F& }& ]2 M
9 p/ K! K- g4 }6 e3 [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"
- W2 L# f Y, a2 c7、monitor命令
+ Y8 ?) V! W* [; G B6 W5 d- D9 t9 w8 p0 t* m" h
必要情况下使用monitor命令时,要注意不要长时间使用。/ a! q1 w% ?+ @ ^7 N% \) s( X
三、客户端使用
( {2 c* f/ J }! x1 ~% k; }3 z6 X, Q) W$ \0 q2 P
1、避免多个应用使用一个Redis实例! N I( ]% E! r+ V' ?; l9 d/ y, Z+ I
# W; v2 \4 ]' |( e
不相干的业务拆分,公共数据做服务化。+ H* c9 o( C; y- O
2、使用连接池
- V# A: @) F% g3 l+ D5 T
. L7 @3 I3 R! J% N可以有效控制连接,同时提高效率,标准使用方式:% ]- @ B: |) o. G5 U
Jedis jedis = null;
: E/ J. P- u7 Y/ s9 v7 ^try {+ D+ n {8 \3 K% O4 Z% ]
jedis = jedisPool.getResource;
: d* C/ N( A" \# b3 g/ D! q8 w //具体的命令
# z9 q1 }8 g4 j% z jedis.executeCommand$ I- y1 }' V& F0 e+ L3 q, [" v
} catch (Exception e) {) @" F$ Q; X, }; q3 T
logger.error("op key {} error: " + e.getMessage, key, e);) p% B; q) l8 T U& b$ ~+ Q6 w4 i
} finally {* S9 t$ L8 N& }# g: f) t
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。; T( P3 m& K# {8 O' T" m
if (jedis != null)
: H; @6 B3 {0 A+ z5 \) |- v3 T jedis.close;
! Y$ k% z1 u: Y S3 P}6 k& l1 Z7 j/ s/ r& ~( J( A
3、熔断功能- U% P5 A" k) D8 K1 `
4 n" `1 |/ y% Q" J* O2 B2 o" m高并发下建议客户端添加熔断功能(例如netflix hystrix)
' [6 x1 G! \! y9 o# Q4、合理的加密& p. w/ t* c% e5 ~ \$ ]" f( I \
' S3 o$ p/ F n2 G/ q设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
8 L7 X* J% g" J- b1 J5、淘汰策略
% \" Y# M) M, Y, o! j6 K+ q# f6 E* f0 x: m) j: w1 c6 b
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。: i" j/ ~* ?' G1 j' @. G1 H8 `
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。% i. p% E0 X+ ~, M
其他策略如下:0 B9 O+ h& f9 @+ K/ B
! S. |+ g9 j* N* Y+ p- ~' U) B9 M" y- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
" x' R0 Y; {0 W4 F' C3 u7 R0 I - allkeys-random:随机删除所有键,直到腾出足够空间为止。- d b7 j3 g5 F
- volatile-random:随机删除过期键,直到腾出足够空间为止。
: ?* m; ^+ v- f - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。: e* G, I! k( c& c# e
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。8 n* y, a; @$ b3 k c. z. Y0 i
四、相关工具
6 `1 K, A9 p) A9 N4 h4 j
3 U+ f% B9 @' C8 A1、数据同步
4 ^+ H; G7 D+ Y {! }8 k! S3 @- [: x; B) ]$ F- J
redis间数据同步可以使用:redis-port
( H) x) c! W5 z* h4 M2 t2、big key搜索" j; A5 }" D/ F, K4 z
9 c% T' k2 K( l! W7 q6 f1 @
redis大key搜索工具5 F' l4 P# i; O" s! S6 Y
3、热点key寻找* r2 w- D- L* o* T! n, k: ]4 s
; E1 g3 N1 S/ P& h! l内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
, ^- F1 X9 k# p. O8 o/ P五、删除bigkey: B3 C/ v# q3 Q0 S) B4 G
8 Z3 g- v; U/ E4 d5 Z6 H$ X/ |5 ?" g# o% I
- 下面操作可以使用pipeline加速。
4 o) N. U: r) Y4 U/ Q$ O - redis 4.0已经支持key的异步删除,欢迎使用。# c2 j% {' `! F% D; t6 z" I
1、Hash删除: hscan + hdel4 v/ k3 f8 `0 U" |
4 |7 U _# J) I1 k* j3 L% A5 Zpublic void delBigHash(String host, int port, String password, String bigHashKey) { b% N9 U( ]. f9 L/ n* B/ R6 u9 V
Jedis jedis = new Jedis(host, port);. n" f' P5 |# o; g8 v
if (password != null && !"".equals(password)) {
& Z5 E' O4 w% {1 u# f0 o jedis.auth(password);
5 Z' R+ ]9 Y8 ~$ c: L7 f( W }& a, [1 O* x6 H2 n& r9 I+ @% P
ScanParams scanParams = new ScanParams.count(100);
; n2 ~! b& I/ T2 E# }; {9 u6 P- ^5 G String cursor = "0";
! \$ g( ]0 ?' D( z do {
e. ]2 I6 Y, A# C' I/ h ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);4 u' i5 n9 A/ G6 ?4 j
List entryList = scanResult.getResult;" W ^- A, r& G
if (entryList != null && !entryList.isEmpty) {5 s8 E8 O Z/ `* n4 g& v% o: k3 W
for (Entry entry : entryList) {
8 G1 D$ J$ k$ o! V2 ]- T+ z jedis.hdel(bigHashKey, entry.getKey);
3 v( z* `- F; }- [0 q }* x3 z+ P# ]0 f1 ~: o0 P
}+ a2 A6 |+ ^( b! ^1 j9 {
cursor = scanResult.getStringCursor;
8 Q; m- [* V# G7 g5 q } while (!"0".equals(cursor));( Q3 P+ s& ?7 P7 U& K
$ z" R; Z$ f! V8 v1 f# s//删除bigkey8 d) V" v7 a: n: N+ e# l
jedis.del(bigHashKey);0 C, X6 X) A: R) `+ W4 `/ z. m
}; a6 i- U+ q! e* G; p2 K
2、List删除: ltrim
' P5 ?" m, F4 X J' |- j
- T" z, C! e) Q6 z) Bpublic void delBigList(String host, int port, String password, String bigListKey) {4 |2 y0 Z" G1 z3 L# U, Y2 M
Jedis jedis = new Jedis(host, port);- L4 z8 Z) N* P9 K$ f- y& l
if (password != null && !"".equals(password)) {
: T K9 W* _! A( S0 S jedis.auth(password);
t2 G- ?5 B; ], Y) z }
6 U! `7 n' A! z& A; E6 ?, z( Z long llen = jedis.llen(bigListKey);
, c! f2 v# p3 [5 Z6 e int counter = 0;
( q1 @9 W/ Z0 a9 p0 [ int left = 100;) j) P x9 ]/ B- T# r; z
while (counter < llen) {
+ ]9 [- M/ j# }3 x7 f+ p //每次从左侧截掉100个/ i; {' T. A! b: G# I
jedis.ltrim(bigListKey, left, llen);
# x# R0 m" q! I7 f# X6 y! i* d counter += left;- N( G: x, ?* G
}
# ^4 T$ T* W' K3 p //最终删除key
! t9 b' e Y) C: D* ?, n jedis.del(bigListKey);* C& X! z& K H
}3、Set删除: sscan + srem
* \4 j- b. w' \0 m) A9 p; J
5 j4 S; O8 m9 F ppublic void delBigSet(String host, int port, String password, String bigSetKey) {
; j% L; X5 ~' X9 t. B# P Jedis jedis = new Jedis(host, port);
( ^) h) \7 F1 f if (password != null && !"".equals(password)) {5 I& i2 r$ g3 q
jedis.auth(password);; Q1 h2 Y; J1 U/ Q: ~+ E' \
}
# V' J2 B( L: z, n: l2 v: W ScanParams scanParams = new ScanParams.count(100);
5 e& u% h) G4 C, t& q D9 k String cursor = "0";. r% e& x3 x6 z+ ^ G; [$ J7 R1 d
do {$ `) e) s* N5 U% {9 `
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);) k1 x* h- V% q& i- k; W1 _8 q ~
List memberList = scanResult.getResult;
; L" _0 x4 ~- c" g+ h3 y if (memberList != null && !memberList.isEmpty) {3 M s& i) E* I0 `
for (String member : memberList) {4 e1 T8 r. C3 W9 S1 Z
jedis.srem(bigSetKey, member);
, t3 `5 d1 M5 K& N q, b3 D }. y0 p" W1 `1 D
}
2 X3 Y' F _6 @/ j* H# A) Z cursor = scanResult.getStringCursor;
, |9 \$ e# ^! c/ p; l } while (!"0".equals(cursor));
, U, V! z3 @/ d7 _' n1 j- n+ v4 Z) ]1 M) ~5 q) S2 [0 n2 s' G
//删除bigkey
; @; L( ~. T+ X2 A jedis.del(bigSetKey);8 B- @& W; s3 n5 V& G; K$ D5 _
}' v4 L8 z, q. Z# C' b+ c: f
4、SortedSet删除: zscan + zrem* ]: }5 E* R6 q4 E% i* m. e- _$ n: q
. R# ~1 q# C3 }4 B1 n8 I* spublic void delBigZset(String host, int port, String password, String bigZsetKey) { ! f' u$ O! `- x0 G
Jedis jedis = new Jedis(host, port);
5 t1 Z/ K v, T; a9 n% @; [, A# E if (password != null && !"".equals(password)) { # j$ o% k1 f+ X; N7 l- x
jedis.auth(password); 3 I( l+ |, `9 i% W& Y
}
& v# m3 f5 C6 A; J+ V' I$ B ScanParams scanParams = new ScanParams.count(100); 0 z3 v6 A& o" s
String cursor = "0";
4 A7 r; e1 d' j do { . w+ _% ` E5 x* M; q
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); & c4 ]- q- ]5 a0 ~( T D2 h. r8 w( J ~
ListtupleList = scanResult.getResult;
' U% M. j: N7 c r ^ if (tupleList != null && !tupleList.isEmpty) { 7 V+ ? S# t8 l/ g3 ~: i- I
for (Tuple tuple : tupleList) {
4 o5 b' F. T9 v, u jedis.zrem(bigZsetKey, tuple.getElement);
- c- Z( X9 z2 |/ e& Q+ q. N }
2 a& k, I' P7 R7 |% ~' N } 7 k7 v0 q/ K+ n: B0 b5 P" X# ~* E
cursor = scanResult.getStringCursor;
$ k6 L" e- a, {; m } while (!"0".equals(cursor));) D( U0 G. i" Y0 C4 l" u- c
, l/ D: ~6 V! i- ]1 A s [' }; _( R//删除bigkey - M, T8 g6 g6 O, G1 g5 v5 U
jedis.del(bigZsetKey); 1 K y1 Y3 r& m
} 公众号内回复“1”带你进粉丝群7 H5 C3 R0 z$ I7 i/ Z. x2 z: Z' z
来源:http://www.yidianzixun.com/article/0LevQm7t
% `& X4 A/ e0 B i% Z免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|