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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9249|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
" x4 c0 A+ W9 p. G3 u5 F4 Q5 V' \+ X
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
' e% g" l+ D; |& y1 V4 P6 {

      j0 d2 X8 D; K  O! |+ Z5 {  t
  • 键值设计" V7 {" x# Y% k1 n& M+ O! D% ~
  • 命令使用5 p5 E7 X$ K8 X: L( ^: y0 E0 G
  • 客户端使用- i% u0 v: Z: j* q- c9 o
  • 相关工具
    7 m0 l8 _$ d  c& O" o/ y
通过本文的介绍可以减少使用Redis过程带来的问题。- k! B! k" ]1 i
一、键值设计3 Y1 q/ e, i3 a* L: [4 V( ]

/ l3 i% P6 ~* i. \( m: b1 e, _- Y5 l1、key名设计
3 F6 T4 ^, v) ?8 G; j, i7 B4 E; a, R$ C
可读性和可管理性. O: p2 B( ], f8 Y, _/ p* z9 x$ t
& d0 C) F, L7 }9 e% |% Y+ @0 F
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id: g' S* s3 A% n/ W

    2 q) s' s, L: R. _. m% ?. }" i4 E
  • ugc:video:1
    ) ]" B% O/ P3 X
简洁性( F0 R3 p9 P! v5 y4 X
2 Z' y2 N0 p/ c+ x) x& L) Q
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:0 y& p. X; Q) B5 n# T9 n8 D
    9 x9 K% C! J! U- |. t+ d
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。$ ~/ u. a% ^/ X
不要包含特殊字符1 o  P" A; H' C3 u" ]1 B

9 Q& Z0 E. a0 {! Y反例:包含空格、换行、单双引号以及其他转义字符
; T$ A/ {  r% e, b2、value设计
; u; n& f0 X' {) j4 @/ I
0 a2 M+ R# a) ^: z4 x* F2 X: s; |拒绝bigkey! q# P- f7 e' R4 ?% @
, e1 j* ?9 b) ?% u+ K8 I
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。  Q6 l! y. v* N; i. t# ^7 l
反例:一个包含200万个元素的list。; Y$ n- A5 @4 K8 ~- j
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
9 S* Y: }& q7 G选择适合的数据类型
: D4 T( C+ ~6 Y& l
6 Y5 n4 ^$ K% E' `. k例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?& n  @2 S% s8 C) p; s
反例:& L& C" w; S- L  s

    ' L* _) z0 ~4 m* v
  • set user:1:name tom3 `" D) H5 t" v
  • set user:1:age 195 a: e6 @+ T/ F5 q
  • set user:1:favor football9 e5 ^& _$ ]+ f9 g( |
正例:
4 e) `7 i; v/ F5 W2 O
    7 J2 A/ n' [8 B0 q' d$ ^5 Z
  • hmset user:1 name tom age 19 favor football  n& ?4 ]9 l% Z* I2 M7 }
控制key的生命周期4 j! h; _9 C# g: i* _5 K4 @( E
( W+ `" j( s* n1 w4 g
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。7 K' \! j0 R8 Z' O% b8 P% l# Q2 K
二、命令使用- ^6 @) Q( S, C) f7 E
; ]1 M! d. m1 ?0 P4 v! h
1、O(N)命令关注N的数量7 z& @' S- S# H/ ]6 i( O' C9 Y

: i! |! E3 [; R7 ?9 Q例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。8 {7 e: W3 {% H6 u8 p7 K! ]6 {
2、禁用命令
3 y! T/ H' b; {# o
/ F" M" Y" m* g禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。' U- U$ H. y: v8 z* [2 |4 G( U
3、合理使用select
. L, v% ?  B5 X8 B1 {
, u( S% T( A4 {. l, Z; M% S

9 [0 _7 X6 t, P' H. f+ gredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
# T! H' t( Z* s, `4 R, {4、使用批量操作提高效率
  C2 ]- ~. X6 i$ ]
