京东6.18大促主会场领京享红包更优惠

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9237|回复: 0

一份完整的阿里云 Redis 开发规范,值得收藏!

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国

  }. I. k: v! Q# G( |本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
3 I* x8 I  V4 n+ v# K
    4 z& r. e2 g; V$ p# l3 b7 S
  • 键值设计5 k" X# C% e+ c9 a* E4 u. A4 K3 _
  • 命令使用
    7 @# o9 ^- F: j* N# r" m) V/ b
  • 客户端使用2 _( P$ [/ L8 r( C
  • 相关工具
    % M+ ?$ x. J( a5 }
通过本文的介绍可以减少使用Redis过程带来的问题。
: ]+ B6 G( j) G5 F, C一、键值设计
0 U6 C9 |) M% P9 {) [( [. p3 S
# m7 E/ }2 Z) ]" h1、key名设计
% ]+ t9 g( U1 `" Q3 W5 D; ^2 A- v! X: D( I  V7 z* C2 x- U/ ?4 V: s# M
可读性和可管理性4 [1 r9 ~9 |' L0 c% b
1 A6 b9 _% @- I0 j; P# Y
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
  f3 E, ~: Q: ~( G: f
    9 X7 }# }0 Y9 c
  • ugc:video:1
    $ ?5 {  a2 J# ~- i9 s
简洁性
6 A4 i: C" Z8 |, V5 f6 g# ^, s
  j" K' s# M6 ?: G保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
+ G# A$ w7 h6 n- u% r
    6 a# |8 n, b% B( |* {
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。( s7 f/ F9 H5 V- h6 O6 N
不要包含特殊字符5 p" a9 [- [9 [& [. p% m

5 U, X  l8 l; ?5 Z6 c; X  Q反例:包含空格、换行、单双引号以及其他转义字符/ S$ G: e& i# G0 {5 R& S
2、value设计
  s: n$ O  l" Y& J* }) q
' G6 z' S, _$ p$ g9 Y/ |拒绝bigkey$ t" t! Z9 Y5 T0 m

  ~& ]+ u% Z. W; B- v防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
1 W0 Y) S- R# `4 X8 O0 D/ k反例:一个包含200万个元素的list。  A2 `) l6 g3 x; L
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法1 C+ o" O' g/ H- r# t/ W; _
选择适合的数据类型
& i. r% r0 V7 [
, y5 E0 {/ B( Y! G; R例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?1 \7 S, I1 W) g2 ]# P* m0 e; F4 h
反例:. D1 U# q) x$ x
    4 G1 R" F$ _  z9 A
  • set user:1:name tom
    , c8 T* o/ _) w3 j7 G
  • set user:1:age 19) G, Q+ v9 s, i- W3 j2 C' @
  • set user:1:favor football
    $ g+ T2 w) g' ^% e
正例:
8 o! c4 [" G& S; b7 G' ]9 E% h! f

    ! `2 Q' n* A5 |+ Y" |& `
  • hmset user:1 name tom age 19 favor football/ s) g6 ^9 {9 @7 g7 \5 l+ A8 A
控制key的生命周期1 ?( f! A" z2 C' w

; p; J$ g/ B8 z; oredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
9 `4 p. w# N3 F0 n) q/ b1 d8 i二、命令使用' b3 V7 x- J9 q' s, m7 t* {
3 \5 e2 t$ f. {# Q: r
1、O(N)命令关注N的数量
4 v. Q4 t2 j) x( E5 |1 O# A, [. E
" h6 }& i0 t; I3 \例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
+ y# b/ _1 Y6 `7 i# y  @2、禁用命令
& x0 W6 ?% c% @6 H9 f- S( A/ Y6 [% E
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
) H& q+ l, C# ?/ Q' _( H4 [3、合理使用select% y2 {! z" ^! t" C  K) d

  M6 s) H' a; O% V) x3 Q
# @: |& s7 b3 U) X/ E# A5 Sredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
$ V( S; r$ d* T+ u4、使用批量操作提高效率; c1 W( e% z& Y6 k. S% s8 ?

) E2 J! m2 S- ]1 j$ f

    2 Q8 Z( U+ p& n
  • 原生命令:例如mget、mset。$ l3 Z6 `3 m0 ?! w, B
  • 非原生命令:可以使用pipeline提高效率。5 A' Q8 i9 u$ m5 o; E
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。, X8 [) R/ S3 q2 n7 u
注意两者不同:" ?3 Q: D6 H- \: m9 W9 S
    9 P+ Z# n% A/ y* _" ?. Y& a
  • 原生是原子操作,pipeline是非原子操作。0 S& }: f4 J$ X6 z5 O
  • pipeline可以打包不同的命令,原生做不到/ V8 T# }! Z. H. t7 @
  • pipeline需要客户端和服务端同时支持。. F% t. `8 ?/ p2 E+ K/ y; _
5、不建议过多使用Redis事务功能
, h  x; n4 U) M6 k# J
$ U( X# [6 ~; G1 MRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!: `" p+ U, U5 w) C6 }
6、Redis集群版本在使用Lua上有特殊要求5 ]. F+ n; @7 D) I2 r
% y9 |9 q: ]. F- [, h2 g
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 F7 e9 @$ b9 R( i& y" o7 [9 U
7、monitor命令% m( Q/ F+ U) G8 H  t( O3 M7 k5 u

9 C/ F" H6 \1 k/ O4 W- r必要情况下使用monitor命令时,要注意不要长时间使用。. L: Z4 I3 o6 K$ p) Q- n0 A
三、客户端使用+ k3 _5 O3 k. N

! P4 o/ \( r6 M( c7 K) s) u1 O1、避免多个应用使用一个Redis实例
) ?2 [  d- A! B" g) F; G5 w. A- |& e& ?# B# n! e$ @) F7 L. k  D: @
不相干的业务拆分,公共数据做服务化。
' K0 n! }. Y+ W$ D: q  u  e$ v! }; f2、使用连接池
5 r: L# P7 T/ u/ O4 {( `# q9 g" \. V$ K6 {) j6 {
可以有效控制连接,同时提高效率,标准使用方式:2 {- u8 t: f6 Y) n( j+ [% E
Jedis jedis = null;
+ A8 K4 f/ R( i: ltry {5 i) W- X: R, r$ t+ P& r8 F
jedis = jedisPool.getResource;
: W6 I/ P$ Y8 w6 A- r3 r" B: m //具体的命令
* D8 F5 _  v# K4 o! K# s jedis.executeCommand
5 R/ u# B9 G) h, I} catch (Exception e) {  u& K) Z& u6 w, G
logger.error("op key {} error: " + e.getMessage, key, e);- U, u" p5 I7 A
} finally {8 o* J" a3 b  |  `
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
' Q8 W# A0 V2 [3 ^- M if (jedis != null)
, }& z' L  p" j7 R! j2 \+ s) d5 y; ^+ W jedis.close;9 b# I, i2 J2 x* _) R8 y
}$ k1 G( H. v) S( m' C% g5 R
3、熔断功能; c% e& H' D: W# C1 Y9 @+ ~4 }5 f# d/ X
8 }1 J# L) w- D0 \; B
高并发下建议客户端添加熔断功能(例如netflix hystrix)
0 l) e0 W* S( p4、合理的加密# S1 F  s& i1 I" {8 c
% ^& V+ \9 L. ~& N
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)& ^* i) S; U0 T
5、淘汰策略4 F9 D# L; S0 ]5 Z2 }  \) a
, a. x, ?5 r! r3 h3 z  S
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。1 T( Y7 l- L, B/ M8 w
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。* E: m. F0 n! o8 E/ M* n
其他策略如下:
+ B# {7 ?1 O7 j' m
    ; a) Q! @# x' t, w
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。$ {& e8 s$ R# {( j! \
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。% }' f9 h5 S6 C) Y" V: p6 j
  • volatile-random:随机删除过期键,直到腾出足够空间为止。4 {5 ]# [( @/ n' y- @5 W
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    " [6 b( F5 V: ~' V: J7 [9 d# [- g
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    & x+ A) g  ^  i: A
四、相关工具
2 U- ]0 ~; o+ Y/ F2 x
: ~0 B; a& a% W! m# ]0 @* k1、数据同步4 b" u/ E: z- K, F0 w6 ]  e" |- X

* `8 O3 v5 Q; l; Jredis间数据同步可以使用:redis-port- g5 f1 e+ Q; [" Q
2、big key搜索' Z, x* x/ A" k+ ~) W' z8 C, t

7 N% r( `& J' O, H) U/ {8 R7 Nredis大key搜索工具) L3 [( I2 O2 @, R, W
3、热点key寻找9 X' I6 _0 J, X5 C

6 S' K6 |: `5 e: J内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
# }+ L1 V  m: J6 m3 @' L五、删除bigkey" j+ V" U& O2 Z& B1 j

5 R; q" ]7 O( l1 }% i

    1 b# h# x3 w0 g
  • 下面操作可以使用pipeline加速。2 s' x+ @. e( A; O9 p
  • redis 4.0已经支持key的异步删除,欢迎使用。
    ! S, }) x% e" ?, H& I! k1 e* C
1、Hash删除: hscan + hdel
) j. G- M( c, N* f' m8 [6 n& _
5 m$ P2 ^2 o* _9 Y2 zpublic void delBigHash(String host, int port, String password, String bigHashKey) {1 B7 e4 F" G) M. j
Jedis jedis = new Jedis(host, port);
2 T! w+ S# h3 \, I2 ~1 B if (password != null && !"".equals(password)) {# U0 b7 L" w: i: S* I+ P6 g# A2 f5 E
jedis.auth(password);
" p0 P* |, E7 C; O2 N) l }
. U# D, }0 G! F0 i5 o ScanParams scanParams = new ScanParams.count(100);
  c! y7 k* q$ Q2 O6 }! z6 Y- | String cursor = "0";! O, O. z+ `3 R6 X, @* b
do {7 s, J# y, W! B! {" r
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
" ], A7 ^) j9 w9 e2 i List entryList = scanResult.getResult;* h: p# H* f8 d$ i6 O' @6 ~* z
if (entryList != null && !entryList.isEmpty) {- _6 A" n6 O( w9 w5 ]" D6 d
for (Entry entry : entryList) {5 i1 ~. e- i. [. B% P
jedis.hdel(bigHashKey, entry.getKey);
: s6 `; j% r! P9 R }
4 o* Y, F6 E. t) g }! q+ {8 n; ~5 Q/ D) ^1 \: h0 K
cursor = scanResult.getStringCursor;
, P) _* D- k5 @ } while (!"0".equals(cursor));+ r& Z0 {& h2 U% M6 P

& u: k: ^1 ^4 {% b$ D% L4 G8 {9 v//删除bigkey
+ H+ \8 B, N' G6 |' ]) E& ~/ c jedis.del(bigHashKey);
0 G: ], ?1 `. m! I: o% H}$ E# j% b2 F; I8 r& v
2、List删除: ltrim) [9 I" `3 m  u; l" e

