|
|
( {& _7 | {- s s; ~3 g9 a2 \
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。, W- `1 Z5 ^8 t+ j+ i
) k `# @; b4 {; x, u( ]- 键值设计! I3 R* k0 R/ W/ }4 }
- 命令使用
0 d5 J! Q E, l8 Q" g - 客户端使用1 Q6 [% F1 T* m: w9 t |
- 相关工具: C% G* @* {2 _' F
通过本文的介绍可以减少使用Redis过程带来的问题。$ @& e& b# y5 ~# O
一、键值设计
1 G* ], C9 B' R) \0 q C3 l* H$ A6 l. y; n* a) ~, {1 i) x: ^" q- J
1、key名设计
f' Y( W$ [1 @3 }2 w; J5 c2 R6 [6 o: m+ s
可读性和可管理性+ ^* S5 |8 K# B/ a1 y
/ b: I( B/ s+ A3 X3 w+ b以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id5 s" J& k! s( m1 b
1 B: O6 c3 y2 \* H8 ^
- ugc:video:1
1 Q( T2 C$ j2 P( V 简洁性
- X8 q4 ^3 O+ M2 Q; @5 f' G% [
$ }5 s% A _1 O, D4 a保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
$ |6 @4 p" Z: x! u2 j
3 z- c5 ^ m# H- f3 X: [9 \- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。6 ?+ u$ c+ \3 |2 z7 b N
不要包含特殊字符
, n$ I& X, Q7 P, Z4 }2 E* a; ?' Z# G' F) T6 }+ C
反例:包含空格、换行、单双引号以及其他转义字符, l4 ^- c; d( j4 o$ _ `
2、value设计6 ?$ y1 m' t( ~+ h7 Z2 j
* c9 t9 [- |3 ?$ y+ \8 S+ N拒绝bigkey
; c T: Q$ a4 b* x4 b: o( B5 y5 x9 y, p0 J3 B. O/ R
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。1 O3 w5 [4 y! V# l3 L2 ] [
反例:一个包含200万个元素的list。! @5 P# w( s2 Z+ y/ ^" z& l
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法! H7 U2 K3 v% h! `
选择适合的数据类型
; J$ c$ [4 \2 T' m @
, ]2 v6 I4 m% u* P" F例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?* L. I' Y9 H$ @9 ~8 w" @
反例:
6 ^; [1 T, W- ^9 L
2 I2 F5 n8 n ] i7 S- set user:1:name tom! k6 d5 z4 T: ?- N$ P
- set user:1:age 19
1 {0 ^9 }4 b3 E: v& U - set user:1:favor football4 H/ F4 g' L, c$ j8 K) E+ _3 h
正例:
" q( ]/ k8 l) @) r+ N6 W: |1 H2 X( u/ |% k$ ?8 G
- hmset user:1 name tom age 19 favor football3 V% ] l1 W8 q* e" \: f+ o& ~
控制key的生命周期5 n: F& i9 y( }, U! _0 {- W0 u
! w- K5 ?$ ] R% m# h9 {: tredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
7 s0 @1 X0 ?/ M* R9 y: S1 i7 h! B6 A0 ^二、命令使用
7 j8 g/ z' I& v% D. Y% U. [7 W
- [6 f; R3 F: i" b' ?1 [1 j# F+ ^1、O(N)命令关注N的数量- ^% W2 Z& F0 O T0 n
; ?9 T; ]+ w" e6 A
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
- ?3 `% v1 G, V6 A7 C- _2、禁用命令
; |1 z3 j$ ^. e* ~9 {: G/ G- c- r
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
) M+ f; c1 S% @6 Y, M+ X3、合理使用select
5 d1 e6 Q9 ~" o& f w8 A5 Z1 ]) f3 [3 k8 N
6 s9 D2 h, t4 x" u4 n1 _3 E0 Iredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。0 q& g, w& e+ [ |
4、使用批量操作提高效率
# Q3 q6 I! [4 |4 R+ \' `) a% k* M2 h2 n; K# W8 f6 y& w
" I. B/ f% G* m; k5 V* u3 B* t- 原生命令:例如mget、mset。
& `$ n0 M$ k4 l: }. y. Z/ i - 非原生命令:可以使用pipeline提高效率。7 h% Z( t# I& M4 C$ t
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。! @1 g U& M' V
注意两者不同:0 ?" \8 G1 x2 h+ R, G7 E1 h7 c8 ?
: Z9 v5 ^" v# w2 d) y1 d# w% q
- 原生是原子操作,pipeline是非原子操作。
7 |5 s2 C5 C6 W$ n3 O# k( a - pipeline可以打包不同的命令,原生做不到
7 m5 f& E( v9 B" n - pipeline需要客户端和服务端同时支持。
! |/ h, C. V5 B" w/ i$ f 5、不建议过多使用Redis事务功能% _( w" ~8 [3 q3 {2 O* d4 [
0 S2 X, p% Z# cRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
7 T+ u' h0 N/ J/ D6、Redis集群版本在使用Lua上有特殊要求
# @7 q& v1 v' c$ J# T* V1 ^+ o( O0 ?% @" R/ c
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"
9 N) L" u( z: {; _7、monitor命令/ E! V% s S- ^$ Z
! ^7 V+ \* J9 `5 B4 j P必要情况下使用monitor命令时,要注意不要长时间使用。
) {0 r5 T4 B4 ^- f( U8 _三、客户端使用, o$ E ^; [$ K" k0 [
$ b: b8 F) l9 w# f. M1、避免多个应用使用一个Redis实例
/ h6 O! R/ T, H+ w* i' H% H+ H# Q8 K$ u5 T& Z
不相干的业务拆分,公共数据做服务化。' s, Q+ ^5 t; [. i4 J6 @$ B6 i1 z! N9 l
2、使用连接池' P4 H7 c" w6 F5 C: ^4 [" X
4 g1 e% m7 Q7 F5 z/ |' T& ^可以有效控制连接,同时提高效率,标准使用方式:
6 ~( t5 S+ T4 mJedis jedis = null;
9 ?9 M, R6 N- }/ V/ X0 D1 A$ htry {6 Y0 z y/ o/ S; T( u3 N3 E
jedis = jedisPool.getResource;
5 W9 _ t( m" B+ x; ~ //具体的命令
0 E0 e6 c. K. M7 X3 A8 z' `0 V8 q jedis.executeCommand
9 c" p: m7 l# n. b6 T} catch (Exception e) {
" I/ a4 U% v( n logger.error("op key {} error: " + e.getMessage, key, e);! J( L+ w6 l6 ^3 N7 o
} finally {
- ~# a8 @% @6 v* Y2 q //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。9 M0 `' [4 t/ n( ^; L
if (jedis != null)
& i2 X% Z. u$ [! ?' ?! X" n jedis.close;, R: ?& r! ?' z- o+ y
}' j7 \" ~1 P8 ^4 x5 W1 W
3、熔断功能2 d4 X8 D; [. U2 c" s) E
1 `6 k: h4 o6 q9 ~" |
高并发下建议客户端添加熔断功能(例如netflix hystrix)
5 ], z% x( }. L' H4、合理的加密$ _% E" ^- h* V: k& _) ~" i
% C5 {3 |0 D0 H5 `9 d. p
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)9 K5 W( `' q6 L3 \5 S" \# W
5、淘汰策略/ G+ e+ ]7 Y! ] m# p4 ^2 F
. f8 i& j0 x8 ~! Y
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。) |7 ^3 w# D$ r5 Q# n
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。3 |) q+ m8 A' @1 Y( E: K
其他策略如下:3 q- H) a B* a! w5 d! p& A% Y
, p7 u! Y% h5 p m2 S; d; o
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
( b6 _/ b, K& N - allkeys-random:随机删除所有键,直到腾出足够空间为止。
5 b% s) ^, j0 v - volatile-random:随机删除过期键,直到腾出足够空间为止。
5 d" y, w0 o* f0 P t, [6 u - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
4 C" P' G/ l- V8 a" V, X9 \* m - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
9 S0 V# k' U+ h( u, K 四、相关工具
@4 k& p7 w; n' c2 r4 V
* P9 Z: w9 a& Z7 S5 z, L- m* Q: A1、数据同步
) u# n* b/ I) `0 B& J" ^) w6 }/ a( s: ^% Q7 d
redis间数据同步可以使用:redis-port
: j. a o1 k# U- _2、big key搜索
6 d0 `" C, y9 F# j- z! T
: {9 P( p; Z* S8 W" qredis大key搜索工具
' n6 c: g9 x) c3、热点key寻找9 z5 t4 @7 o# f7 m/ Y: b2 b
3 Z2 H) J! N$ y( [; g内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
9 g, S1 ]8 W4 L$ ?& m五、删除bigkey
, }' Z2 j% d: D; U; m# {7 l0 I8 x' ]& H) ~, T& ` n
7 | d; W3 f5 w+ I+ I
- 下面操作可以使用pipeline加速。
5 l7 r0 l" z; f/ F" G4 Q! X - redis 4.0已经支持key的异步删除,欢迎使用。
$ e8 V5 D- X7 o' P6 v 1、Hash删除: hscan + hdel
, m, O7 n- g) `
- p, j$ }) Y" s! J' Y: `public void delBigHash(String host, int port, String password, String bigHashKey) {! u% t7 T' q9 U8 t
Jedis jedis = new Jedis(host, port);9 q4 L$ H' c# a+ h% \" T$ q
if (password != null && !"".equals(password)) {
% A3 o: \3 F0 H8 h( S jedis.auth(password);
+ r2 I i3 \+ y' S }
! @) T* L) ^4 U ScanParams scanParams = new ScanParams.count(100);( S% y6 R4 {& u: f5 M2 w O4 F" Q
String cursor = "0";
1 E+ p4 {2 X5 B, |8 o3 i0 T- q do {; i4 C9 l- c, L. c2 K; K
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);5 K$ r* e' I, ^4 { g+ \
List entryList = scanResult.getResult;3 x: a8 ?2 c; C6 Y+ m
if (entryList != null && !entryList.isEmpty) {0 t( f: I. }$ m: Q3 r; Y
for (Entry entry : entryList) {
1 g) A$ l- c9 V- j4 n0 H" N jedis.hdel(bigHashKey, entry.getKey);6 ~: ?8 F, B. t# U( ~& w
}
. m5 Y# d8 S" _& L }% h5 A' x! E7 f, O
cursor = scanResult.getStringCursor;
- ^# p) P7 Y4 F2 X$ H& V6 ]# @ } while (!"0".equals(cursor));$ y9 m A0 `$ {8 ]
4 D7 ]* L. K, Y' A5 I
//删除bigkey8 I7 e0 @: b4 O( x* w& H9 w; n0 N
jedis.del(bigHashKey);
+ i4 H" E6 p% o7 s- }}/ E( @) V0 ~0 m, U3 I) a; k: I
2、List删除: ltrim
: F: S" h) X' f# M# _. ~( [( `- F, \2 _% i; K2 Z& `; X
public void delBigList(String host, int port, String password, String bigListKey) { V I* C2 e5 L
Jedis jedis = new Jedis(host, port);( V1 s! H% P1 L3 B0 o& w% `
if (password != null && !"".equals(password)) {
. t0 \$ H: y, ] jedis.auth(password);
7 M# e# A1 t9 y z" @ | }
2 W' g7 }- ]9 y# r! N. i long llen = jedis.llen(bigListKey);/ L$ K7 P9 e2 _' Z
int counter = 0;8 Z: @) e- P' J" N2 l
int left = 100;
8 I6 n D. e1 N$ s) V j3 ? while (counter < llen) {
. L5 o% v1 K% d/ V9 c //每次从左侧截掉100个; u, x; l, C2 N# h4 D
jedis.ltrim(bigListKey, left, llen);" l1 h7 }/ H; g
counter += left;9 C$ P; `. b4 P4 V
}, c! m, A2 s, e( M% A; t" f
//最终删除key
( S3 z( x) F% B3 V" s, ^& h+ Y0 } jedis.del(bigListKey);
) O( [. u* r1 Q! ~( [2 A( l}3、Set删除: sscan + srem) Z6 ~/ B2 E, Q1 D
0 T+ F6 l, a' m5 V9 _) Dpublic void delBigSet(String host, int port, String password, String bigSetKey) {
8 x0 G* e: [5 m% {$ N0 e/ i Jedis jedis = new Jedis(host, port);
7 {# ] d5 X9 V: m2 k if (password != null && !"".equals(password)) {% D0 }; k% j! a+ o+ u: e! M2 m% c
jedis.auth(password);- G7 V/ l1 X. P# u5 \9 u
}) R N+ P [5 z' w9 V7 s$ z* r
ScanParams scanParams = new ScanParams.count(100); V( O. B$ I: Z! I+ ?/ h6 V
String cursor = "0";
1 _" \3 z4 f- `6 { do {6 |4 o, `& B5 M0 S5 ?
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
$ f, c- `2 _$ U8 u& S List memberList = scanResult.getResult;
7 N* s- I2 V# k$ i0 Y3 t8 ]2 F if (memberList != null && !memberList.isEmpty) {
7 I$ }7 T% {: u9 m; }1 V+ l" Z H for (String member : memberList) {. ], Q6 W5 u; R( } \
jedis.srem(bigSetKey, member);
8 @* O( B/ y9 | }' O5 `1 s- C3 J0 a' Z0 ~! r- D
}( l$ p3 ~: y" F, |1 u4 S
cursor = scanResult.getStringCursor;
& e0 e0 o: c3 C$ K } while (!"0".equals(cursor));
- L- F2 t7 d( n1 Z3 W) v, |$ \2 ~1 w3 ?; _/ l7 Y' f
//删除bigkey. L! D( Z9 {: g% i6 m; k
jedis.del(bigSetKey);, `* V6 S+ ?( V6 J' R5 n5 u
}
9 |8 f7 e6 N3 B5 ?( a- N4、SortedSet删除: zscan + zrem
& A; t. b$ B: q# }$ I- J" H& E. f2 z" J- D+ ~
public void delBigZset(String host, int port, String password, String bigZsetKey) {
8 E6 K1 J* t/ g/ w* ^. A Jedis jedis = new Jedis(host, port);
% {) r) ^. Q% V3 j+ q5 G) m if (password != null && !"".equals(password)) { ' e7 c; z6 ~* k
jedis.auth(password); 1 r( B4 x5 Y. c# l- v- ^# [$ m
} - M. ]7 ?" w9 P- j4 g, R% ~; k
ScanParams scanParams = new ScanParams.count(100); ) A2 e! w' ^$ g: u
String cursor = "0";
7 _. F% B9 O) c' M9 [9 w3 }: U do { - p& `! p9 x! I
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
& ]- n# H; a( u! J3 J2 K/ H ListtupleList = scanResult.getResult; , j, }! ~) S5 F: ]% w
if (tupleList != null && !tupleList.isEmpty) { 9 T. U) m+ k4 J. U+ D; `, }% d
for (Tuple tuple : tupleList) {
1 R" y2 N- I9 ]: u jedis.zrem(bigZsetKey, tuple.getElement); * _' @, c9 l* Y0 x# g
}
5 `4 {# \) }: U2 V8 S! R/ ?. y }
, N6 E# B t' r) }! x4 Z/ i cursor = scanResult.getStringCursor; 4 A- [4 U8 R% X: S9 l
} while (!"0".equals(cursor));1 q( S/ M8 y7 l, |! ]4 m0 P& p
3 s- t$ \+ T7 W# w0 k" j& k
//删除bigkey C) t6 M0 c% V3 s
jedis.del(bigZsetKey);
2 F$ y4 ]) F! Y4 W# q% K) A} 公众号内回复“1”带你进粉丝群% g# H7 \, N2 \, p! W
来源:http://www.yidianzixun.com/article/0LevQm7t* L; h4 [3 E' }0 g# ?3 X* ~+ V
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|