|
|

9 R8 |2 ^5 d& O* a/ ?5 ?本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
0 Z3 m5 a4 g4 z- j5 H \* Y: D; J+ V1 {
- 键值设计% E+ H& _- v- A- ]. v* S# r
- 命令使用
/ k& V" t# `" h0 p) x% l: |4 M o - 客户端使用& l& | y6 e2 ~9 D3 s% v
- 相关工具6 ?8 [% X& p% e e: B: n$ F
通过本文的介绍可以减少使用Redis过程带来的问题。
' W2 X1 Q3 ^; s' F一、键值设计
, A$ R8 d/ M# D( y: X6 z; R }3 Q1 C, ~7 F; M$ n6 Z
1、key名设计- H9 b) a$ a; `+ h6 ]* |- T) h2 t
# H; V9 C# @7 L3 A% z N0 h
可读性和可管理性, W7 M; n/ W: A @" e
# |$ ], `& C9 c! g9 c9 S. {, v以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id% o2 v( k7 D/ G/ e% I
" {: \8 |; g7 X; o- ugc:video:1
( z+ V8 t4 Q: L3 b7 G 简洁性3 u& B: z4 G) ~5 M) i
6 g1 E2 v L9 {: B9 T
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:; |# S& A. ?1 ~/ S( Q o, S9 s6 K; a
0 q7 R! o2 q9 \- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。& E( E: Z+ d& ?3 _+ e: v9 }
不要包含特殊字符
- i5 K3 `7 ?1 X B7 \1 h# f1 |4 N y4 g! Y4 f- y
反例:包含空格、换行、单双引号以及其他转义字符
l8 F* B$ L. L4 q5 m. M2、value设计/ \4 }' f7 P, H+ f* h7 d2 u
. X4 g, f7 S7 A. ` N& a& j
拒绝bigkey* O+ q. p' u5 [( A! R& I* E
$ e+ k) [; j6 T, l防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
% P: h. ?! b% X( N- M" ?) e# K: i反例:一个包含200万个元素的list。 e8 ~/ H* Q3 F8 ^5 ^# r
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
! q! C) s U% L7 f' y选择适合的数据类型
) u/ X" J0 I5 L# O( B! A8 Y. B, u; m% ^
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
0 I+ g) x W0 s& V) Y/ a反例:
4 S" v7 y" ?! M% z( Y
$ X \$ [% b5 S- set user:1:name tom+ b8 _% P8 [) L: n2 Q5 t
- set user:1:age 19
' @! o7 c, Z1 F3 N b - set user:1:favor football
+ t& {& v: q" u* C4 f$ { 正例:
7 O4 V6 k$ u1 N& Y
; f( ~; c. t. `' ]1 @- hmset user:1 name tom age 19 favor football8 l7 f# P1 ~! i, S. T) \$ A
控制key的生命周期; c4 J ~" o1 h. A' c" w) e" A
; s9 G% E/ _9 Z! dredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
& A0 S) e6 \) j4 e0 U' U二、命令使用4 e; ?" H6 b9 U8 Z: w5 d+ T+ ?
4 k1 x+ M' @$ F4 @6 [1、O(N)命令关注N的数量 j% a# A* `* f+ s
/ T$ G. \/ c4 M" H& m3 `( l例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
# o7 P7 k+ A/ T$ B, Z2、禁用命令
: ~% z) N: _/ U/ l3 c5 y F4 ?' f/ S
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。3 _, B1 j% b' \: ^7 M
3、合理使用select
3 ~" u1 ^8 W9 o9 [
& p! t; n( m+ D9 F
q7 g9 W9 ]5 s% Mredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。7 _- L: w" C* i/ b. E0 K+ i0 n
4、使用批量操作提高效率
7 o3 b& e8 x+ C! U& I. b9 Z3 w3 ~4 O0 [) @8 }, m
6 ]2 z9 b5 D# @
- 原生命令:例如mget、mset。
2 ?( y5 r* k6 A - 非原生命令:可以使用pipeline提高效率。
5 n p# S9 r7 d7 {% ?8 [/ L" I0 u 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
% _& E4 c! `3 ?9 q8 P+ _' x/ g' n/ e: E注意两者不同:
- ?: Y5 k4 S3 |+ H; i6 F+ Q4 f" i) [# }2 ^3 N
- 原生是原子操作,pipeline是非原子操作。
6 X& B: t, {$ G9 }; q* b- V! c - pipeline可以打包不同的命令,原生做不到
6 C- s$ E2 \ e1 ~2 W; a- { - pipeline需要客户端和服务端同时支持。2 |* w2 G! L0 r, @
5、不建议过多使用Redis事务功能$ y. D; B0 v5 I2 F
- K! t7 U/ ?/ G+ N% Y( O2 gRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
' x. ]# F: S2 L) Q1 K6、Redis集群版本在使用Lua上有特殊要求% I5 |% q, ]+ r1 V9 Z2 o# t. \
# C/ z! W+ k6 w: v
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"/ P# Q6 b1 e, B% {% A
7、monitor命令. d4 E" \/ `; a' X
& l# _+ t L) y
必要情况下使用monitor命令时,要注意不要长时间使用。4 l" K' w4 h2 \# U5 j7 k
三、客户端使用: C* ?8 F9 K9 \. X
u. G# B/ _4 [' _. ^1、避免多个应用使用一个Redis实例
! D& I% w; V! ?; ~# P9 S- I. `1 ?3 U8 h* x2 a
不相干的业务拆分,公共数据做服务化。. T" ^: m- o1 y2 Y9 V, U1 `
2、使用连接池' v9 \: ?9 q" w5 R
4 m. U2 h. |' R$ X" C: a
可以有效控制连接,同时提高效率,标准使用方式:$ \; _9 v Q/ e1 l2 E: a
Jedis jedis = null;3 n4 v* e( o- N; Z9 U
try {
: q! Q3 x" d1 F9 O, J* I5 r W+ q- U8 A jedis = jedisPool.getResource;3 W* a( r% g5 r: l8 [- U. I
//具体的命令
: {& O4 a" f! x jedis.executeCommand
, ?5 X& y! w" F x} catch (Exception e) {
3 R1 w' O4 N G2 o6 @ logger.error("op key {} error: " + e.getMessage, key, e);
* ~. _; E! [6 b7 L# f} finally {# c* {. _# t3 `. N8 {; Z' T( E; `
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。4 c/ M) i2 Y; { x. k j. |* D
if (jedis != null) ) R5 m L$ X4 x2 ~8 k" e7 o5 r
jedis.close;+ h' x% f& a. `9 a8 C
}! s# D0 `( r+ \% u M
3、熔断功能: @, q1 S- s; L) W
" O _+ K% D2 X高并发下建议客户端添加熔断功能(例如netflix hystrix)) O% |* P& P. `9 M3 R1 o
4、合理的加密
* z& `( I- h2 S4 a7 C# I+ J# a# {1 {0 l7 W9 b7 h* D% l
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)/ m2 N1 J0 N) Z+ R. |( M
5、淘汰策略
2 z2 e+ N: }$ _' ]3 ?/ j. q i( }, c! E0 ]! u1 l) q
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。0 @. K/ ]+ t9 W1 V, j% g r
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。- x3 a) x0 U6 j
其他策略如下:1 l% p ^$ H s" L+ j2 h
8 {+ o7 f& m1 i
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
' O. `0 p% h0 o& Y* v m3 M - allkeys-random:随机删除所有键,直到腾出足够空间为止。. k0 t- G3 p, t- W5 `! I# p
- volatile-random:随机删除过期键,直到腾出足够空间为止。: X e: a' R: h w8 E, `
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。3 x5 R' i, y" V) Q( l5 F T+ K
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
) @7 C1 [7 G B( K6 ~. k& A( @0 G 四、相关工具 Z1 ]) O' j Q2 b
; X) F& O3 ]- u# |1、数据同步
* n( }" u) }+ l8 h* w! X7 w. e3 L7 p# y
redis间数据同步可以使用:redis-port
/ X, f3 m. @6 s$ Q2、big key搜索0 \6 L( W4 L7 M7 v; |
2 y# K. t' N2 {. k/ u1 k3 ?redis大key搜索工具
; q7 Y6 o7 y4 b. D/ q: _3、热点key寻找( G2 ^" S0 c0 p4 U- H/ X
! a8 h" r1 e- X# c h4 @# ?/ P' J* l
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题( O4 v S4 W" J
五、删除bigkey- d8 a S9 L( k( l7 r( W! B) s
# g$ @' x! q. y5 ]. X
0 {. k8 s* @4 U: D
- 下面操作可以使用pipeline加速。5 U1 @- t' g. P( q( F
- redis 4.0已经支持key的异步删除,欢迎使用。
% x2 p. Q. o' ~8 O' {$ u# j 1、Hash删除: hscan + hdel
0 T* f+ P/ b3 m9 X6 O \9 C& S! p& [- k; B( ` z
public void delBigHash(String host, int port, String password, String bigHashKey) {. S+ ?; u: q2 L5 ?* A1 M# `* M9 z
Jedis jedis = new Jedis(host, port);
+ {* {) X6 |! Z% b; q2 a8 [ if (password != null && !"".equals(password)) {9 Q, V: |) n8 g# n' |2 C
jedis.auth(password);
8 A; P+ ^- x7 a: `2 W4 e) p4 g }! D1 {9 c- L+ S$ I
ScanParams scanParams = new ScanParams.count(100);5 T9 J/ N! x9 K' b8 c5 s* i* L. Z
String cursor = "0";
2 v, l/ n2 o* P% { do {
b- u- C* T5 @# J, ? ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
0 Z4 y5 K# u* I3 [ List entryList = scanResult.getResult;
9 E( k5 `8 U/ o7 r- u, X if (entryList != null && !entryList.isEmpty) {) x) r% Z9 K N7 ]+ c; q
for (Entry entry : entryList) { V( v D' j) h! D4 A8 f
jedis.hdel(bigHashKey, entry.getKey);
6 K# {, t- z5 t8 H }0 X, C+ Z4 I! s* ^) a
}" {; `1 V8 K3 S, A5 p8 |" N" y( q. |
cursor = scanResult.getStringCursor;
2 w! ]" a$ X( a, n7 X) y" D2 E5 w } while (!"0".equals(cursor));4 @9 c. ~5 j* c& U# Z
# g/ C$ b9 R' F! V/ Z& f0 V5 p
//删除bigkey
" f0 M, c; f! v9 m9 b jedis.del(bigHashKey);. g4 W+ u" s( G0 [7 N
}
) ~* Q' k+ |$ ~# P! r* c. t9 n2、List删除: ltrim8 ]7 r% X& p* J0 _
( `9 _2 |" F: ^6 y# V$ ^- lpublic void delBigList(String host, int port, String password, String bigListKey) {
/ B$ u2 d* Q% j4 \, x. L4 U Jedis jedis = new Jedis(host, port);1 m8 x+ V' D9 k/ q8 x6 F+ s
if (password != null && !"".equals(password)) {
5 {0 F( o' K3 W jedis.auth(password);" u: y3 o3 l( r: s7 K3 k$ m: H# Y
}& [) e0 N3 Q- M2 @& m0 [/ `- c3 I. ]
long llen = jedis.llen(bigListKey);& S1 F8 \$ x' p1 f9 g5 g
int counter = 0;3 u0 t, h4 q& t! C' s: c' m
int left = 100;
; L5 S( z- Z; X$ ~; A while (counter < llen) {
; B0 P8 j# e! z' K m //每次从左侧截掉100个
3 n/ Z0 `: k6 J) m% d" p } jedis.ltrim(bigListKey, left, llen);
1 Y. L5 a3 W) R5 V( |0 c4 o counter += left;* C3 g+ [2 m; x$ F
}
. q4 |" q" [; a% Q //最终删除key: R2 D8 d0 e: {/ o. Q7 ^6 t
jedis.del(bigListKey);5 g, ?0 d- N# z) L2 A6 l6 V- O. h1 J
}3、Set删除: sscan + srem
: [- G7 t* e! V6 s: d7 Z
5 i. a4 r1 q6 y0 W" o0 Rpublic void delBigSet(String host, int port, String password, String bigSetKey) {
* G7 D1 ]8 I1 Z Jedis jedis = new Jedis(host, port);/ P$ l! T' M8 P
if (password != null && !"".equals(password)) {+ }2 H/ W4 Q' m) f1 k# X& k- ~( E+ w
jedis.auth(password);% F9 J7 T0 G- X# L; ^( [
}+ {9 w- b/ Q1 h2 k# i' z M* w! d8 u
ScanParams scanParams = new ScanParams.count(100);, @4 W# h2 \: R; J
String cursor = "0";
- C4 W7 p3 _8 X2 h1 |2 ^# l: c& p do {7 n0 e: `" f% I% c- M! C2 L- R' {
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);/ c) I9 d+ i' e1 I4 a" g
List memberList = scanResult.getResult;
+ C1 o3 Z5 m/ S. G5 `$ |- _ if (memberList != null && !memberList.isEmpty) {
% C8 i" y! g* G: U1 i; ^ for (String member : memberList) {1 z+ L# E1 ^: y1 n7 F, W3 a
jedis.srem(bigSetKey, member);$ g' C5 C6 e. `' L: a2 l
}) `: u- e$ S, T1 }
}" }6 B) C' {& \+ `
cursor = scanResult.getStringCursor;
8 F/ g7 k r2 ?2 H, |4 I } while (!"0".equals(cursor));
( G. D; f8 Q; g1 B3 C0 D1 s' @) L% d6 S! J# V+ t
//删除bigkey
?+ P2 g; X, z7 I; o jedis.del(bigSetKey);
; ?* I2 b9 A4 ^3 r; q}
5 j5 d6 e" o* i7 `2 R" @4、SortedSet删除: zscan + zrem
* W$ P5 v2 ~! `1 n2 v% l6 j$ i" c' x1 L/ E7 i- `
public void delBigZset(String host, int port, String password, String bigZsetKey) {
; [+ h6 {5 E* R Jedis jedis = new Jedis(host, port);
+ v/ ~* p* K3 D, l2 d8 m if (password != null && !"".equals(password)) { 1 [! C3 p0 q/ k y/ n
jedis.auth(password);
0 G9 d0 C1 T, |. ? } / K" j7 A6 n1 c, M
ScanParams scanParams = new ScanParams.count(100);
$ e" N/ K# l0 x! {3 r8 k String cursor = "0"; $ E: F0 E& w* k$ k
do {
- @" H+ L) ]2 m ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); % `6 y/ S$ v* L+ x
ListtupleList = scanResult.getResult;
5 T* K: H5 W1 U O if (tupleList != null && !tupleList.isEmpty) { : _; M7 I- R6 \! h/ O: A) G
for (Tuple tuple : tupleList) { 2 v8 T) h c6 f2 p0 h, I
jedis.zrem(bigZsetKey, tuple.getElement); 5 @2 z# {4 S7 @: P h
} ' r2 K% s. } e2 ?( `% l
}
! J+ i. I8 w# X) e! W+ }5 o8 t$ G# Z cursor = scanResult.getStringCursor;
& `2 C2 U& |6 u3 L- G9 l } while (!"0".equals(cursor));) J9 V, k6 @* X- B+ X5 u
* C: u' [' A6 S5 P4 Z3 W
//删除bigkey
w2 s* o3 X ^% x, B. M9 R% q jedis.del(bigZsetKey); 9 ^( V2 O; R: S. B
} 公众号内回复“1”带你进粉丝群
" f" r- B9 S5 z) C4 }+ }来源:http://www.yidianzixun.com/article/0LevQm7t6 }( ?3 V; s/ r( L! m, u$ R/ i
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|