|
|
3 X" i0 f+ F+ X: K ]; G$ k, a
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。1 m0 W: C$ b* ?' O
+ g5 R9 K# U8 D8 R/ s3 ?( A2 x- 键值设计
3 b/ j7 T+ o3 H& s5 h - 命令使用
; A9 Z& G/ T8 o/ Z - 客户端使用 g8 f1 @% m% K* R/ }% J( D5 i
- 相关工具3 {/ m8 y; ^) z$ f q
通过本文的介绍可以减少使用Redis过程带来的问题。
1 W/ ~: V6 {* h1 j& E一、键值设计
5 Q! o; C9 U- e/ y# c: F
/ J- A: @1 Q7 I' y$ [; e# U1、key名设计
/ a: G! d4 a2 W6 V
8 C( X+ Z5 \3 N9 H& `5 o+ e) ~% h可读性和可管理性
! g' \: k |& v+ K& b \) r7 L, M* W/ T- T9 g9 t5 Z
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
' ~2 m: G2 ]( c- E$ n% h4 f, t
# A3 _( W+ _2 S8 l8 ?: v; z, ?- ugc:video:1
. }& p6 A: \9 k* f0 c4 T1 Y% } 简洁性7 K. o; [ `* L& v7 [& s: x
( o: S5 D9 w( `
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:' {* g1 F9 {7 w" j
2 [; M1 _, \: _. G* Y# Y4 h$ i- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
' w L4 f# v2 y U/ b4 J' @$ V 不要包含特殊字符; M% b: n+ ~" `+ X( w
. h9 k3 Z- c3 o反例:包含空格、换行、单双引号以及其他转义字符6 t2 {! L7 D' a- \8 @) \* F' E
2、value设计9 q9 m# ~0 C0 {. K- D( R- j/ q N
4 w; W/ k4 q" c& N3 ~) n1 I, I. C
拒绝bigkey" V! B9 u% q$ ~- m! p! y! c# D
* f7 ~* w7 E# n( j/ O防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
/ |) u v0 `; \/ h1 l, r反例:一个包含200万个元素的list。+ ^' e N6 S- S$ J! L7 x
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
. p/ V8 V6 H3 S6 F选择适合的数据类型
- L- }2 Q U) _# i' [8 j% x
( n7 z7 a5 D5 \" m3 G例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?- Y! }( p' z9 J8 ]
反例:
7 H9 b% m' ]9 G# ~2 B' a- v
/ E/ w- d2 E+ G! `9 g" F9 r- set user:1:name tom, R& I& ~5 D& @& F2 K A# C( o3 N) s
- set user:1:age 19
% ], c. O6 w7 w1 X2 ^$ \6 O2 N - set user:1:favor football5 t: t* R( l5 T) s; g9 E2 z
正例:
/ {' n, _% B3 I) {. N$ P5 M9 V7 ~, r+ }0 X. i0 C
- hmset user:1 name tom age 19 favor football4 x7 C6 D0 C2 b7 w' ^. k
控制key的生命周期7 b1 }" j( `9 u7 _4 R- f6 P
% k2 [9 Z! A8 J- M! P& {# J; j; R3 Jredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。9 P: l/ z& }. h* v( w
二、命令使用1 q. O% m4 _% {2 ?7 |
5 i: Q* f! \4 s9 u& h
1、O(N)命令关注N的数量! \7 `) _8 F: Z9 D. y$ ?/ [
/ E, _* c9 M" h# v# i/ W& s例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
+ [6 e/ e/ X+ Q+ S2、禁用命令1 Y1 q' g6 _1 @, F/ @
' f; G9 w* o; C2 v+ Y# `; n
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。- e9 W& z" b6 a2 F5 J$ p! f, n0 h
3、合理使用select
% D5 O# x7 W1 t5 D
# O$ Z+ c) D9 r/ l2 p$ A, Y$ a# N! J7 ^- e# O
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
) B" P/ K7 L+ v% e, _4、使用批量操作提高效率6 |+ G( G/ V/ {- Y3 z/ |# }" ^ @
/ [# L0 d5 G+ L, @- o7 W
: f, q) C- `% T- 原生命令:例如mget、mset。3 ]* I c( f1 |& a' J
- 非原生命令:可以使用pipeline提高效率。' J6 {2 z% D; s( b, r
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。( B/ p/ J% t# M$ n
注意两者不同:. B5 y2 ~4 i2 p8 E4 ^' }
' l$ g: g# W( `" W. l- w1 ]
- 原生是原子操作,pipeline是非原子操作。, e, S4 _2 ]7 c) d: e
- pipeline可以打包不同的命令,原生做不到, V9 { w7 e- q2 K5 a
- pipeline需要客户端和服务端同时支持。
! v; m( a$ m t; r; Y 5、不建议过多使用Redis事务功能& o M* g# r7 a: }8 ~! m3 }; d
# K5 D) R9 u/ T$ j5 |( b( N
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!6 V- w* m4 i7 H0 J
6、Redis集群版本在使用Lua上有特殊要求" j$ R l# l8 h1 a! W" n F
8 U3 K* Q( Z+ `- S3 f# J1、所有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"2 Y( c. r0 `( ^2 q& }7 P; y4 F
7、monitor命令
- c/ f9 m) L7 t) C; g Q0 v
5 Z; H% U3 v y必要情况下使用monitor命令时,要注意不要长时间使用。
/ e% p' [- } t+ [; E8 d三、客户端使用6 d0 j2 C5 M% [- J5 Q
" J+ y9 w6 {+ Y' Z; E& [6 O& A- c
1、避免多个应用使用一个Redis实例' `1 N# y" T+ V0 |! t( H, o
5 \1 ~( n# n5 x不相干的业务拆分,公共数据做服务化。
/ M1 U- S9 s4 l6 A2、使用连接池
* e, j) w3 J& @3 F9 A0 {' V7 q
3 G1 s- D' o# q) ^' m可以有效控制连接,同时提高效率,标准使用方式:
8 L: j0 `. j% _. h( `- N# [$ d5 BJedis jedis = null;: Y' F/ D7 ]; H
try {
/ g! B O2 J4 Y3 _3 p1 N" `% L jedis = jedisPool.getResource;! @; G* o5 A4 F: b# F
//具体的命令
. H3 d3 a# ]. X7 }6 z- F jedis.executeCommand" r% e0 @% d5 i6 j" r* C( t5 Q
} catch (Exception e) {
. ^( p. z* l) a! X+ [ logger.error("op key {} error: " + e.getMessage, key, e);
! q' s9 ]$ B) B( P( M( ] I* V/ Y0 ^} finally {; V1 m& p W. o/ q5 T
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。6 H W1 C, s0 b6 W9 L; s3 P
if (jedis != null)
( a' \! R; W/ U jedis.close;
% @. A( C* G8 P}
6 S. m, y7 `, g$ O: d3、熔断功能
6 T4 S! {* q' B: h; K( Q8 f8 g6 |" k! u8 h
高并发下建议客户端添加熔断功能(例如netflix hystrix)) |' k* A2 H. ]2 b( \: K! u
4、合理的加密, ~0 s. ]0 e3 r; o: w
! [! f! p& s. |# f# d$ T9 v
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)$ l& n& V7 W2 ^5 U3 I" }2 C. \
5、淘汰策略
' t; _; U. t$ G; d4 E
1 u1 ]' @8 Z8 d根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。0 O: q7 S- ~! Q, l: @
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
& V* T" n# _2 i( a' K; L& X6 V其他策略如下:& F& z( L6 k: g: D3 H- X. I
, }7 P0 t7 q6 @* \
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
. V4 O0 D, T$ B& e8 W, w - allkeys-random:随机删除所有键,直到腾出足够空间为止。
6 ?+ f& ?* m8 p5 r$ P# K3 I - volatile-random:随机删除过期键,直到腾出足够空间为止。
, _$ J4 @7 M* n - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。4 B7 Y, W# Q5 s( `. A2 ?9 R9 b8 O! C
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。6 J" A# G/ x* ^$ r1 \1 E
四、相关工具1 g* t9 F7 b) E, R6 O
" i5 G% Z* s6 o1 G1、数据同步
7 r* @4 p9 i0 b6 D! |0 L7 Y. W7 n
. J* v3 M+ |5 z& C7 `redis间数据同步可以使用:redis-port
7 D2 h1 x& w% @8 ]2、big key搜索
2 o# V. c1 ]9 i; d$ l4 h# f [, A
- b+ T5 ? i3 f# \% x3 predis大key搜索工具
0 D+ p/ r, h1 v2 p5 Z3、热点key寻找
3 L& q( {$ L q4 d; R7 f
* u' }$ [" s1 V) ~, P内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题6 Q* ?7 s/ W5 I0 w i5 `( w5 {
五、删除bigkey
# s& n, Z5 p8 u, d; L& W
- ^1 [3 Z9 n4 I- p7 r$ e- ^
# M! l, W9 V- w9 p- 下面操作可以使用pipeline加速。
( r9 u% L! V% Q! s: ^ M5 S1 U% W, e - redis 4.0已经支持key的异步删除,欢迎使用。
$ o+ @( `' r/ _0 j, q: {; _6 ?2 Q 1、Hash删除: hscan + hdel
2 y0 p$ P5 M5 E5 |6 r3 q
9 X( ?. N( C5 m! H* _/ u fpublic void delBigHash(String host, int port, String password, String bigHashKey) {+ P. q8 D" d' r; q) s1 f2 E
Jedis jedis = new Jedis(host, port);- C. A: [6 P6 P; u k$ ^
if (password != null && !"".equals(password)) {& ]6 S/ H. O- k+ M% u
jedis.auth(password);
0 D+ V5 U0 {- I: R0 a- d }1 t5 v- E$ u1 B, \* q
ScanParams scanParams = new ScanParams.count(100);
. J/ e* d q: _0 X; o String cursor = "0";& T( x+ C) p6 z+ c- j
do {; m3 F# ~+ U3 b+ o% X
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
) A: E6 K4 \" @- V- _7 h List entryList = scanResult.getResult;
. I# {+ j2 N- V1 W" ?9 [- e) ^ if (entryList != null && !entryList.isEmpty) {
& P; w/ m! y( \- h7 F" u% @ for (Entry entry : entryList) {# ^* [1 ?& M# n, Q" j
jedis.hdel(bigHashKey, entry.getKey);( B8 X4 o* z: ?
}
7 R2 r# C& y- J* f4 L/ _4 m }
7 w9 C0 U4 p. c; L4 E- J cursor = scanResult.getStringCursor;0 ^5 i, E8 }9 k: u6 o
} while (!"0".equals(cursor));
5 T1 v6 V$ j6 h& j; Q- v: L3 c. _ c* m- U+ b6 D# p9 a
//删除bigkey, Y4 P" ^( r1 g- N4 ~$ j
jedis.del(bigHashKey);& l% g) c9 T6 a/ A$ R& E: M( s
}
7 O) D* B7 |' |$ [1 ~$ O0 r2、List删除: ltrim
+ \, `+ t6 | |
* ~- J# A- ]) U. Spublic void delBigList(String host, int port, String password, String bigListKey) {! f1 h( H3 U5 Z* Y( h9 e
Jedis jedis = new Jedis(host, port);$ e: ]& y# U; O" _7 U0 N+ Q
if (password != null && !"".equals(password)) {
, X+ q/ x, B* f jedis.auth(password);
- j5 {6 Q2 s l3 e }
7 b1 d, |/ h D7 o% @7 I long llen = jedis.llen(bigListKey);) C M# i( J9 s" m' R
int counter = 0;
$ _* e, [7 b4 {+ Q8 N int left = 100;
9 C. _, L2 S& ^ f. r while (counter < llen) {" N! v' }1 K5 ^' ]+ J; E
//每次从左侧截掉100个
; w2 L7 R9 k- o. ~6 ?. {, O jedis.ltrim(bigListKey, left, llen);: Z# a7 K- c M9 b, Q5 s' b) l
counter += left;. m4 H3 R# D0 [1 ]3 s$ s
}
, l" F1 E6 M3 i9 \ //最终删除key3 k; U* p& P9 A% Y$ L) s6 ^$ a0 G, k7 i
jedis.del(bigListKey);! X3 N* J4 J1 y* r3 E
}3、Set删除: sscan + srem
5 P, X1 ?& l1 [; v0 C& g8 ^ X# V3 R/ t. l% Y
public void delBigSet(String host, int port, String password, String bigSetKey) {$ W- A% Z0 K+ a- S
Jedis jedis = new Jedis(host, port);4 { r3 p9 z' U) B/ f" W
if (password != null && !"".equals(password)) {. y: U% E5 x; X3 Z! Y' L! }" ]7 ]
jedis.auth(password);
) a: L8 I) T3 ]" y, {# O+ _: r }
% I* y0 j$ d# ?/ g, S# T6 I ScanParams scanParams = new ScanParams.count(100);
% ]& ~9 c6 c" c: E) [ String cursor = "0";
7 f. y: Z+ _$ o do {& @" A4 ^+ E4 i! ]
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);+ v. y- H2 g* i! R. z. U2 E# C
List memberList = scanResult.getResult;9 ` n2 b) ?( O* ?
if (memberList != null && !memberList.isEmpty) {
8 B0 D! c* z. Q/ ]! J& T for (String member : memberList) {
- {6 H* W% x% L+ l% r/ x. ? jedis.srem(bigSetKey, member);
7 C" N$ K. `- r" w2 O& d, y) I }$ ^) D) F6 E7 X1 W4 e( ^# h
}
0 ]5 j! G6 l( H5 U8 C cursor = scanResult.getStringCursor;. l! V" }, ?& w& ~* t1 h
} while (!"0".equals(cursor));
# R7 J, w9 T1 p1 @# l( K+ t3 U0 ]3 w0 |/ M' y+ Z9 D: ~
//删除bigkey, ^4 t$ }& \3 j7 i9 K, n! j6 F. a
jedis.del(bigSetKey);
, R T- G: P5 r: R}( r0 K4 Q0 k' p
4、SortedSet删除: zscan + zrem* x t/ e. v, h1 Z$ U1 j. I7 K
% P; e( r) w" M! A3 t$ ~# F/ u* g
public void delBigZset(String host, int port, String password, String bigZsetKey) { 8 n. N7 a, G ?) l* O! Y8 V! O
Jedis jedis = new Jedis(host, port); 2 {1 P* r7 Z6 }( o1 A6 S
if (password != null && !"".equals(password)) {
( j% m# Y( f/ p3 ?$ X- U* N jedis.auth(password);
5 J7 Y- ^9 Y z( X }
' f) b" @2 E @" [ ScanParams scanParams = new ScanParams.count(100);
$ C5 n6 @& @5 o2 P: r String cursor = "0"; , I, o0 j/ v4 F8 W3 W) E& ~
do { ) Z1 l1 ?+ \% S- @" f( Q3 ^
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 9 C; u- y( ]2 [) ]1 ~
ListtupleList = scanResult.getResult; 5 l& v, Q- Q! B# d" E
if (tupleList != null && !tupleList.isEmpty) {
7 a& G( ?* h- i- u3 Q3 | for (Tuple tuple : tupleList) { ! S1 O! F: ]" C8 i [- ]* W
jedis.zrem(bigZsetKey, tuple.getElement); $ |4 A+ ]. a. w7 X
}
2 v) m1 l& y' q } & k+ I# `5 k# K; {, \
cursor = scanResult.getStringCursor; K! P+ X8 P9 w, a3 G: P0 i
} while (!"0".equals(cursor));# S% j, q3 u' C: y' ~6 P* A
$ I- G4 L- b# {3 c7 x) e5 y
//删除bigkey " C* i& _9 S& {. X8 Q' ?
jedis.del(bigZsetKey); 3 J7 K0 e# j& O) C
} 公众号内回复“1”带你进粉丝群
7 T; Y3 P+ t* j4 t& ?1 W- c. R来源:http://www.yidianzixun.com/article/0LevQm7t; I" l- _8 b3 f% K8 l
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|