|
|
6 X4 ?* P' }% Q, g" H
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。: [6 [, U T M; O
% @. @( g/ q6 |- 键值设计. h9 ?. }; W' ]& ]. [' \& N4 l
- 命令使用
+ O7 J# ~* Y, }, G' w# P1 c: j2 J - 客户端使用
3 I9 c- p% P6 m7 ?( }# b - 相关工具
6 ^4 K5 P; S0 q4 l; n3 L 通过本文的介绍可以减少使用Redis过程带来的问题。# O/ L. p( S y4 i( y* c
一、键值设计; }$ {6 C. d ` v" n
7 C) I: I. D4 d( l3 Q1、key名设计
) i% s% F' i" p1 C7 H; x, A" G/ s; B+ R
可读性和可管理性
# R4 P6 `% X+ H0 V, R# }
' N4 w) g/ `) F. A- M/ ^以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
2 Q, X) i) @! [' k8 G* g# c+ y
4 s8 q' |/ E1 y8 }& K- ugc:video:1
- l: C- S5 z4 t 简洁性; Y! ?9 M( x- u: ]& Q
1 Z6 c2 H6 l! |% ~9 T
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:- D; ?7 u# |( B0 L. R
) I( l, I# r) r9 H! b8 ^% Q
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。1 k8 ~/ M# k5 v" W/ D
不要包含特殊字符
) j$ c Z+ l6 p3 @1 R, h6 V
! ^' D! e, f9 |$ U: Y% a反例:包含空格、换行、单双引号以及其他转义字符0 j4 T5 U7 X& p( E
2、value设计
0 v( P; a2 O& [ Z8 J- r6 |
0 [# F, @1 Y7 M$ g( `9 @' a/ _拒绝bigkey
! s( G* k( U6 q$ y! h
" s4 P& T/ D. ]& ^' g& e防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
- c6 d3 \4 {3 y3 v% L反例:一个包含200万个元素的list。* x9 K3 u' W4 \/ D
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
; s5 O. r8 T" [选择适合的数据类型
% ^! B- P' S0 r c6 p: R, m% g- N7 `' O3 O
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
& z% N U |& x1 ~7 |4 ?& S反例:
/ @5 U6 O# |, P2 Y% F0 o! Y
* x1 ]. P0 w4 F, G3 }! @- set user:1:name tom
4 Q) M% M; G- M" E0 E3 s* | - set user:1:age 19
" j/ }& ?3 x( v+ w. u3 k0 W% ~; s - set user:1:favor football
5 ^& [ P% x) y7 V# D: q& G L ? 正例:
o- i4 @# [ r9 L, N0 p& v8 `- e2 L1 V
- hmset user:1 name tom age 19 favor football
) S. l6 v( z% s 控制key的生命周期
7 T; g8 U. ]0 b5 m& r( |: \' }5 L4 f& n+ C* _) Y
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。* ?8 `6 Y9 c6 d( F
二、命令使用
, Q4 V1 }8 S/ R ^5 W! b3 J% N! ?, y; D
1、O(N)命令关注N的数量
5 [- P# X7 u- \, K S7 j7 S/ l [" y. g5 I9 M3 K$ ]. d( C4 P9 a
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。# ? f& a) n* n0 c6 n; I
2、禁用命令
1 \, T7 r& v/ T) G+ s
( X6 s0 o# i, N9 [* `禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
* g# Y" i( z' v0 y. p3 U* E6 L. L3、合理使用select
1 @: F+ W7 S2 v4 t) f3 i# z7 R+ w4 [- |8 q( e ~
0 p- N, G$ A9 P; g& u
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
6 a! E! X+ n9 v3 U( p w4、使用批量操作提高效率+ j ~( m" ?- @& s
+ j) P1 ^- H7 X7 D2 K6 Q& h; x4 @0 o, M+ |1 O
- 原生命令:例如mget、mset。 m& R+ T6 h2 v9 p
- 非原生命令:可以使用pipeline提高效率。2 C \- E+ h9 X, W( S2 A0 N# [
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
; S- A; J2 A, N, K& K; ~注意两者不同:
, W9 j; ^0 g* K7 q! z/ {7 r- v8 L" @" X& N' ^4 _
- 原生是原子操作,pipeline是非原子操作。
' g4 P1 n% b. x$ c - pipeline可以打包不同的命令,原生做不到
. f6 c% C& N+ ` m& t0 D1 B4 c - pipeline需要客户端和服务端同时支持。( ]6 ~2 P. h: J' f9 B! N+ ~
5、不建议过多使用Redis事务功能4 a- Z1 J/ w! S; E% k. V
! M% w# U. _' n7 d( ]Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
7 w5 m& E5 O0 v7 w6、Redis集群版本在使用Lua上有特殊要求
7 L9 D4 z: d5 S7 [3 n8 o8 ~* r+ c' U9 Z1 {/ Z
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", [8 ~* O7 K. a. o6 O
7、monitor命令8 v/ i2 P) C9 j1 E0 z0 i
! n2 p4 R) u+ C. g' y$ E6 O1 W必要情况下使用monitor命令时,要注意不要长时间使用。! v: s V; v; M
三、客户端使用
; d! h6 [3 l* P& W1 ]3 Q8 Z0 k6 d& s% A8 r5 a9 Z
1、避免多个应用使用一个Redis实例6 Y K7 k+ i7 [# ]% p& z2 Q" A
& e& s$ f4 h( ]. d; x& @
不相干的业务拆分,公共数据做服务化。
) u1 R% ]0 j% C2、使用连接池
* a0 n) r2 j/ v6 m$ f1 p; }$ ^7 z+ f$ U) q8 S k" @5 z0 v7 H
可以有效控制连接,同时提高效率,标准使用方式:
+ W; G8 q# `8 d; x( |6 W, Q# JJedis jedis = null;
+ g8 Q' k3 p6 h" m) {) Gtry {! a' ]9 m( ^. K& p! Y6 a0 X$ y
jedis = jedisPool.getResource;% p. E: l' Q: x' @6 @7 `0 L, ~& ?
//具体的命令
( i8 R) d7 M0 n jedis.executeCommand. ]- }6 H/ p% ?- Q+ G* k
} catch (Exception e) {, p( j0 X& Q+ `( ]
logger.error("op key {} error: " + e.getMessage, key, e);
5 P" a- I, c* U+ z% h( r} finally {7 a5 [# y; e, Z- C
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。+ d$ N# n) k& {* E- ^( k
if (jedis != null)
* z! k' F8 Z( i' }0 ]1 P$ i& Q( Q jedis.close;
. r' r7 E: \- p l! u B0 A$ P}3 t" c8 e8 @9 t& r _
3、熔断功能: f [9 A9 {7 n5 d
" I& e9 G3 Q: g7 ^高并发下建议客户端添加熔断功能(例如netflix hystrix)
# P4 c# w9 o3 P$ u/ I" V4、合理的加密- W+ D6 M% z6 w' Q
5 h7 l7 ?/ T. f; H* e; w0 v+ Q6 Q设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持): W+ C7 u4 x- ^! X
5、淘汰策略
9 F6 @5 f5 R9 s$ d2 }' ?0 P- H
* n2 U8 a0 k8 |$ }, ~4 N根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。5 D8 g6 d4 D3 |& ^$ \
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。: U% M6 r' A( @; ]1 ~) e' M
其他策略如下:8 j! p! B) p4 s* x. J# E
3 w* r% N, m: L `- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
. x6 e+ n8 U! [* s- u/ k - allkeys-random:随机删除所有键,直到腾出足够空间为止。
$ n/ d- S) k6 u6 O3 s - volatile-random:随机删除过期键,直到腾出足够空间为止。
% P& J0 f& o. y2 d0 f: B6 j$ ~ - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
! \: g; g3 E% J7 z - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
8 c3 T3 p3 q3 E- @' i( r) D 四、相关工具9 r6 K5 \% [" b
1 p8 @- F; G: a( D2 A1、数据同步
7 M! G W; n2 Y/ l- `+ \) o9 X" e8 f$ ?) L/ w) N
redis间数据同步可以使用:redis-port
' s$ A, P/ I5 P- Y5 _ m2、big key搜索
, W/ {$ n4 [' \( |: v9 s3 u3 G, I% N/ j* y
redis大key搜索工具% h% t/ T7 d* d/ @0 j
3、热点key寻找1 b7 R! X+ w4 S+ n: o
+ K* E I. q J- m0 X
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
; q6 t, i7 `" f" @ }五、删除bigkey; f6 q) F3 \5 F* \, Q
2 A7 b# ~: p4 E9 a3 P0 q: |4 t) E; `8 W8 ]) a
- 下面操作可以使用pipeline加速。
) f1 ?' X( N% X# Q5 R! E9 X* N - redis 4.0已经支持key的异步删除,欢迎使用。
1 L$ G' `7 u# @8 o2 t 1、Hash删除: hscan + hdel
) q7 a- y. M; f F; g" v ]) w7 q/ R
public void delBigHash(String host, int port, String password, String bigHashKey) {
+ _# J) h6 Q7 }0 E/ e- K0 l Jedis jedis = new Jedis(host, port);
, r, V1 y/ Z3 F3 J6 z if (password != null && !"".equals(password)) {
: K. a. O% ]1 S: K# [! i. C jedis.auth(password);0 n$ k+ k9 W# Q# h$ d! `+ w* H$ M
}, w# \& F$ Z3 w
ScanParams scanParams = new ScanParams.count(100);
8 r& [# Z E9 r/ p+ J0 s4 [ String cursor = "0";9 o1 n% ]( T& q) r
do {+ R+ p3 N& I; }. |: ?/ A/ ?" \
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);" E- a" j( \5 C: P+ b3 ]. R
List entryList = scanResult.getResult;
4 A4 ^4 X# Z9 I' i/ b s& t if (entryList != null && !entryList.isEmpty) {
1 N1 r0 K7 F( D for (Entry entry : entryList) {' ? J) x" o) A4 A2 Y+ y( j; D7 Z; o
jedis.hdel(bigHashKey, entry.getKey);9 u q5 t2 A5 u. [
}8 x# A% }, k+ b
}
0 [6 Z4 w9 E$ ` q) | cursor = scanResult.getStringCursor;3 Q1 ^2 Z3 W9 X" C* j/ ] A5 B
} while (!"0".equals(cursor));
6 W+ [! U4 ^0 H! Q8 c' `1 k
' g/ h) Q! \; E$ K# u8 \6 r//删除bigkey
; O1 l+ \! V5 y! p jedis.del(bigHashKey);7 D, {/ h" U7 j4 N! Z! G- r8 \) _
}' B& T F6 e" a: N9 ~
2、List删除: ltrim \ ?5 m% ?; {8 }# E
4 w% v: z9 C* f( z1 o4 O9 E1 F7 S" P l
public void delBigList(String host, int port, String password, String bigListKey) {
k. \& I, H8 d4 z. K2 j( T Jedis jedis = new Jedis(host, port);' W+ ~! I3 M$ b
if (password != null && !"".equals(password)) {
8 h/ g: e. a- Y$ S6 i& _- J jedis.auth(password);
& i. a7 Y8 B8 g; T }4 J+ t$ c: c/ [! s- f
long llen = jedis.llen(bigListKey);
1 ?) t2 j: Q1 u, o" i8 n0 t int counter = 0;/ q' A- W3 P9 `6 p& W$ Z
int left = 100;% }: g& A% k+ `" B$ \: w
while (counter < llen) {. f k1 }6 L) h5 ` m. w
//每次从左侧截掉100个" @- g+ `9 R/ _4 \
jedis.ltrim(bigListKey, left, llen);; e. `3 J3 Z. ~( A0 s- Y+ v
counter += left;' w9 I2 l" A" X" } X
}
D( T3 s4 ]' ]* |) T4 z //最终删除key
2 L3 |1 Z* j3 h+ f# z" M jedis.del(bigListKey);: v3 | f* T4 c' M1 A7 r
}3、Set删除: sscan + srem# x$ Q; F3 I4 X B7 l% [) b
2 `; {, q; u/ v
public void delBigSet(String host, int port, String password, String bigSetKey) {+ D ]2 _. v9 v j. V2 n; F
Jedis jedis = new Jedis(host, port);
/ l Y1 k7 Y# J4 }+ m2 [9 H* r if (password != null && !"".equals(password)) {
. B2 a1 m3 X' y: G! F X9 o jedis.auth(password);- m4 p( Z% n$ s! v
}
! w2 u1 f, T" y2 H1 Z ScanParams scanParams = new ScanParams.count(100);$ ^2 z& [+ ~9 K5 X7 I
String cursor = "0";: `4 ?1 o" Y1 i) ]0 F
do {1 v. d7 P) p2 ^4 I
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);! {) G+ H# k! r) D+ T! m% `
List memberList = scanResult.getResult;, s! E# A- ~ }. \! R3 E
if (memberList != null && !memberList.isEmpty) {/ M7 |- ^" d2 g$ y5 K$ o1 Z- ~2 b
for (String member : memberList) {
! D5 l2 A% F7 P- t! }: ] jedis.srem(bigSetKey, member); W b/ T. |% w; Q: { c' B* p' ^1 m5 a
}# x5 N/ p! S' r# x5 t9 `
}
+ J4 B- O) P/ w( k8 c cursor = scanResult.getStringCursor;
& {2 Z) o; G( T' Y3 A5 x' U } while (!"0".equals(cursor));; n7 I3 y! e' m, c
" ] i' g4 C4 A$ ^6 y; e4 j
//删除bigkey
9 w5 j M J8 [- I0 a, d jedis.del(bigSetKey);8 Y4 b0 q$ @' e1 S5 _. r
}) [& ?0 R. K C2 t% q# T" B0 c d" l& e
4、SortedSet删除: zscan + zrem; W% v# i7 r9 W" a% ?1 c
# |% W* w& \( c" |3 Z
public void delBigZset(String host, int port, String password, String bigZsetKey) {
6 l, s, n- G" _; g Jedis jedis = new Jedis(host, port);
$ R% X2 `8 o2 m. ?) F if (password != null && !"".equals(password)) {
. n9 P# N+ K; a, u) f jedis.auth(password);
2 U$ |: v4 r2 J# a# w } ) _" X d' M4 ]4 c' N: n: L3 `1 C
ScanParams scanParams = new ScanParams.count(100);
4 Q$ e' a( _! [! {! b" e String cursor = "0"; 2 E- A+ ]7 }( p. m2 @. e
do { ( b+ n" L; g. m1 Q! u6 J5 |
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); ' I& L, F2 M: Q
ListtupleList = scanResult.getResult; 3 b5 k" d" |. \, I% h
if (tupleList != null && !tupleList.isEmpty) {
6 ]( q) ?( ?- T0 u' R for (Tuple tuple : tupleList) { 1 E% @3 x$ \' U' u9 ?: o8 y
jedis.zrem(bigZsetKey, tuple.getElement);
1 e7 w2 n3 T/ ]$ [8 V } / F/ ~ c0 `( n9 ^; v
} 1 A! Y. y& e1 Z- s8 |! D# X# v4 a2 ~. Q. L
cursor = scanResult.getStringCursor;
6 P' o0 h, Y; P. x! [+ o/ d& ^ } while (!"0".equals(cursor));
0 C! R. m9 Z* }, |
+ X$ ]+ W3 z1 w9 }" I3 D//删除bigkey
5 O6 t( \' f3 ?/ [3 p+ L jedis.del(bigZsetKey); 6 O" e1 l1 p' [1 w
} 公众号内回复“1”带你进粉丝群& F# j. ~5 X8 o6 X2 \
来源:http://www.yidianzixun.com/article/0LevQm7t) f4 Y: j+ K* I
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|