|
|

7 s- `! d7 j+ `: w/ d6 Y本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。4 T+ T; c* Q/ }" t K* h
Y$ e2 E3 v7 ?& o" E: ?
- 键值设计
6 x7 }. ]! C1 ?" F* t9 L/ J: o } - 命令使用8 Q2 p# y+ A5 q, D: \, Y. o
- 客户端使用
! {+ T5 C, `& Q' b - 相关工具
; ^3 w( b3 t v5 m! Z- ]) K# N 通过本文的介绍可以减少使用Redis过程带来的问题。
% V- j# a$ J- k$ u: D3 ]8 ?一、键值设计
/ j' N3 x, z3 t5 E8 c+ `+ l/ U# m9 E
1、key名设计
. \/ A" m, L" K# D, d( P4 t& o2 Y* W9 u; s8 \2 B' Y( e& c2 b. F
可读性和可管理性: c; i5 n7 z9 @& r) U, E
2 x( b: a0 W0 a0 |4 D
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
: T$ }0 w0 t4 b* ^$ o. A" \- p/ ?, O" q, I: j' |5 X# i' D6 B
- ugc:video:1, T; X1 P" F3 q) {5 \6 E. s- |
简洁性
: N( i" R0 Q1 X J8 }
c# F2 s( w O/ @, q1 p: d- b保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:7 l6 h2 x; Y' ?* d# ?
& |: v* e! N( K) {6 k
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。! H; |; l: ^% r6 j* E% h
不要包含特殊字符
8 r7 l8 v9 J9 _2 ]1 I: t( Q9 C- p( A( y0 R" O9 b
反例:包含空格、换行、单双引号以及其他转义字符
) {, F/ v. W( o% m% ?4 \/ n8 o2、value设计
% a% m, R# l: @+ ]1 t7 T5 j$ T0 a# R% {
拒绝bigkey T- g5 U4 J: W5 W* E; P
' x2 Z& }1 t: R) U( \+ H! N7 Y防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。! X% A g! ?( h9 b0 ~. `/ A$ c* x
反例:一个包含200万个元素的list。
$ [3 @8 W) i- W非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
) S+ {2 K0 l+ h& n2 _选择适合的数据类型* c3 q+ } n9 {5 k/ l
) R$ x% F" f5 `$ x. E$ D8 f例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
: p; j) Z* t/ x' k a1 D# H. b+ Z反例:
`( I5 T- [; \3 D$ l/ Q3 ?( d4 h, H5 v2 }9 ~5 @' a
- set user:1:name tom( h/ u7 I) H- @4 W0 e- J( V
- set user:1:age 19
. E9 {: A0 F% ]& p) @% Z - set user:1:favor football
# j! [6 O1 i+ G: G- R- N* j 正例:8 F5 N& ^+ q! h {* `2 B( y2 u- T
" j2 [$ u$ f: `- hmset user:1 name tom age 19 favor football( g8 f) b# H& n# J! I
控制key的生命周期1 [# w: l$ l9 N @
. g: _9 T E$ |2 Oredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
1 G( p4 i" k0 `1 z; o二、命令使用
0 ]: T" ?1 O" ?5 }: L' C% r% @0 }2 V3 B: }
1、O(N)命令关注N的数量
3 Q# s& `, m) d: H" V! W% J- v9 u- Q# J; d% K2 C$ E
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
1 F7 O! D- [" A/ j# R0 {6 s2、禁用命令
; P7 }+ `0 X- @6 E- n9 e2 w5 c7 r0 f( E) ]3 f, p
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。5 C. G% I2 G4 o( E
3、合理使用select b8 D5 ~( O5 ?& Q
, {0 y! f+ M1 p) E6 n0 E& U( s% K. {" [: |: Y1 Z' ~
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
$ ~3 ]" e5 a7 L; X4 m0 M$ e3 s8 ]( {4、使用批量操作提高效率
/ L% g4 d0 p- o% w ?9 e- c/ C) a6 b8 ^7 T% u Y. y
( o7 o5 @* R' n% n3 n" z
- 原生命令:例如mget、mset。
% y$ M+ j& n Z* J! r - 非原生命令:可以使用pipeline提高效率。
) Z$ C7 d n# A8 a# o$ | 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
% G3 y Y2 c' K3 a. d注意两者不同: U& w2 ~1 r7 Q9 K, C( D
! x7 V$ y( S" }+ U, q4 Z; S
- 原生是原子操作,pipeline是非原子操作。5 j+ g/ I5 F3 J9 w5 p
- pipeline可以打包不同的命令,原生做不到7 ~8 k( F: _1 z( a! v) [
- pipeline需要客户端和服务端同时支持。
% k( O3 Q) L/ d( W. N 5、不建议过多使用Redis事务功能2 U4 L I5 l% f* |4 ^) y
+ \" B! e0 b9 m" h8 b( W: J8 \: L
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
' a6 U! w9 l" ~6 d6、Redis集群版本在使用Lua上有特殊要求
7 ?9 V( m! j0 _" R
- } K8 q2 o& q$ h1、所有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": b" n* n9 Q" F
7、monitor命令
4 l; `/ Z7 z3 |6 o. |, |
0 d" E' h. z; M1 ^$ t' b必要情况下使用monitor命令时,要注意不要长时间使用。
3 p) V) p4 F) a8 x% V5 `三、客户端使用
1 f; W6 T) B) q, M& m; N% e3 g0 z3 f7 q# v2 v
1、避免多个应用使用一个Redis实例
+ @: ]3 i8 U5 Y) p1 v. B+ r% h2 W
$ g+ V8 e& Z( c+ T1 f7 l$ f不相干的业务拆分,公共数据做服务化。# q- e, B+ j. w7 a j3 {
2、使用连接池
: ^6 C- z8 j4 _: H. z( T4 d7 |+ |0 W1 Z8 ]- p5 w
可以有效控制连接,同时提高效率,标准使用方式:( T' ]+ i7 q9 e) A
Jedis jedis = null;$ N$ {8 E+ R+ D8 p4 O
try {
. D! u- X( f' j* d jedis = jedisPool.getResource;
& s; N7 p9 p7 T3 K! l //具体的命令
& ~& h* j ^7 O, ?$ r jedis.executeCommand- U T C' R1 J' u* Y, B! ~
} catch (Exception e) {, j. J' A1 t1 L2 D" N% U' A- N& F% M" S
logger.error("op key {} error: " + e.getMessage, key, e);/ O5 [5 T( T9 h/ C
} finally {3 c& X! V; F5 w2 j+ Y( @8 H
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
/ Z6 H9 X$ g3 L ~9 q% e( h6 f( u" p if (jedis != null) 2 @# ^9 W" ^! @: ]; g4 l" t
jedis.close;& B! Y M( v) ~; h- T9 u
}
9 e* ?' ^) p' K9 `7 l8 b3、熔断功能/ a, {; h4 Z) A2 u* f7 I0 n
6 z/ b( u. p& `7 @7 E0 P7 T
高并发下建议客户端添加熔断功能(例如netflix hystrix)
$ @& ~) I9 X, E! d- `% B4、合理的加密& R. d) `3 N/ Q7 u
# Q4 \6 o2 g& T/ j3 q0 i" F设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
$ b# s t+ _& B5 x( C, z% K5、淘汰策略6 b$ P2 g& C; i0 O+ ]+ z
, ?( I7 }! n7 {' u/ ^根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
& y2 p( | @( k9 j5 O% m6 z默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。( b F# J$ |2 r9 R& }# O0 k7 }
其他策略如下:
! O- |) N+ Q. p2 U) p* U; v. H( k
! V( q/ h! d0 X9 P- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
, C4 ^& X0 C. e) X P+ ~7 @ - allkeys-random:随机删除所有键,直到腾出足够空间为止。
( n* _9 T& U& s3 C. b' [' k9 f3 q - volatile-random:随机删除过期键,直到腾出足够空间为止。
2 U- ?4 R, M k: p$ a, y - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。6 l0 R2 J2 @. Z6 K5 ?; T
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。$ ]1 ?1 R; A4 G: w. s
四、相关工具5 u/ F5 e* h U& }+ U/ T
6 S( C G! h5 F' j
1、数据同步! a! p' R/ L. X: y" H
- A9 C1 D; l3 V; N0 I: Nredis间数据同步可以使用:redis-port. T9 _% d3 ]0 g; i
2、big key搜索- u0 m) Q3 s1 J/ _4 Y: _
9 t1 S% E6 H" \# M
redis大key搜索工具
& e9 M1 b# N; h' p! q7 i3、热点key寻找
. }1 t; \6 o$ { ]5 N2 H* T+ S% I/ s" j6 I! ~2 q# g
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题9 }. h) ~5 p& I) u7 T
五、删除bigkey
- L7 n* P& G" e: |/ V2 A+ K# w8 m! d9 r- a* k8 D: i+ @4 r" s
: f# Q* j: u* N" t$ P D0 m- 下面操作可以使用pipeline加速。9 d. J9 a2 ~7 z4 b) V, Z
- redis 4.0已经支持key的异步删除,欢迎使用。# C8 V; K% }* B3 Y: l
1、Hash删除: hscan + hdel
) K( q. ]6 D' a p- [0 Q
' A9 |: T- [0 D- [8 a2 bpublic void delBigHash(String host, int port, String password, String bigHashKey) {
% L& H6 D u# V# Z. ]3 i Jedis jedis = new Jedis(host, port);
( U. d/ R2 a) [% S( e, G h; v if (password != null && !"".equals(password)) {, v+ x! y2 s7 `; r6 x: Z6 Y) ~& G
jedis.auth(password);5 L4 K( u" y( a; B
}' g6 z G/ a: g) F
ScanParams scanParams = new ScanParams.count(100);/ W+ K4 p% b( r! S% q
String cursor = "0";
# W" ~4 G& m/ r- I$ Q do {! S) r# W. a2 l, X* c4 a, s' Z* w
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
" j* l) r+ j' z: ]/ y: \, A List entryList = scanResult.getResult;
3 S$ e! X2 c# W9 f. ? if (entryList != null && !entryList.isEmpty) {% y9 Y* S) E- g- f
for (Entry entry : entryList) {7 g# d: w8 g* ?3 z$ M! e- t
jedis.hdel(bigHashKey, entry.getKey);" E/ F& u" z( E, G* l$ e
}( ^ ~2 f$ D/ Z7 f
}. C3 w) \0 X0 O
cursor = scanResult.getStringCursor;$ I/ w1 ] a4 e' B% A
} while (!"0".equals(cursor));* I5 j" q* T* m& }
# a5 U" D3 c: f( Y$ A% U//删除bigkey) j* x8 a7 `6 Y1 V( L
jedis.del(bigHashKey);: v* v; A/ Z$ x0 X) Q* y1 O E
}
" j6 v. `& L) |. B h6 S2、List删除: ltrim' L$ A2 U6 d) x0 t
; D& Y; a1 u( o2 {$ q: u, N3 y
public void delBigList(String host, int port, String password, String bigListKey) {
/ m+ p! y8 T/ g% C; { Jedis jedis = new Jedis(host, port);
$ t1 \5 ^$ A5 E2 x if (password != null && !"".equals(password)) {
5 s( B% e* J) P5 ? jedis.auth(password);
7 J9 w! K& y7 o1 r& r3 [& q' f) o' h+ B }8 L) t1 C9 I- q( w8 }
long llen = jedis.llen(bigListKey);
7 {( F& H( ]& x& G int counter = 0;
7 J+ d ~ p' M$ L: q( W3 l0 q( X4 { int left = 100;3 i. k" f( ] {; R) f* D
while (counter < llen) {. _) R0 `9 [) H6 F3 E ~
//每次从左侧截掉100个- j8 z6 M" s; @ v. y+ B
jedis.ltrim(bigListKey, left, llen);9 f, z* _7 _4 S) C
counter += left;
3 ?" S0 T3 f9 |% b }
& P. H0 W5 H- D# U //最终删除key6 _1 g! q% Z" @! y
jedis.del(bigListKey);' e! r+ G# D% u3 ]3 E* I
}3、Set删除: sscan + srem. a: g* ^# p0 E1 e0 d. c) I
7 h! U/ @% u5 `& o6 V( Z# ?public void delBigSet(String host, int port, String password, String bigSetKey) {* r; Z- T! B# @! O6 [4 Y
Jedis jedis = new Jedis(host, port);
% s. a$ {( b& E! @ if (password != null && !"".equals(password)) {
4 T8 e a% N8 T6 h. [8 h jedis.auth(password);9 a, P0 S, z' O! h5 ^. X
}9 i0 ~6 P8 f) M0 y$ k+ T* t) e7 M
ScanParams scanParams = new ScanParams.count(100);9 D6 R, B: g6 D& j. [% _. I
String cursor = "0";
/ F, H. N" L: E j do {
6 [- R! y7 }, D2 R ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);: S U$ d3 o. u9 s9 T
List memberList = scanResult.getResult;
' c8 ]8 K2 Z# J$ F if (memberList != null && !memberList.isEmpty) {
- c- F% A# i- c" s# n for (String member : memberList) {7 D9 l/ `5 a9 K8 K# B
jedis.srem(bigSetKey, member);
. [& J' r+ r& M% X! ^ }
. K3 ~3 {; I t" |+ N1 c k; d1 b5 Z; U }! `7 r: g0 ~/ y$ ~
cursor = scanResult.getStringCursor;& z& t3 K% v" v$ G Z. U! |. N
} while (!"0".equals(cursor));
) h- b/ N; E* `) @( H* r, ^6 @+ N, |
//删除bigkey0 u5 L; k3 x; B: s7 U4 r
jedis.del(bigSetKey);
% z$ M2 H" h, Z$ |2 r+ N3 K}
1 F! `+ F7 v3 u/ B; a7 g4、SortedSet删除: zscan + zrem# ?# P- F7 y5 Y/ X
+ |2 ]6 l7 @! ~! F) M
public void delBigZset(String host, int port, String password, String bigZsetKey) {
& U* Z+ z9 j r# m) y; B, [ Jedis jedis = new Jedis(host, port); : ?- C+ h5 E+ M& A) E9 C6 g
if (password != null && !"".equals(password)) { 9 z% E# E3 l. h; u& x- o3 u% F# l
jedis.auth(password);
8 V" A& Q8 ?3 G/ t+ V1 k5 k }
a! a. _$ v/ x ScanParams scanParams = new ScanParams.count(100); 9 N2 C$ O$ C9 ~. w9 g" _8 w5 W4 H) w
String cursor = "0";
) k# g* V/ ~+ t) { do {
/ a) d8 E& K8 C/ G" K ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
" n1 _* {, F, j ListtupleList = scanResult.getResult; 5 M5 D. U0 [. i
if (tupleList != null && !tupleList.isEmpty) {
" f4 v* q' ^) y) q: b. ~: E H$ F/ N" S for (Tuple tuple : tupleList) {
: P) U; C# l" C$ t) L* I/ ` jedis.zrem(bigZsetKey, tuple.getElement); 6 z. j! R3 @( C
} 9 Q+ ~* p2 R+ v1 E
}
6 G$ Z7 F2 |2 z& a0 V, I# [ cursor = scanResult.getStringCursor;
7 h" j) `, p0 w) Q5 A } while (!"0".equals(cursor));/ w8 ?# M" I2 @; [
6 x4 r, F, P9 X+ E# K. m//删除bigkey
9 P/ S( O( a3 N. J jedis.del(bigZsetKey);
6 G9 k( }. P9 l) T1 O% e4 p3 @7 Q} 公众号内回复“1”带你进粉丝群# Z( J: z" w, h9 a- s) K- p
来源:http://www.yidianzixun.com/article/0LevQm7t
% C4 U3 `1 @1 L% h- D4 B7 B免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|