3 Q2 i$ {4 G! |1 r. p2 S9 [% z% b9 Ppublic void delBigList(String host, int port, String password, String bigListKey) {0 G( B/ P0 Y# q: {, y5 x; S
Jedis jedis = new Jedis(host, port);
6 {+ L- t+ j" D; [9 N if (password != null && !"".equals(password)) {
: E' m, w  F* A$ a4 O9 N" p3 R) ? jedis.auth(password);6 W  N5 e/ z4 m
}" i0 h* m  }7 c: p' Y. G4 {
long llen = jedis.llen(bigListKey);
# ~" W" n; D- A' T! k6 E; K int counter = 0;8 p: U: w0 `: T& O4 A- b% T2 \# ?6 s
int left = 100;) x1 ~- @7 n! N
while (counter < llen) {
/ [1 N3 z" e8 w% L1 u //每次从左侧截掉100个
# b) _0 C: \/ s( M& J jedis.ltrim(bigListKey, left, llen);
8 {6 c3 G7 J2 I: X2 B counter += left;
# R, r) D+ z% ?$ Q) F/ Q+ E' b }
1 I/ U% I8 P5 w //最终删除key8 g0 C- ?* e7 x$ ?. h+ p
jedis.del(bigListKey);5 z8 [3 ]2 \. w0 w5 Z( `$ ?% \6 ~
}3、Set删除: sscan + srem" U4 y1 H- `9 {. e- a' Z; |
& V$ V' ]/ a% t9 P, R, j
public void delBigSet(String host, int port, String password, String bigSetKey) {6 C( D1 C8 J; L8 Y' R% d2 @% @8 i
Jedis jedis = new Jedis(host, port);0 m- U5 P6 ^9 u$ M4 P* B. ^9 s
if (password != null && !"".equals(password)) {! E$ F- Q/ ]+ T" a( J+ u
jedis.auth(password);& c- H; ?! W/ B, f( D
}6 Z$ z7 M' u; f3 t, p# Y! C
ScanParams scanParams = new ScanParams.count(100);
1 `+ z, j& V- a7 q, \9 p  l String cursor = "0";- o) W: p9 P2 i2 t
do {
" E4 u$ \/ L" r0 i  f ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
  f  d1 J7 B- i$ c# f List memberList = scanResult.getResult;
. u$ q  w( N, f% b& \" w- f if (memberList != null && !memberList.isEmpty) {
! S3 [, Y% u8 V for (String member : memberList) {
/ b6 _+ \% ^- O2 ] jedis.srem(bigSetKey, member);
2 @9 T' v2 w9 G. ~- E" h }5 h. O. F: E8 v% `# ^$ \
}
3 V3 v5 a, B4 W& F cursor = scanResult.getStringCursor;
, u+ l1 `; Q5 R% X; E } while (!"0".equals(cursor));
3 p% W8 ~, O2 M; e+ t& a# X" j$ I/ U& B8 ~! ?) f
//删除bigkey
# b2 H- l; L* i" V- j jedis.del(bigSetKey);
2 K9 o" `! n# T7 Y) X) E3 a- Z! `$ U' W}- \/ S* i4 ^3 j+ W. `
4、SortedSet删除: zscan + zrem( o/ J: x" I- O) `% z

, |8 U; s" L( f! x7 Cpublic void delBigZset(String host, int port, String password, String bigZsetKey) { " a1 m0 O& H6 b1 @3 V
Jedis jedis = new Jedis(host, port); : H& I% ^) v! I- z( d, ]
if (password != null && !"".equals(password)) {
) p- [2 o! r" K# E jedis.auth(password);
* F0 `5 X: |6 X/ Z } ) n8 n9 g* U" X0 |- C2 a
ScanParams scanParams = new ScanParams.count(100); + h& H. D, b. b# s
String cursor = "0"; & f" H  E3 Z2 a7 S- C
do {
% O+ p& F0 z1 O4 K( O ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
7 i$ e, K1 Q3 r5 [  F ListtupleList = scanResult.getResult;
- c7 v; p- ]0 {1 I8 O( X if (tupleList != null && !tupleList.isEmpty) {   P  h8 R* C& [0 _6 P. V
for (Tuple tuple : tupleList) { 4 ^7 ~$ Q2 U$ W, W# j( m* a1 |7 `
jedis.zrem(bigZsetKey, tuple.getElement); , m1 x& ^: L- h; m$ X7 F0 e
}
& v& A6 R7 v% A( ]/ t/ j8 P }
3 X2 Q2 [+ g1 U3 b cursor = scanResult.getStringCursor;
7 m: E  v% {% F- v% c/ H% }! G1 ? } while (!"0".equals(cursor));* Q, O4 V2 ?- Y* P& |* O' u2 |7 m/ S

, H7 L+ H  R2 [2 A. U//删除bigkey 7 z0 {% v7 M) V& Q- e1 V6 W
jedis.del(bigZsetKey);
- t9 X( ]( A. p; c} 公众号内回复“1”带你进粉丝群' Z5 n, b+ v3 q& }; t: N
来源:http://www.yidianzixun.com/article/0LevQm7t& Q  A: c! s6 X+ Q( e1 [
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×

帖子地址: 

梦想之都-俊月星空 优酷自频道欢迎您 http://i.youku.com/zhaojun917
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|梦想之都-俊月星空 ( 粤ICP备18056059号 )|网站地图

GMT+8, 2025-10-26 01:57 , Processed in 0.037244 second(s), 23 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表