|
|
! P j) e& G2 F3 N3 Z# y# Q
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
5 ?- W2 N" p+ S$ v' ^9 @
w& o3 P- U7 Y- @7 h1 [- 键值设计
3 ~, E$ P+ ~" q" @1 _" x2 T - 命令使用' [4 h5 i2 q f& c! |
- 客户端使用) L7 v6 F2 O! {' A% ~
- 相关工具8 ^3 ~. A; `( j( l+ v5 r9 U
通过本文的介绍可以减少使用Redis过程带来的问题。
& x( U; Z) y" x) d. E' p' N% P一、键值设计# Q6 q" |! S$ p! M3 c; e+ @
( ~" f9 v3 j+ ]6 r
1、key名设计
6 Q% T5 }; p( w- D Y9 V% r! H
; K, _$ `; ~+ R/ S/ J0 c4 A可读性和可管理性0 b" a. Y/ g# U$ n U
8 @( Q7 r7 M/ W4 o" @6 ^9 d以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
8 w* `* s- t' e& k8 B! Z9 t9 b- Z7 Y
- ugc:video:1
. [ e6 P5 b" C* O 简洁性2 y$ t9 d- X* `
- a4 ~9 W- E5 ~
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
- ?; ^0 H6 C6 V X9 z$ J
& K) u, _ c$ X k& W6 O% e! t, g- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。9 r6 a! P# t4 G/ q
不要包含特殊字符* d1 P, Y) Q1 Y+ m* [; B( `, q
" s' i/ ?; E5 C3 J反例:包含空格、换行、单双引号以及其他转义字符
: B K1 D& c1 B6 x7 N$ [# N# `2、value设计# }6 G) c2 v2 f, ?" p, r
5 c. w5 W% b m( w6 u& b6 e拒绝bigkey+ ^0 `) [; {6 S% K) ?5 W
# k- W# I3 l. |( E7 n, |3 u; R
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。- `1 ^) V2 O2 i: [# u6 a
反例:一个包含200万个元素的list。
) a9 S7 W9 q) v1 a非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
* H8 B' e- k2 [ s选择适合的数据类型; u* d7 Y4 ~5 h. t/ B% g4 E+ D
; I8 {6 c. g+ f* ?' g2 r& Q
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?) t* i9 f2 [$ e
反例:
: n; i$ u v* w% H; k$ G, O. B! \! h- L3 T) K6 S4 R
- set user:1:name tom
( V. Y( E i6 Q3 _( y( Y - set user:1:age 19) d4 d" f3 `0 f: C; ]* h9 M+ Q
- set user:1:favor football$ W; U8 n: y _- _
正例:% e) I. t' T6 H
: c( a( g) `- {' j, ?* G, h: P2 X
- hmset user:1 name tom age 19 favor football4 \, S* {% j- D, R+ t- j8 _, S
控制key的生命周期
. d# d. m- H I% @ x) m G$ J) R2 H! z; J5 q& }% x, U2 @9 Y
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
6 h4 e& L0 L# V二、命令使用
2 I( X/ L( i' j' [; I$ v. h4 f' T6 m
7 i$ a/ m9 Q, D- Y1、O(N)命令关注N的数量7 C. [. |, n1 D c% U$ ~3 M$ N H
* M% j; N q) N7 P7 l8 f1 [
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。0 B- e/ s6 ]- u5 |6 ^
2、禁用命令
7 {9 n! w- {* }1 j* j- }$ [ S. G( n F0 p' K
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
5 [1 t+ F5 c& o3 S+ q3、合理使用select
( A6 C) n! o8 Y( v' `6 H) X- P$ N" _* K8 ?/ v: n4 b' ?7 R% l3 ]
0 ^# u4 f- \$ X0 u$ E, J, m
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
2 Y& G; ?* C1 f6 s& R! K4、使用批量操作提高效率, g3 f! U4 ?: o5 C; y3 D* d
2 W+ z5 r2 Y* j) e% p
& f6 e0 N, Y- {: m, d- 原生命令:例如mget、mset。
1 ?3 F2 c+ E' G! R9 } - 非原生命令:可以使用pipeline提高效率。
$ n. t H4 N8 p3 p6 D) G 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
* W5 g3 R: C+ ?- }1 O注意两者不同:6 G" }# f+ A. b. |
, U9 Z9 f: Y/ z" z* h0 S! S- 原生是原子操作,pipeline是非原子操作。. {: P( a) k" s9 S; R
- pipeline可以打包不同的命令,原生做不到' C* O) x1 Y) W
- pipeline需要客户端和服务端同时支持。
) A3 N; D& a. Q8 [. Z 5、不建议过多使用Redis事务功能8 H; x1 n9 `4 q3 m4 I
/ w( a9 X& o, l; c3 I+ U; gRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!" P3 \1 q. p* P
6、Redis集群版本在使用Lua上有特殊要求
% X+ \# Y* v! J0 M* b/ \
& D1 O1 d! y- e& p' n E1、所有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"
2 Q+ l$ [5 F! N7、monitor命令! N1 P7 ]! [3 p6 H
. X8 l+ X1 y& [* r- ]7 y9 y3 F a必要情况下使用monitor命令时,要注意不要长时间使用。
) c6 D1 l* p7 {( r! p三、客户端使用
' r( O& P S! J2 O
) Z9 f" Q9 r/ ^& m$ |4 _1 n% O7 a7 c1、避免多个应用使用一个Redis实例4 i, S" d' X( G7 O9 T
/ ^- `/ x' @5 \: [$ [3 A不相干的业务拆分,公共数据做服务化。
; V& J6 g! ~" Y* p/ t2、使用连接池- B8 V6 ]% A1 W! w) [2 g
5 `& s1 g8 a! x3 l! D! b可以有效控制连接,同时提高效率,标准使用方式:# b' h- f2 ]* X- \+ o3 I
Jedis jedis = null;
. k+ J( t2 {" Q5 _8 b" _) Mtry {* L' ]! f! Y' [1 W
jedis = jedisPool.getResource;- Q% ?0 R1 P* _' m- {& d1 H
//具体的命令7 ]3 C0 a7 b, m* \
jedis.executeCommand$ s$ G0 l& p4 ~
} catch (Exception e) {' f. y# b/ K2 b2 F) V
logger.error("op key {} error: " + e.getMessage, key, e);6 q8 Q* g6 i0 N6 p8 D3 Z
} finally {
$ @+ g! @- W' x# V' S: ~ //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
* f. J {" _' Z9 h/ V. _ if (jedis != null)
1 q% q% r5 j7 r jedis.close;2 B% w t" v) ?( ~8 _9 Z3 ]* _- B
}
8 K. C$ A/ r$ s4 X, S( t3、熔断功能
% J& ]7 U9 ^2 [# c, n t( ^& |9 ~, c! ]% P4 R0 e& e* v) N
高并发下建议客户端添加熔断功能(例如netflix hystrix)! i: g, y& j0 {
4、合理的加密
3 z, i0 w/ S2 x) ~$ F! a. D1 o/ O) b# F. s6 Z; Q
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)5 ]4 U3 \; {' y' I6 G) s$ j
5、淘汰策略
+ \5 [. y5 S% U" x& F! J7 `& Y) s: O. i6 @- V2 b
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
8 k+ O6 [: n: H2 ~+ _默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。6 R s5 C1 c# @/ { e
其他策略如下:" k; P( u( W P' e. F) R
( {2 q! F& d2 \1 _1 o
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。) U8 \4 w/ Y, m3 b% h$ j
- allkeys-random:随机删除所有键,直到腾出足够空间为止。; b% G6 j, p7 {8 @& X
- volatile-random:随机删除过期键,直到腾出足够空间为止。
6 w. m8 ?' Y* L - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。9 r$ k, |6 R" P, L( i
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。* a* h. a+ B% q1 `
四、相关工具
3 z* L8 r _7 k. i0 s# Z
, O9 G( D# y% I. t# ^2 j1、数据同步0 r, ? }. [$ x; i
6 V2 a. |7 M' C2 Q( {4 n/ Y; Eredis间数据同步可以使用:redis-port
/ t8 \. Q- q) x1 C: s2、big key搜索
^. l& q5 f* o# A# v: ]* D7 k: }" u( S
redis大key搜索工具
M; x/ e2 o) i: e' m% Z3、热点key寻找. b4 ]& f$ F- `8 R# @# q1 v
, h' j1 l8 y" W) o内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
# k/ a; C! n C2 D五、删除bigkey7 Y7 X+ \ j* |7 N! [2 p
- }- _0 v f8 ?5 a# Z& w; x
4 f/ v: u# b* _4 N4 O7 G- 下面操作可以使用pipeline加速。
. M/ F4 E* @1 W, Z - redis 4.0已经支持key的异步删除,欢迎使用。
7 i- I- l& b4 R 1、Hash删除: hscan + hdel" e5 d% a- `! l" f( A0 s/ j
! u/ u3 L- B0 A& M: rpublic void delBigHash(String host, int port, String password, String bigHashKey) {
) c! b! l! i, K* h' N& D7 q Jedis jedis = new Jedis(host, port);" d. O% e5 k$ ^6 e( w# j- o B
if (password != null && !"".equals(password)) {, h+ D% ?! U! c: Q. B; W; q; l% y
jedis.auth(password);5 p( h: A- v. y5 J. L4 x% b
}$ G& D! r4 A$ z1 i" O: G7 i
ScanParams scanParams = new ScanParams.count(100);! h; h+ u& a; T1 @! E5 Q; m/ a, E
String cursor = "0";" S) J$ I; T4 p' a4 K
do {
' F+ L* }; \3 Y7 m" E7 w! N ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
; d* F3 E0 h9 I9 N/ |; E List entryList = scanResult.getResult;# G8 q& \5 N! G$ b# G2 c x: H
if (entryList != null && !entryList.isEmpty) {/ R) T! b- |, C
for (Entry entry : entryList) {9 U1 `7 K4 c- a4 d) o) Q8 c: y
jedis.hdel(bigHashKey, entry.getKey);1 a% }' ^, D n
}! c& @$ n* D4 w1 a4 c
}
9 P7 Q1 f( K+ W+ M0 K8 M! U* ` cursor = scanResult.getStringCursor;
& _+ c- [; v- Q } while (!"0".equals(cursor));1 h# E. I) D4 {9 i1 I
: C. }3 r: x" F2 E. E
//删除bigkey
5 L2 z( G' C0 ^# B4 c, M! Y l jedis.del(bigHashKey);- C1 A6 g- j9 b! }+ c- T5 w- ^
}
+ a( H! k$ [. e2、List删除: ltrim
- B4 j8 t% R7 Z6 ^ K9 e( s& e
. ]/ t- J- Z+ {5 {2 O/ Upublic void delBigList(String host, int port, String password, String bigListKey) {3 B* Y- H0 B4 y2 }" M0 T( s
Jedis jedis = new Jedis(host, port);
' ^ f7 {* n' H( U' [% X t' z4 O if (password != null && !"".equals(password)) {' c" u0 h7 }$ @2 j: L$ N2 V
jedis.auth(password);8 \* ^- T. V/ j3 {4 t5 G% A* e
}7 V% h! h) O Q3 x
long llen = jedis.llen(bigListKey);; I) V/ g' ?' B! E& e6 ~
int counter = 0;
, ?1 O7 I9 h) V int left = 100;
5 C9 N3 L$ w. D4 d% q while (counter < llen) {( b7 U5 j: K* z
//每次从左侧截掉100个
* l. G' W) X% F: f jedis.ltrim(bigListKey, left, llen);
/ N$ D1 e; I8 a# X5 J counter += left;
; q9 u# E7 F. r, s+ m. d1 w' Y( I }
- ~+ G0 g1 c. f* ]3 f( u2 o c3 u //最终删除key+ W4 ~) [; i% W- K
jedis.del(bigListKey);
# `$ ]- _5 O& i% y8 {* R2 K}3、Set删除: sscan + srem; P: ]' n9 Z5 S
7 P# Z; z0 V qpublic void delBigSet(String host, int port, String password, String bigSetKey) {* X% V3 i' J: _2 r/ U
Jedis jedis = new Jedis(host, port);2 k: N' l. ]3 ]; R
if (password != null && !"".equals(password)) {
: B t# u. o% V; q. x2 O jedis.auth(password);
5 D6 L7 `- s7 ?* R3 h }3 A* D. A1 ?/ e
ScanParams scanParams = new ScanParams.count(100);
3 l4 M: Z+ q3 |6 k2 U String cursor = "0";/ ?' {4 ?+ o0 u$ R
do {
" v9 s* Y% W8 l! {. {/ n1 ? ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
6 E, x {4 {( K g List memberList = scanResult.getResult;# L) z Z: W3 c2 i
if (memberList != null && !memberList.isEmpty) {
1 W) J. \3 Y) o5 t9 I for (String member : memberList) {
& k0 _6 n3 ?4 b _6 n) h* S jedis.srem(bigSetKey, member);
7 ^% f4 G2 E" ^ |0 b9 P* x+ f }
1 \# ]& L x& k4 C }: u' T1 \6 Q- q B
cursor = scanResult.getStringCursor;
, R* T4 S0 L; X- k4 W } while (!"0".equals(cursor));% l: r0 h, m) |1 }- ?' w
/ }% x4 |$ e5 @
//删除bigkey- I# W. f5 @- l
jedis.del(bigSetKey);
5 N v- B7 F. T' U. U" T}9 v+ {/ j* e5 J
4、SortedSet删除: zscan + zrem
3 x [ Z( B- g; ^( w
. e, s! v' W$ F1 ?4 Y" g% upublic void delBigZset(String host, int port, String password, String bigZsetKey) {
$ y8 `. x( @ ^2 Q$ f$ S8 O- ` Jedis jedis = new Jedis(host, port);
" a5 _; U; a" |1 T- v) A/ G2 u6 @ if (password != null && !"".equals(password)) {
% M8 k) _8 }3 ^( Y4 F% U jedis.auth(password);
% I7 {4 [. T% y# B) B% ], U+ } } % r$ Q- ]8 {9 {
ScanParams scanParams = new ScanParams.count(100); . X2 c, ?5 R) X) `3 M# B! K( \, ]
String cursor = "0"; 0 B5 W t) @$ C' f' D7 t( _
do {
" b3 _6 B( J; K+ R ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); : E; Q& ~/ ]- `
ListtupleList = scanResult.getResult;
) [1 g1 C! D# X x- k if (tupleList != null && !tupleList.isEmpty) {
! m. z' L2 w& `8 m% t: e for (Tuple tuple : tupleList) {
6 |; N; [2 p- b; I jedis.zrem(bigZsetKey, tuple.getElement);
( h2 q9 U2 o; i } . G U3 X6 U6 D& ]% O
} * l9 Y3 p. b' J2 M: O
cursor = scanResult.getStringCursor; 2 P" z; z! C9 A) Q! U, P3 A
} while (!"0".equals(cursor));
- r; c1 D; x' l! B+ F( |
4 ~ M( y' u" B: t//删除bigkey
7 ?) p& D; Z" t9 X3 J9 m$ s/ T3 l jedis.del(bigZsetKey);
9 Q) M+ I2 w7 v* W} 公众号内回复“1”带你进粉丝群
* v1 v: l$ \; S: G% j来源:http://www.yidianzixun.com/article/0LevQm7t+ I6 A6 e+ M. ^" r
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|