|
|
+ U; [1 X9 I# I6 `8 E+ ]5 x4 G
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。) U6 B) u* T( W8 S
, c" _: ? `1 {6 U+ i6 u- 键值设计5 d) `9 _( i9 y
- 命令使用5 ~: T) B9 m0 e4 u0 m e
- 客户端使用5 x! s1 J& ]! F
- 相关工具# s2 f: O4 S$ V* ?& V; ~
通过本文的介绍可以减少使用Redis过程带来的问题。6 E+ U/ i/ R' L$ h
一、键值设计
0 k ?6 V3 L# W, E5 d6 V
* F) k/ g4 d2 L( x1、key名设计
- w# Q6 G2 ?9 Y5 D: [
8 o" e; e+ p/ p! R' }5 ~0 G/ R' z7 N可读性和可管理性 d; R% K/ F: H0 L- `; ~$ U
7 n- C5 r1 n) \- u9 J& M以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
9 m5 S, X5 n: g) \3 z) e9 t# N+ B% O1 @* y: [- F% i& P
- ugc:video:1
9 z/ L/ H/ t7 j# u 简洁性: x9 n7 e/ Z/ D7 }" l+ _8 }: h8 F" ]. p
$ T8 `: x# A6 Z) c8 q$ [9 z \保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:" K3 u8 k* @9 @$ F
: F7 T# ?5 O# a9 L- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
8 k/ _% B* o8 A! \( _; L 不要包含特殊字符* F6 c1 M, d0 ?6 a5 a
: h/ r5 {4 c2 U2 i, c
反例:包含空格、换行、单双引号以及其他转义字符. @; R7 O# @2 k; E/ d
2、value设计1 r4 t" c h; l }/ f+ h8 o7 e
$ G4 r( X$ ^6 n' T" U$ c
拒绝bigkey
* f" c5 Q3 Q0 q9 G. W9 W: C2 u$ j' O6 r; }2 C- w8 b
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。% @" }$ j% u9 g' r
反例:一个包含200万个元素的list。
( V9 ?1 x& W& H# i3 Q" \7 z6 S非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
8 ]& G: t& F" i1 v& Y选择适合的数据类型
; U% t# y ^% \* N3 d ]: \
( p( m* @! I+ U& n% K& b e例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
% o: a n5 c. ]反例:
/ j! W% B3 U# f$ g3 G
8 `, D9 {( Y+ D6 X: u' M& Y- set user:1:name tom
, K' p1 E) l S, W; D8 U - set user:1:age 19 ?0 @$ O2 T; u. F, R; n7 F# Z
- set user:1:favor football
: O3 |) ?: V6 ?- B5 E9 V* W& ^ 正例:
+ F0 t, h, Y, X9 L8 p' F
+ ~ `' W* l: ]& v6 [$ D4 B- hmset user:1 name tom age 19 favor football/ Z' w: }& o$ C8 E- }0 F; z9 K8 a
控制key的生命周期$ r( ~0 X* d. r* k% s3 C2 }
" J- V% W0 K. w5 f/ P" d
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
; f. }+ O' b F1 Z/ a4 R二、命令使用
( o* p& s, i0 a, q/ Y- ?' X3 o6 M. D8 }
1、O(N)命令关注N的数量8 r1 i$ L) }% y3 B& S0 p1 y. y3 ~2 x' V
@/ [: y; Z$ [9 e2 I0 q: q! |
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
M0 s! J" o, Z0 C2、禁用命令
' Q4 Q) y1 G0 Q& F8 v
. I6 D2 U: a7 ~! w. p禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。) E8 A. e. i2 x/ _0 Q# n8 s
3、合理使用select" Y: J6 [( \4 p9 g
3 }9 K q% n. x3 I4 a+ H( U; J
# W0 x/ L2 J' }redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
' a& e9 e$ `) q* S4、使用批量操作提高效率- h$ h' p% z8 w$ s$ J5 U. Z+ @# ^
$ e# Y& }1 m) N' G/ v: l
c6 r$ N% I$ m% e2 n2 p- 原生命令:例如mget、mset。- K9 M$ a6 {, p4 z8 A
- 非原生命令:可以使用pipeline提高效率。1 P+ f. n Z4 K( {& k6 ?0 @" M
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
' Y o3 V# G$ A注意两者不同:
" R8 t- P, l; @ D: F& q$ f2 n1 @; Q; `
- 原生是原子操作,pipeline是非原子操作。
. x' }; T1 y* _7 F1 h- o - pipeline可以打包不同的命令,原生做不到. \" S: R: n$ ]( T: ], p
- pipeline需要客户端和服务端同时支持。' |4 ~& p2 a2 s( Z+ J, I, G
5、不建议过多使用Redis事务功能
" `" C* H' C/ y' i1 x5 t. h7 c: b: Y! j. `7 v! `' z# p1 i2 Q
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
1 _7 W" a- x( ?$ v7 D6 u! F3 o6、Redis集群版本在使用Lua上有特殊要求$ B+ [! \) f/ l
$ I C: o7 ?% e0 Q0 p! w: S% |! Y1、所有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"
1 q7 f# s5 k/ @. h0 b; N0 r& q7、monitor命令
! [% S( o" |8 J6 ]! k8 U8 B
8 L3 q8 |; x) p必要情况下使用monitor命令时,要注意不要长时间使用。
; d: o x% Y1 @: m1 I. _2 K三、客户端使用
- }$ t2 B, e7 G5 @- K ]2 C! f! ], |2 W* Z( F
1、避免多个应用使用一个Redis实例6 @) f5 y! i8 s
0 K! A0 B! y, Y7 a& Q7 P7 |不相干的业务拆分,公共数据做服务化。7 w! q) R( a. h( B! h/ z7 F
2、使用连接池
, _. P3 r9 a' F! [. B! n: m, b0 m! g8 y
3 s e( p6 ?/ ~( Q- S- C( u: i" H可以有效控制连接,同时提高效率,标准使用方式:
9 @) @2 g2 y6 z) ZJedis jedis = null;4 v0 K! r- ]0 U9 m
try {4 m) Q% [/ f# w
jedis = jedisPool.getResource;
8 h' ?* m8 {4 j. `9 p* o //具体的命令- S) r j% F! Y7 X! q% P& P5 f
jedis.executeCommand v T; Z2 ^+ }
} catch (Exception e) {& C' A" d d1 r+ R% R
logger.error("op key {} error: " + e.getMessage, key, e);. x! c8 F, Q+ [6 e
} finally {0 E* d1 | D9 e( z% D
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。5 |# Q: Q; O8 r. p/ s
if (jedis != null)
& b, e0 U- b& X7 N' {' W" h jedis.close;& f# _ J' W2 L2 I' H
}
( d1 R6 |% z7 F' k0 _# {3、熔断功能* A, k* G+ Y) `, h5 D/ S2 s
- i8 q# I8 E) @& B q% D
高并发下建议客户端添加熔断功能(例如netflix hystrix)$ d' D9 G9 w: [- w, R
4、合理的加密
0 [7 a! \4 } C. N9 g
( W( O- x, w( H0 l: W% y设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
- Y. m# c( e# p+ m5、淘汰策略
% A9 c6 Z& U' n: v- V& a; e( _/ B
. a7 C& y5 i* B) x根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。+ n# {1 `. Z4 {" U' i! k' }/ V
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
+ A' f a9 x& `. e# B2 x其他策略如下:! ~% k, M) N- ]% x1 `4 D, z& m6 l
+ o' r) I5 a, l6 R- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
! B C9 L( ~" {: S' g2 V - allkeys-random:随机删除所有键,直到腾出足够空间为止。
v/ V# d3 Z3 s2 @ - volatile-random:随机删除过期键,直到腾出足够空间为止。, ?% I' T } g& }- |3 Q
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。* n8 F- d9 D$ @- N7 M1 q
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
6 p& X+ C3 b/ `8 Z4 ` 四、相关工具
, T& z& t1 F5 z
z/ w$ R) f7 K0 a$ W1、数据同步
2 k# w8 \( W2 c# z; V4 J8 G% x2 s. Y+ r" O: g$ p8 S7 n
redis间数据同步可以使用:redis-port
9 k! {8 N3 a/ j6 X' \2 f }2 R2、big key搜索
- V8 P- L* K! L' L* p6 l! ^3 Q. |, D7 U$ q+ z7 Z+ a9 A
redis大key搜索工具3 J9 e c J: h0 K/ X# h7 B
3、热点key寻找' Z$ N) c/ t6 ^! K
+ b3 x) u& v6 d3 p/ N8 _内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题9 }" l% b q) i% p7 r7 K& A
五、删除bigkey5 b( z) A# l0 [7 m; I
* V Y0 T( u& C
+ F, p, x$ v- I9 k# u! w; s
- 下面操作可以使用pipeline加速。8 ~* b& V: o4 R0 ? `3 B
- redis 4.0已经支持key的异步删除,欢迎使用。
. e& m# B& S' m1 ~. n7 D& F3 F, o 1、Hash删除: hscan + hdel/ @( J0 Y; ~. o- w2 y7 d7 F
& Y/ y0 C1 {% U" C; `0 X8 ?5 o3 dpublic void delBigHash(String host, int port, String password, String bigHashKey) {
! v; }5 ?4 C. _& A0 f( e/ m Jedis jedis = new Jedis(host, port);0 Q( I9 x7 L2 e3 b
if (password != null && !"".equals(password)) {+ B0 I% G# \8 }( l
jedis.auth(password);: H, E a' G: X# i+ N
}
7 _8 M! m% a$ v( ^# W. h( |+ ? ScanParams scanParams = new ScanParams.count(100);
1 _, h8 X; Y2 Z! k String cursor = "0";/ @: p+ r, e; S8 q1 g4 x
do {+ @1 r8 p9 Z# E
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);% f# `: O" t. J7 N$ \" @
List entryList = scanResult.getResult;/ S7 h$ y6 C2 j: P. T
if (entryList != null && !entryList.isEmpty) {$ g7 ?& i( b7 C1 z! ^
for (Entry entry : entryList) {( @' r0 Q: j8 R
jedis.hdel(bigHashKey, entry.getKey);) x! M* X M$ p" l- f+ p% N J( C; i
}
7 N0 H2 O4 R3 [. s. O! y- V# I }/ D4 G+ [' w: P! N% \) u) Z4 H
cursor = scanResult.getStringCursor;
3 O$ H( t% q5 w$ Y4 u } while (!"0".equals(cursor));/ p* B9 g' }+ k) s* y& i
% h C2 D" L+ @5 j L+ X2 {
//删除bigkey
# ?4 R; d5 L8 V3 F3 _4 x$ q$ W jedis.del(bigHashKey);$ Z: u- ~+ [% M1 a. E/ b+ H* N
}
( | ?9 ^ B" C4 h3 j# w0 O+ w3 B2、List删除: ltrim
. q7 k3 B8 R( V* j7 P, A. G& \% O' w. P2 P6 v, k9 Y
public void delBigList(String host, int port, String password, String bigListKey) {
% s" n% f. @& B o Jedis jedis = new Jedis(host, port);
% s _, Y2 m s8 x/ F I if (password != null && !"".equals(password)) {6 l4 B w2 W" d: W q/ r& p" G
jedis.auth(password);
3 P8 t! S" [# _: a" L* q* d7 [ }4 \1 u% _ b! O. m: e
long llen = jedis.llen(bigListKey);
& b7 ?! o, D' P( i7 k" k" d int counter = 0;& |" O9 s" N; _; S% M
int left = 100;
# {6 T4 D* H; G! F4 D while (counter < llen) {, h0 M [' ~; u, c7 [. _1 V
//每次从左侧截掉100个
) z: w" I/ A8 l* _2 J( T4 G4 i jedis.ltrim(bigListKey, left, llen);9 q- u3 X( I1 t" k% P6 H4 ?
counter += left;) D* V" l, Q3 q, X! E* a
}
8 g* J5 f$ X( ^. k1 K; E //最终删除key
( b7 X$ p) v$ x$ t: b jedis.del(bigListKey);
8 l, ^' ~3 l' W9 x}3、Set删除: sscan + srem2 D- B2 \) n1 g9 T% }4 Q$ s
6 \; _( o% Y; w8 W- M) X( Z6 A
public void delBigSet(String host, int port, String password, String bigSetKey) {& S' o( K: v& c) f' i3 {
Jedis jedis = new Jedis(host, port);
0 @5 Q: W- Y% A$ }2 K if (password != null && !"".equals(password)) {
( V- z+ W$ L" f U: l% p jedis.auth(password);* ?! X& u( l W# E% t( s
}
# S) z7 E4 D9 _. M ScanParams scanParams = new ScanParams.count(100);6 n6 V2 a7 ?8 p. X; j* f
String cursor = "0";, U C& V* S# O" ?* A9 p H3 v
do {
0 G% Q# T% \6 i. \- w- A+ V# } ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);. I$ s+ R+ H6 X) X+ C6 ` q
List memberList = scanResult.getResult;
7 d: x8 P- z X if (memberList != null && !memberList.isEmpty) {. ^8 k* N- d4 S1 I6 @
for (String member : memberList) {
7 t2 ?9 ? K! _! |, \$ ] jedis.srem(bigSetKey, member);
" H8 ?8 G* @; z( w/ U2 H# q P } t! }, n; a8 g; m. i4 W
}
4 u1 X3 O% d/ ^$ u4 f# i( ` cursor = scanResult.getStringCursor;: ?& |% F* g% D* K
} while (!"0".equals(cursor));8 p8 N2 M+ D: b
: U. F) @/ N! }, y* y+ {& O
//删除bigkey
0 E# w) E; K$ F4 Q+ w jedis.del(bigSetKey);
$ w- D3 D$ H, R9 I$ P% v1 J}
4 t$ ]0 |. _0 O4、SortedSet删除: zscan + zrem
/ H3 n4 ~" @0 h5 ]. y5 \% u
1 s M" g" j: K; K. e" E, @9 v6 Xpublic void delBigZset(String host, int port, String password, String bigZsetKey) { 1 Q- L% m; ~5 I. v3 k; w+ i' O
Jedis jedis = new Jedis(host, port); / f1 Y1 k2 `/ S- I5 e
if (password != null && !"".equals(password)) {
$ F! @, ?: c; d- l! D1 t& s8 v7 N jedis.auth(password); ( E3 i( ~* f8 v1 P0 p1 X: G
}
0 S$ [+ `5 B7 {: B% A ScanParams scanParams = new ScanParams.count(100);
# ]1 ^- [1 f4 J. D' O( F String cursor = "0"; ; _* ?+ u5 o3 N) {
do {
3 z2 J) |9 s/ x7 d7 z" p ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 8 ~1 l6 x7 v3 i
ListtupleList = scanResult.getResult; 3 O: I+ S G# `" ]. k0 w8 O1 x
if (tupleList != null && !tupleList.isEmpty) {
) {2 p6 k& M3 u& o2 b9 t for (Tuple tuple : tupleList) {
/ k' {3 Q. x3 H4 y: |$ [- I& H' z jedis.zrem(bigZsetKey, tuple.getElement);
4 w! \9 Y1 l. S6 J, p; P }
: u3 J" W# x0 }" o }
0 Z2 H- Y4 O! _6 j: v4 ]0 Z cursor = scanResult.getStringCursor; 2 B! J2 G3 j% d$ K8 v+ Y( j2 Q8 c9 s
} while (!"0".equals(cursor));7 u5 t9 i& P3 D: X2 s; g/ c, Y
" L! g, K+ T" c O% I% S
//删除bigkey
! h, S: v3 o4 o, f+ n jedis.del(bigZsetKey); w# l/ ]; U' Q1 @5 M! E
} 公众号内回复“1”带你进粉丝群& H4 y1 {1 [( P0 u6 g
来源:http://www.yidianzixun.com/article/0LevQm7t f8 [- o4 Q) d2 t, z1 v
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|