|
|
j6 E" B0 Z7 D+ }6 z9 I/ ~# c4 q
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
7 c! Z# q% h+ W4 H0 R) Z$ Z7 K" B, i
- 键值设计
4 e+ W: s, q9 g/ W- k% l7 X - 命令使用6 @4 z1 U p' ?9 {0 T: J
- 客户端使用
0 e) e c4 P3 ~9 A- d - 相关工具
6 Y9 P# i* h+ V3 w: U* [. c) s ^ 通过本文的介绍可以减少使用Redis过程带来的问题。
* Z- \, ~/ w" r$ ^# u一、键值设计
3 _' l9 i6 c e, w% Q4 L9 }( @. S) c+ ]1 t* i1 _- \1 ?
1、key名设计
$ T! I0 J: S9 p* a5 S" [! M# b' [
, X+ B) U; |2 m: F$ [4 k. d可读性和可管理性
; {% i7 `6 r. M; N; ]" f2 N% W/ h7 q4 [7 _5 _, `$ W. A
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
; R- s' z, s# t0 e7 [
! ^& m- e3 P- f0 Q$ Q% \3 s5 T- ugc:video:1
( H* g( q) o3 r5 W0 j 简洁性
# _+ f% M2 b3 `& @+ A3 _6 R) P
/ L4 C, k3 ~) }; R V3 B# A! _. @" \保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:$ @9 W- N6 [' f. }* H
3 x2 w( G4 O9 \0 D% N
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
. B( a D, E0 p5 l 不要包含特殊字符
4 m# ]; Q0 @# Q- }5 @
- J; q3 m. y; \: p! ~& u反例:包含空格、换行、单双引号以及其他转义字符
5 z* R, ~' F: s1 e5 \( i0 Z2、value设计8 @# h& @$ r( r9 \1 s
+ X( N9 J5 ^5 o T8 }" @2 B* R& G/ x
拒绝bigkey3 u0 { Q7 F7 o; d$ k, h* S
5 a l( j6 ~3 a9 y- k& e3 B
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
8 s0 }5 @9 D! `. P$ ^, v, i反例:一个包含200万个元素的list。
: J. G, Y2 S6 l+ t l- c5 o非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法* F& ^8 J' p( d* G3 e9 }
选择适合的数据类型
- s, ~# i1 r( K4 \# X5 ^( P6 ~+ C/ h& B( A1 {0 H/ ^2 G- W
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?) w6 G* G3 K, P8 j) [
反例:/ V; \* w' f* [ u0 B7 h9 h+ |
, V- W5 D5 d3 w3 y4 g9 t- set user:1:name tom
# ?, r! r2 f) n$ C( N - set user:1:age 191 V' c, b3 Z' P1 c" l1 E8 U
- set user:1:favor football/ v! F5 L4 k" h' a9 ~9 W' t8 u# b9 J
正例:
: x7 [2 g6 S; q3 _3 K2 y+ A( x, T0 G
- hmset user:1 name tom age 19 favor football9 g& g4 m b8 a% H8 W. B& z. I
控制key的生命周期6 w2 F& `" E8 W& u) f. l
) b9 l. y) r% y i
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。/ P1 u: H @. a0 t2 d) P# C" ]
二、命令使用- Y& H) T2 O. t6 D/ R
2 A$ {4 J, U$ f3 E5 J2 z' q. k d1 O
1、O(N)命令关注N的数量
1 X& g @; E4 T! N" F2 X% ~0 ~- Z. C: U8 ?( T
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
2 @! ?% r- b g& \6 u0 P2、禁用命令) N E- C3 M2 f7 D* [, |
9 \. ?! X) x6 Z) V5 A2 l禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
# `4 U- b7 Y9 J* u/ B* x3、合理使用select& Y8 o1 w8 w1 b, `
( h/ h% C5 f l4 L' ?5 l
& k" j, f/ B/ ]8 ? E4 b. e7 e
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。/ J& P* u. p. T
4、使用批量操作提高效率
3 s: @8 E3 U6 @4 |; Z4 H
+ Z- R" w! ~0 k. N9 i3 w# T
( y2 j( |2 t. q1 L' P- 原生命令:例如mget、mset。. b8 y6 a2 ^8 C+ M h+ j3 y
- 非原生命令:可以使用pipeline提高效率。1 P! ?4 f9 e7 I5 ?( x
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
0 d+ d/ }$ O* f1 M+ Z, {注意两者不同:+ c+ m% ^+ N; u
3 d/ q1 S. Y3 Q) @+ m- G- 原生是原子操作,pipeline是非原子操作。0 g+ U5 C4 M9 [4 ~
- pipeline可以打包不同的命令,原生做不到( Y1 J5 D" i' F& o& h
- pipeline需要客户端和服务端同时支持。
$ c5 d) Z' h' p# C) U" V' ~% j0 a 5、不建议过多使用Redis事务功能) m+ i5 }% x" X
" d7 {% G& ?6 O K& zRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
# S7 g! b4 r: G! Y2 V6、Redis集群版本在使用Lua上有特殊要求: _. [3 H0 X* s% J7 L+ \
4 `5 @4 ` I; m% w1、所有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"
- Y# j0 v+ B! F( s: t7、monitor命令
$ [4 m& ^5 E0 l( Z
* f; M: O* X- c ~1 x" H必要情况下使用monitor命令时,要注意不要长时间使用。
! S. C/ R( G. U8 ^6 h: Z% ^三、客户端使用; E& M$ T$ m0 z( u4 O+ N
. {+ H0 }. e8 H: m; P- Q0 [
1、避免多个应用使用一个Redis实例
% M" M4 e3 O+ U4 \$ y0 A0 r7 L* B) F. G7 F* |
不相干的业务拆分,公共数据做服务化。. z, X" E+ Z; w0 U r
2、使用连接池2 C7 V, |$ |# s6 @& m- s
% D! |8 F. Z/ q6 B
可以有效控制连接,同时提高效率,标准使用方式:7 s7 Z8 l" p# E4 |2 [( O* U
Jedis jedis = null;- l" N& a- R# x! ^. i" Y; {+ H
try {
4 q9 }% V& ?; F jedis = jedisPool.getResource;
9 c- _+ o2 Z& |8 m //具体的命令2 r+ N) o3 J. V' i( p$ U% ?6 I
jedis.executeCommand
1 u4 D- v8 [( ~% q7 I( l} catch (Exception e) {
/ W' s0 p b6 L, [- k logger.error("op key {} error: " + e.getMessage, key, e);
* K- x% v. P! [. ? `4 E} finally {
8 d* r% S/ q6 T //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
& v, W6 s; { A$ e" ] if (jedis != null)
5 w9 p9 a p! f8 _. |0 } jedis.close;+ n/ g3 Y1 n8 D, q
}8 U/ y, A1 u( F( B# h& q
3、熔断功能1 g4 p4 Z3 T' a7 i! X4 k. ^
( L# ]: I( F+ Z+ f, b p" a高并发下建议客户端添加熔断功能(例如netflix hystrix)! \2 W' h( `* }, K h) J
4、合理的加密
; z2 L% a( ^( D Z
6 Y" v, ?( d3 X* d2 D设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持): W: O+ g, U: j" z
5、淘汰策略; m* ~: i1 Z, }. }+ z& K' u; v+ N% R
( Z* V9 ?4 S; S4 L根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
' F0 f( ^3 }$ H8 c8 d4 G0 A1 B U默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
. R' b7 Y( ] d4 k: N) p% Z其他策略如下:
/ H) P3 n- r+ q6 h7 t0 u2 W) P
8 N8 { B! j1 z( d; _" u- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
/ V! m9 J) J7 [; C) `9 ^6 D - allkeys-random:随机删除所有键,直到腾出足够空间为止。
0 H, i6 Q4 R3 I" a6 G+ O( U - volatile-random:随机删除过期键,直到腾出足够空间为止。# c. w7 e8 W( u3 ^0 Y9 q# c
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。6 \( T3 N+ s6 G- Y
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
3 l7 I& @* H4 x2 O! S 四、相关工具" F& u7 i& U: O" x
: k3 L3 i# j( }5 l) @8 h. A& S1、数据同步- V, U% M4 L6 m# [. @/ c3 s
9 o, D# i3 F$ O) V& q( J; Eredis间数据同步可以使用:redis-port
( v8 d2 Y) [* _1 F8 e! ?2、big key搜索; ]4 `, W& H. z
4 E; R! b. s! y: X, r( V+ W! }9 u
redis大key搜索工具7 a: x* c/ P2 E( H7 ?6 E
3、热点key寻找
# P5 } I: z& y) f, C7 V8 \1 _
* t7 N! U9 ~$ \8 N1 q内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题) ]( e9 ]8 f, x' D: u1 u1 ~7 q- T
五、删除bigkey
3 N* G- a) w' a& ]3 y) F8 K$ H
2 v" \9 R! |. P9 Q
2 q& B; z+ q5 H: L9 x- 下面操作可以使用pipeline加速。$ n& [: T ~6 h
- redis 4.0已经支持key的异步删除,欢迎使用。0 s& C0 i. X1 j1 L$ \
1、Hash删除: hscan + hdel8 d* u7 Q8 N+ ~& N# Q3 M1 ]
" k3 A' V1 @4 h: `6 Q2 _6 Cpublic void delBigHash(String host, int port, String password, String bigHashKey) {. e5 a, J1 @+ g. g
Jedis jedis = new Jedis(host, port);
$ o5 y3 z- D, P6 { if (password != null && !"".equals(password)) {
: p) S" B8 E/ q# \' M( I! n jedis.auth(password);
) t N4 Z- K o5 n }
# \, K5 z: N5 K7 n4 @7 Z ScanParams scanParams = new ScanParams.count(100);
2 M6 Q* R7 `+ T6 j( m1 P }6 [/ w String cursor = "0";
0 x" ?* s( a+ n do {# W5 k! N7 j2 s J+ F# D+ ]- G2 h
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);/ }+ m% Z% J& c- t* n& @
List entryList = scanResult.getResult;
L3 \3 c. l4 f+ L& R T. E if (entryList != null && !entryList.isEmpty) {, z# R) v T/ f; R1 j3 p
for (Entry entry : entryList) {8 X; p8 G, s N; E( O
jedis.hdel(bigHashKey, entry.getKey);
- t4 E0 y0 D- {% k+ V5 |$ |) q: f }
0 [: P; V, i9 C8 z2 s4 t1 |) S }
0 K+ Z0 o9 b* q, g7 X cursor = scanResult.getStringCursor;
; S0 _; `& S' g } while (!"0".equals(cursor));
' H6 l, ~9 T& b$ F0 z2 k, v1 y& H6 O" p+ |& H, T" `
//删除bigkey3 N) |5 o$ c3 S+ E1 O' Q4 Z6 o
jedis.del(bigHashKey);
$ Z: O" E3 d0 f, p3 i}$ S2 D: g: U6 b9 E
2、List删除: ltrim) f/ i) U, e% z1 N/ w( @. p( ~
3 G5 C/ r6 n" \/ s' D* S+ Q
public void delBigList(String host, int port, String password, String bigListKey) {
5 N# \: z5 q" }2 E5 v$ ?& E Jedis jedis = new Jedis(host, port);
" o8 _. [. ^3 ]* D if (password != null && !"".equals(password)) {
. b# G4 A$ J1 @9 Z, T) j jedis.auth(password);
, q( @( o3 [. n* t. m }
3 R J; o" S; T, z8 s long llen = jedis.llen(bigListKey);
' `; g% y6 X" ]$ ]6 L int counter = 0;# ]5 r% U; O/ Q* b
int left = 100;
2 `0 ~- A5 j+ g! D+ G while (counter < llen) {! F/ R, ?1 {. Z* `. I
//每次从左侧截掉100个
+ i$ O7 ?' o. u( o9 t jedis.ltrim(bigListKey, left, llen);
+ W9 ^: W& L4 {& C! E; c3 r counter += left;" j) Q% |9 C3 r4 X- ^6 |; o
}1 t* p$ @) B% o2 m5 w
//最终删除key/ p3 A( i8 B: f6 |/ n( f
jedis.del(bigListKey);; {- ?# _) d6 L
}3、Set删除: sscan + srem
. E6 n& {* [ Z+ Y6 b6 T3 @3 j' B5 q8 H E x# v) H
public void delBigSet(String host, int port, String password, String bigSetKey) {4 R# {& `) |% r# u1 G( v. N6 c
Jedis jedis = new Jedis(host, port);
" V7 r; E6 Y0 {4 r5 y3 V' ? if (password != null && !"".equals(password)) {+ ?6 z) C+ a9 ~3 |6 ]$ E
jedis.auth(password);, ]6 R1 T* ?( B1 e' K# f) f2 B
}( R& {$ {1 Y3 \7 J* `
ScanParams scanParams = new ScanParams.count(100);
/ B" z/ ~6 ?" j& D% | String cursor = "0";
1 q* F& C5 p) U1 @ do {# R1 z* R; T0 S
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);( B5 |! c6 V0 W7 F/ z
List memberList = scanResult.getResult;! j, L$ H6 I6 B" p, C6 \3 y
if (memberList != null && !memberList.isEmpty) {
* i; h/ w( } C/ U for (String member : memberList) {
$ f; @. L* q$ Z) `1 b jedis.srem(bigSetKey, member);
/ J, i/ |4 X5 F3 }' w7 E' z }9 x D5 t5 |5 ^$ g I5 Q' e
}
# D% V5 O# t0 e6 Z$ z cursor = scanResult.getStringCursor;8 E7 L/ W2 n9 \9 R5 ~% v5 o! w
} while (!"0".equals(cursor));
6 \/ P7 Y. O7 v# U* D9 A9 o/ M; j4 g0 |- ?: K/ t, D
//删除bigkey0 u3 }4 _6 }& y, K7 \
jedis.del(bigSetKey);7 [. U; G- p, x8 M
}
) P$ r' e: f* L/ G8 A! ~, l4、SortedSet删除: zscan + zrem
& Z8 r' J8 @$ e" X6 G0 _' V3 C
3 U1 u) F) J7 y/ M3 o( Npublic void delBigZset(String host, int port, String password, String bigZsetKey) { ! ], v( q* Z) S. V3 S" c
Jedis jedis = new Jedis(host, port);
# f8 r( A# p2 k% m/ r! [ if (password != null && !"".equals(password)) {
2 ~ w) W% c6 P0 C% ~% f jedis.auth(password);
! l! k1 [3 @3 h! {) H } ' A: h$ o1 k T8 C/ R' H
ScanParams scanParams = new ScanParams.count(100); O( ~3 ~# a7 d8 m
String cursor = "0"; 9 x6 N1 G& F/ M7 r ~$ z+ \
do { : w+ S$ A, w5 z4 A
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
( q0 v; i; z" U! a c8 { ListtupleList = scanResult.getResult;
- c* J# d# O0 m- t E4 X5 w8 i if (tupleList != null && !tupleList.isEmpty) { ( N- u h; d1 S3 ~
for (Tuple tuple : tupleList) {
& j/ q8 V$ ]5 i- d6 D jedis.zrem(bigZsetKey, tuple.getElement); 5 f: [5 W9 e3 r# p0 q; b9 F2 n
}
2 h K- y6 \2 k* \) p! J" e } # Q# X- O! s2 q- @9 d9 z$ w
cursor = scanResult.getStringCursor; " t. h1 z2 \: V/ Q
} while (!"0".equals(cursor));
* u7 V; A$ A# k% e7 t" k i/ ]6 w% |
* s) U- Q+ l0 @//删除bigkey 0 R# }* @2 ], X, G5 ?+ |* S8 ^
jedis.del(bigZsetKey); 0 Z" n: Q4 Q$ d& a( z- B0 C# Y2 I
} 公众号内回复“1”带你进粉丝群
: ~: F8 u6 }3 F2 u1 k4 Y& x) c& t来源:http://www.yidianzixun.com/article/0LevQm7t) H, _( R* L: _' [/ q
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|