|
|

0 D7 z3 V. p! n; z5 N8 e4 A* ]本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。9 F% z+ S5 U7 z3 s: `/ }: `% G1 I
4 g3 R5 U, |; \( o& h
- 键值设计, g4 f$ d* t$ V/ Z( z( Q+ G
- 命令使用; e) R4 `" Z2 ^ d9 M
- 客户端使用! _) }6 d2 X5 d2 R3 [
- 相关工具
5 P$ T/ j+ t. H% i/ o$ M 通过本文的介绍可以减少使用Redis过程带来的问题。: q( v! r1 ]& g( Z8 G
一、键值设计. @' q$ Z: g- V P
7 {3 h! R/ l7 T3 W( n
1、key名设计
3 w" G4 D. Z8 J" c2 I
& d& g# u; T! j可读性和可管理性& X, S9 N8 }7 ?$ x: {* p6 [+ _& ~
5 q9 W3 A9 e5 q$ y9 w0 @# K# B
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id0 S$ |% J/ \. f
9 f. k& V3 ^+ T$ o
- ugc:video:1
; k& A" Z$ _7 ] N# ? 简洁性
0 n0 p+ T5 j2 |$ H6 Z1 F8 |8 J$ c! j/ o# p' v3 z% Y8 R7 }
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:$ @& s- k7 N& ^) u7 Z) @
9 A: C! \4 A9 I; g: a0 w
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
( @) J% J- D J; {$ j. V 不要包含特殊字符
% a' d# M# h% C) G$ F% X8 I+ R, _
反例:包含空格、换行、单双引号以及其他转义字符
( z) {* Y3 i( }& K L% Q' F2、value设计2 U1 B4 J& l; n, k( k4 {
! R; i7 ]- G. ^8 }; ^* U8 v
拒绝bigkey
: N4 S" t7 I! T9 o4 L" E
9 u* X. z8 m! f0 O& \: c防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。, N5 ?1 f. N0 a# v- M! U+ U
反例:一个包含200万个元素的list。: q4 c1 J! x4 g% p/ j
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
; c* Q9 W0 n" _4 c% |选择适合的数据类型 {- l. E+ f! J8 B3 T1 t
9 q, A9 o* |" W
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
+ z4 R1 a/ n; j( D! Z1 F2 i反例:4 i3 J. S: p6 p* l( Y( p* J' y
' A# w$ h/ V4 T" b. `* D; D- set user:1:name tom
: b T9 _9 G3 h) P+ r# W - set user:1:age 192 q4 P+ r* i/ J' }5 p7 j, m" Y7 N
- set user:1:favor football
9 F# L% r2 ~7 u 正例:* @+ O& Z$ }' n, _" R
, _9 C1 |( V+ t/ R+ f- hmset user:1 name tom age 19 favor football
3 d g' z3 q e6 k 控制key的生命周期
$ W+ \( g) c0 n; p. W3 F( h+ d% d: G
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。& G! O- q8 f6 N. d- A5 Z( m! B1 s
二、命令使用
. [$ Y% ^" l) b) @. H- O/ V: G2 m
1、O(N)命令关注N的数量: \( C1 M$ J: |* r: H
- }7 G# t% ]5 a/ B) g
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
/ d: ?1 V+ z. l2、禁用命令 a+ _ q1 u' O: s
* V3 N" d+ b* \8 i) ~0 W7 j7 R禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
k: t( ]6 A7 H J3、合理使用select$ c% U/ I2 w9 G S7 y0 }
% I" V( J6 z) P9 J2 s5 X \' a9 S& i
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。6 u5 k1 c, ?) p7 p
4、使用批量操作提高效率7 Y! G! z) X) q4 L0 I9 n
; R& S( F' m3 o! t" `: E
; M% ^; B2 E |
- 原生命令:例如mget、mset。; Z) A! h( b. R/ ^8 r" _
- 非原生命令:可以使用pipeline提高效率。: H. |& `/ x5 B) U
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。( D K4 E9 \1 y R6 z o# O
注意两者不同:. w% e# l2 l6 _8 i% K/ w
6 J$ e) m# b# Z5 t' p& ^& U
- 原生是原子操作,pipeline是非原子操作。
% F$ |- c" {0 b. S- m - pipeline可以打包不同的命令,原生做不到
9 k* H8 c1 h' K& ~; J5 g; J - pipeline需要客户端和服务端同时支持。# V5 s; B% J5 _
5、不建议过多使用Redis事务功能: o9 Z3 M2 b4 U% d5 A$ v! r9 N
- h" w4 j4 S- L* Y7 r: v
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
: A; g; g* e6 {& ]% x8 h6、Redis集群版本在使用Lua上有特殊要求
' Q" n, v" e! c% N. _( E2 x; p4 b1 @4 u
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"7 ~0 S% @6 n8 q' M( m, K. h
7、monitor命令
+ b. @+ d: w. i5 j$ s
' K) \- ^4 d& ]4 u) j必要情况下使用monitor命令时,要注意不要长时间使用。
* l1 K8 B8 B5 D: U2 [( o三、客户端使用
, D1 c% h% L2 S7 ~) A: H1 R9 H& @6 l$ j% u+ |7 Y
1、避免多个应用使用一个Redis实例9 b5 h; L0 q9 |
$ t- z3 S0 J8 n# n, p不相干的业务拆分,公共数据做服务化。 ? O1 X3 ]7 a9 ]: w0 u3 T
2、使用连接池
* T- v$ `4 G. L& n! p8 B4 P6 |
6 a) G: ~& _1 H9 W可以有效控制连接,同时提高效率,标准使用方式:
4 c6 g2 N! y- N& DJedis jedis = null;
9 N" `6 u, d! y t: s) C: @# i1 x2 Itry {
2 \. o1 G7 c* \- L" a2 d jedis = jedisPool.getResource;
# e# K/ J( @5 g7 z! X2 x F3 @: G //具体的命令% x' B% L6 h: \: w# O
jedis.executeCommand2 Y0 ^. A; r4 M4 p% l
} catch (Exception e) {8 _4 B4 j o: {9 g3 f [& r/ e
logger.error("op key {} error: " + e.getMessage, key, e);) ^! G% J9 Q1 _* ?& ~
} finally {
! K. N3 M0 H/ u$ ~7 R: c' a1 Y0 K; u //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。. H0 L3 ?: @/ K% I+ K/ Y
if (jedis != null) ( c) N* [6 X/ x4 z4 u: t: e
jedis.close;( j/ O+ W q1 p5 G8 V
}5 \" Y! m! z2 t
3、熔断功能
9 e2 x6 T9 Y1 I, O) N/ K
$ j2 O# P! @: F. F- p高并发下建议客户端添加熔断功能(例如netflix hystrix)4 L$ w+ t/ c+ Y; P0 m
4、合理的加密
8 p2 e- l8 V, ~# q! e
' S3 t9 t/ J# _' T设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
- ^ Q f% K* }4 u* K* }6 G5、淘汰策略
. A: X6 |1 [" A& u W: N6 i2 I7 M/ b0 u: K! b; @2 r
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
( Z- n' {$ x( H8 @' U默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。+ I! n6 R b8 p2 z
其他策略如下:( ?0 R# I" N5 k: c( `0 O; i: ^
* }9 G, Z$ k1 z
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。( }7 g* s. E) c# w# r7 @ G
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
) W* Y& v7 x& ?% u! W' r: Y& l' G - volatile-random:随机删除过期键,直到腾出足够空间为止。
% B3 ]' b# G) m+ u - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。4 N4 r! P/ W" n8 H7 g4 d g
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
+ f6 P/ N. x" J4 i& r7 J 四、相关工具, X/ G: z p: W1 B# D
; u! g9 J5 j8 u( i" |- @1、数据同步) ~ P. H8 u9 y, r5 v7 I
& B3 _2 v5 H. b( s" S( u$ Q2 Q
redis间数据同步可以使用:redis-port0 m, u0 x X7 u+ B3 |/ k
2、big key搜索) X9 U1 Q0 h9 Y1 Q6 _) r i
% s# ?. f; Q" a9 F; U
redis大key搜索工具
: p: j2 _$ n r* \- G6 Q3、热点key寻找& B3 w& t0 r& V
% n Q* J! G1 ^, M: g# J& G$ h内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题( O! V( Y5 I2 w9 o4 O
五、删除bigkey6 {0 F7 b/ A; _& x
3 D) g* K: F' f& f3 m
9 G3 j- d7 B; S7 T8 Q- 下面操作可以使用pipeline加速。% x5 o2 V" Q% a' c& V( y. s
- redis 4.0已经支持key的异步删除,欢迎使用。" i! y) P) d; f' E) d; Q3 C0 }
1、Hash删除: hscan + hdel
' `+ l/ u2 k. Q/ k5 ?( }
' @' M* T* t/ A# _. X( Lpublic void delBigHash(String host, int port, String password, String bigHashKey) {
- q* L! t: ] b% B8 u. K: W% o Jedis jedis = new Jedis(host, port);; o8 ]7 c/ y& T
if (password != null && !"".equals(password)) {5 R, a; C7 d O0 b- t- B
jedis.auth(password);; |: Q" v( K' e( l& S7 g. b
}0 w/ q' {- q, z9 e; _
ScanParams scanParams = new ScanParams.count(100);7 e! a1 C& S# _+ W8 r# x7 l' I
String cursor = "0";" ^) z8 Q# h2 O& h T
do {
7 G; e: r- c7 S$ X ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);2 P7 f2 t- Y; O
List entryList = scanResult.getResult;: I9 d2 } c4 ^' ]
if (entryList != null && !entryList.isEmpty) {9 J+ Q1 Z2 h" W
for (Entry entry : entryList) {
2 Z1 Q( H. o: o4 ~ P jedis.hdel(bigHashKey, entry.getKey);6 k* Y0 K1 W g. _$ ~# ^
}& C4 z" o& ]! ~: a. Y$ M, b# ?
}
8 }) P! Y1 Z0 i cursor = scanResult.getStringCursor;& q3 T! H0 ^7 g, n1 |
} while (!"0".equals(cursor));* z$ B/ {' F. d- B2 Z5 T" U
* B. J- P/ ]/ L+ @2 h//删除bigkey3 [+ F6 z& P+ h" [& X" l
jedis.del(bigHashKey);
; P& l! U$ o, g; U7 ]7 c}
% x* a! z* F6 s2、List删除: ltrim
, h- l+ F* x4 Y% j; h8 n! V' l1 L) N8 r* j2 ^ o1 ~
public void delBigList(String host, int port, String password, String bigListKey) {
6 B% D3 g: y7 N# @ Jedis jedis = new Jedis(host, port);3 [; k5 J; w7 X/ g0 P, c' g5 W
if (password != null && !"".equals(password)) {; |+ \- S1 I4 I) Z. e4 y/ o
jedis.auth(password);
5 G- V3 n2 k' q2 z }
" X- r# y( D. M$ I( O* g* O long llen = jedis.llen(bigListKey);/ z0 p( `4 V* z W- Y$ X
int counter = 0;
6 [6 B) m8 w5 I1 V; v7 X int left = 100;
' J! J5 R, l6 M* Q2 d while (counter < llen) {
7 T7 V" J N( r# W* | //每次从左侧截掉100个
" ]- z2 q2 t6 A/ _& s- [ jedis.ltrim(bigListKey, left, llen);
) i% W* ?# l( }5 }' {1 Z4 ^8 ^) } counter += left;
' [# q9 R% w5 I7 c( k }
1 o s9 O4 p! o5 Q //最终删除key8 H4 L C, }$ x% J, j+ j
jedis.del(bigListKey);
# o3 f4 z+ Z6 a4 R* M, A}3、Set删除: sscan + srem# q* x2 q0 J+ m( t
$ t8 O: E6 b5 c- E* D
public void delBigSet(String host, int port, String password, String bigSetKey) {) r3 r2 q; S& ]2 [. U/ \
Jedis jedis = new Jedis(host, port);; W1 ]7 [1 ]6 x3 i: f' }* D4 Q9 K# M
if (password != null && !"".equals(password)) {
& {8 m' r& _2 {6 f) m$ A* c jedis.auth(password);
6 a2 M) R. q- s, {. |8 {9 ? }
% h2 @1 q# r! S2 Z! x5 P; s1 Q ScanParams scanParams = new ScanParams.count(100);0 z B. \; `' O4 i
String cursor = "0";; V: @! {+ C, |1 n5 [9 W8 O) |* T9 \
do {
9 n6 O7 Y5 n$ `* v# [6 R; x) k" f ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
! o. `# m2 |5 f, P' M- c- L& z. h List memberList = scanResult.getResult;
4 w) R; t! T9 N6 Z if (memberList != null && !memberList.isEmpty) {% ?( q$ F ?# p( ]
for (String member : memberList) {, N& ~) ^, `, @ F) R, L# p
jedis.srem(bigSetKey, member);# I6 i0 H% s5 t9 i
}4 A0 L1 |( Q* D3 y
}# ?, P- w e1 C, L( R" q
cursor = scanResult.getStringCursor;& j* R- N' w6 Q
} while (!"0".equals(cursor));; c0 M* o7 h. e0 f8 L
4 t1 D# N+ B7 m6 P//删除bigkey
! G8 `& c' @# L+ s; g8 \ b7 L5 \ jedis.del(bigSetKey);
/ c6 f, {" R* v6 U# q}
0 o1 ]. k% P2 [) _/ c; c% x" Q4、SortedSet删除: zscan + zrem# a8 x+ f6 H! l( }/ V! J4 n
+ c+ ^% \# ^$ ^9 v* M7 c/ d1 Tpublic void delBigZset(String host, int port, String password, String bigZsetKey) {
% A+ S1 E" k; D' E# c& z Jedis jedis = new Jedis(host, port);
( Z& q M$ E- a$ I5 m( }5 E$ F7 R if (password != null && !"".equals(password)) {
# G' D6 ~* E1 o; a) |0 B jedis.auth(password); 2 C5 x/ i- ^0 w
}
4 }# i; b4 X$ Q: z ScanParams scanParams = new ScanParams.count(100); & `. d8 K; P3 B2 F: c. I( E
String cursor = "0";
9 ?. e# e' _ O% R) Y6 [8 \# D do {
5 V4 y$ @1 E1 B& j% Y ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
* \. a) y8 K$ S7 D ListtupleList = scanResult.getResult;
6 a) S: W, Z% [+ r( H if (tupleList != null && !tupleList.isEmpty) {
' }: J/ Z# A% L# h# \ for (Tuple tuple : tupleList) {
3 |9 V7 ~3 a, u- ^9 R# J jedis.zrem(bigZsetKey, tuple.getElement);
: G. c' t$ M2 f! ~* \/ k9 l } ) z" s4 C8 _7 B4 }- c& \
}
% T! I# r0 f/ v" n cursor = scanResult.getStringCursor; ' y8 _4 M+ G, t" p8 ? |3 h7 G
} while (!"0".equals(cursor));; j1 h& ^# {( {, I" L2 f3 e0 P
* b- M6 @2 h: D# R
//删除bigkey
' X3 f3 O& y& {; y# k7 o2 i* a jedis.del(bigZsetKey);
# E8 U/ k% Q/ G} 公众号内回复“1”带你进粉丝群# g2 f3 O# x% N! G; X
来源:http://www.yidianzixun.com/article/0LevQm7t
) `8 q9 t* C# V免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|