|
|

$ R, B1 |! d0 o% Z; Y- k本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
- ^" h$ Q8 N, Q% M7 G
; Q% J- R' K+ c- 键值设计/ m, j4 l9 ^1 C8 P
- 命令使用# I% G2 u: p/ X( H
- 客户端使用
! a$ Y9 w0 a# P* b/ n6 D - 相关工具' {: A0 V+ o; |/ J5 f; U2 w9 _
通过本文的介绍可以减少使用Redis过程带来的问题。
* I: [: G" n+ U1 C. j0 R一、键值设计
( h3 d7 j2 }8 n: @" c, [& o8 _5 ]6 F0 T" x7 o
1、key名设计: d+ Q2 `/ f+ j. e# l
0 w1 B* H: r) i5 y) G可读性和可管理性" F% V7 B! }+ J" e$ Q
0 f2 N: f3 V3 K) v* u以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id6 r* P: M( h. ]0 ]/ n( ]
/ Z" `) Q2 O" P/ y7 S* @4 V
- ugc:video:1
* C8 L- f' D6 B 简洁性
% \# O. a6 T' T: L6 X8 Q' K
6 ?( [) f5 Y& k- m2 `保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:1 ?0 J5 s$ `9 s$ r. N$ _
1 F" b4 f& e: @# F- W- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
4 H, p# o1 W, h 不要包含特殊字符* K3 t( e& D% H. v' o+ K8 U( e* U
- Z9 B7 j8 J6 u0 h# I; \. |# x x V
反例:包含空格、换行、单双引号以及其他转义字符: j: }7 J5 I* G m, Y+ ^
2、value设计
0 y! h$ P, h4 v. \& @' [5 C( G1 R
* D! G) l4 g% c0 |1 h6 U拒绝bigkey1 K2 u6 V& B% C, w
; H/ K8 Y7 ~5 b6 T( A0 S+ w防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
. \9 p6 W5 K. t2 j5 [! |8 J反例:一个包含200万个元素的list。
( V% u: J1 S7 K- f非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法9 Z' }" D! t+ Q3 Z6 T! ~
选择适合的数据类型3 n# K8 A* k$ Q2 m" k
1 l* m0 c8 ~; L
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?% n$ i% X7 x5 p
反例: \$ L; Z8 y- o9 u# J: R
2 `* p5 ^+ m- T( P
- set user:1:name tom
+ U" k% Z3 a6 Z5 B" z# T - set user:1:age 19; d2 X$ d; F( H
- set user:1:favor football, L& i- U0 L4 s. S+ V' V
正例:
! E+ Y \+ _1 S0 i: {0 I! B) j- U
5 R. Z, ]" [' B# z- hmset user:1 name tom age 19 favor football
9 k" o! E% W* R4 B 控制key的生命周期
4 x0 [# g# Y# J9 z( e F7 a6 q- j, C" T3 G. |9 _7 B3 T
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。 x5 R" ~. v1 w, {+ Q
二、命令使用* O! y2 ]8 z; v6 ^, `; @8 f
' ^4 S/ t% {& W" A# |
1、O(N)命令关注N的数量
, l6 r8 z+ K9 B$ A* L# l
& c$ I4 ^( J9 T4 V0 o例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
; B' `# U* [# }. @2、禁用命令+ H9 K1 S( n# v' j2 E1 d
( n/ X' f3 k2 G$ Y
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。* f+ y# e" ?* `
3、合理使用select4 g0 y! c; ~; L9 F c
8 i0 J! P4 T8 v7 I* E8 _2 D
: C3 }* K3 @8 d6 z% x1 M. n
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
! B5 V! ~: }% Z* }2 Q4 q) ~4、使用批量操作提高效率# y& c3 _. c; B7 M) r* U! t4 ?4 h
, [, b' G5 G, a4 c, q/ {+ V
9 O5 I. Q2 W1 M; R- 原生命令:例如mget、mset。5 @" N& I3 X$ S
- 非原生命令:可以使用pipeline提高效率。/ g4 c$ W$ p. A- A. G
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
) \$ g! U/ a/ V" ^$ G! P: f注意两者不同:$ K O1 G0 n$ M6 A; G/ y
8 }, L6 u# d3 u. s
- 原生是原子操作,pipeline是非原子操作。
* E8 t4 e2 {9 F0 t - pipeline可以打包不同的命令,原生做不到
" w3 I9 c: ]$ \. o# Y4 s: V! M$ T - pipeline需要客户端和服务端同时支持。
" w# a5 f' {" m' i+ R4 s4 o9 }0 p 5、不建议过多使用Redis事务功能
/ w# n/ h+ M) F% j1 f1 ?4 |6 K( F W3 ]: h
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!: H& E) W7 e7 L
6、Redis集群版本在使用Lua上有特殊要求# d* c' j8 p+ R
a- x3 c2 U1 w7 d: [" l/ B2 a1、所有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 z8 c& z3 j% I3 s, b! [, [7、monitor命令
$ C8 a2 O/ g% e. z3 u: x5 j, I' N; t1 o
必要情况下使用monitor命令时,要注意不要长时间使用。
9 h' n+ \" j" E6 T' a* b( v3 u三、客户端使用9 R$ K' ?# @1 [9 G6 ~4 _
4 z* y( s$ L( ]* Q1、避免多个应用使用一个Redis实例+ D# K. n, S m, k
$ z) G, e! s: O
不相干的业务拆分,公共数据做服务化。5 w, Z2 i2 D. ]9 V
2、使用连接池) C! M; X) N1 H9 f0 l) H/ ]
: ]6 K# J) D; ]9 k可以有效控制连接,同时提高效率,标准使用方式:$ [6 [& E, U; k( Q
Jedis jedis = null;# U& h0 \) O3 ?* H
try {
" Y. P! k- z1 e) f jedis = jedisPool.getResource;* I9 F. B9 t2 U" i
//具体的命令6 d- v: }- c2 P+ A
jedis.executeCommand
. t5 |" Z; o' ~/ I% @} catch (Exception e) {9 Q" l7 l k2 o6 ^- u5 Y* @
logger.error("op key {} error: " + e.getMessage, key, e);& k6 z$ i2 F; p2 w6 V( G2 t
} finally {) A: m4 K. H7 E1 U7 ?
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
7 h2 [' y' r- G) q if (jedis != null)
" O$ @/ P; H5 Z jedis.close; M- Q# `; k% B$ W2 K# N: ^; i: V0 }
}
3 P; P: v" A/ m8 [1 H2 O- V3、熔断功能& h1 K0 N0 M: c+ r1 l* p
! s, `- l+ ~9 w2 g l. ~! `( T4 X
高并发下建议客户端添加熔断功能(例如netflix hystrix)
' N1 _1 k! [# r2 E0 O8 `5 c4、合理的加密
. y/ H! k* W. m$ X* P! d( g& j; ~ J: O9 ^3 W
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
1 e- J2 Y3 [$ c0 e0 [5、淘汰策略! X! m. [/ I1 J
) L. z b4 T& X, @2 Z0 x
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
/ n& k D4 M" v; `, W- I+ l# d默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
3 u( f" d3 J1 ]: Z5 A8 g0 @: h其他策略如下:" N8 u) I' J. }$ ]# z2 b# k
( K# \& K6 R7 M" b; L, `* z- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。: n; \9 M7 {3 Q1 o- C" @
- allkeys-random:随机删除所有键,直到腾出足够空间为止。) U4 Q( X) v' S! C2 G ~. d
- volatile-random:随机删除过期键,直到腾出足够空间为止。% f$ F7 e! V8 S' |. c
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。1 }+ ~! G5 A A1 F2 a, n% K
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。5 W( }" B* p1 y9 W: x I
四、相关工具* B, x" s+ O. p7 ]& K! r
" [) |" ~. c5 A' H; l7 l( \) g; p7 w1、数据同步
, c+ U" q, s3 r4 C; u" \! V
& H# E ?5 s8 N: U* T' Fredis间数据同步可以使用:redis-port
- ^: U7 G3 y9 D8 Q5 e! H. G2、big key搜索
* M [) P+ z; X8 v: I! {9 I! H: e& d
. [8 Q, D. K9 Y( U& ^ d. @# V3 l( \redis大key搜索工具
# d. R0 Z7 i/ @- M3、热点key寻找
8 D2 l- u: n/ {$ d* T% i5 d% ]9 w4 k! w( H; W
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题4 [ Y( n) t9 u% w
五、删除bigkey/ m u- S7 N; h- E
5 h7 B8 H1 M* Z1 k) P5 w/ n
]1 }2 D8 M0 [9 {0 a- 下面操作可以使用pipeline加速。( b" h p5 q6 i$ R4 y7 d5 K! N
- redis 4.0已经支持key的异步删除,欢迎使用。, ~- y3 N& r8 ~* p
1、Hash删除: hscan + hdel, z" c4 i/ ~/ K8 S6 A3 v
0 a) i$ q2 H3 u: `" p4 K* A
public void delBigHash(String host, int port, String password, String bigHashKey) {; I/ Y# [6 e: b. i5 u# O
Jedis jedis = new Jedis(host, port);9 C% s# u% S# c: @; {# v
if (password != null && !"".equals(password)) {" o m$ q/ R' M) n2 \ A5 S
jedis.auth(password);
- Q+ h1 ?3 j! x4 Q; Y0 E }' ~) s9 j& W$ B- a0 x; D# J
ScanParams scanParams = new ScanParams.count(100);
$ `$ h+ G2 @ g$ H j4 q9 a) D String cursor = "0";+ }2 R: H$ R8 E8 Q
do {
$ V, S/ z2 d$ p5 ]/ p/ m' x ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
9 }' B& Q$ C) O4 V List entryList = scanResult.getResult;
6 k \% m% Z% S. _+ p; y if (entryList != null && !entryList.isEmpty) {
8 I5 M: y+ J9 B. E, p for (Entry entry : entryList) {
6 b& R- [) T' f2 P- }7 z jedis.hdel(bigHashKey, entry.getKey);6 q* L' v$ G1 k( B
}
0 V) p$ d1 w# {, ^) S }" w1 ]" z% A/ r# e9 T/ p% V
cursor = scanResult.getStringCursor;
* D' T; ~. Q& t0 r } while (!"0".equals(cursor));# s( W8 n1 l) a) Z
7 |! J0 f! b+ `0 h+ p, N& p4 x
//删除bigkey% {4 Y7 N6 s1 v Z7 N8 `
jedis.del(bigHashKey);( d) Z: v$ c' ?/ V/ v
}
) {) z6 ]& v/ \" f( N% g2、List删除: ltrim
* P7 C( {1 O8 \; L6 A a+ X& l' f9 U& q" q
public void delBigList(String host, int port, String password, String bigListKey) {5 D2 J- K. U1 S: o, u
Jedis jedis = new Jedis(host, port);
8 ?8 m& i% \9 h, I- ` D# p o if (password != null && !"".equals(password)) {
' ^; E0 S6 W) X. { jedis.auth(password);$ i! B5 o: y- s& m" h/ p
}
0 |. q$ S$ C1 { long llen = jedis.llen(bigListKey);$ T7 _; i, q+ W4 B1 Z0 H9 s6 o) T
int counter = 0;- q6 P) m9 Q0 s, I. u
int left = 100;% F0 ]; ?7 y( d- i9 R% c$ W3 z9 `1 L
while (counter < llen) { P8 J' K, q, S1 c, c# M
//每次从左侧截掉100个
7 _. w F7 @% P$ a3 ^ jedis.ltrim(bigListKey, left, llen); k) ?+ F% e0 T0 ]# d* t: B7 o2 J
counter += left;3 q- d; P2 W; u/ y, q4 s
}
1 j- N6 l8 n$ o5 S/ N //最终删除key6 l( s, |5 W* Y
jedis.del(bigListKey);
3 ?0 ?6 [7 _# Z7 `7 I}3、Set删除: sscan + srem
1 j, E+ }4 Q3 X6 [* T7 _( \% V; N6 y' l) [
public void delBigSet(String host, int port, String password, String bigSetKey) {& v, r" X# {3 F% t5 Y' p6 H9 @! @# X
Jedis jedis = new Jedis(host, port);0 s( I8 |0 V! {* ?. c" y# E5 `, l: O: d
if (password != null && !"".equals(password)) {
4 h& Q8 Z7 }$ m8 m7 \( d jedis.auth(password);
5 ]4 D' G+ g$ n4 j: Q4 \ }
2 g1 s, F9 V* l ScanParams scanParams = new ScanParams.count(100);
! V% ?* V1 m& n) N( C/ T String cursor = "0";) k* Q8 x* e- {1 s0 P/ @
do {0 Q9 g" Z( `) i$ P2 [/ e- h( p
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams); ~% y" [. X2 @ A- ]# s
List memberList = scanResult.getResult;9 J/ Y5 U/ q1 S; l0 A
if (memberList != null && !memberList.isEmpty) {
* u5 S& P& G- ]% F7 S. ] for (String member : memberList) {0 K0 M) I( ^7 ^& o* ?) s1 k
jedis.srem(bigSetKey, member);
1 Q8 X. ~$ d* J }' k$ E( z$ H+ S$ z4 G D
}
7 `" Y- Z* J: T- w6 h: ?5 e* Z8 k0 I cursor = scanResult.getStringCursor;
. B8 n* R: }# X& V9 K; h } while (!"0".equals(cursor));
5 F8 X/ |( F' I5 g' Z# u* A( S7 q4 w: F2 X
//删除bigkey$ P+ Z& V1 f* a2 u$ ^
jedis.del(bigSetKey);. F' _( i/ K+ `5 Y4 G
}
# R/ X& j, w2 _7 N4、SortedSet删除: zscan + zrem X8 g: s0 [/ K6 r8 C, G o
I2 k r7 E! Z$ B/ I: wpublic void delBigZset(String host, int port, String password, String bigZsetKey) { . F6 Z0 w- c( b- f3 i) L
Jedis jedis = new Jedis(host, port); ! A% R1 n- \3 G& Y
if (password != null && !"".equals(password)) {
& T- g+ ~5 e" h1 R jedis.auth(password);
: U% c! ]1 @/ \ x. Y }
& P# i- y# i( @8 X! @3 o ScanParams scanParams = new ScanParams.count(100); - `- ]3 q8 }* p- V& {2 l
String cursor = "0"; ! \# `7 T3 b. y" x- Q
do {
5 ]) u# O+ c4 p" _ ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
. C( S+ w+ w+ q0 m6 | ListtupleList = scanResult.getResult; 0 C; _& P. s( P* q6 t
if (tupleList != null && !tupleList.isEmpty) {
9 q4 ]0 e6 w# G S for (Tuple tuple : tupleList) { ) k( Z* r- K$ d$ L( O
jedis.zrem(bigZsetKey, tuple.getElement);
9 }, Z# V- U5 i9 Z2 X" Y3 { } + Z! T0 v! g! u* M% g) |/ X; f6 T
}
, m4 W. u/ Z! D cursor = scanResult.getStringCursor; 3 J6 c0 n0 A8 }) g* P3 q
} while (!"0".equals(cursor));
* c- Z: n- j8 ~! T; [/ R2 b
, ^" z7 W ]$ e& d9 p7 Q+ d//删除bigkey 3 ~) O$ e5 p5 v5 E0 q; e
jedis.del(bigZsetKey); . x5 @0 T( g2 G; w: L
} 公众号内回复“1”带你进粉丝群
7 r& e/ m; M* d来源:http://www.yidianzixun.com/article/0LevQm7t
6 L* ~+ |3 G( {# `! d! s7 K免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|