|
|
0 l# T& O% G) R' f2 \3 f( e/ [
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。7 d4 p) d' U7 Z4 ]; `
+ Z/ m4 D; T8 i& o- 键值设计
7 L0 D8 K1 ?1 b# ^7 ?4 { - 命令使用
8 Q9 T$ R' R* @4 ?! \) X( ], n - 客户端使用. S6 C6 `8 c# h
- 相关工具# i: U/ w* p3 D) N9 u
通过本文的介绍可以减少使用Redis过程带来的问题。
y/ f8 z2 `* Q+ o4 o' Q8 _一、键值设计
+ D5 P* \$ r" C/ P: O* O7 W2 Y6 Z1 C; Q% p
1、key名设计
: }! A! H/ [! I6 t. C" p. ?, _: z' ~
- F( m n: |5 Z) W- ^, L& W可读性和可管理性
+ z3 D( m' ?3 z- {! k. Z
5 I6 i7 O" ~5 b& v. }6 e以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
. q7 l' D0 `) m& E
. x: O& a$ ~0 q/ X- ugc:video:18 B+ T* g. q0 I1 t) `: X/ r7 [! ~3 W
简洁性
0 c( U3 |# y' s5 ^; Q4 Q
+ C/ M5 ]& I9 v( [; `8 x1 F3 D' p保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:/ w( _8 \* M2 ?/ q8 s3 H, M; U
% T& I r% E, D8 \
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
8 ]# V* I, O( r8 Y+ U: S% f5 y 不要包含特殊字符; o ~6 T/ s1 }# z4 \
+ j8 c( d. ^" n; g& A8 H
反例:包含空格、换行、单双引号以及其他转义字符6 K# }, X. M2 N. C, e0 b
2、value设计- G7 w) ]5 x: I X$ l5 {; A; T9 p
! K0 b' Y& F& p9 `6 I+ l) ]拒绝bigkey
1 A8 }- H7 U4 p
* |1 [4 R e+ _; b! u3 }, N% P防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
+ x5 k3 l( j/ A8 k% \反例:一个包含200万个元素的list。
0 h; E$ T/ J- I8 Y# j: ^( |非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
9 F3 L8 G9 ?* z选择适合的数据类型- s4 f3 x/ u# ?: A. ^5 O# Y4 F
- i) \9 Z! u3 y+ P- V
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
$ r; a/ c6 |6 U; h$ J" i/ b j反例:$ X6 c( M5 [* A( R' i' k9 E5 a
, d' I% K+ E" j; l* _' ]
- set user:1:name tom2 s& ?. U& ] ~
- set user:1:age 19
: v3 |! W. d P7 v- ^ - set user:1:favor football4 Y+ V) ~$ R; \9 G5 w; i1 |
正例:8 j1 u% E/ G# u- y" N! B
; G/ e' u7 D" V1 j! e- hmset user:1 name tom age 19 favor football
" o) }& g! N( D6 p5 ]" K$ U- Z 控制key的生命周期: R& V4 G, C" o
( i) f: c- S& J; k* p% E/ l$ k
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。8 w* k" R C7 }5 N$ u4 p, @
二、命令使用
' p0 t5 O" n0 B4 ~: R9 _- B: r( H
/ G! @$ [! \+ ?, s# X2 S3 K1、O(N)命令关注N的数量
+ F8 T4 @% e, o2 J. W
5 M C5 w" j7 a K/ ]例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
~5 _7 G0 G! A2、禁用命令& I' r/ x# P9 k. Y" G, w; U2 T2 b% Q
V% }& e9 R; l {. F* c
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
- W c: z- S4 T; p3、合理使用select
& W) F3 f/ G6 m1 u# f0 k$ y/ E5 O9 m1 C1 Z
( z) a$ c. o* ~redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
2 r2 Q9 n) Q' X8 [$ \1 l! v, b4、使用批量操作提高效率
' G% w2 g+ f9 I
) ]8 j0 s; J0 O% ]4 h9 H
, F: g/ A6 L) d5 _7 ]- 原生命令:例如mget、mset。! I, b% [. C8 q1 K3 X
- 非原生命令:可以使用pipeline提高效率。4 Z) ^- T" x" `0 j! \4 U
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
/ ?) m; B: N" D' N w. S, X注意两者不同:
! B0 R4 }+ M8 G5 ?* q) W% s1 C% f$ n
- 原生是原子操作,pipeline是非原子操作。
. A/ Y" o' K# y" ]; n1 s - pipeline可以打包不同的命令,原生做不到
1 V1 p+ l5 o3 o2 o - pipeline需要客户端和服务端同时支持。
( G- p9 Z/ r0 m8 R2 _: p+ z0 } 5、不建议过多使用Redis事务功能9 [9 Z+ G% \' A* E* n
; Y' L' Y. L4 ?/ m4 ?, `Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!) z5 |0 r& D" U% q* Q$ G9 X
6、Redis集群版本在使用Lua上有特殊要求6 z4 h; J3 [& |5 B
' C6 U+ I% p6 _* u4 \6 T
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"
6 A+ l' V5 X! e* ~" q1 x% ^7、monitor命令
' B: W0 a( x' W7 s) J9 h
2 [4 U- N- x& d9 }, N' @必要情况下使用monitor命令时,要注意不要长时间使用。/ |, N# z5 @# K" T* Q% Z) S; Q9 v
三、客户端使用
" n5 }% _: i4 l7 h Y5 T# T s$ G9 H/ |! B) `
1、避免多个应用使用一个Redis实例
4 d' m, b6 d! C1 F7 t* A, u% `/ I) m! H: |4 c' Y3 ^7 J
不相干的业务拆分,公共数据做服务化。
/ d1 n0 U1 o' [( C6 n" v2、使用连接池5 T1 K9 i1 R$ M
% W8 _- u$ I' j7 g7 Q' |1 o' A+ O
可以有效控制连接,同时提高效率,标准使用方式:
* s5 j" F, D0 O; i7 X5 N5 l; |' c JJedis jedis = null;
* U4 K3 ^ ?: d# U6 xtry {5 x$ l7 r6 W* u2 h) l
jedis = jedisPool.getResource;
. T4 K w8 Q7 C //具体的命令6 d. B( z2 z- \7 a2 I' z
jedis.executeCommand& D+ V0 C% @/ I+ J+ C" {1 t
} catch (Exception e) {% {: d( m* F K7 M
logger.error("op key {} error: " + e.getMessage, key, e);
9 x8 ]" }2 m! Z/ N/ D} finally {2 U1 U. ^5 c" W; Q6 `3 t5 H
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
( U3 G+ i" @; ^; X- I* z6 h if (jedis != null) ; a: w; W2 L6 v$ F
jedis.close;' O+ {* c- [+ w, Z9 D
}' d; F. a& W$ T) k! }
3、熔断功能1 P' ]2 z3 i. e" J g p0 X$ N
d& x) `( z$ a/ O3 u高并发下建议客户端添加熔断功能(例如netflix hystrix)+ E' o1 |6 O5 t% W$ a
4、合理的加密
% r' y( q" k p! x4 W% Y
, \2 [6 c$ [8 B ?9 I* U1 q& k- Y' Y设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持) f& f4 M2 q7 l
5、淘汰策略* l4 C/ M) d7 g3 {- e
- h% x$ G0 ]6 o" X5 A( T. U* M根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
% G( b0 p4 p4 X# {% C4 g默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。2 g5 G* z7 R7 w/ F
其他策略如下:/ v8 B# d& M7 S. }8 u
, q) w' q5 h- B y" ]- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
) \, K0 y, O3 `; t C - allkeys-random:随机删除所有键,直到腾出足够空间为止。
7 p) f( z# S) D6 _3 r, n0 ^ - volatile-random:随机删除过期键,直到腾出足够空间为止。
. J, c1 B: G3 S8 f4 @ - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。. o I% P: e% N+ n1 P! ^! U
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。9 D# l7 Z- T. b8 u+ e. o) c4 R" P
四、相关工具
4 [* m @1 ?* Y# B$ Y8 O: p) q5 L c3 Q K# U( l* t
1、数据同步* K' V& D0 D3 w( G
) P/ c# D1 k: w0 n! }
redis间数据同步可以使用:redis-port
- f0 T0 D4 d n$ ^3 o) |2、big key搜索
( c3 y0 E# h- g( o/ n- e3 ]) D
* p' \7 |& G$ z: M0 Lredis大key搜索工具
( m6 O7 C, b* z9 N6 x3、热点key寻找
/ g! N+ \3 i/ B) E0 \! _3 K# a/ U% L; `. A
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
) ~' I G8 M5 n' q五、删除bigkey- T( R Z! g& \" a+ t
: G, s, ^3 w1 @# ?8 J2 r& Z
8 ]! `* N3 [( M) ^! c- 下面操作可以使用pipeline加速。6 A# [6 w- g; x& q, s
- redis 4.0已经支持key的异步删除,欢迎使用。
; G# a4 o% I" v+ @+ e p 1、Hash删除: hscan + hdel
6 a6 ?7 M9 l6 b- N0 D9 ?
2 m; k7 d/ {3 U4 r% i! r, tpublic void delBigHash(String host, int port, String password, String bigHashKey) {
, Y+ W1 J3 {/ j( b Jedis jedis = new Jedis(host, port);
/ q# C. N2 T2 I9 k% U if (password != null && !"".equals(password)) {. F; {, N- h& ~! C4 P
jedis.auth(password);
N: `2 t: q7 L: e3 A7 P }2 w, V& [" p2 M$ e5 s" Y' E! D
ScanParams scanParams = new ScanParams.count(100);* v7 J9 y/ K; J' X
String cursor = "0";
7 v7 T1 T: _4 s7 y5 o do {$ W5 K, v$ p8 M
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
|: H; q( k! d# y: S List entryList = scanResult.getResult;& f: y( [1 u+ z2 j; z) s
if (entryList != null && !entryList.isEmpty) {# c6 [- Q) y" f5 A
for (Entry entry : entryList) {
# D4 ^5 {7 ]+ T7 i. z5 G& p* L9 T# O jedis.hdel(bigHashKey, entry.getKey);3 h$ B9 N5 J- |) ]8 g
}$ j+ Y; }1 X( b5 w1 c; ~
}% p# O8 G5 K6 f( s, h
cursor = scanResult.getStringCursor;: Z2 v: F+ Z- w1 s9 x. d
} while (!"0".equals(cursor));2 t* N% D/ I( [
/ u. _! ~1 D* q1 V& ~
//删除bigkey
7 V" T$ _% s+ v# A! ] jedis.del(bigHashKey);+ z2 \1 c( W/ b3 _
}
" v0 C1 s9 r% B5 g8 L7 T2、List删除: ltrim7 _, \. l8 o6 c1 R+ M
. Y$ N- M+ k) u& V7 n- Y
public void delBigList(String host, int port, String password, String bigListKey) {$ k# l+ R( [# y7 j) X
Jedis jedis = new Jedis(host, port);) @- Z( h+ c* U0 \7 Z, i6 v1 ~+ B
if (password != null && !"".equals(password)) {9 D& g }3 d* j
jedis.auth(password);2 ^' n: l+ f& b% {* ~+ L
}
: R# N: x7 y# \5 h- ?( Y7 f* Q long llen = jedis.llen(bigListKey);
( g& f' S. ?; m int counter = 0;, b' D6 Q v. T# x
int left = 100;: I3 c+ q& j0 `1 {2 W |! X$ Q
while (counter < llen) {3 ^9 R: t3 v& d: R& ~) R2 ?1 C
//每次从左侧截掉100个
. } M6 ^7 m8 X; R7 J4 I jedis.ltrim(bigListKey, left, llen);
+ f- P& N4 ^. B& e1 F/ u* m3 X7 b counter += left;
9 b, s; K5 t0 W+ @* E# i }2 [$ f( o7 \6 W) X$ A" K; Q
//最终删除key
) J9 \7 B: H, z* D% i jedis.del(bigListKey);
; l7 x: Q9 |0 X3 D}3、Set删除: sscan + srem- h8 p( J* p! i' V |
! P3 i: u# N/ {* \6 U
public void delBigSet(String host, int port, String password, String bigSetKey) {( e4 y7 a, f0 d$ n" y
Jedis jedis = new Jedis(host, port);
: L. @! e; |7 D' D3 @/ V if (password != null && !"".equals(password)) {
2 W, l- C) P6 h jedis.auth(password);
: Z% G# @1 u1 Q' ^ }
; \1 x, _$ H2 {. Z5 Y ScanParams scanParams = new ScanParams.count(100);
* }4 f/ K+ P% `' w5 x$ ]8 Z String cursor = "0";3 W- f. p" U$ k3 D* D1 \
do {
; {9 }9 L# n+ H0 L: B& T" a ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
* N9 \2 W) l% A7 s& M* C! d2 S List memberList = scanResult.getResult;6 z3 n; Q& d7 h; a
if (memberList != null && !memberList.isEmpty) {
6 _7 M) U! Q+ P0 w" t for (String member : memberList) {) L+ m6 Z# f1 |5 z$ `
jedis.srem(bigSetKey, member);
4 x9 T$ C- }" F9 ^% U% V& R }
4 i3 \) G" }3 o6 N }
5 W; I f* z9 G7 V. G4 j cursor = scanResult.getStringCursor;
5 ~' o* v" }. N3 _ i4 N } while (!"0".equals(cursor));
$ Z; Q# J8 O7 m# j0 X0 [2 x) ~9 f9 i- {
//删除bigkey
' R. n4 L% |" M7 Y! r jedis.del(bigSetKey);7 a6 K" ?, w9 y/ U4 K. b
}
! f0 ~7 V [) p% }. ^" V4、SortedSet删除: zscan + zrem: M& \1 U; S. R( p+ Y/ g: g3 f4 ]
$ q1 B+ n, F. t3 c- g* T$ C5 ~public void delBigZset(String host, int port, String password, String bigZsetKey) { / v. v B+ _+ C# B0 Q6 H2 X
Jedis jedis = new Jedis(host, port);
( J& ~" g5 F1 S" H if (password != null && !"".equals(password)) {
`- A! n3 ]& F" y jedis.auth(password); . o0 e# T/ H' {+ N" C% E
}
$ o( M2 `. F- i, j5 x ScanParams scanParams = new ScanParams.count(100);
" n9 U8 ?0 R: L: a* N0 k( A String cursor = "0"; & a, Q6 g8 c3 U6 f# o
do { 7 X) C& K' y$ r, r+ N% A8 ?, A% [
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
6 l ]$ R7 [' h- o ListtupleList = scanResult.getResult;
4 L6 ]# A3 ^- m' T' A if (tupleList != null && !tupleList.isEmpty) { 9 r, @% x% w3 }6 V5 r
for (Tuple tuple : tupleList) { 9 _6 _8 ?" ]1 y4 D% W9 w6 t: S
jedis.zrem(bigZsetKey, tuple.getElement);
7 q. u, i+ @# Z" u) A- c: j }
9 ?8 l$ {2 \4 \ a } " P) X q, R2 A: J. r
cursor = scanResult.getStringCursor;
$ Y3 \$ Y. A4 m' c } while (!"0".equals(cursor));& _- Y; a+ m7 i
: N# w$ j5 ^7 P: j3 N( X0 I
//删除bigkey
' j6 @* H& }8 ~8 k0 a5 V! K jedis.del(bigZsetKey);
# F3 P, }- W, e: f8 v} 公众号内回复“1”带你进粉丝群
$ O m6 p% [2 q来源:http://www.yidianzixun.com/article/0LevQm7t. b: L0 u, I& Q5 x6 ]& J' o
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|