|

8 z0 R: t9 t0 }4 {% }本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。* w8 ~; @) u/ W0 G; `
, ]6 Z$ ~/ a0 {2 L, V% a- 键值设计# @' b! |/ Y. }9 T1 H5 ~& T* a
- 命令使用 u6 v+ B3 P9 F; J9 n; d8 c5 }
- 客户端使用
/ `) _: z9 d+ }+ {( x# X9 V - 相关工具$ s+ V( l1 h) g9 D0 L
通过本文的介绍可以减少使用Redis过程带来的问题。
' ^$ d9 F. D* ?, U+ Z一、键值设计
, u7 W: `) n4 V0 J, B: \) x" I. T, `9 H. \* }& B7 B6 b8 \8 ^# L
1、key名设计/ U! @2 R U; f+ a* }2 R! G
0 Z+ i; b" Q- P
可读性和可管理性6 a: @/ {1 F: K1 z/ O
; k, N' ]2 k/ s+ J" x以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id0 t! V [7 I; i- ~: \6 i( _
. @5 K5 | `% U$ s: t- ugc:video:1
u8 b+ d, g6 w3 s( F" y/ C, r 简洁性5 D9 _+ W1 E% x, {( I3 @ N6 f% n
6 L/ [6 K i- S T6 J2 ?保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:) @) ?1 N8 s) v. j0 i) q4 r
, n8 F, r. d4 f: t! T* L) j' r
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
% S7 s. {8 R- E8 P 不要包含特殊字符
, l' M7 u3 l7 ~! K/ `- ]! o) a$ s* c, @8 L' O% H
反例:包含空格、换行、单双引号以及其他转义字符' V( P$ y7 K/ a$ N3 n
2、value设计
/ h! T; S; D4 L: R9 f( {4 \# D2 h- F; R
拒绝bigkey
2 H- e; ]. Q$ y. p8 Z9 @
4 e9 s G, j& u. K: J0 i+ p防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。# l. S6 l" E# C g, g
反例:一个包含200万个元素的list。
8 L7 A% I. W. v: q2 ?非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
. d% D3 G3 S% E' H选择适合的数据类型$ V! J9 |& x4 ]
7 x+ t% U' c- p! Z* b! }4 ^/ x例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?) `& t5 X" B& N2 t6 x5 W6 p4 ]
反例:) P2 q; Z9 u; T+ V8 Z
$ ?) n$ m7 x) w5 t% X. Q- set user:1:name tom
& [, b( T" p! s% _& | - set user:1:age 19
3 V+ C; i# ~3 s4 f9 ` - set user:1:favor football) p4 s- {6 p6 b6 W
正例:2 d2 o i( U1 X- y% h: z# _+ z
! e- ?3 o8 J8 x* t! o: m+ C
- hmset user:1 name tom age 19 favor football
. R+ ~8 x8 w! y# R0 ]6 a 控制key的生命周期% \; ~; L1 {- k% ^- b- L
! B R% y1 }4 u ^2 L) K; lredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
4 A) K: n" @7 I2 V8 R C* l二、命令使用
4 r y0 R* H- }% n$ w! d7 @3 d+ J, `. U# ?
1、O(N)命令关注N的数量, K* w, o! |4 {! t
$ i( A& [% ^: F" V6 U
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。4 e' O ]$ m' p" u
2、禁用命令
4 Q/ e: s+ e8 F3 d: Y. s$ x, Q* p. w: `2 d' [& B; A3 G& f2 k1 o
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。$ t1 v& J% L" I& x: s$ S
3、合理使用select. c6 D i) l# ^$ N/ Z. {; |% ^
- L6 g- O% c' T2 ~
% |' m/ z. E9 ?3 W; U A% b
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
4 O8 w( q \0 Q" J3 d1 Z$ l4 d4、使用批量操作提高效率
: c( U- m6 n6 ?$ g9 h% h$ x% O: @/ @9 ?" g8 M/ H; M( a5 q& q
& P4 I" y& b X6 g2 E
- 原生命令:例如mget、mset。
- M/ c; ^, _/ g2 b4 m - 非原生命令:可以使用pipeline提高效率。- A. U! b- ~. d
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
/ c$ d# q( @2 k9 i# S注意两者不同:
$ B9 P0 M& `0 i/ T8 a+ U( K5 f; G* v" ~. a7 X5 t& N2 g1 {$ \
- 原生是原子操作,pipeline是非原子操作。
% d2 m0 U( d; F$ |+ _& W" ?6 D* Y: y - pipeline可以打包不同的命令,原生做不到/ R7 B& J2 s: `' p& P/ v' v; i
- pipeline需要客户端和服务端同时支持。# C; o; w8 _( a% L! w7 v% T
5、不建议过多使用Redis事务功能
- L! Q5 T1 X* i: O; |6 k; X, B/ q `* [
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!- ]) N, g' h9 s* c/ N$ m, a
6、Redis集群版本在使用Lua上有特殊要求
+ O6 G+ |( d, `8 J2 v* t- D: Z
( D5 u7 @- W) Z% Y/ e' O8 v1、所有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"
& q/ J7 N# k2 |- ~. g7、monitor命令
: v% f: V8 O% q# ~" S3 l5 b1 r1 W8 ~% A4 T* s% j0 u
必要情况下使用monitor命令时,要注意不要长时间使用。- s5 k2 E( o* M9 U( s$ [
三、客户端使用
3 |' p6 i& I) W* x# i+ a
. y. d/ J# M% |8 N- T/ E1、避免多个应用使用一个Redis实例
% I: \: x9 [7 K6 j3 \! i' a- Z
* D4 ~! L8 S3 s8 z. x) H) P不相干的业务拆分,公共数据做服务化。
* v6 b2 r S; q+ h& K( f. i$ i2、使用连接池
" S( J0 M. A! X: g. d6 b2 i( S, D% m
可以有效控制连接,同时提高效率,标准使用方式:
! x! m a& W8 CJedis jedis = null;6 r/ T- v! y1 E$ v! q4 j; a) A
try {
2 K- w& J, L+ B/ t( \' o) }4 i) c jedis = jedisPool.getResource;6 y Y1 m# ~* t
//具体的命令" K+ C+ f, d4 f1 b+ J/ C! q" v# d+ a
jedis.executeCommand
% x4 U" L& s! L% m; {# L} catch (Exception e) {5 K% o4 d( B- h$ P; n8 v3 L1 i
logger.error("op key {} error: " + e.getMessage, key, e);5 K. d" U. l. S! Z' h( s
} finally {
+ F8 r$ q, M* q$ G1 G //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。. s/ U+ ^* v# U, N
if (jedis != null)
0 v! [/ o% Z( p+ K7 r0 N/ A+ {1 Z5 c jedis.close;0 p" h1 o) k/ N2 @
}
+ y* [2 a, }2 o3、熔断功能; f1 E3 W8 T% m3 t0 U
' k) k/ e M" V( }
高并发下建议客户端添加熔断功能(例如netflix hystrix)! Z1 o+ b1 N( R# F2 v
4、合理的加密. J: H0 j. b% j E8 F+ k+ |
$ [+ K8 E# B2 ~, f. k8 d; S
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)% U! u: X' d [% f0 j) Z
5、淘汰策略
1 `* S/ U3 k$ L1 d4 Z- E3 k$ z8 C+ A2 z& X
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。7 O7 D$ ~, B/ H \/ R6 T0 m
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
8 ~( [- h3 O, k0 ]; L其他策略如下:
% N- D$ \# m/ Z+ L( b. F: B
- a* w( d' s; Y3 |- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。$ {) F) \ C. [; [
- allkeys-random:随机删除所有键,直到腾出足够空间为止。$ p5 q) X$ K" u4 V) z
- volatile-random:随机删除过期键,直到腾出足够空间为止。
+ z' E) J: @* ~6 ` - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。+ j# Y8 a5 a8 K6 [5 ]
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。: x9 K8 G6 P T' j
四、相关工具
6 x+ @+ R8 r9 A# F0 i! @
# C3 u4 N4 O# Y$ h1、数据同步
' z+ N" e/ ?' n) U( }
) C/ R, l; t+ t9 Yredis间数据同步可以使用:redis-port
& j. u L3 ^/ U( }5 v2、big key搜索
* R" N% h( Q( B( F! w
0 U: c% o4 N) d6 ]9 B3 zredis大key搜索工具
( w6 t; C/ Q8 ~! V8 j" J9 v7 C3、热点key寻找
; L% A" Z0 l9 z& f3 {8 Y) V' D3 j! h8 ]: i$ T' z5 \2 G
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
7 s. G1 H9 r2 k2 d# o五、删除bigkey0 l8 W0 c' ^ a2 U$ U. j
. b# ]! ~$ p$ K. K/ h
1 j0 Y2 s- U \: k* q" l- 下面操作可以使用pipeline加速。8 t+ i, P4 [8 z
- redis 4.0已经支持key的异步删除,欢迎使用。
4 M7 b' l' {1 Q0 R* a1 n 1、Hash删除: hscan + hdel
- S1 [0 `8 b" ?+ @& ^ ~" |5 h% P* L6 J9 N+ @
public void delBigHash(String host, int port, String password, String bigHashKey) {
; v0 ~; X+ x, j) p8 [' l Jedis jedis = new Jedis(host, port);
+ r! q$ b( M q6 }# S% P if (password != null && !"".equals(password)) {- _$ R2 q( ?6 P) S4 w
jedis.auth(password);
( u+ b+ v: a- x9 x+ [" R: E8 L2 X8 K- S }
$ R* ~1 A: _2 }+ K( ` ScanParams scanParams = new ScanParams.count(100);
, o8 K: d; j! Q# K String cursor = "0";
: X$ s) e. j {4 n# H I* _$ b' |" ^+ J do {
4 O$ b5 L# N$ A ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
3 f) B6 o. x5 |& n2 | List entryList = scanResult.getResult;
+ F% U2 l( C7 u/ S4 {* f; q* b if (entryList != null && !entryList.isEmpty) {
! v i9 T' r& n) n/ ~! l4 ~. p7 ] for (Entry entry : entryList) {
8 S& _7 P, d2 f" p jedis.hdel(bigHashKey, entry.getKey);
+ {/ G3 P* d) |0 Z r$ B }! B6 h* U# x9 b& X/ u4 E
}
6 `; O9 \) f0 a( [3 V9 I" u: ^ cursor = scanResult.getStringCursor;
# H, P+ ~2 B0 U* W+ N6 Y } while (!"0".equals(cursor));
) H8 P) K& |" ~( z) x% j' w0 v4 l U
//删除bigkey! R! T3 k$ {9 f/ ] K8 z P; h
jedis.del(bigHashKey);+ L$ D$ ], N, s% ?
}8 T7 s9 ^7 Q# h- n+ w
2、List删除: ltrim
, P* Z" k7 ^. m" ]& [
# h& y: r$ B# v2 f4 P7 Rpublic void delBigList(String host, int port, String password, String bigListKey) {
2 ?- l) [8 v* _' H% L Jedis jedis = new Jedis(host, port);
% D2 Q- W- Z& b if (password != null && !"".equals(password)) {1 E( P: ~) j L P3 C% Q
jedis.auth(password);. [) y/ i& y' ]( e6 c' `
}
: `4 L/ U, a- Z% n long llen = jedis.llen(bigListKey);* ], D# ~' O. ^$ D! U( |+ S
int counter = 0;
( d! A' t+ B4 f. i int left = 100;/ B9 H4 h8 z$ N
while (counter < llen) {9 i5 \6 t* W" s; {5 \
//每次从左侧截掉100个% J v9 g2 f7 C0 u& O& ]
jedis.ltrim(bigListKey, left, llen);
) V# v, O$ P) V4 D/ |- S7 x7 e- ~, n counter += left;1 _1 }- U* t! d: A; w6 n
}
9 z/ x- K& `6 C: z( x l1 W8 l //最终删除key2 {" d4 A* e! @* r5 t
jedis.del(bigListKey);4 {1 S3 b+ S) n$ ]0 e
}3、Set删除: sscan + srem! F3 A! h2 X2 f h( O* X
: k+ M( R" y6 h6 [( T8 [! i& P# Z1 h+ V
public void delBigSet(String host, int port, String password, String bigSetKey) {
`: l K* w) @# { Jedis jedis = new Jedis(host, port);( |- V& V7 e8 ]
if (password != null && !"".equals(password)) {
. |" u' P0 G; _6 j jedis.auth(password); }8 J9 Y( ~3 h& g
}$ \/ \- S' I! u& w Z4 `
ScanParams scanParams = new ScanParams.count(100);+ [( y ~/ a8 k) U5 d* Y$ g+ y
String cursor = "0";
9 P* H5 \# h) ]/ q$ r2 ~7 w. M do {6 x4 I2 k u+ M0 @ K
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
0 {( J" H$ K; h List memberList = scanResult.getResult;( s" N T% @& Y3 |; @
if (memberList != null && !memberList.isEmpty) {' S% } S o) W9 H7 s0 C7 b
for (String member : memberList) {, l( _- f0 X( A p0 L# N
jedis.srem(bigSetKey, member);
# p6 M; T- N# [4 q( m5 k }
/ q( W. @1 Z- n- n }- Q Q, h$ {/ u" g% V
cursor = scanResult.getStringCursor;# W$ B, T2 L! W, A0 Y1 x
} while (!"0".equals(cursor));
& r' |* l' \3 y4 N( @8 b* P* U: o$ ]1 h$ H( P, D
//删除bigkey
, e% ~& a. Z# M% ^- ~4 | jedis.del(bigSetKey);* ]' V o9 [0 l0 a/ E% L' |% m
}
+ L( U: Y8 H. W4 w# A$ d8 F4、SortedSet删除: zscan + zrem
4 c( M& d1 p: \2 w
7 h, F, B1 E; O# K$ Jpublic void delBigZset(String host, int port, String password, String bigZsetKey) { $ g1 P) F7 D; ~/ g* I6 S
Jedis jedis = new Jedis(host, port); 5 q6 s2 O9 ~; Y6 ]9 ?
if (password != null && !"".equals(password)) { , E3 u% c4 w% Z) q
jedis.auth(password); ) Y1 H& k* L! J9 R$ p. z
}
6 }& {9 q% w. X' P. F J ScanParams scanParams = new ScanParams.count(100); - s7 `3 l) I9 m; O4 x9 Q* ~4 O6 \
String cursor = "0";
8 q3 y+ k9 X7 W do {
f3 p7 M' `/ f ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
4 B! t1 h& I& Q# t7 u ListtupleList = scanResult.getResult;
! M) J8 e3 L9 r if (tupleList != null && !tupleList.isEmpty) { . Z: c2 u \6 M: t8 I+ _* y0 i( s
for (Tuple tuple : tupleList) {
. p" J: P2 p, s) c" ~0 Z jedis.zrem(bigZsetKey, tuple.getElement);
' }5 S6 d& _2 v% H7 ] z& K }
2 x) t9 N+ e- R0 u7 B }
4 T7 Z3 o( O, ]. d) o( L" e cursor = scanResult.getStringCursor; ; |" d0 k. N$ N' _
} while (!"0".equals(cursor));2 a9 F- V. \- ]9 k$ j. f
; X: w6 @$ k8 V) [7 X
//删除bigkey
' }# g# V3 G: f3 F5 O4 Z jedis.del(bigZsetKey); , b6 r% s. G0 }& v/ j
} 公众号内回复“1”带你进粉丝群
4 `7 m. S# r( p4 r* i1 p& t7 L& X来源:http://www.yidianzixun.com/article/0LevQm7t
/ `- q# T$ r' z$ |免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|