|
|

n* {4 L/ Q* ?9 t4 Y本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
% l7 p2 ~2 z; m. [0 Z7 z4 ?* V1 {8 U9 B; y4 w
- 键值设计
+ A* a# Z3 d" r* Q8 W% { - 命令使用& e; {9 g, c- v' O- @4 I; u
- 客户端使用9 [. ]$ d& q& J
- 相关工具6 S3 e5 S- W5 t( k3 |1 i
通过本文的介绍可以减少使用Redis过程带来的问题。
# a) _) O) J7 L" L一、键值设计
- N O5 c: ~( Z" s0 h, c/ {5 T; \6 {! ~
1、key名设计9 X5 Q* W# W3 G4 K7 ?% h/ l
$ Y2 h; \# D5 L9 L* J
可读性和可管理性$ M( d1 u' @/ a8 q" i) b
! q( j% T: T- W. `, X3 U/ P
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id8 I) S9 ]7 m: T9 g
/ }, U, \+ `0 l- ugc:video:1
. i# P# I( M% p8 o8 c8 I 简洁性
) h2 z# V6 z) H2 j, B4 T4 d. R( l0 j9 ~6 Q ^! Y
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:& e/ D0 X! c1 g) M( l: f6 C! H* [
2 V+ P) B: g% f- w
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。2 o, q0 N. S2 `$ C* G# U: B8 W
不要包含特殊字符2 ?/ t. |+ `" W" n
5 D8 {4 Q! l) R w! G0 e! w反例:包含空格、换行、单双引号以及其他转义字符
7 i) T- { Q; d- W* j* D$ V2、value设计
1 d7 q" r' E" I2 C7 g4 I+ b1 k2 t, _1 |" G
拒绝bigkey$ x9 S. D: `, i( F; _' C$ l+ [* y
- k9 h Y) O& k4 v$ S- n1 P防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。: U# c1 K: d6 D0 J+ k4 L
反例:一个包含200万个元素的list。
4 p* F; j7 {4 Y; d非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法5 Z# Z. Z+ p" E
选择适合的数据类型
# T$ i9 L# ~) S( {* g# w* j5 x- f, t6 ]/ }
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
' F) R9 U% x, p' f: _; C反例:
" J: C. \. [; b$ c/ R: G f' i* P% D. Q/ R- j: t
- set user:1:name tom
$ \$ H$ i6 |, X5 x/ V1 o$ F) `8 V0 A; E - set user:1:age 19
) {6 M$ `% L) |3 q) n( v6 } E - set user:1:favor football
3 d; `9 s8 U2 }# i! b% z& q 正例:8 W) I) v4 U4 f3 w$ _; G
4 y4 ~6 a8 J* j( S+ C# E6 B4 \
- hmset user:1 name tom age 19 favor football
* p: U' i- {' N# P, u1 Z- B7 ? 控制key的生命周期
4 C/ i+ C( l! X" Q* L2 l# W6 y7 c- p4 o: n! j
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。! B1 U0 q W0 ]7 l4 Q! t- l9 B+ r
二、命令使用+ r, t# q, b% L% o9 i, U
, b3 v% s- P" K0 K Q- e0 p1、O(N)命令关注N的数量3 t. r" q7 I& y# F. j1 a' G
/ _% W( }& |+ A% s6 Y$ y4 r, S例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
7 M. E: Y9 A# z2、禁用命令: c+ ~' \" j2 k% _8 \: E
6 Z. f+ j# t/ @! x, m* ~" I- R0 p0 |禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
2 L9 K5 i9 N$ J9 I% E3、合理使用select
, f' y8 q: j2 c( k" f" S- ~7 N( R& b: L! n* ^7 H* z: `/ G
9 b. _! C, N' b6 O- {! Q0 mredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。0 T8 x% L5 g6 d- l. d! T* e1 C: F
4、使用批量操作提高效率) w+ f) l$ u1 L4 o
( u3 f( e6 R. ]; G9 L, M& g
) \3 T- g! u% J) D+ {! P- 原生命令:例如mget、mset。
. d+ W% r/ q5 }* `1 d) ^ - 非原生命令:可以使用pipeline提高效率。
) d$ q2 Y" @ P! F5 j5 Y2 O& `6 { 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。6 l8 Z4 \+ f+ z( U3 ^- I. V
注意两者不同:
0 {4 o0 g6 J" x: \: \9 ?) d# y' ^$ d6 i$ V. d
- 原生是原子操作,pipeline是非原子操作。7 V7 p) ?/ E- }5 w4 T# E( s
- pipeline可以打包不同的命令,原生做不到+ {/ j) {+ q j) }- A2 T e1 I
- pipeline需要客户端和服务端同时支持。
, y5 t/ G. Q" R% Y5 @ 5、不建议过多使用Redis事务功能
8 u3 b5 }4 @$ S" x. f1 m( u: }$ H0 `% X0 _% q2 i( N" _
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!& U7 T0 z& S$ `) k9 P) x. ?) a( z
6、Redis集群版本在使用Lua上有特殊要求
3 s# `1 t& g, K( n4 h) M$ Q- `* C$ h4 Z0 b' C) P& K
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"& \8 j& U/ Q9 `& A. \+ _
7、monitor命令" |0 g$ T8 W8 Q# ?" t/ v; z9 s% I
# a' S0 C( g% O- h! @2 Y. L1 Y必要情况下使用monitor命令时,要注意不要长时间使用。
+ e4 e8 e" J2 f/ P" p三、客户端使用. t0 U" C2 U$ n* v+ S k
0 F) Q( W8 \4 ~' N# Q
1、避免多个应用使用一个Redis实例
" u# o1 s% ~4 j' V) \3 _* z+ {7 o0 U7 D1 s
不相干的业务拆分,公共数据做服务化。
; A8 |' E1 b! B+ _+ X2 t- m: X0 t2、使用连接池
2 b' Z5 N8 ]- A& c* W" `3 h
]6 h" o1 Z$ }. |! S- d可以有效控制连接,同时提高效率,标准使用方式:
# g9 `* l7 R; Q6 |: @8 tJedis jedis = null;: J6 M6 c+ A3 } Y O- ^% X* h4 [ L( l. P
try {
$ n. g7 h: B) ~4 m- n" X, [1 X jedis = jedisPool.getResource;& U0 L5 [, \, ]7 y6 y8 T. E
//具体的命令 S6 }' D" Q/ r M* p
jedis.executeCommand7 T j, c( t; r6 D1 d
} catch (Exception e) {
4 ]) W3 \6 p" p3 M, [" ]7 I) i, G logger.error("op key {} error: " + e.getMessage, key, e);3 A1 Z- p7 [- a. Q
} finally {# D2 A* G2 I$ [/ a# g& \! C
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。- }; ~8 N: P/ e: e+ A6 Q
if (jedis != null) ' X' d1 V( Z0 o
jedis.close;2 U0 s: M& Q) F6 |. z a$ O
}
$ `1 l3 a" G0 ~1 K3、熔断功能
4 p; u8 `# z( d) G% o9 u% ~
$ U- h7 f. {* W% U高并发下建议客户端添加熔断功能(例如netflix hystrix)
6 g" _6 P o) ^! d) F& A g6 c4、合理的加密
- M5 ?. D- f: ^8 x2 O/ T2 ^5 v1 d- o" b
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
, S8 w% h) M, _7 b5、淘汰策略
& x7 ]1 l2 }# B i* l; V) Y5 c4 y% i! ]- x+ s2 C; t. l2 c
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。3 Z" H# f( ~0 {. o- O
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。& |1 Z. P* P t
其他策略如下:* d- t+ o. `8 M9 D! ]1 ~
9 O# h7 Z7 o; B: v' a' k: t- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
; B% c; `6 i) b' S - allkeys-random:随机删除所有键,直到腾出足够空间为止。& j* y+ z$ t" B. G& ~9 [
- volatile-random:随机删除过期键,直到腾出足够空间为止。+ w" c; X8 N. C
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
( E* d: ~! |! R) D5 F - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
: Y, w* ]4 y1 [7 m, V6 u$ ]- a% u 四、相关工具# z, E8 ^7 e* ]4 d# Q- `% B4 I1 k
# b; j' R) t. L3 T
1、数据同步
- y- Y4 k4 g6 B
# @% D2 ]9 T& v) g. e4 fredis间数据同步可以使用:redis-port" I) z2 j; ]7 [! E: ]+ f
2、big key搜索, k9 S' g; `* G! I1 o
5 W& _3 v7 e+ Jredis大key搜索工具9 a$ f4 m) @ }2 `# L8 v& K
3、热点key寻找
_) G' `! r* R# t( ^1 B- G3 r6 k$ }) w2 i
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题9 A# I U% p. K+ |/ c7 J& }
五、删除bigkey
* n4 S4 h3 V1 j1 Q* R+ c+ G
; `9 p/ S! L k( @0 t8 k8 S" P3 Q% L. ~2 S3 I4 i
- 下面操作可以使用pipeline加速。% |8 |6 H3 k6 O7 b E6 d( |
- redis 4.0已经支持key的异步删除,欢迎使用。
+ d$ v0 W2 \- [" n% g* R t 1、Hash删除: hscan + hdel
2 {% E7 _ c4 C+ R- H- q5 t
, J4 ~$ s4 J+ r' B3 ^( S5 Gpublic void delBigHash(String host, int port, String password, String bigHashKey) {
" R8 J: w5 D' {4 m: ?# q2 s7 ?/ t Jedis jedis = new Jedis(host, port);* @, k' o" s% G) n* r
if (password != null && !"".equals(password)) {% W I5 Q7 Q% U7 Y2 L: U: V
jedis.auth(password);
4 C& n# N% h: s3 J8 t }
8 T0 u" S9 V9 S" o7 F2 N/ W% t* Y6 l" \ ScanParams scanParams = new ScanParams.count(100);+ z# w4 s' R9 B/ [6 f1 o+ C2 p
String cursor = "0";
, ?* |2 ^! j8 F ^. @6 j do {
* O% b' u `4 Y# `* D; Q! w* {0 p ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
9 s! Z3 n1 d- h6 Y/ m/ S List entryList = scanResult.getResult;
; ]% v7 A6 u: [% v1 P3 m if (entryList != null && !entryList.isEmpty) {; C- m7 M2 D ^
for (Entry entry : entryList) {
: D2 Q3 X7 k& [2 o( l5 k jedis.hdel(bigHashKey, entry.getKey);
" b" h+ @# ~+ x$ D l, Y9 i9 w }
0 O. K3 a( Z8 a: j } A" [2 G# a1 p }6 X
cursor = scanResult.getStringCursor;2 s7 u% L" j! R& e5 q$ ~
} while (!"0".equals(cursor));( T; s: G b* m
6 i+ k, L& D: U7 m2 J//删除bigkey8 m$ C4 [: S; \2 X4 s9 U' Y
jedis.del(bigHashKey);4 e! g. `7 ^8 d
}: a/ @# Q0 A2 x4 I5 w7 y
2、List删除: ltrim
7 V1 E0 m" P# U$ Z* [/ h7 x9 \7 r2 L4 U) s! z+ d
public void delBigList(String host, int port, String password, String bigListKey) {
5 ^, M. W. K& [6 q& x! f Jedis jedis = new Jedis(host, port);0 q& |, @) T! Z" n+ y; P, |
if (password != null && !"".equals(password)) {
3 A& W$ U2 q: d$ Z) ^" _+ m' U jedis.auth(password);
/ G+ P% F, g1 V& K }: _+ F6 ^) ]) p- V5 C2 ?
long llen = jedis.llen(bigListKey);
9 Y0 ^2 ?# O3 u0 ^1 W7 p int counter = 0;( m9 Q* T$ i# k0 s7 b0 x# |
int left = 100;
: q7 @9 Y6 t9 t3 N ]* o while (counter < llen) {3 c' V M9 [0 ~, h, _8 x
//每次从左侧截掉100个
: m. s$ |4 T* A* d& |; H jedis.ltrim(bigListKey, left, llen);1 q3 E+ H7 t6 ?4 f/ u
counter += left;
% l0 z8 p+ Q4 I7 M5 \; m7 ~7 \& J }* B3 J9 P: s8 z3 n
//最终删除key
# E( j" s m4 C( M. d$ B6 v2 B jedis.del(bigListKey);8 U- C# J- O: ]/ c! t0 E% J
}3、Set删除: sscan + srem6 m# o( R1 b2 M4 p+ `8 g
! k* n+ u" {2 f0 x F
public void delBigSet(String host, int port, String password, String bigSetKey) {. v5 P0 x [ v5 e# d
Jedis jedis = new Jedis(host, port);
2 P9 V, q* R# N7 j/ s8 A if (password != null && !"".equals(password)) {
; ~2 ]3 r* G1 i jedis.auth(password);
1 ~9 v5 x! U, k) Z4 }) I/ T" p }5 P+ y6 B0 z* `/ ?/ c
ScanParams scanParams = new ScanParams.count(100);0 ?2 X3 C4 X" o( f# ^1 p+ @* ?
String cursor = "0";
v5 b) o/ W; e# _ do {
9 ?. i* k; O0 K D" v9 J ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
- [" |6 f& k ?2 s* u, q/ r+ Z/ K List memberList = scanResult.getResult;5 X5 _9 g8 U0 ?/ X8 [
if (memberList != null && !memberList.isEmpty) {
; z, `+ D* {+ S0 m' G for (String member : memberList) {
( O$ a- ?# u. a0 j7 g& S8 c jedis.srem(bigSetKey, member);6 U7 \; r! |' F5 \5 h
}5 x8 Z1 _, x8 S) `' B
}6 r5 m& k/ b- `8 M% S1 W% H
cursor = scanResult.getStringCursor;- q) J" v& e3 i7 C
} while (!"0".equals(cursor));
) d! a5 N% Q0 Q
; C8 l, x$ B# t//删除bigkey" O2 H6 A P: k: q; _" a
jedis.del(bigSetKey);
, t' ^. q8 H' ?: m3 ?0 @2 y}
/ ~3 k0 b( _6 g3 N! G7 m# N g4 i7 N8 v4、SortedSet删除: zscan + zrem
+ Y& U5 b5 j- i, p$ t7 d0 y/ X K" Q4 s* h4 b) l, ^
public void delBigZset(String host, int port, String password, String bigZsetKey) { 3 b+ I9 E0 w/ m$ Z3 O$ w
Jedis jedis = new Jedis(host, port);
( | D" D2 J$ W e if (password != null && !"".equals(password)) {
4 L9 C0 i$ L$ D jedis.auth(password); 0 ^5 z" _9 s l/ U
}
3 f0 I- i9 s6 p2 S5 S) _* T4 x6 j ScanParams scanParams = new ScanParams.count(100); 7 T3 m) q! E/ K% @* @$ t
String cursor = "0"; 8 w6 p8 c9 h8 C7 y0 i* O) q
do {
( d, }0 H+ W8 x' a( c# u ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
) P' W2 N/ y1 \. `0 N! x' G ListtupleList = scanResult.getResult; ; e2 C4 e/ [' ?: s# r
if (tupleList != null && !tupleList.isEmpty) { 4 B7 d9 t1 x3 z$ c
for (Tuple tuple : tupleList) {
5 C1 O+ I, n$ t0 ]4 c3 C* k9 h- K jedis.zrem(bigZsetKey, tuple.getElement);
/ O6 ^8 ]3 S, p7 o% p& m9 f0 j1 T9 y } 8 b+ B# B0 u* a1 R$ f
}
# i M+ I5 J$ y' [ cursor = scanResult.getStringCursor;
& \' {* z4 x2 K, ^! v) g } while (!"0".equals(cursor));
4 B5 k8 h7 `" o* m4 a# B% D1 T4 M6 k! s7 \
//删除bigkey . P+ V+ @+ F t* B1 f- |
jedis.del(bigZsetKey); 1 T8 V3 b g% c0 S: @
} 公众号内回复“1”带你进粉丝群
* U& e$ K4 E; ?来源:http://www.yidianzixun.com/article/0LevQm7t
. n" p! ^" U" F1 R, p% s! J _免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|