|
|

; k# e0 ?9 Y" _( {6 @本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
2 x& x) A L7 D4 ^) ]* |/ r; p v5 `7 c
- 键值设计4 c3 M- K2 I* B+ U, T& y; O
- 命令使用
' a a; [, g- F5 T - 客户端使用
2 U2 q- f6 \, }7 { - 相关工具. c5 K, N m6 ~! M. R& \
通过本文的介绍可以减少使用Redis过程带来的问题。
$ G- ?; n9 s$ e: ^& j一、键值设计
( `+ `9 B% y5 L& x& r2 x; g2 [, B- F2 B/ i6 X, @5 F# `
1、key名设计 S! H# L' X2 G1 j: O1 h) [: t' E
' h+ B: ?5 R0 J% _
可读性和可管理性
1 X5 o7 h" X+ b- ?9 ^7 e+ m- j* K) K3 F
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
+ l. ^. T0 ^6 O2 T2 u/ y
+ _, C# F! ~5 z" s1 C5 G4 X8 |- ugc:video:1
u2 s9 e5 r! j3 C D) I' S' I9 ^ 简洁性
& F) _* J8 L% ]4 U$ Q
/ o* l" R8 y3 M; [7 f保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:. F5 j8 b; G# C5 c: _
0 `: `* a2 N7 ^2 c
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
" p. G6 N. z1 h 不要包含特殊字符
% R9 b! y1 x- y
9 A( z5 ~$ x1 C6 f4 R. G反例:包含空格、换行、单双引号以及其他转义字符
! U( x" ?4 i2 M, q0 L2、value设计
7 R% _7 y. `. U
5 \$ e" F$ Q; b' t$ R; a. w! [拒绝bigkey) W5 A/ O+ J- x, U% V" [
9 N# ]2 m8 X4 ]9 F) n" B2 A
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。* n/ Z0 m4 H3 O+ ]( a( ]# ~
反例:一个包含200万个元素的list。3 J# T/ @0 Q8 E3 P
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法( U6 z! a' E+ O6 @4 [- g
选择适合的数据类型, g& g) ^& x/ ^( f% ]
2 u( A! u' x+ n! u) b例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?4 ]' P3 b% E! ~$ m- G
反例:
& Q8 J3 _$ z$ M4 U2 Q: ?: A7 ?1 V9 H) o, O
- set user:1:name tom
# i6 b7 d/ T$ q- w - set user:1:age 19
8 s$ H5 s) @1 z* L2 Y% f# W - set user:1:favor football1 @8 ^- u* \6 e
正例:
6 f b$ q5 f6 {' Z7 w5 k( C. k% j X7 ], K8 j
- hmset user:1 name tom age 19 favor football
; B: ^0 ^7 u, n6 [: T) ^, M2 W 控制key的生命周期
, |7 H/ d0 D# m% X: s. F/ M L$ z5 K* {& E: L/ a3 }& D- w' q
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
. n: N2 q1 \9 ]9 U, b! O0 Y; \7 i( ?二、命令使用. m) N8 ~% r. m. |7 v2 R i
5 O1 d) ?( C, Y) I& C1、O(N)命令关注N的数量
) h6 r; X, r7 A% x9 m
# \* A3 f S. |$ E# {/ q( L例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。0 X+ o. c7 x, x& M2 J" w) c) ~% m
2、禁用命令
4 u. A9 z T6 b& L/ L7 L/ @# P1 W; T5 O- b
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。0 Q0 R H: s2 @+ f/ U
3、合理使用select
% W9 H# s3 ~8 ]3 g/ h! P4 B
. x0 `1 N& n; S
# e4 W! E; k. F5 Y& d! K# Oredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。- l: I7 n6 D- o6 t
4、使用批量操作提高效率$ {: G3 G/ t" V" A7 h
' s8 [ C# W6 r9 F7 q) m2 t4 c/ C' Z
6 Z; t2 l' K' z- R6 Z9 k0 Y) h& a- 原生命令:例如mget、mset。
" d; U3 R# b, Z& r( x - 非原生命令:可以使用pipeline提高效率。
: I3 _# w, r1 {* T6 P3 _ 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
1 ~- w1 c: b6 C( m$ \2 Q注意两者不同:1 M2 ]# \; l1 B; e
2 {5 }$ c, {2 n8 ~3 m2 F" `- 原生是原子操作,pipeline是非原子操作。: u5 r& |& H/ z. X# R" s: n% g5 y
- pipeline可以打包不同的命令,原生做不到
( J7 H) |: V* R6 G - pipeline需要客户端和服务端同时支持。
z, C0 N. v/ {1 T 5、不建议过多使用Redis事务功能7 f* @1 [/ O& g6 g
& f; \; ~; _: U7 D
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
- z% o0 v7 X/ [3 R6、Redis集群版本在使用Lua上有特殊要求) ~2 |2 r" v {4 |% C6 w) i" v! s
/ _6 l& k5 a* ~ u6 Q1、所有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"4 k) K& F/ v3 l& \/ q+ Y" w( {
7、monitor命令
4 ?! f+ s7 ?4 y. n+ X, a& }
2 i8 n9 c# E6 A {& Q必要情况下使用monitor命令时,要注意不要长时间使用。" O- w6 N1 _! _8 |' y
三、客户端使用. I( m5 b6 m) T
# r" A: O7 _! Z9 v4 v5 G1、避免多个应用使用一个Redis实例# O; z6 W; X2 j
6 x: B+ a3 s. W- a o$ q( G不相干的业务拆分,公共数据做服务化。
- E* \! S8 M! d( L! L& D2、使用连接池7 P4 W+ H+ k5 M1 Z
3 V3 L1 l& f5 x0 ?5 X
可以有效控制连接,同时提高效率,标准使用方式:; }, N2 F- ?1 ~" g
Jedis jedis = null;
0 d8 T5 d% @$ Y" etry {
' I) q) ^3 ~) W9 u2 a jedis = jedisPool.getResource;
% b! p6 c( u% ? //具体的命令( Z1 P$ u: ~) L6 u
jedis.executeCommand
8 l9 i4 g9 Q( g} catch (Exception e) {% g1 ~& B: a; {2 }; k
logger.error("op key {} error: " + e.getMessage, key, e);
, E1 Z1 d/ H0 y6 m, P7 t} finally {
/ Z- C3 l' T# e& s/ g //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
: C" `. T# A7 v: z% C+ ?6 _/ U if (jedis != null) ! [% B& X4 Q/ }1 S S5 [7 L! |
jedis.close;
$ L) |* R5 y7 G% m. o1 P}# i8 [1 M% F" q& y/ N$ ?
3、熔断功能, ?- x J+ ^: l6 R6 v3 A, u8 L2 ]
) w2 a2 r" A: c4 Z" U
高并发下建议客户端添加熔断功能(例如netflix hystrix)
8 o% R( g( ^2 {5 h) z$ _+ ]- O4、合理的加密
z1 ?; w+ _& \) F
6 z/ j# P8 F' w p- O. `% R设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)% V6 {! I5 l" z) l; ]
5、淘汰策略
4 c" f6 `; H* a$ y7 o( H( C
+ F" [ {2 N- ~2 `根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。% c! A) M$ M' p: t+ o1 X* f A
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
8 `3 T+ [# L+ v其他策略如下:! o/ v+ v( }2 r6 S" ^5 i) x% I
8 }+ C/ G4 E, a! i- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
6 h1 d6 ]. P& {: ~- l3 G - allkeys-random:随机删除所有键,直到腾出足够空间为止。
7 e6 Q" P3 x! |0 ]. T4 a* Y - volatile-random:随机删除过期键,直到腾出足够空间为止。
% @3 P1 C. ?9 h( k - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
4 J; W( m5 b$ ]# [ - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。2 a. W3 p# y7 h1 p' Z r' g1 |/ X
四、相关工具$ K0 `6 }0 R0 r
0 v" k- z, _' n
1、数据同步
* _$ V# X+ @+ [. x' C7 z, t2 S
2 G( l- ^4 I& l9 |& ^redis间数据同步可以使用:redis-port
! @) Q! L" ^8 F& M2、big key搜索4 L2 C1 p" ~( C2 M6 f
6 y- R% |3 l) }5 R9 z# r
redis大key搜索工具$ i8 a5 }5 Z+ k% b8 w! s# {6 h
3、热点key寻找
5 V2 g# }- ~/ Z0 V/ f2 h* C8 T# Y. a$ s2 W
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
" ?$ k9 S9 E9 l( Q1 n" X- m+ V; m. o五、删除bigkey' X/ N$ x( B6 ?9 ^: K ~& W
! H0 f7 z* z/ [3 `) O8 k, D
/ P! c5 H' d7 \: z6 Q1 l- } N9 j- 下面操作可以使用pipeline加速。
( n" n* |' i/ t, b- S - redis 4.0已经支持key的异步删除,欢迎使用。* b* ~9 }+ f5 H3 Y# P, \
1、Hash删除: hscan + hdel
4 R6 t. A7 e$ y( p" Q- Q( a" a$ W/ \- I: c, Q. o$ p6 a
public void delBigHash(String host, int port, String password, String bigHashKey) {5 R$ }) Q6 x5 M+ `- d9 W& F# C
Jedis jedis = new Jedis(host, port);
7 Y$ ?3 f6 o; Z4 w4 [5 o+ e# d" F if (password != null && !"".equals(password)) {
; ~9 s% n- ]0 m9 i+ J9 L, b0 P9 C jedis.auth(password);7 O, T' {- a5 t; }, I
}
! D( K- y( `) \ ScanParams scanParams = new ScanParams.count(100);" J0 ^9 |4 x3 y9 P
String cursor = "0";
. J9 b" U* x# V0 p; r7 \ do {
, ~/ V5 `7 [' ?$ d! J2 V ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);, q3 ^. D$ X1 O/ _) q! [5 t) \7 q
List entryList = scanResult.getResult;
# }/ Y3 R3 R7 O* p6 Q* k" r if (entryList != null && !entryList.isEmpty) {/ K9 P* P7 R6 S. x
for (Entry entry : entryList) {
5 z# }& u# `( P0 q: p; n jedis.hdel(bigHashKey, entry.getKey);
9 `% d/ b. y/ b, H$ i, |0 k }
$ P" R- b, y5 D. z }
0 ]# B1 q8 _& L: Y cursor = scanResult.getStringCursor;5 B4 x9 v& ?; r+ s, P5 ^
} while (!"0".equals(cursor));
0 v3 @3 m1 n% C3 K( x+ {& s. R! s1 ]( M
! u, K2 L- g" R5 z+ ~& R s//删除bigkey- N& S/ Y$ C1 o: `
jedis.del(bigHashKey);
/ E9 |, C" Q% m3 Y$ H/ S/ s}6 y# Z8 }& \) B0 Y7 G
2、List删除: ltrim* N0 Y1 B( c* O& ~* Q
- S! d/ d, n F3 T3 h- Q1 x/ i9 Xpublic void delBigList(String host, int port, String password, String bigListKey) {
A. D: ^ t8 T) X% Q0 e1 f Jedis jedis = new Jedis(host, port);3 z' Y1 \% w7 t& p7 z! I
if (password != null && !"".equals(password)) {( F k9 q( a7 n$ e, w, r
jedis.auth(password);/ p* e2 w* n- _4 `' e4 m) o
}9 M6 c; u1 W" C8 } T* a% A
long llen = jedis.llen(bigListKey);
8 L6 j* s6 [3 u, s3 O int counter = 0;: {( Q% m$ S/ N# r( E
int left = 100;( q7 B$ u( V+ H& g3 U; i3 H
while (counter < llen) {
9 M- j( j% |6 P( T# }( w* k: K //每次从左侧截掉100个
- C% t) c z* V! \$ i jedis.ltrim(bigListKey, left, llen);! R1 O; @9 B+ @+ t/ `, d n! P
counter += left;
k# n w* J6 H% I) m, y }
2 I/ J' i' G# [5 q: H) l //最终删除key
9 i7 J0 L; V5 A' I jedis.del(bigListKey);
6 E2 [- U( O* P& \* J/ \3 z}3、Set删除: sscan + srem
7 {- ?" R: K$ C3 ~5 n% @: R% E4 ]( \. `* ~
public void delBigSet(String host, int port, String password, String bigSetKey) {% f6 r- X: I+ f* D
Jedis jedis = new Jedis(host, port);8 P1 s% {( y1 A$ k/ U- w. R
if (password != null && !"".equals(password)) {
9 K2 P# V1 b. |3 E J1 a# R R; e jedis.auth(password);" @2 a6 C# D4 ~( o+ s0 O
}
6 _8 t) ~, F% h9 i( o ScanParams scanParams = new ScanParams.count(100);
" j1 o6 @( w y l) B String cursor = "0";
3 J1 r m$ o; F7 S do {0 Y3 d" m* ?# y
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
5 E3 R3 N* \7 g$ n List memberList = scanResult.getResult;; h) ~3 M6 ]. f. k. z! l8 W
if (memberList != null && !memberList.isEmpty) {7 O. p. i/ @3 [ V1 L+ K# P6 \
for (String member : memberList) {- g3 j6 z8 m" z3 c2 T E# s$ _7 ?
jedis.srem(bigSetKey, member);0 a0 V% t* V$ |/ C2 F; _
}6 K; m6 l1 P: d
}7 l [3 u: k$ j# c' O4 J! Y- O; O
cursor = scanResult.getStringCursor;! l4 v2 g9 \" n
} while (!"0".equals(cursor));3 i5 R! y+ j* q1 c5 D1 V
. ]+ @2 k8 \* J- a# y) M) E9 [//删除bigkey2 ?6 K% n( C9 u) C
jedis.del(bigSetKey);/ o3 C5 |7 N) B0 M
}* b3 b7 W$ P C% ~9 l
4、SortedSet删除: zscan + zrem: S6 ]% `, _ W* b# b2 I9 w
3 S3 U9 t5 V! j3 | \' Ppublic void delBigZset(String host, int port, String password, String bigZsetKey) { 3 {3 Y- ^' d" o' r& s' v
Jedis jedis = new Jedis(host, port);
, Q1 h& z: m: q if (password != null && !"".equals(password)) { 3 W2 p$ F- @( S6 p3 }4 w
jedis.auth(password);
9 `* ]0 z9 d+ Y } 4 |, Y* E6 @0 W$ Q
ScanParams scanParams = new ScanParams.count(100); * I+ D, Y4 O% A
String cursor = "0"; 7 |7 ?* u( E5 l+ [, P; Y
do { 5 S# b6 B/ ~8 [# w7 r7 u/ W1 U% p+ @
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
5 w5 C- G* _* J0 x' g% m, {7 e3 @ ListtupleList = scanResult.getResult;
6 h2 x) ~' T- x, m3 { if (tupleList != null && !tupleList.isEmpty) { ; `3 T: q8 V7 {
for (Tuple tuple : tupleList) { ( a5 L* u5 o" ]! z) I- D
jedis.zrem(bigZsetKey, tuple.getElement); 2 [3 Q9 I4 I! z2 q) F4 O+ x2 O
}
; V. A8 @8 J: A% X j } 5 a( C5 `9 l) K) p6 f) U2 T
cursor = scanResult.getStringCursor; 7 }5 j* o) z, _8 \; k( G
} while (!"0".equals(cursor));
$ R( L' i8 u% I- q' y7 [7 @6 [0 S5 t! ~! v0 o
//删除bigkey
$ d6 ?% B0 n6 { jedis.del(bigZsetKey);
2 `* X4 p5 w9 k8 y3 g- c} 公众号内回复“1”带你进粉丝群
, r: q9 D( E$ N+ A' V0 ]$ o9 k来源:http://www.yidianzixun.com/article/0LevQm7t
) f$ ?" w$ w2 [0 e1 D免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|