|
|
1 i* D" `( S" {! S9 g' m9 m2 F! h3 |
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。 ^2 i7 p5 j3 l6 Q
+ }1 ^" M8 ~. V' Z$ u4 E
- 键值设计- M; w: e. Q/ W5 S: E: ?) y
- 命令使用
3 Y" I8 U/ l. t4 E; X U" T( X9 } - 客户端使用
3 S" N2 Q; K, M W" q1 X - 相关工具- r% h! R# A3 J* V* V) l4 G$ e
通过本文的介绍可以减少使用Redis过程带来的问题。
s ^9 Q( l: K# j# J9 O一、键值设计* a, A, Q, e0 n" K
( i: u! @# K) x& x+ }& s% d
1、key名设计
8 }$ a# B( a- p; ^( N" N% ~2 L3 t
可读性和可管理性: H6 ^9 S7 Q; b: L) P+ i
8 t9 i/ X7 B& L3 i6 U: G. G
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
1 ]- T, A. U4 u- u5 J- W
) C' {+ O1 X4 U) Q/ t: u( N! ]- ugc:video:1% N" Q$ I, r; [0 a; {
简洁性
" H- ?& U1 i" H: ?, d: M! E) \) j' E1 p0 r) a. w
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
- J3 L3 c7 u& `+ a4 G3 ?
( o) H& D9 {' O- Z% ^% [' k7 v9 ?- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。0 a6 ?( h4 _8 K2 K
不要包含特殊字符5 b d& s" G# h) o- V: l. f
$ v- v) r- _6 z5 f反例:包含空格、换行、单双引号以及其他转义字符
- w( @( G8 X+ F6 {( _; {& m2、value设计
6 K, ?' M+ [1 M/ W6 A% P3 L& W
* [- x( ?) r2 i! t. a拒绝bigkey0 Z' R7 t8 }5 s, {7 O6 g
8 h5 R6 R& A; Z# a
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
7 |/ A, v$ A3 _, b* S# S" L5 A9 @" s% `反例:一个包含200万个元素的list。9 r9 { T C! f/ a$ z9 k) l
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
1 \4 @5 r% \5 S+ ?选择适合的数据类型9 q: P0 B1 {8 ?0 {
4 e4 j. \% W" @* h) {, A0 h
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
# z( V) p1 M8 D" j: V- m2 s& Y7 {8 X反例:! }" v( l; r& j1 V+ {' J
: Z- }6 E6 e y+ G- set user:1:name tom
) t% M+ N6 W/ X/ x, }( F4 ] - set user:1:age 19
8 p- m Y5 s2 }5 q - set user:1:favor football
2 [4 d" h6 a+ \# d! n$ P4 s) p 正例:
. t' B! [" p9 m* Y- u9 o& n! H- Y% v! K$ r/ a% ?) K
- hmset user:1 name tom age 19 favor football- |8 @7 {( `- S4 A8 Y* s
控制key的生命周期& Z* u& y/ G1 F$ W; f9 A' m6 Q% |; J
; E& n0 \" o, \8 [redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
8 e' i) m2 _9 }二、命令使用
6 O( `* X0 h% B$ Z
, P* D4 w" n& k1 N7 g' w2 }1、O(N)命令关注N的数量
4 @! ?7 ^1 K& ?& p( }4 m0 a$ R* r7 y, ^1 g
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
: k( p: {' J" @9 `; ?( `2、禁用命令5 n- `9 K' g4 U) V* y# k
( p! c! X! d% v1 p; L9 f, ?禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
5 X& h5 { f6 n7 c3、合理使用select
+ C3 q% b& t! x( v% `" r# ^- h% j/ l1 m- f
" U% Q: s# \+ D- q) iredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
6 o7 K, u& S3 O7 k) |. L4、使用批量操作提高效率3 L, E r9 x: G& p% \- |1 H
) M5 q6 F) R4 t+ X0 ^* I8 a) A8 |- E. S/ o5 r/ Z6 n% @
- 原生命令:例如mget、mset。9 z: b7 n% \, S! r% P, p
- 非原生命令:可以使用pipeline提高效率。( O+ [2 j7 b& g+ J5 S/ X' [# w+ n
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。& z9 f3 _/ U& o A1 U$ M
注意两者不同:
" V7 L) _: ?1 d! ~$ a
- Y, w$ k, `$ j p- 原生是原子操作,pipeline是非原子操作。
$ S4 e) U/ \; ] - pipeline可以打包不同的命令,原生做不到
# t' g7 W. F" n) H7 x - pipeline需要客户端和服务端同时支持。
; D9 C0 X2 {: x" H# U9 _0 {+ U9 C 5、不建议过多使用Redis事务功能
% g: ?4 l% K6 v/ i0 `1 k/ P# z/ E& ?
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
2 M7 P# L2 Z- Y6 q% B6、Redis集群版本在使用Lua上有特殊要求
- K0 w! b3 A. R4 g
; Q0 l% A$ J0 M5 b4 W/ b' 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") [* {5 p0 s- F. e# b
7、monitor命令 l: }. B: O2 r! W
4 S7 T: D4 U( \2 ?! N5 u必要情况下使用monitor命令时,要注意不要长时间使用。
* ]3 M% A5 O, D: Q三、客户端使用) e" |; s. ]# @& i
) [ {; x, y! I4 ~1、避免多个应用使用一个Redis实例
: ?% W2 y( m2 l
& `: ]# Y/ C; Z$ ]8 ]' D2 J% Q1 h7 I不相干的业务拆分,公共数据做服务化。, F8 w' R/ j9 h4 n" R
2、使用连接池
! u: G1 o% M6 B6 g$ k6 N* I$ x9 ]! @) m: s- T) B9 t5 e
可以有效控制连接,同时提高效率,标准使用方式:
, N' E" ?; {% m5 f1 ~9 GJedis jedis = null;
5 R. A% {0 b% Rtry {1 q0 o/ o; Y! M( q. i; V
jedis = jedisPool.getResource;
6 B- f: X: o7 [2 D+ Y //具体的命令
1 w6 D# i1 r) f7 \6 l. o jedis.executeCommand
3 Q7 O( Z: ?7 P* {/ i1 n} catch (Exception e) {
) S" C9 ^$ u6 ~9 d/ x logger.error("op key {} error: " + e.getMessage, key, e);7 q- o1 O& W& h5 z5 ?. k0 }' N8 E
} finally {
% p3 f& u/ O3 D //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。$ q% y& Z1 w: }5 c1 g7 @ a
if (jedis != null) $ h8 b3 G2 x9 K; q; R1 `
jedis.close;" h# F1 q3 F) l! ]5 J# ?. j
}8 @) g- `0 a5 Y# r
3、熔断功能1 H1 R2 R [3 b3 [
) J- a7 @3 k" L2 w7 m$ n高并发下建议客户端添加熔断功能(例如netflix hystrix)
0 |3 R" A3 V7 T4、合理的加密
3 e n% w# D$ X5 ?% d7 Y
- ] U; Q5 s% |6 h* u设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)4 v. C: x& B E4 E
5、淘汰策略; ]; z9 O# n/ t' d
- _, F0 w8 d+ |7 j
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。) d( o4 @) z; A1 J7 \ k
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
" ^) ?& C0 R* [6 b2 E其他策略如下:$ P1 ]4 A( g; a
7 I5 q5 ]" E) l1 p/ c1 a* u7 U- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。, N# P. F' R7 P+ {+ }7 o& Z$ E
- allkeys-random:随机删除所有键,直到腾出足够空间为止。4 k) C1 C- Z/ P$ ?5 q( u
- volatile-random:随机删除过期键,直到腾出足够空间为止。4 o: U8 m V4 w: i) F# G2 A
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
2 a: D1 r' i# m- ] - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
) I. ~9 J2 K) u 四、相关工具4 z3 D' Q/ N* z( K
2 H- U" c& z, z4 h0 e
1、数据同步, ]& {* I3 Q# o. q/ l. R& @
( ?* W- P/ ?- F, M' T+ R
redis间数据同步可以使用:redis-port
5 k0 f. b9 }9 q( K% f) `- y+ f2、big key搜索
, G) ~ D* d: Z3 d" i% y4 I* P' i: D* H1 }3 S
redis大key搜索工具6 ~9 g3 `/ t' x6 T" c( T2 I
3、热点key寻找
# v8 d" s' _" t1 `/ B+ x1 U2 j3 X: i6 L' d3 v2 Y) F
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
( c: h9 e3 c1 l; S; H五、删除bigkey
" L7 ]+ c8 b0 B0 y4 @
$ U' M, Z+ g+ a0 F* Y- U: D1 T
( B M: T( R" P4 L% C# G' ]- 下面操作可以使用pipeline加速。
1 t. d* O% p& k2 B. H/ \4 Q - redis 4.0已经支持key的异步删除,欢迎使用。
! Z z0 J0 L6 @) w% V1 X% P 1、Hash删除: hscan + hdel
" W* w9 u# K5 n5 Y; j
" m+ j+ i# y& z. q, Cpublic void delBigHash(String host, int port, String password, String bigHashKey) {* j$ ~1 x8 `! \( P6 b& F5 s1 G
Jedis jedis = new Jedis(host, port);
, i6 Y& ]) e" \( @' H if (password != null && !"".equals(password)) {
|4 S+ i. q: B& d% l5 u9 ~ jedis.auth(password);! m5 u9 ^2 g I; z- Y
}6 \) e: E. ~$ f0 N C% X
ScanParams scanParams = new ScanParams.count(100);
* y' k" D7 M U/ w. ^. {7 P String cursor = "0";# K: f9 d9 [' m2 s: ]
do {
% X8 Z3 f3 K/ u# O ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);) n3 b1 G! j, h6 B
List entryList = scanResult.getResult;* {* D) w H$ B% ?
if (entryList != null && !entryList.isEmpty) {
' s: T7 K$ x2 r* y for (Entry entry : entryList) {) M; Y% `, c2 L; w8 U' E5 z
jedis.hdel(bigHashKey, entry.getKey);% X! [) u4 W# W- j8 e$ ]2 ~! q3 g3 A4 Z7 y
}% {" G8 g4 }/ F1 k2 w9 ~- u
}# ]) ~7 b! |* m% v2 y3 ~5 N0 n
cursor = scanResult.getStringCursor;
) m0 g. ^2 p- P9 K7 C } while (!"0".equals(cursor));/ o5 a" w! y! l, S5 i
6 ]3 H# N: d/ p; y* |9 A//删除bigkey
2 x9 H y# A1 x* n jedis.del(bigHashKey);
- c" P" s/ _3 J}
& q) n0 x- I" A3 ~3 v2、List删除: ltrim: P+ V/ c7 N( c& G' q+ D/ d
- d1 H8 y9 ~9 l' I8 F2 B2 ~ l" K
public void delBigList(String host, int port, String password, String bigListKey) {
% y( }8 w- u+ x: Q6 D Jedis jedis = new Jedis(host, port);
/ e+ _' ?: t$ D0 [5 q! H- d" M if (password != null && !"".equals(password)) {+ K- u0 q5 q9 _" r! l
jedis.auth(password);
% X+ E- D- A e) W# \ }
I# a7 s: ^- z' }4 C long llen = jedis.llen(bigListKey);
; R. N2 O- H5 C$ `4 Q3 ? int counter = 0;
; ~4 @# K k# y) M9 O* L! u int left = 100;! O5 _. r8 ?3 `* g( n4 n
while (counter < llen) {- F G- `9 i+ T( E# R9 u8 d: ^
//每次从左侧截掉100个
0 n3 E8 }: e+ Y3 B. H% C jedis.ltrim(bigListKey, left, llen);, `' Z% e+ v( C* Z9 l
counter += left;
% Z6 R9 j1 q* j" x1 U }3 a% ^# S% }5 L* P/ ~/ P' M
//最终删除key
! a. O. H) S+ ? jedis.del(bigListKey);
5 g D" {/ \& e) {( e- ?}3、Set删除: sscan + srem
1 a" V. @& @9 l2 B1 S3 S8 I# i' v* ^
public void delBigSet(String host, int port, String password, String bigSetKey) {. p" J& ?! ?9 W- Y9 g4 q/ k: I
Jedis jedis = new Jedis(host, port);% d) K% Z+ B7 `4 _; M
if (password != null && !"".equals(password)) {# `8 w) g5 D, i. r1 ~5 B( `+ A- h8 b
jedis.auth(password);" R- M% n/ u0 B9 l, P5 e
}; r# R! A' U4 j- B7 J
ScanParams scanParams = new ScanParams.count(100);
# c1 v( V3 Q5 l# ]$ Z. Y6 \ String cursor = "0";. _% U$ g: d7 I" Q$ V4 e9 @1 m
do {
0 p& Y6 p% c# Y5 b% N% k ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
9 ?, f( l( \; K6 I List memberList = scanResult.getResult;
2 M* t3 @. x7 M/ {2 p& G if (memberList != null && !memberList.isEmpty) {& f9 H* n; `. X4 ]) o! Q; t
for (String member : memberList) {1 _3 D! z" a4 |" M, w3 q" Q
jedis.srem(bigSetKey, member);
6 _) \# m4 c3 M& M( ] }
$ j+ W5 Q. J+ k8 }/ r' s }. ~+ j4 q* Y9 o# i" y( p
cursor = scanResult.getStringCursor;
2 s; \% h6 ?+ C5 T2 w* I } while (!"0".equals(cursor));
& g4 z* |0 a/ r$ z) ~4 [0 {& `. E3 I. r2 i# c/ G7 w/ e
//删除bigkey% B( x9 K) X* I7 w4 C. @( b% P
jedis.del(bigSetKey);! M+ o, [7 T6 a5 f5 j
}& c0 Y2 o6 o6 L4 Q% F
4、SortedSet删除: zscan + zrem8 z3 t6 f7 J" w; `5 @- s8 z
/ L. |$ o: y8 w, Y9 Y- D
public void delBigZset(String host, int port, String password, String bigZsetKey) { 7 f0 H! ?6 p. A+ G/ C: D# U Q* F
Jedis jedis = new Jedis(host, port);
0 L7 v+ E) C! L+ U* l$ z if (password != null && !"".equals(password)) { $ ~0 T: ~/ S6 f8 ?7 p6 k
jedis.auth(password); % K/ y) j; t# n" f9 R2 k, [7 Y2 _1 M
} ; n: A1 Z( |4 J# M0 O: a/ C0 Y
ScanParams scanParams = new ScanParams.count(100);
* w0 L+ i' ^3 E& t# E- F6 ? String cursor = "0";
7 w- l8 J4 o6 N: r/ |" A do {
) U0 J9 c/ b; | ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); " X. x+ n1 V! X) I
ListtupleList = scanResult.getResult; - I' X# `+ f2 U Y9 y( G
if (tupleList != null && !tupleList.isEmpty) {
9 w2 I8 H" s0 G! n q0 q for (Tuple tuple : tupleList) { 9 p$ _* d7 Z* @# S
jedis.zrem(bigZsetKey, tuple.getElement);
- b8 k: O" I) N }
% \0 a% `7 m( `! y& J! {1 i }
+ f) c( h' R9 l2 m: m& L& v5 Q2 y- e1 R cursor = scanResult.getStringCursor;
, ~- E# V8 l6 B/ U } while (!"0".equals(cursor));3 |, q- a) b, x; g1 X
4 g! [) N: E7 O4 H( c% ^) I
//删除bigkey . C! ?. W9 e, \9 y" D
jedis.del(bigZsetKey); ) w" Z P- T# `% O8 _; ]
} 公众号内回复“1”带你进粉丝群
+ m: {0 Z* L' b来源:http://www.yidianzixun.com/article/0LevQm7t
$ H- |$ Y9 I* `免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|