|
|
' I1 K, T8 y f) ]
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。; {% H: U; U4 Y& [' p5 W0 j
9 r9 I0 G/ H2 s; G- Y1 M9 w5 z- 键值设计
; C/ V& Z5 t2 J" j/ o$ T7 ]7 G& G2 s - 命令使用
6 U+ Z) L2 C5 C' R- S1 | Z - 客户端使用% }' a( B2 D5 f" s6 J; F& c
- 相关工具) e( B: O" [4 D6 U
通过本文的介绍可以减少使用Redis过程带来的问题。
" I: P2 {/ m( c一、键值设计7 Z0 W6 H! T& t" t; w$ u8 X
1 a) g9 K' X+ {9 G1、key名设计) T9 p% @+ i+ k6 z1 r/ N
0 ^5 \/ H. q! M8 I5 k$ |可读性和可管理性+ m% K, J5 j Q: L& {' x# T
! p# d- A& R$ B* g以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
: J8 M& _2 X+ Q/ w, d, N( e
x3 X" B( ^8 U+ H" f0 q9 B- ugc:video:1
. r* T' W5 q8 h' P: P 简洁性/ l- \. H& O, g; U% w1 T y; C5 ?5 P
( A5 b0 E& ~* Q2 `8 {保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如: o4 j4 i9 \1 P7 l: Q z
5 v5 m+ A. N/ L
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。" A3 b% {3 ]# ]5 w" D$ J8 i
不要包含特殊字符% f6 |( N- ^ b. D$ f
( ~# ]: y6 A( B4 U
反例:包含空格、换行、单双引号以及其他转义字符
- F# r" n9 r z8 v$ ?" f2、value设计
" [- F l. |$ f
+ c" l0 r1 O+ D8 p1 G6 M拒绝bigkey5 o: i( W, Q& {1 G4 H- @0 O
9 p$ {% c: m* E4 Z a: F( u5 ~
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
) h; n, M( x. I- d& H* f, @% j反例:一个包含200万个元素的list。
" ]5 @3 l: h+ w; \: V非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
; g4 Q H* U/ s# v5 f" ]选择适合的数据类型
( y. I" d g" x2 r+ i5 F0 ^4 s- ]5 E! X$ r
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?7 w/ {8 `0 J, j& ]2 ]4 G( `+ M
反例:
8 Y* j Z4 Y6 W: w8 X! N
3 L! e2 G4 d8 G$ [" {- t/ o- set user:1:name tom `* x }! N+ h- ?
- set user:1:age 190 F: h4 x- q& V0 m4 S& l6 Z
- set user:1:favor football# ~. a2 U: T @$ c# V# Z5 Z* E
正例:
1 M+ J4 M3 s7 O- y% ~ p5 ^! k1 M0 L2 q/ ~
- hmset user:1 name tom age 19 favor football4 b; l; @% R! K* k
控制key的生命周期9 Q! V; z3 K2 m I
* P0 U; R) }, @8 A, y1 \9 ]) @; zredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。& v$ [% o% V e5 h9 g2 e+ A2 V# O
二、命令使用
5 t6 O5 R9 N3 M4 J$ G1 @+ N4 i% m6 S. e" s3 K! t I% B
1、O(N)命令关注N的数量7 y4 v [; L# _( G$ O
) [0 C: r3 C1 r/ T( {7 W* A
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。9 d7 M) t2 Z, ]3 O6 b# s/ D, Y
2、禁用命令) b2 Y$ I' w: H3 C1 O9 i5 a
3 M+ r$ D, k' S3 v禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
: ] l7 Z& k; x2 B) u m+ `1 c3、合理使用select
, I% |8 ? j; ]- J5 a: p* u) @8 S z2 D3 q. F
( O3 L) p% y g% o! _
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。 U" h+ n" ?7 s
4、使用批量操作提高效率
- h! n! L6 m* L0 q" Y
. @ d: L1 Y; Y& v! h! T
. e: R( B- u# M' A, T- 原生命令:例如mget、mset。
+ f" u8 x- b7 j6 Z; R9 t4 e - 非原生命令:可以使用pipeline提高效率。5 x# K5 m' c o4 t5 n
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。( G P+ F5 u& s& S) h l
注意两者不同:0 O5 i) q% R& a. I! \% c
; c. ], V( p8 x
- 原生是原子操作,pipeline是非原子操作。( t% O8 k- A% O- v
- pipeline可以打包不同的命令,原生做不到! Q; X- L% v8 d6 l. i0 G
- pipeline需要客户端和服务端同时支持。, p0 O1 r: K$ C7 n1 {0 I* M$ Y
5、不建议过多使用Redis事务功能
2 I1 {# T7 {7 e: E2 d2 ^) |1 A- @2 V: e' Y7 [8 _! t e" N& x1 g+ U) r5 }
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
% s3 n2 A& l$ G$ n# o6、Redis集群版本在使用Lua上有特殊要求9 [$ z# c/ W9 P% ]9 l
3 [" {) ~% n) h M5 y& @
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"; t) H8 t3 p$ G: }) S; L' k
7、monitor命令1 c8 v! c! O" v; i8 _9 u! y4 p
0 L- Z d, G/ d2 ^: \
必要情况下使用monitor命令时,要注意不要长时间使用。0 m' S. N6 h( f q5 S' p4 K' ]
三、客户端使用
# w7 ~% _: B- L8 `5 }8 X8 Z! A' ]* D% M# r' k+ |- }: N
1、避免多个应用使用一个Redis实例
- m: l" O# a) L& }5 M7 ?8 v, I6 k& |/ u6 J* ~2 K
不相干的业务拆分,公共数据做服务化。
S3 R O4 v2 L. X2、使用连接池: a8 E3 b+ Y) F; |: E
( }" P1 t3 r h2 f4 s. r
可以有效控制连接,同时提高效率,标准使用方式:
' T' `$ `& ~: W' ^% a8 c# MJedis jedis = null;
- X V0 X+ g; J! a( K B( ~try {: q0 _; K: w5 N7 t% d: _2 y
jedis = jedisPool.getResource;8 Y) n% y5 Z C
//具体的命令
' V' d6 M" j% I. _ jedis.executeCommand! g6 `% y3 w8 V4 Z4 ^4 H
} catch (Exception e) {7 {! f Z) @ B( F7 a
logger.error("op key {} error: " + e.getMessage, key, e);
# C& `; p" K$ S% O/ A7 C9 D} finally {
: n0 V' W c# y% U8 x4 h6 l //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
/ N( t+ ~: r$ \* a* ? if (jedis != null)
: ^+ u, E/ z0 F' o jedis.close;. s( W; F8 V/ ?4 M( Z
}9 X. P J! w2 G5 L8 f8 ^# S2 y
3、熔断功能
; P0 x. E. O- z W& s7 k
. y1 q, q9 X1 r, J2 {高并发下建议客户端添加熔断功能(例如netflix hystrix)) O# A$ t- T4 q; g/ ], N2 e
4、合理的加密
, w& s" \1 x/ w: c
6 U( N1 H% _; m8 R% p- p设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
; ^2 f9 U1 p9 }$ H$ I- g g5、淘汰策略
- f; r& u2 ~9 g$ ~8 V
3 h; e3 u3 `" I) y: `根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。( {% a9 [( [- U
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。9 e1 Z* R9 @. [0 i: f- A: h
其他策略如下:& }5 X0 F; x- L0 N6 g. l
' B, j O/ K% Q( L3 q- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。5 p# L+ n3 O1 W
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
7 F+ {* j6 S' n - volatile-random:随机删除过期键,直到腾出足够空间为止。
4 g8 e1 v2 e; z6 n6 {( ]7 B - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。7 S. F* N0 T; f" b L- [# U
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
* A& z1 E7 V" m 四、相关工具
: a, r- J9 Q5 u' y
2 Z% N( U7 o' h z( d) U1、数据同步3 `: n5 |, M) [. [
! w% O& [ D) ?
redis间数据同步可以使用:redis-port
1 z5 q, _4 D, V" [1 r) K9 B0 x; E2、big key搜索
: ]' `; ?. e/ O& s" r: \* z" t0 a5 x: ?
" h2 t- H8 J$ Z+ n6 k. O8 E" Tredis大key搜索工具2 V7 F2 w7 a& u2 p: ]
3、热点key寻找
/ G- n: t+ E' q
# h \5 z; S# d9 P( Q M8 h0 D内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题2 e- N7 Z7 V9 [2 w/ w, T. o
五、删除bigkey
6 s8 v$ g) M$ e3 |& O
5 |5 }# ^7 ~: } s: p
3 j$ o) c- X4 D( k8 o. r* R7 M4 d: e8 \- 下面操作可以使用pipeline加速。7 ?7 e0 ~2 q( g/ v; Z
- redis 4.0已经支持key的异步删除,欢迎使用。# E) \3 F. Y. w
1、Hash删除: hscan + hdel! l% d4 Q2 e/ b$ d. l, D' M1 z
2 T# T! h$ ~* g) a) p
public void delBigHash(String host, int port, String password, String bigHashKey) {7 V$ K3 a' F& S3 ?* `) }4 K
Jedis jedis = new Jedis(host, port);
/ H% Y7 Q7 `* p, r& o' T0 H: @ if (password != null && !"".equals(password)) {
! e' W {. V. B) y jedis.auth(password);% w) @' o. R( V" _
}
2 ^$ \0 N0 U0 t* l4 Y ScanParams scanParams = new ScanParams.count(100);0 n. S) _- Y( G
String cursor = "0";
% u( K" r- o6 M6 U4 a1 z2 U* M do {
9 m8 x5 \* W5 Z$ ` V1 @ ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);, K( Y# s! r8 ]8 L- U; [
List entryList = scanResult.getResult;
" h! X+ _) B1 _' h5 E if (entryList != null && !entryList.isEmpty) {
% n$ z- V' _& H" \+ H for (Entry entry : entryList) {1 U; t: J" e8 d! j2 ?( G
jedis.hdel(bigHashKey, entry.getKey);
7 u1 ?7 _. X2 k) {) B/ s }
' f, a& w, w9 Y" J! r* ?( q' f }1 z- l- J1 q, M4 I
cursor = scanResult.getStringCursor;
. [* J0 O: w8 z. Q* s+ }! y2 Y0 Q# p } while (!"0".equals(cursor));' |" S5 l" F. C" _- B# w/ U& ^
: ^+ j0 c0 ?6 Y% J
//删除bigkey
# {# \: p! Q+ T: y; [ jedis.del(bigHashKey);
2 k$ w: a: [& p$ W}1 y' O: @/ Z' {% t4 b8 o
2、List删除: ltrim
) j0 o: y# O- C4 ~& Z2 o
- Z- t& r, j) S0 E& ]public void delBigList(String host, int port, String password, String bigListKey) {
2 S! R! x0 q' q! a% L6 H# O7 L' S Jedis jedis = new Jedis(host, port);
! C/ `+ n* V3 b7 n/ N5 J. { if (password != null && !"".equals(password)) {2 G! B) d: b. Q
jedis.auth(password); k6 ~: T# ]* W5 Z
}
' C" ~0 |% G3 r5 [5 S7 i* U( m$ f8 b long llen = jedis.llen(bigListKey);
4 i4 q* D$ U& w# a: w7 W7 r5 z int counter = 0;
@/ W& S0 e; A+ t, D; C int left = 100;$ s. T) s7 I& q9 g
while (counter < llen) {
1 b) d' W% A4 h; L6 O1 p; c //每次从左侧截掉100个
* j3 Q2 T. F4 u- g5 I# V6 Q jedis.ltrim(bigListKey, left, llen);, `) Z8 r3 a6 q# `+ ?) e G L
counter += left;
0 s0 v% g1 q2 n# ?7 a5 E* x. ~, n }
3 T# u8 l! a! ~" { //最终删除key2 C) u5 g( a1 ^1 ^" v
jedis.del(bigListKey);
! B# c% y# b6 G- e}3、Set删除: sscan + srem
) a, Z0 q% D5 i4 R* k/ Q, {1 E! s; i+ s7 @) f5 J: q, y' w
public void delBigSet(String host, int port, String password, String bigSetKey) {9 T, O7 z/ c2 Y1 y- {5 [" h
Jedis jedis = new Jedis(host, port);
" Q% w! c Z4 W- n9 o7 W. B2 j if (password != null && !"".equals(password)) {$ l+ ?; z/ d0 `) j
jedis.auth(password);$ G C, Q5 w9 y/ A% p4 k
}
w n! F6 ]/ M6 I N ScanParams scanParams = new ScanParams.count(100);
9 b7 w) z/ M3 ^1 }7 a5 h String cursor = "0";
8 }$ G% C6 u, ~: l' P6 x* ]) S do {; \' g8 u8 I4 q1 R
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);; H: o# f. @5 B9 @
List memberList = scanResult.getResult;
6 q4 ?6 e3 L6 o/ x if (memberList != null && !memberList.isEmpty) {
9 o; Y* d( Y7 S3 N for (String member : memberList) {1 x# c9 ^/ ^) O3 y" n$ ]- i- \
jedis.srem(bigSetKey, member);
6 L1 o. z$ w; i. ?' v } ~- ~; P5 K0 J |
}
. w( R" v# g+ ?3 K$ [2 s' A! K& ~ cursor = scanResult.getStringCursor;# Z- K- Y$ Y9 T+ G5 }
} while (!"0".equals(cursor));
- t; L A+ A; T) D% G. C6 F" n- Q& _/ a$ L
//删除bigkey
l& i# p) i' Z0 F& A9 [' n jedis.del(bigSetKey);
" k/ }0 k7 H( ?1 U}
& m' F% w8 K7 E X7 ^) _+ z4、SortedSet删除: zscan + zrem
- O3 Z3 e3 c5 A, a9 t
& f$ P0 A6 {2 D/ mpublic void delBigZset(String host, int port, String password, String bigZsetKey) { , c/ W3 ?2 F2 S6 l8 D
Jedis jedis = new Jedis(host, port); 8 f0 E7 @7 w; M$ E4 [4 t
if (password != null && !"".equals(password)) { : ]% ~& X& y: [1 n4 n( p8 V
jedis.auth(password); : h0 @: y, D& a0 C% E$ E$ x3 e8 L
}
! p% x* K( d/ Q4 ?. o- c ScanParams scanParams = new ScanParams.count(100);
3 U" D' J& H' x( ]% \! ]0 ` String cursor = "0"; 1 {8 w; y" G6 c2 t5 n: S# H1 e
do { 0 ]. S8 }4 j, U' s
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); + Y! d! V8 P H. ?
ListtupleList = scanResult.getResult; . e0 W: I9 s, F9 l) E8 T
if (tupleList != null && !tupleList.isEmpty) { 9 K6 I, |8 Q9 L, B- e5 Z+ i: L: M
for (Tuple tuple : tupleList) { % c/ B! Z! D* T3 A! u$ M/ @% E
jedis.zrem(bigZsetKey, tuple.getElement);
2 W- s8 s9 c, J, y } ) u/ v0 R( K r3 B" p, I$ ^; c
} : z: l# s+ z4 L; L
cursor = scanResult.getStringCursor; 6 s H( X2 @7 ]" c' n+ j
} while (!"0".equals(cursor));
' o0 e8 Y& f- p, a- a& X _6 y' x) V# @ A
//删除bigkey
2 Y2 f Q7 n: `: F jedis.del(bigZsetKey);
. P; Q5 W5 `. ^" y3 B9 l H7 C$ x} 公众号内回复“1”带你进粉丝群* C2 d; `& y. o3 S; r# o( d
来源:http://www.yidianzixun.com/article/0LevQm7t2 [. m8 Y C, W9 o, ^; V
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|