% ^$ }. J" T0 g( F

    % @: _9 ?8 s8 Y, c0 J  x
  • 原生命令:例如mget、mset。
    ( E7 s' D7 Z  k- Y
  • 非原生命令:可以使用pipeline提高效率。
    * [. a! I- q) r" |$ L/ [. D
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
9 d6 F6 z  y9 R; F注意两者不同:
- }" j! K" c' }% H# {
    $ m$ c* t9 m1 K+ y3 {% S
  • 原生是原子操作,pipeline是非原子操作。+ o2 d, ]( ^: T: A7 G' T
  • pipeline可以打包不同的命令,原生做不到
    ( G, F* b: N( P( Z
  • pipeline需要客户端和服务端同时支持。
    * S0 S; ^1 K; |& k& R
5、不建议过多使用Redis事务功能
+ Y0 h  X1 y- g& v2 e- x# c. I, l1 A/ s8 R7 S
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
% ?0 n, Z  m, H- R9 `# t( M) X9 H' {6、Redis集群版本在使用Lua上有特殊要求
8 W  _1 k  [8 X- ^9 m
/ o" J, a# |$ Z" u% u5 j$ a. l) O1、所有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"0 [" x0 c8 c9 ~% q$ @; N% q& I8 o3 c; Y
7、monitor命令9 G" T9 B, n: H2 p3 v& j- H
( W! D1 Y4 O: R# R0 `+ K: |
必要情况下使用monitor命令时,要注意不要长时间使用。" O/ V3 f* [2 y+ X* W- c' R6 _6 u
三、客户端使用0 p) y. o& B; S

& J, b% @9 d! p9 ]# K. N, R1、避免多个应用使用一个Redis实例
  t- ]; q, B5 @
5 f. z0 g0 w; k+ x不相干的业务拆分,公共数据做服务化。5 Y5 R( t$ v1 d6 h/ P5 I  v
2、使用连接池) c! |2 A3 m8 a8 P

. D% ^/ F( V) X可以有效控制连接,同时提高效率,标准使用方式:9 s. O, p% Q+ |* x/ `$ f: V
Jedis jedis = null;. v; ?$ i' u% G/ w! h/ u7 j; k3 N
try {6 G  K: A& b$ `+ i+ C! `
jedis = jedisPool.getResource;  k6 u" |6 E2 p  g" z
//具体的命令$ D6 Z8 H) r0 \
jedis.executeCommand
4 {/ Y1 D6 ]8 ?: R, Q) v; e} catch (Exception e) {
1 M* R0 Q1 ?9 [+ g! H% @, b) @1 E logger.error("op key {} error: " + e.getMessage, key, e);
3 c, f5 S5 G9 y# \6 X- `+ a  }2 q} finally {) D. c* b5 {5 ~
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
: D9 g% |; z, g' s if (jedis != null) ' H& Z' S+ ]5 Z7 C8 q$ }
jedis.close;
0 a- c: ]! b- T}
4 [& R" Z% b8 s0 l3、熔断功能3 b# y. j# j0 y( `! R, ]. G& Q
5 _" H  m7 Y8 J
高并发下建议客户端添加熔断功能(例如netflix hystrix)
0 l  F5 n/ I1 w9 M0 a4、合理的加密: j: L9 j8 T# h6 B6 f1 k

: {  v, c" R8 n3 r. R设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)$ [7 _: U$ O) P6 ]0 h6 I
5、淘汰策略
2 q( _" m, l4 C6 Y- x6 {; k, y* r% S- u9 y7 u& N
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。7 P+ N4 _, f1 E" N
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
. y& f. X0 s4 \0 A& N/ j6 ~3 S其他策略如下:: P8 }8 p0 P+ E/ J( [4 V
    - p* t6 q. b1 j; W1 B
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
    . g- k( R/ W3 ?, l/ K# o4 p
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    * E1 R4 |9 y2 C6 S* `8 e
  • volatile-random:随机删除过期键,直到腾出足够空间为止。+ k9 O+ C" D" c* ~
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    , K; {. w# l) ~( f6 }
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。/ m$ N( k' k- \9 c9 t8 z) _
四、相关工具
$ `! |7 R* D4 w4 e+ ^1 m
* @+ ]4 s% z6 G3 M" k1、数据同步4 p% k5 E  E. ?& v6 N$ }6 `
/ e# `/ F7 i; |
redis间数据同步可以使用:redis-port
: I% q8 e1 Z5 w0 ~+ u2 }' k2、big key搜索* \6 @  i+ v$ Q
; d% I5 ?! J: _1 K0 m% V
redis大key搜索工具. l2 Z- N1 ^% C0 R3 r
3、热点key寻找
+ b( k. C$ b: _8 d+ ^  d
' a; m) d6 A! k5 s2 t9 L" T内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题# S) o$ D8 R! G0 v8 \  P3 [5 b
五、删除bigkey9 z* Z) }; `, N" q

+ b0 z; Q' ^" a4 I
    3 l& J) n2 A- d, v$ ^( S
  • 下面操作可以使用pipeline加速。/ U' x4 R; ^% T; S
  • redis 4.0已经支持key的异步删除,欢迎使用。
    2 U% J. p4 ~* h/ A8 p* L
1、Hash删除: hscan + hdel$ U+ d- p9 e+ H- x

8 V3 J/ L1 j, H$ y* J4 Npublic void delBigHash(String host, int port, String password, String bigHashKey) {
% w1 O/ D* z  q3 J# Z Jedis jedis = new Jedis(host, port);
  |  x2 S: _8 k, t if (password != null && !"".equals(password)) {$ ?  g0 J- F! v
jedis.auth(password);
$ n6 B9 t2 x' f( \7 J' X1 \, G% b }3 o0 L: ]1 k8 C/ P9 ]1 V
ScanParams scanParams = new ScanParams.count(100);5 w8 O% O$ l8 j+ d) w* J7 D, ~0 \
String cursor = "0";/ a, |4 U* {) c0 X4 R* l
do {. U7 u( r. h5 Z& w; i8 H8 ^
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
5 U# ^! @" K* P List entryList = scanResult.getResult;. f# Q- V/ U/ _1 u, d2 b* q  R: k/ q
if (entryList != null && !entryList.isEmpty) {( u0 b, x8 ]+ G* m
for (Entry entry : entryList) {6 O$ N1 {. t% ~# V6 b
jedis.hdel(bigHashKey, entry.getKey);! j8 F. N! z4 `5 _2 O, N
}7 u6 g& P7 \: I# O$ C, A
}
1 N4 X. ~' S- Y$ c* V: U( c cursor = scanResult.getStringCursor;
% r1 R, c' U, H } while (!"0".equals(cursor));
& o' I5 Z6 B9 O' N/ h% h
; u+ b7 c' @& ~; r8 W  r2 H//删除bigkey
3 S. C8 [) W7 n' b5 ^5 ]. r jedis.del(bigHashKey);. D- {2 D: T- d2 N7 x) a0 ^9 j
}
+ S% I7 ^# x: i+ h- r7 p2、List删除: ltrim
+ H$ B' G5 o$ i" H: N% m8 _# @. T; j9 w0 d
public void delBigList(String host, int port, String password, String bigListKey) {$ {4 i+ i6 C$ x$ {5 N+ H7 U& o' y
Jedis jedis = new Jedis(host, port);) V9 z; z8 F# Z7 u; [
if (password != null && !"".equals(password)) {. K* k2 {7 }; f5 x! N
jedis.auth(password);* y& S( j+ P( l6 Z$ _6 O4 h7 D0 F
}7 D6 c* q. M1 z& m" B
long llen = jedis.llen(bigListKey);
/ \' H+ p5 d7 g9 M# Z int counter = 0;0 V. g2 q# t# n4 v5 _( P2 b- q
int left = 100;  {3 D. G: E/ `! `0 J/ b
while (counter < llen) {6 D3 p. }1 x; L# U  ?
//每次从左侧截掉100个# X) i, _, Q4 S$ M' h/ U
jedis.ltrim(bigListKey, left, llen);
# z& t) @1 U/ b: ^, ^1 T& T$ u counter += left;
; q: Z) L) p: d  Q+ n/ j' i }% G9 v7 D$ Y3 \: d  u
//最终删除key
1 z' G' V: z5 Z# A" g. x5 ` jedis.del(bigListKey);$ y! m5 H( h4 [
}3、Set删除: sscan + srem2 A" \+ \  ?3 b7 |9 ~6 V- N8 g3 r
3 ?  m3 f7 I/ f5 X) @+ p4 S
public void delBigSet(String host, int port, String password, String bigSetKey) {
5 h: p8 `# ]1 }- X Jedis jedis = new Jedis(host, port);
6 }6 _  v8 d4 W. x- V7 W: X if (password != null && !"".equals(password)) {1 Q4 d. Q5 g4 H2 S
jedis.auth(password);, B- F; G8 }* B; |9 Q+ `% E
}
7 B: w/ o6 {, G, C- k& D ScanParams scanParams = new ScanParams.count(100);
1 d. u. N/ i, k String cursor = "0";
1 A# ~# p4 f/ K  _# h do {* ], H5 [( l; E+ p# w( i) N$ t
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);1 ]  b& B4 D5 w: F3 i# _) q
List memberList = scanResult.getResult;
; ^: X# B4 ~9 {7 R" H1 } if (memberList != null && !memberList.isEmpty) {. C& U9 S8 s0 l, \, e
for (String member : memberList) {
: J/ ]9 ?0 k6 d4 N0 P( a% I jedis.srem(bigSetKey, member);
3 }' m& _4 g" o& l& t: v }' g, W& @+ j1 d& }
}: o+ z! x& S, P. \
cursor = scanResult.getStringCursor;
) d! X! E' N5 F+ m7 a% R6 I } while (!"0".equals(cursor));* V) g) |/ n6 _1 G
- M1 I0 Q, [8 S* U& o8 i0 ]% x8 Z
//删除bigkey
: H) l: J9 N& W jedis.del(bigSetKey);
- [& |* X+ w. Q( B" t" O- S}
' [) |0 \3 i  F" P  g4、SortedSet删除: zscan + zrem! Q. @- p7 b) ~3 S2 I- V1 w

8 ]+ B0 ~9 c6 F( H2 Spublic void delBigZset(String host, int port, String password, String bigZsetKey) {
4 ~$ G. o  t- B Jedis jedis = new Jedis(host, port); ; l6 f3 l; P& {% Y3 H4 R
if (password != null && !"".equals(password)) {
# x: E6 `: I2 G7 p/ y# ]- t jedis.auth(password);
6 t& h2 X* h  w; Y, L7 W. u( x }
" B1 Q: \' V. {6 O" O5 r" K6 e5 ~$ K ScanParams scanParams = new ScanParams.count(100);
1 C7 W) x. i+ Y String cursor = "0"; 0 D% g3 x' o" k2 R/ u. I
do { ! i0 L9 C, W3 d2 l* u1 _" c
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
9 y" a# s( `# z' [$ w  ~ ListtupleList = scanResult.getResult;
8 ~' p2 Z' r) v1 ^3 G7 c if (tupleList != null && !tupleList.isEmpty) { ) k4 S+ ?3 u( t8 x
for (Tuple tuple : tupleList) { ; K; Z0 z( r) D
jedis.zrem(bigZsetKey, tuple.getElement); " {4 A0 i: y4 e" [- H! `9 }
}
# U) n  H. V7 f } 3 t# L# ^8 P% n, [( {& X: N8 b
cursor = scanResult.getStringCursor;
5 d) m6 s: K6 J; Z6 L } while (!"0".equals(cursor));1 H, A7 u3 g+ x' [) D
7 r( H' @9 ?# ]9 ]* S) \/ ~: P
//删除bigkey " H' n3 A0 x$ o
jedis.del(bigZsetKey);
- u7 @. p+ H# V} 公众号内回复“1”带你进粉丝群+ K* d* ~6 S7 `& F& N" ?
来源:http://www.yidianzixun.com/article/0LevQm7t' G; {$ k* C7 T
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-11-9 17:15 , Processed in 0.059678 second(s), 23 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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