|
|

' A9 j4 M6 q; u1 G本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
; A' V z. ?5 s! u$ c& L, N1 w1 X7 `- @+ a9 A, R! W9 {
- 键值设计/ ?& h i- I% O4 N
- 命令使用
( X! Y' C* O* Z: R, V+ n' F% x - 客户端使用7 U, y/ r, i0 m3 a
- 相关工具
3 q L0 v9 ~, O7 F" l: [ 通过本文的介绍可以减少使用Redis过程带来的问题。
0 f! V0 L1 [7 G% k. e7 H0 \1 M一、键值设计
7 a6 M- A5 ^6 v6 j5 \
7 t; l; T/ @8 o1、key名设计1 b# v1 T) ~; m& ^/ f* M
3 r4 u( e$ [6 T可读性和可管理性
0 _9 T. k' `( e4 l2 C$ j7 M" I0 @( x
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id) o4 U: W0 S" i! Z' t/ D
/ w; k5 k, n- C* I6 U- ugc:video:1
% W: l2 b m2 p; g 简洁性
4 E8 J+ f, ^3 u, l, W# b2 V) Z, _1 |3 H* U. o
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
" z# z! F) U: B& T
; h$ r I; l( E# A3 y- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。+ A# K( n: V, l! H" `% o. k
不要包含特殊字符
( {7 L8 [0 T* J8 Y6 T
4 y! E6 d* @: j" C- n0 J反例:包含空格、换行、单双引号以及其他转义字符
0 @# g- V; w6 p1 e; ?2、value设计1 J. k7 i5 t* p( Y$ F# F
& i; e: n% ^1 l4 s7 m
拒绝bigkey) l n0 X- e. ?# ~# }
2 d4 M+ C: c/ ? p
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
1 Y5 _0 T* z( M0 C/ h) x: i' J反例:一个包含200万个元素的list。* X. c" G: _: [3 { h
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
6 l1 B! G; l2 b# I& x- r选择适合的数据类型: X" M7 e3 Z. Z+ u- }' ^7 p
( |" b! [+ v* I# q* a; A, p
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?! X R& y+ i& R0 r6 j
反例:) I8 q) Z7 P. W; \3 B: c
) a% s. A7 z% A6 k1 w- E- set user:1:name tom( p, j$ \: s( o2 G
- set user:1:age 19
+ S, G" L) B# R" ] - set user:1:favor football/ m& d( ^7 y. }% n0 N5 J
正例:" z' K6 [1 s7 m$ \6 h+ h, A! \
) v1 T' K) U: E0 P7 x3 m
- hmset user:1 name tom age 19 favor football
3 g1 o- o) Z* B, r 控制key的生命周期- ?7 {) b0 Y- F5 A7 T# y
6 t2 z5 q5 V( _ A: @0 ~redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。/ ^ k: @ B8 [$ Z# M
二、命令使用+ I/ |' O0 T4 S" f3 f$ r, D
# e. U( b. C2 {9 H# |4 q [1、O(N)命令关注N的数量+ M4 B1 B; W. v4 ~, w2 n
; v A0 Z+ Q/ _1 s& s9 C# k/ H例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。5 y- O& d8 f2 n/ g7 w7 _: C
2、禁用命令
( a0 ?3 i7 V2 f
4 `1 O+ @, h& R( u* u9 W3 V6 q禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
1 G P' E& Y$ L6 d3、合理使用select
! T% n# A% ^; m* o" i+ q6 z+ U0 P3 X1 l7 C" l8 [
" M j) ]) {) yredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
; |! i; T: W8 Q! t4、使用批量操作提高效率
0 c! x# |" L- Q8 E% W
3 a- U' q1 W" M8 V' Q) U5 O9 Q3 B* \$ M
. t0 r5 D# _9 r$ t4 j& X0 ~- 原生命令:例如mget、mset。/ @+ R2 ?! V7 J7 g; ^5 O' {: w% z$ |
- 非原生命令:可以使用pipeline提高效率。+ u5 u; Q6 d" y5 C/ s
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
" F' s0 u$ P, D% b% n注意两者不同:: V3 e. O0 z, F
' w6 X+ N" R, A" H1 G/ g- 原生是原子操作,pipeline是非原子操作。 _: h3 s# ?! s
- pipeline可以打包不同的命令,原生做不到( m5 z, b+ J7 G% a1 f' E
- pipeline需要客户端和服务端同时支持。! M9 |* M' K, `
5、不建议过多使用Redis事务功能- H9 G8 _+ M; S6 ^1 C! @. B
% y; R7 r. \: i" w# v7 WRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!6 W8 x, @$ U) ~4 J/ j
6、Redis集群版本在使用Lua上有特殊要求
- V) n2 x; P6 x5 p$ h9 _
) l, r: E% C g3 j$ i1、所有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 A" n/ S, @7 h; b ?: y7、monitor命令
. |; U& ^- S0 L0 u& r2 o
p0 r6 i3 @6 d3 Q1 w必要情况下使用monitor命令时,要注意不要长时间使用。
0 c" a7 H) f2 U' l' n7 H6 S三、客户端使用1 ^+ q+ B, u. l# z% e
# C5 P O9 Y/ U, c/ x1、避免多个应用使用一个Redis实例* g. R# H% @5 k, y0 e2 S
% r+ @8 @+ \" `& N7 k
不相干的业务拆分,公共数据做服务化。$ Z3 v; u* H6 M. o, ^# c4 u* L, s9 j
2、使用连接池
2 G8 X( E T, |/ y
9 U% ~6 B( x# r- D- A* l) {可以有效控制连接,同时提高效率,标准使用方式:( a* Z6 q8 j, M' i6 Z
Jedis jedis = null;" v& L6 A$ V, l- c7 H* ?
try {, C# S4 D5 v) U _5 y$ l1 z/ k
jedis = jedisPool.getResource;
' Y- N1 a+ m" @" P ?! | //具体的命令" S! \, S3 @$ J5 [* c
jedis.executeCommand& T m- d6 U8 t$ p4 m
} catch (Exception e) {
- g' K" Z# c9 r& [+ P" l" M logger.error("op key {} error: " + e.getMessage, key, e);
$ p2 F- x$ m" [) g5 f4 D} finally {/ k' u& `1 A6 }' @& |% ^( _
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。8 B# V( f* q% T' F) i4 |% J
if (jedis != null) 5 K1 v- w" b2 j4 N0 |7 }7 H
jedis.close;* N7 ]5 r) H. T
}3 w" L! b$ E2 d5 A0 Q4 N
3、熔断功能. ]) j, r5 F, ?: D
0 b2 m d% l) I" f1 @6 n
高并发下建议客户端添加熔断功能(例如netflix hystrix)# E5 K) \0 C r, R( h' d
4、合理的加密2 i1 M! Q% s, h7 w( K6 S
8 J$ X$ \7 D# l6 K& L" J设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
+ X2 y' U1 X& m, k4 C5、淘汰策略3 U" b) S" _2 U
+ Y D9 l! e# C- W9 j
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
7 b$ h C) A6 I0 M) {- G+ D5 {' P默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
1 H% `$ V, }8 b其他策略如下:
+ G$ `9 v, Z" U% k. Z0 l
% P5 Q& @. d4 i0 b9 v2 l8 U' m9 M- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
0 I: E& `: r9 t3 \& M - allkeys-random:随机删除所有键,直到腾出足够空间为止。
' z$ D" h3 ]' q7 ], C - volatile-random:随机删除过期键,直到腾出足够空间为止。( c9 \2 O4 ]. ~) Q% \6 O! f. P
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。) D5 A) }8 Z {# D7 t
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
6 U/ |6 ]% J/ ?( r+ R0 r8 c 四、相关工具
1 W* y0 d* w/ M8 a' I
. b" b& N% |) v0 O6 t0 W) ~8 k8 J1、数据同步; e2 t/ R8 l1 U1 R5 o, ^2 }' R
2 B F5 z; D+ |2 Q; Aredis间数据同步可以使用:redis-port1 S7 T9 S( D" J. u
2、big key搜索6 r' \9 U7 {$ V" M5 v
( o4 d f; R6 l8 [redis大key搜索工具
# ^, T+ l1 Q3 U1 R* j3、热点key寻找
4 [; ~2 B5 o: w% d' t9 H3 L: L) |7 U1 z* `7 y5 [8 H; S
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题3 C5 g6 J6 M. F1 o
五、删除bigkey
5 X9 Y8 C) D# j W4 P, m0 V6 Y4 B
# u: v* S% K' m* U7 z G$ ]/ _! @; E( @1 @' g+ D9 B; _
- 下面操作可以使用pipeline加速。
+ ?& v+ t) v. [; W6 u9 W - redis 4.0已经支持key的异步删除,欢迎使用。) M8 c$ _7 ~1 w3 a4 D7 K
1、Hash删除: hscan + hdel
9 } ?, v2 m" `3 L: H0 j0 F: O
public void delBigHash(String host, int port, String password, String bigHashKey) {$ d. g. P, X- j( y% K$ X1 ~5 q
Jedis jedis = new Jedis(host, port); O& d# \8 J1 s. O+ x
if (password != null && !"".equals(password)) {* C' v* u' e7 G) f% e0 z7 p4 V: P9 C
jedis.auth(password); `5 U. O1 @1 L. n. c5 T4 G
}$ s/ E3 g# N5 m9 S
ScanParams scanParams = new ScanParams.count(100);5 Z7 W- `7 `# s5 U/ Y
String cursor = "0";! x- d4 T2 \ f; ]: ~$ \- P$ b: |
do {' s( U4 y$ x9 B5 N+ i
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);6 W% ?& O& F% g5 A, n6 u8 k
List entryList = scanResult.getResult;
3 x1 C# y6 [: v: Z if (entryList != null && !entryList.isEmpty) {
- L+ t- o* {8 p8 ~3 o3 } for (Entry entry : entryList) {
4 ?% m# z0 I& Y jedis.hdel(bigHashKey, entry.getKey);& d+ E( u9 S" x2 l: ^; P
}1 i: o" i7 _5 X$ S
}: ^1 I7 N6 m. V# u& W/ H( s+ |* o2 C
cursor = scanResult.getStringCursor;( k( \0 `& e/ t" V. \; c3 W" G
} while (!"0".equals(cursor));
% e" |( f- e8 w9 y/ q$ e I6 a, P3 O/ X! J4 C" ^
//删除bigkey
* s/ z- C4 M# T H L jedis.del(bigHashKey);9 ^6 I0 f/ `" ^9 }* ?7 B( X" Y$ G
}
: s8 k G8 `0 t: w2、List删除: ltrim& Q! o: S- B5 i
+ z! |9 m1 K" ~* s$ J$ l1 `
public void delBigList(String host, int port, String password, String bigListKey) {
# f. _9 f' E" ?- ` Jedis jedis = new Jedis(host, port);7 a: q# B7 M4 ^; T5 k; }
if (password != null && !"".equals(password)) {
) b9 Y5 j) M/ K$ q9 S. N. J& B+ X jedis.auth(password);# C* A P$ q% X; h6 L3 C
}& w) G( Q' K! N, `* Z/ X
long llen = jedis.llen(bigListKey);
8 F( J) h" i5 ~2 R- p0 M; [ int counter = 0;
- [- j8 o: U7 h$ |, F5 ^ x; \# B int left = 100;
0 F$ ^' e3 G* h) W6 L1 Y while (counter < llen) {
$ a2 f4 R; O, ]. F //每次从左侧截掉100个
9 o2 K3 O9 d, l ` jedis.ltrim(bigListKey, left, llen);8 e3 s; Q6 e: ?) V3 P* J+ w0 z3 _0 r
counter += left;1 O' X; w) ~# N- v' H7 L
}
$ g; t0 {. G# t7 w' @ ^# C //最终删除key
4 ^' E$ r& O+ {& _+ Y. } jedis.del(bigListKey);- w/ l$ E9 E2 f* a" s' L* u+ p7 T
}3、Set删除: sscan + srem1 m l; j! E" U0 H* G5 A
' a/ H' F: }. u. F0 vpublic void delBigSet(String host, int port, String password, String bigSetKey) {
6 a! G: k+ S5 N3 Y Jedis jedis = new Jedis(host, port);# E' l4 L4 c! f4 N: _/ r# i( c
if (password != null && !"".equals(password)) {
+ B* X5 J& f. t2 A jedis.auth(password);# }) u7 f7 i' F* ^2 E: I3 z7 z
}
, u [4 {0 V* m& r4 g( `# p' W' C ScanParams scanParams = new ScanParams.count(100);2 V6 b a, u0 X# \& V( d
String cursor = "0";
2 @ _" Z3 ]' [7 _# o- N4 h do {
" w& ] L, m1 t4 m+ C/ H, E& b ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);* ]8 K6 E& z; w" @
List memberList = scanResult.getResult;
: _+ _+ J: x/ b0 j, p4 N if (memberList != null && !memberList.isEmpty) {
+ T( F) m/ W$ t v for (String member : memberList) {
% k* j: W' F8 s$ Q' n0 k jedis.srem(bigSetKey, member);
9 R- f8 F, R4 ^4 r }) W9 B/ F/ x! B& O
}
" r* c1 u( d' i cursor = scanResult.getStringCursor;
1 C9 W) U4 Y7 e4 o. v } while (!"0".equals(cursor));# h% M( _. P4 O7 U% P, `
: J+ t1 A6 M/ g' G( Y0 \/ E- E4 e//删除bigkey
; @6 S% _7 S! y+ r3 x- { jedis.del(bigSetKey);
3 [! }) P8 `# ^4 |$ ^) \0 |2 H}( }* d* M6 T) u/ A/ p; C
4、SortedSet删除: zscan + zrem
/ f1 @8 q! G1 U9 d; O
. w( Y4 ^) B0 n- ^" o+ L1 N/ V* apublic void delBigZset(String host, int port, String password, String bigZsetKey) { 3 e* c. ]; L& y- W4 w' Y- a1 K
Jedis jedis = new Jedis(host, port); # t2 S6 B9 `9 k2 }& U
if (password != null && !"".equals(password)) {
0 O3 q' r- I, E) L/ }1 f jedis.auth(password); ) H F- `7 |3 `
}
) M! t% W' R0 R2 s2 N/ t2 C# Y( K1 s ScanParams scanParams = new ScanParams.count(100);
6 Z. N& a4 \' q* E" r$ i1 @* T5 P String cursor = "0";
; U* M( M6 A3 c do {
9 S ]8 j( o& e8 E/ e0 D5 m ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
, ]) D g8 b9 b9 K; @" j ListtupleList = scanResult.getResult; / ^0 q( ]5 S2 h) i6 k! f( r
if (tupleList != null && !tupleList.isEmpty) { b, t8 v( q# n0 M% G
for (Tuple tuple : tupleList) { $ K6 O; e6 \# x* O; v( F0 j( U
jedis.zrem(bigZsetKey, tuple.getElement); }* q! D s. v0 d
} ' P/ J" p9 ?5 E- e/ W7 ~0 t" [' C0 `1 j
}
$ S" ?) j# y; Z1 N5 ^ cursor = scanResult.getStringCursor;
( x! ^( T# [# K } while (!"0".equals(cursor));
# b' w! N) }3 n% n6 E9 j/ Z, `1 v: o
//删除bigkey
9 z, w, Q8 H) V. f! c jedis.del(bigZsetKey);
: v; W+ m- |; o2 u" m6 M1 d! x} 公众号内回复“1”带你进粉丝群8 l. |: A/ @) u
来源:http://www.yidianzixun.com/article/0LevQm7t
3 \2 @( `/ ?* ]免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|