|
|
. {' `, w" v5 d8 C3 i; e9 k
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。7 H6 l7 M* q, g8 v8 h5 `6 ^% U0 z
# d! Q" H) ?# o. z0 ?" V t u
- 键值设计. d/ @9 L) L3 O" l) r0 B# A9 n
- 命令使用& ~) ^2 j' X* |& z }* a0 @
- 客户端使用/ h7 ?" \1 ]% A7 d1 O
- 相关工具
( `; \1 n! H0 B5 g% M( j* ` 通过本文的介绍可以减少使用Redis过程带来的问题。/ I* o7 ]5 |1 w' ~5 a3 G
一、键值设计7 p4 \( T# ~; } c
$ w# X' m* A5 R7 }4 _2 E
1、key名设计
: B- ^% g7 p3 \- l/ y/ C0 m8 ]3 S& t( a% `1 ^( Z: j
可读性和可管理性
3 H8 t9 K i/ O" \4 \" ~4 }: Q0 F* b' z+ ~ G" P3 g6 t
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
5 n! c/ o4 k; e s6 A: D8 Z: L. B ]1 ^: m1 V; L, S
- ugc:video:10 Q/ W# `7 a8 N( i8 h+ |% k
简洁性4 V& Z* M X' c2 Y, U% z
! E4 ?9 k# Y7 P保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:3 _. t' g, C- o5 l* w' D
% H! p+ E: [) x6 h9 M( M- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。" C2 D, }9 E/ ?' b& m& M
不要包含特殊字符1 h1 O- Y N: p) A* J ?
& g+ j7 j! U/ S2 q5 {5 P, H8 \反例:包含空格、换行、单双引号以及其他转义字符
% H2 v2 S+ \* j& J: S, v2、value设计9 t* O. L8 g6 L6 i
9 o+ W- I/ X8 f; D M" Z2 C
拒绝bigkey7 d* ^' d. z5 ?0 l u5 O& Z
& c& f# x: H7 L% |防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
; u1 C$ F! Y9 D- A% _* O) z7 ^反例:一个包含200万个元素的list。, W& j' Q% ^3 ^6 ^$ X. `3 E- W
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
* y' g1 n; a# d: P5 D# @! P选择适合的数据类型
$ S: O; [* I% @/ i) x4 X8 ]
- L' c* h* N& h) j# r2 {# q( m例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?6 n$ p% e7 f5 M+ G
反例:
2 X) y$ m4 D+ B) Y
; B9 W" x, ^ B9 H6 b d- set user:1:name tom
% \2 ~. @* H* x, g7 F - set user:1:age 19
+ e; R( z2 n* k. D* x/ }5 i# Q - set user:1:favor football5 u& m0 I) e" D3 d5 ?
正例:% E2 k j: v, ?& s
& g' g( v# ~1 c5 F" F7 Z
- hmset user:1 name tom age 19 favor football1 m8 x6 E' a6 Q0 C
控制key的生命周期! k! ^: `* a+ f0 C* ]5 s p! P
( e0 l) J8 h3 V5 d2 G$ r predis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
~6 e( [2 H6 f3 T X. ?( v {! z二、命令使用# Q7 g" j; E+ g( S' o7 {
) o) T( e; L n$ U% B4 o, B
1、O(N)命令关注N的数量
6 A: L$ E. T- f* V
! W7 R( w: E, Z: ^8 w9 J例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
* k. X+ H% X% V0 m1 J2、禁用命令( h- @! u4 J1 u3 y
6 ~/ ?% Z u/ H2 @3 G; @, ]
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。% @& g2 x9 R& P* K- X
3、合理使用select" [% j. g7 X/ P5 Q% _/ e
5 b: |1 d; J. Y7 ~. d$ U1 v
. u& C3 @& K/ F
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
1 g8 ]( ]8 U: c4、使用批量操作提高效率; J- [2 C4 U; b( b m+ w& V
* e! U7 c4 I& q9 a& S- }
L2 E* g! w; ]6 D) F- z- f
- 原生命令:例如mget、mset。( ^5 W6 m& N# I O
- 非原生命令:可以使用pipeline提高效率。
' M& H, Z1 e$ H" J 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
' @) a( D4 @$ q* {' V! R注意两者不同:+ B7 {. v% J3 B6 R# [0 s) A
! E: B5 W2 h: Y3 P, ]3 V4 B- 原生是原子操作,pipeline是非原子操作。
3 G* r. R7 t, Z3 Y3 J - pipeline可以打包不同的命令,原生做不到; I) E5 o" b- e d
- pipeline需要客户端和服务端同时支持。
0 W0 {2 x5 A4 j8 }" g 5、不建议过多使用Redis事务功能0 S4 o: z4 P1 l3 K
- p4 u/ @; F- i) u$ W$ s% m- B# f
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
& E1 r V4 Y( i: D# k4 w% z6、Redis集群版本在使用Lua上有特殊要求# f4 n% j3 r* |: \5 B! y
" [! y! s9 }( q6 y* n. ]
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"
# F% q9 J D- X c7、monitor命令+ z$ M9 o& l5 v/ V( b. r
* \: o& U6 K1 C" N; v
必要情况下使用monitor命令时,要注意不要长时间使用。. M0 b: x# y: \4 n) U
三、客户端使用
7 `( g/ {$ C( [4 W4 h5 P
, D; I$ M+ z# v' @2 O c+ p1、避免多个应用使用一个Redis实例5 s) T9 `7 m0 j, x' a' n. P5 v
5 C$ K# L8 g: ]/ f0 j" C不相干的业务拆分,公共数据做服务化。7 X3 e; ^; U! Y
2、使用连接池 K0 t$ ]* q/ y. q; }0 }
& e- O6 f" Y/ _* I/ x可以有效控制连接,同时提高效率,标准使用方式:
* J$ C" B5 {6 j, C( j$ CJedis jedis = null;) z+ v2 O' w& o6 y0 W8 k' g
try {, Q; X9 c9 ~! R" c/ b4 F& ? Q
jedis = jedisPool.getResource;1 [* Y' U4 a3 a+ T) R4 Q
//具体的命令0 U, |% J P$ {+ n0 J' I B- x
jedis.executeCommand
2 v+ X! p, P& [; Y. h* v} catch (Exception e) {
8 R! B) N4 f1 V! }1 x/ ` logger.error("op key {} error: " + e.getMessage, key, e);7 e# E1 ^/ l! K5 C
} finally {
& A+ D* `! N/ |) @9 g1 z2 h //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
) U2 X4 p i5 X8 c/ }3 j if (jedis != null)
+ C# x7 o8 e( \9 \ jedis.close;
6 s0 b2 X2 k5 {1 }. }9 H* ^7 [# K0 W- I}0 f- X1 t! W, B! W7 L
3、熔断功能
( q! s( B+ A9 Q6 c3 W3 S, u% v6 c
- J4 l8 d. d. `2 J2 W" o高并发下建议客户端添加熔断功能(例如netflix hystrix)
) T7 F H* ^2 ^4、合理的加密. E% c( [# a7 d6 h% z
# O! r1 j7 P6 U; Z4 `9 N
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)% r' p6 P8 J# m) A; J/ D+ g
5、淘汰策略8 T# L: T% U. L5 x; X/ B5 [4 [9 W2 R
7 a, ]5 ?0 J; P: m: T根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。0 \/ A7 J+ Z7 u Y. T
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
/ e# B# G& K, }/ Z其他策略如下:. p, N* |/ a5 s8 U# f1 s
; p! n Z) x; ]) Y2 \
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
% Z5 Y2 M. ?2 ]& l4 k# M - allkeys-random:随机删除所有键,直到腾出足够空间为止。
4 P+ x; n' P9 Q p - volatile-random:随机删除过期键,直到腾出足够空间为止。0 o( I; h; [2 i1 X9 }4 V0 i) p
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。( d, m! e7 ?/ {6 i0 M
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。' V( p3 v2 X0 g# M" o
四、相关工具# O5 F8 @$ u( Q6 O8 P( [: i
9 g* I$ ]" A( S1、数据同步
/ ]6 t) C; r0 h1 X2 Q
/ [+ p, k3 |- \$ G7 n! N3 bredis间数据同步可以使用:redis-port2 x$ p; {8 k6 \& W6 z6 H
2、big key搜索
9 f6 _# c7 _, ]* J: }% `3 U& }/ k4 U! Z7 p6 Z( n) |) V" b
redis大key搜索工具) _7 n' Q9 n4 \0 n0 O
3、热点key寻找
: c* B4 ~8 k: K1 z* g9 R7 }6 K6 o, _) T
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
2 l! v& Q; _. q0 ^ u五、删除bigkey
% D) D5 m6 s4 T0 w7 r# Y
# q @! c q: I$ H3 D6 R) n6 m
+ z% q0 {0 u. }1 W- 下面操作可以使用pipeline加速。6 b. v8 k' v8 d+ g
- redis 4.0已经支持key的异步删除,欢迎使用。; s: l( p6 z' h# K* x$ a
1、Hash删除: hscan + hdel4 n3 d5 c' n. k. a
9 |. }9 y6 b3 U' r" e* Q& A" @
public void delBigHash(String host, int port, String password, String bigHashKey) {
1 o/ o! r! q2 k% y: u Jedis jedis = new Jedis(host, port);- m4 S! J1 ^, {2 |; N
if (password != null && !"".equals(password)) {
6 r7 l( p1 Z& _" C" B jedis.auth(password);
1 C" Q( p- F: O1 k# q5 m$ X8 t }( q. E( C( Q7 \- v i
ScanParams scanParams = new ScanParams.count(100);; |# |7 p `- D5 K
String cursor = "0";4 B. E1 D% T+ B4 h$ n
do {
; s: T+ g7 B; f4 J1 {7 t ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
$ z2 l9 M& @3 y4 | List entryList = scanResult.getResult;8 X7 `8 Q- ?5 y" I! Q
if (entryList != null && !entryList.isEmpty) {. q. m/ V3 W5 v+ J- f0 `/ f8 T- e
for (Entry entry : entryList) {
- Z% z4 C8 a5 m2 v jedis.hdel(bigHashKey, entry.getKey);
& g& G4 h) N G8 B/ S$ x n }; `; c& f* ^: c9 O( m
}
) j* O5 n6 o$ b% f& h3 H cursor = scanResult.getStringCursor;
^; l _# @5 e } while (!"0".equals(cursor));5 B; c5 c z8 W" ^
% E& A$ W) F \# a. T4 N$ G
//删除bigkey" [: Y+ ?: U1 t( x1 F' @; ?
jedis.del(bigHashKey);" \6 ]% G8 @8 Y+ z0 y# r. H; h
}
& D3 u9 j7 c* L( e# r; O; {2、List删除: ltrim
) E; f+ ]/ r0 X- H c# H8 c; M) D
' W: u5 b: b2 \1 W; o$ d) Apublic void delBigList(String host, int port, String password, String bigListKey) {
. n/ s1 _+ Q" B4 z# ]2 C! N Jedis jedis = new Jedis(host, port);
1 v7 a( n6 r2 U) P if (password != null && !"".equals(password)) {7 e d2 a. O0 N5 j5 F$ J n) U& k8 m
jedis.auth(password);
/ [, q4 K# G9 T" A* } }
( s# }' d, ]2 k7 f2 V, b! x- Z' L long llen = jedis.llen(bigListKey);, A( b; M+ B9 K! w" n
int counter = 0;
2 L) Q& v9 [& Z* E8 R int left = 100;
1 t* |8 R; `7 ?( r! R while (counter < llen) {- |8 D) b( I P" ~
//每次从左侧截掉100个
$ P4 h( _' R- _! k& U jedis.ltrim(bigListKey, left, llen);% t" l! l4 j& i6 O. S# P+ u' m
counter += left;
& C, M. m/ G. l }/ N2 D! J; n( e' d9 _
//最终删除key
- C( `1 E7 M+ K2 x jedis.del(bigListKey);" \5 E, p. [( y9 ^6 h; I b W
}3、Set删除: sscan + srem. Y% P, X3 }) i$ n7 q! s* o: O6 C5 }
4 B3 o% X- |4 O! C9 _# Qpublic void delBigSet(String host, int port, String password, String bigSetKey) {
9 z% ?5 w* D1 M+ `: ]* u Jedis jedis = new Jedis(host, port);- n' ~9 X3 |9 b7 d) P& G
if (password != null && !"".equals(password)) {, L, w7 g; N* ?$ z# V% W
jedis.auth(password);9 t, T* z, |* m0 g w
}
6 U- i) \' d/ {* H ScanParams scanParams = new ScanParams.count(100);
% M- M3 m& T" M- f( l String cursor = "0";& o* d P/ a! g, A
do {
' w' e8 ~( B1 ~ ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
" v6 Y) x+ T" _8 X s, ^0 } List memberList = scanResult.getResult;
" H- P( G, I" V: t: }; L if (memberList != null && !memberList.isEmpty) {
* o6 a t' o$ I" r. b for (String member : memberList) {
$ j: E/ m, r0 }/ z: [; t jedis.srem(bigSetKey, member);& P5 g& J8 F! K- g* U
}" i Z: a8 u9 e, L" ]+ ~
}) \" E$ i/ D! } c" Q$ u
cursor = scanResult.getStringCursor;
2 b8 Y+ K0 d0 c) L4 X( s } while (!"0".equals(cursor));: h: i' g2 V, u1 A( g. G
) |5 c9 ~7 u" j! Y8 v/ g2 }
//删除bigkey( e; w. n# C+ Z/ d0 p! R
jedis.del(bigSetKey);
) Q9 R; K! K9 J6 u' f}
: }0 \7 \( F; J, t3 H. O4、SortedSet删除: zscan + zrem
2 y8 x& A8 x9 F- j6 |. N) J
& S* R& `4 _% k7 D1 mpublic void delBigZset(String host, int port, String password, String bigZsetKey) {
: w2 C% D: J( K7 _9 d' g' {. k Jedis jedis = new Jedis(host, port); , i5 D* l5 d- t' o! |: n. G5 F, }
if (password != null && !"".equals(password)) {
0 U, f# t; s7 v {+ C; o% d jedis.auth(password); ! j/ n1 T" H! v( E8 O. g) s
} ( { o1 e( \ y( @ z+ N
ScanParams scanParams = new ScanParams.count(100); % I0 @" e2 H: U+ p F
String cursor = "0";
1 v4 T4 i' t) u* ?9 [9 X/ e2 p do {
+ M+ l1 d+ P2 P: N4 V' _) |$ { ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
9 ^$ T0 ]4 I& I3 Q% P" y ListtupleList = scanResult.getResult; ' h, {. T: \; X, R0 ]
if (tupleList != null && !tupleList.isEmpty) { 2 g: r; A) t! W* U
for (Tuple tuple : tupleList) {
/ @* x" r t) o7 ^8 U jedis.zrem(bigZsetKey, tuple.getElement);
' ]8 `& n0 x9 j( b* e6 Q }
8 k& f) \8 O% _9 D7 e! X8 b } 9 o2 `4 I5 q# {: ^5 { a
cursor = scanResult.getStringCursor;
/ {8 |0 G: c) X8 X. _ } while (!"0".equals(cursor));
6 k' T- k9 V6 C. h4 q
2 l: }, ?; L5 J2 C9 h( A2 F//删除bigkey
. ~! m) Z" ]) J" O) T5 C jedis.del(bigZsetKey); 5 z! w# Y: |' N: U6 ^
} 公众号内回复“1”带你进粉丝群
7 \" ]- a, M6 q9 f0 a# w" V来源:http://www.yidianzixun.com/article/0LevQm7t
( e6 U- R* d$ s6 G免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|