|
0 {: U1 L- r$ l l
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。8 b* l$ |; J# p2 U
- v% n( {0 \0 z4 a8 S. j [
- 键值设计) _! Q* s) ^ Z& L/ [# u2 Q
- 命令使用9 R, f9 ?, v; ^5 N
- 客户端使用
) | u1 |+ T4 s$ B% [* t - 相关工具4 _4 M$ f: j4 k1 H, L0 N
通过本文的介绍可以减少使用Redis过程带来的问题。4 A$ O: g( @; o' E
一、键值设计3 [( u& f$ }/ s* E; y
3 h+ Z: `8 ?6 s! k8 B# T
1、key名设计
/ V4 v' [3 P/ B8 Y/ w
* Q3 N3 V S4 k+ v# Q0 g3 ~$ ]可读性和可管理性4 {+ B$ x% k, Z) {
4 w6 B' I7 k/ S3 J$ d% x5 k
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id/ _# e! O3 M# k. ~0 L0 Q
- h1 H/ ^5 d5 O5 S7 Q' q5 |
- ugc:video:1# |: E( h6 I+ D8 G
简洁性$ X* g% e( \: K+ F6 I; r
9 J& O6 @5 J c. `( V' d
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:1 ^, _6 x0 d3 O2 S% Z" D
0 r' }7 ^9 q; [* W5 F' J- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
$ b4 x( N. Z7 }) N+ u9 K 不要包含特殊字符+ u* k* F- G; ?+ c; H9 t* {
) M& J+ ~( j/ l1 X6 l) |! Q反例:包含空格、换行、单双引号以及其他转义字符9 I- @+ v; C# O/ @5 o8 o, |
2、value设计# c& j; R% _$ I; h
# M1 N6 s% \4 Z8 f) C3 N% a
拒绝bigkey
% F" H5 J1 _% n1 ^$ H2 v) R/ P6 n9 ^8 U
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
) T, |' U8 o" G. Z. T* a反例:一个包含200万个元素的list。5 i1 I& r+ K: X% [
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法1 ~+ p {' v0 u% z
选择适合的数据类型
5 { \1 Q4 c" o9 L& b/ ~+ ?: f3 g6 }9 A8 K7 K6 I) l+ G) l
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?5 K. _' C# s' w4 W2 x2 U
反例:9 K$ G6 i2 f& r$ b, J
! o# E/ W6 Y' l
- set user:1:name tom% y1 ^9 i& q0 `. U/ ~
- set user:1:age 193 K0 V- m+ ^; C* W3 w
- set user:1:favor football$ a t9 |% W5 Z* V n3 H8 C
正例:
0 K' w6 F4 p& Q# p$ q: f
, _7 ~: n5 |* c0 ^- hmset user:1 name tom age 19 favor football
: l3 p3 i N, M: Y) G/ ]9 U 控制key的生命周期
. u. X( T! C+ D! P) I9 d$ [% Z7 f" v( @# Z! w
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。1 C5 D8 j, g1 T" y' [( U
二、命令使用
1 s9 h8 l. b- k" P- j0 W- w
' T- j- X( p! |1、O(N)命令关注N的数量/ J$ \7 g6 v; b3 }& w
' w. @ @" {. M7 V, K+ Z5 c例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
0 T! U" p8 b* _5 `) s+ r- H. i2、禁用命令 u- f; D- @- f) `
* n+ o1 ~) U$ w7 _( z
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
1 u* u; h$ n4 D( r7 I' {3、合理使用select
B- _- l. j" E% p0 O7 n0 g L4 E. ?! E& E6 \# t1 j
4 @ E- H. D( Z* v4 ^- C( ~" T# I
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
5 N { Q( S* [ _; _1 f5 k6 g4、使用批量操作提高效率
' |3 Z# F% u" L2 V3 `8 x
, u3 o' r% E0 N; v5 I9 v* \( m: q0 a- R! M$ K! v; ^
- 原生命令:例如mget、mset。
7 u. u( j& E* {. [+ \! k - 非原生命令:可以使用pipeline提高效率。- A$ {8 ^& H- U' n
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
6 d5 Z: b3 [; t, L+ `注意两者不同:
9 Z9 [3 W8 a1 o5 b8 X* R' s
1 D A1 p& M7 U* m; }8 h- 原生是原子操作,pipeline是非原子操作。
4 t. j# |8 g' N+ k6 B/ m8 l7 Z - pipeline可以打包不同的命令,原生做不到
" H" P: h, G6 O; M# o8 ] F - pipeline需要客户端和服务端同时支持。
4 C. ?0 G1 J" s3 S 5、不建议过多使用Redis事务功能) ]. ~# h4 l- z5 o Q: @3 l
8 ?! r: T$ G# w' W
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚! T7 K" x6 [( }
6、Redis集群版本在使用Lua上有特殊要求! w4 q) l6 {. I4 {8 O
9 u8 X0 |, {9 l) v6 `/ Q
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"
2 o) w+ K' U" L# }: @7 M7、monitor命令
+ U+ s; l* }+ O5 b
1 K/ S( l6 \$ [: M$ x. X* f' }: J/ J) B必要情况下使用monitor命令时,要注意不要长时间使用。* _+ {( A+ Y! }$ E; `6 z% U6 T& F2 H& x
三、客户端使用
) C- h4 C; [. q. |0 n' w' n5 w8 o; ]. g1 t5 \8 t% A
1、避免多个应用使用一个Redis实例
( S2 F9 f4 ?. L/ s9 f% i4 d5 ~/ u2 }7 ]/ f& K8 g( Q6 [
不相干的业务拆分,公共数据做服务化。8 i% r* o* Z' q7 s$ v
2、使用连接池
( B) I( }* `( U% k' O* z: U( T' W# p/ H+ W" y$ ?- y# I
可以有效控制连接,同时提高效率,标准使用方式:. P3 d9 U+ j H8 K" a
Jedis jedis = null;, i0 o, x0 r; Z2 J* r
try {# x' I6 c9 }9 s" x6 B
jedis = jedisPool.getResource;
: u; C& T |6 N& T+ ? //具体的命令5 L" J6 V1 X5 T! K6 j
jedis.executeCommand7 g* K( q* Y3 w- x6 k
} catch (Exception e) {
) P" z8 \" _3 H- | logger.error("op key {} error: " + e.getMessage, key, e);- @1 _9 Z4 h0 x/ k" Y" @! B+ s
} finally {
" }2 U4 b5 c9 I5 \( ? //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
- T; J8 k8 r- `7 M2 c2 u5 t8 F* s s: M if (jedis != null)
( ], Z3 E# Y5 c& x' s: q" r, e jedis.close; h& Y3 S9 n( _3 ~' G! H+ W! H
}
/ }# b2 l# n' v3、熔断功能
/ G. O$ s {1 v+ e5 w6 p. G1 @1 R/ ]3 }! [; b* U% K/ X
高并发下建议客户端添加熔断功能(例如netflix hystrix)
3 l) C5 t. n6 Q1 c4、合理的加密
$ F h; N" v3 \2 K8 m
% X8 T/ ?7 A( L+ V0 T' {9 A设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持), d4 `% q$ B% K7 x
5、淘汰策略% |' V+ `0 L7 q% A/ F9 _
; Q3 u& w3 n) P1 H9 Y- x根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
% i9 ^- f; ~$ \( E) @默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
/ C- F) C: N3 z: |, k7 N: B9 @+ H其他策略如下:" d% v2 h: y# K& k; G6 }
. @" X' A/ O& r9 |& z
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
8 t0 o# e* E, Z2 s5 V - allkeys-random:随机删除所有键,直到腾出足够空间为止。- s0 a% K* Q; M; }0 k' W' P7 k$ I0 s
- volatile-random:随机删除过期键,直到腾出足够空间为止。
% S, ^4 @" t- D& i& P- F - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
' O7 E4 L; x G7 P# |; u- L - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。9 t* t$ s& ^1 c C
四、相关工具5 A: Y( @2 k4 Y
, |1 `& A7 m" R, u% n. M1、数据同步
+ l5 A% @6 [6 Z; \6 v+ k6 S" p8 C5 [7 a+ v
redis间数据同步可以使用:redis-port
3 }) f& U" u# y+ b2、big key搜索
# F4 o- Z4 t7 `4 u2 H7 o/ p7 Y! N" W A9 O& D+ W
redis大key搜索工具
( z0 |8 l* C0 y& R3、热点key寻找$ S. H& n! H0 k6 ]; Y
9 ~" Y9 M& K8 H+ P: W- D
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题. \. h9 s8 X" w( C5 Z$ ^
五、删除bigkey$ h) y) h- P" c+ m
' Y' U6 x1 i+ `
5 E# X' j2 R7 V, k8 a' q2 Y2 a0 D
- 下面操作可以使用pipeline加速。
7 [1 V) T' a. l) V& V3 \ - redis 4.0已经支持key的异步删除,欢迎使用。( B' u" c0 `4 E) v: l
1、Hash删除: hscan + hdel5 e) ]" Q1 k# z+ F7 ?+ ~- m5 d
# h9 g7 a4 e( } spublic void delBigHash(String host, int port, String password, String bigHashKey) {6 ?$ q( U C' a* ], V
Jedis jedis = new Jedis(host, port);: y. |1 G& X; e
if (password != null && !"".equals(password)) {
0 ?9 \% j: |1 K8 ]0 a' b8 K. J jedis.auth(password);
! { M( a# u- r0 i }
& M/ ?1 g; K. Y+ L, S5 P# A) ] ScanParams scanParams = new ScanParams.count(100);
. H4 J$ _; Y, {- ~/ o String cursor = "0";: V x1 v( \6 k# O9 i0 j D
do {8 m- u& b$ @8 I$ L+ i0 D2 n& k; F8 H
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);! P6 h4 q7 q% \( J8 g1 G& z; h' j6 O& _
List entryList = scanResult.getResult;
8 E/ h* @; r) e* U# i2 \ C if (entryList != null && !entryList.isEmpty) {. ]: I" L: Y. U: {
for (Entry entry : entryList) {9 D7 {( z2 Y( f1 g: M
jedis.hdel(bigHashKey, entry.getKey);
$ a' W; K' g0 g4 a }2 g7 L' W7 G+ I/ m% D
}
9 C* j8 W0 [9 ^ cursor = scanResult.getStringCursor;
+ i1 M: }& ]# P9 v* \1 h9 }) H } while (!"0".equals(cursor));! R: s f4 ~: Z' f9 Y8 G
! V. \% q' o+ ^& p
//删除bigkey( q0 S3 ~1 L) {4 q! Z) c( m3 K
jedis.del(bigHashKey);/ M1 T2 I" x" k. L
}$ L) z6 e" h: S5 I$ g+ B) y
2、List删除: ltrim/ T% q7 Z" P. x2 z1 ~
9 Q; V+ p; F& A s% ~; Ypublic void delBigList(String host, int port, String password, String bigListKey) {& @1 ?/ ? o! C& Q
Jedis jedis = new Jedis(host, port);2 p5 P# [2 Q# p% C" C
if (password != null && !"".equals(password)) {
1 P# P: Z. e& d" `/ O& n% i jedis.auth(password);
! x- M- g S9 |6 g* d }
# d! Q4 }+ C! i6 P. } long llen = jedis.llen(bigListKey);' u2 V8 a9 S: R/ v# }1 Z
int counter = 0;1 x$ P4 O Y: [7 {- d: v0 Z
int left = 100;
2 Q7 i& o9 r. B" t4 p; U* u$ V8 g while (counter < llen) {1 z1 y$ P- t o$ _6 J* ?0 j$ b
//每次从左侧截掉100个
5 `4 R v- P$ i8 K I [) m" U jedis.ltrim(bigListKey, left, llen);' |5 r0 {% }! e/ [
counter += left;$ R9 a' ^7 }. s9 W- E
}6 Q* d1 _$ }1 @1 o
//最终删除key
1 t, G( h8 q5 n, v; r5 B7 N jedis.del(bigListKey);0 p& G4 h7 x! G) K: U
}3、Set删除: sscan + srem# l* G5 Y Y- q* P& b, u' f) I
1 r& g6 ^3 t7 @
public void delBigSet(String host, int port, String password, String bigSetKey) {' S$ O2 V; e4 p& q) ^8 J
Jedis jedis = new Jedis(host, port);
3 x u0 t8 T. {' _$ S if (password != null && !"".equals(password)) {) U& Q5 \2 @5 S* }2 R. ?1 n
jedis.auth(password);" y8 _# P7 x t& t6 B: R( {
}
3 F/ O! `0 h( s# w. W2 } ScanParams scanParams = new ScanParams.count(100);. H! E8 t' K# c
String cursor = "0";
2 n0 r5 A1 x x/ x1 g) W do {
# H& @) M4 H6 I( x" H8 l4 T) L! | ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
2 e7 j) w6 |- z( X( z6 H List memberList = scanResult.getResult;, x* |' I) S2 {. {5 a
if (memberList != null && !memberList.isEmpty) {" {& b/ J: a( B; e" e' `* {4 I
for (String member : memberList) {
0 n5 C. G% w7 i: Y3 |6 N jedis.srem(bigSetKey, member);
2 p& s% C* M0 c- M }
# g2 E1 r, R2 [$ f' F: S' x, M }
5 ]4 @: Q2 W3 x* }1 j cursor = scanResult.getStringCursor;: U2 U# z+ q. N5 n
} while (!"0".equals(cursor));
5 }! C( |5 a9 ?/ C# b2 U5 T0 h0 H4 k# u& m5 v& M" M7 z7 n
//删除bigkey
: B/ `! s; c' s& Q; B+ r# u jedis.del(bigSetKey);
# s# v- v% t- i}
( `- U' |" s% |+ I* N2 Q0 z4、SortedSet删除: zscan + zrem
6 |. h6 I# ]9 c: Z6 k, Q
# ?3 c4 l/ J0 B. ]public void delBigZset(String host, int port, String password, String bigZsetKey) {
& k- a$ F' G, }( g/ J% k4 k Jedis jedis = new Jedis(host, port);
& J R: h6 d1 G4 D8 d3 r4 J$ i if (password != null && !"".equals(password)) { 4 o. a2 _8 w+ h+ H% F" a
jedis.auth(password);
% c' s. p1 L7 Z+ ]1 k } [( T( |" k n& O0 c' \
ScanParams scanParams = new ScanParams.count(100);
( \) w/ A* B( H8 R' }) k String cursor = "0";
) N/ I6 W$ k1 G# ^& P3 g do { - i3 Z; w- _4 F3 }$ _& |+ P a
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 9 a) j$ ^) }3 X$ U. v
ListtupleList = scanResult.getResult; 6 K- f$ F5 T8 k( D. f4 |) \
if (tupleList != null && !tupleList.isEmpty) {
3 f+ k& T {, f* D$ G3 n for (Tuple tuple : tupleList) { ; w% K$ B$ H. A, P8 d' _
jedis.zrem(bigZsetKey, tuple.getElement);
2 {) y' e& M( b# U& q } ' H+ [5 w3 ?: V/ ~# n+ }
}
, f4 o) T; n/ b9 q K cursor = scanResult.getStringCursor;
6 }- n) E# f" _; b: h } while (!"0".equals(cursor));
# e% k7 r2 V3 l c0 K$ L/ W
2 j, n) y1 {2 p. Z, S j//删除bigkey
3 R F/ U+ a. o& ^2 ?! S jedis.del(bigZsetKey);
' S% i1 Y! K; g6 T2 q4 V} 公众号内回复“1”带你进粉丝群
: u( _) u0 y! X来源:http://www.yidianzixun.com/article/0LevQm7t7 v1 }$ g" M: s) C F
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|