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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9194|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
3 k5 P$ `& k* m) v7 N. b1 }
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
, y: U& t, p# ]* C, O0 c3 j4 G6 i
    # n" h; F* U: g4 k
  • 键值设计
    # y4 }) p" f* L0 D
  • 命令使用) |; p& i! c9 E$ u+ O
  • 客户端使用! ]  n8 v- E" T6 t+ Y+ g! G/ F8 a
  • 相关工具( L8 l6 u0 ^: h7 L
通过本文的介绍可以减少使用Redis过程带来的问题。! Q  N* E% `4 A: b: }4 I
一、键值设计
2 c+ e( |. X2 o% g- t& |3 P
/ P8 Z4 T0 G. p! S; g  ?1、key名设计- H; T9 x1 p( n9 l9 g. u: ]

1 H7 y, h( ?' f0 h* o  ^) a可读性和可管理性+ {2 g5 q( B* M( }$ N$ ]

9 y( r; ^* w! h* @5 D以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id1 \, d+ e' b4 C4 W6 t6 Z

    0 F* U/ R: ]! U8 }2 S( P
  • ugc:video:1' L0 N% F! N! c4 _
简洁性
8 r( E5 J- D  T! P* \5 g4 w' ^$ y. s; N/ s
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:: _; X( c' B! j; L/ o) Q5 v

    " a' N9 ?4 r' o  u6 a: I
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。- I7 _. }! Y% s! J
不要包含特殊字符0 Y4 j, X+ z% s9 V! |2 ?* u

2 B8 r, n  s5 Y$ W9 C# X# y反例:包含空格、换行、单双引号以及其他转义字符4 l! R. F0 r7 l& e# l6 r0 l7 }9 `% @
2、value设计
/ V, q9 H  v' H/ w; y9 @1 t# j4 ?5 A1 `" v. d- ^% L
拒绝bigkey
1 S; l. [7 c$ j2 O4 d3 r9 H# ?  g, n
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
0 I, O. d! h0 {. r反例:一个包含200万个元素的list。' t- T7 D2 S5 m) N
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法8 u3 w7 i$ \9 Z. z
选择适合的数据类型
/ J' Z& F: V: }( J& _8 p# c( J$ n2 |7 N- K1 c9 C
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?- G5 @6 c" ?1 W6 Y
反例:8 @! y7 s- R- ^$ m; W, K2 `
    ; v& z; N0 K8 P8 Z5 r2 D& J
  • set user:1:name tom" H" }# O( I0 j. U$ R
  • set user:1:age 199 _, p& T0 k6 h3 R1 l( w1 F
  • set user:1:favor football
    7 Y- W% ^: ]! B5 ?
正例:. R( N4 N! @" F2 n, r$ N* p4 s3 X
    & f9 J# }( {/ W. y. K
  • hmset user:1 name tom age 19 favor football* W3 X, `3 C; z: O
控制key的生命周期
) G% E0 u: s; Y. x, J. a0 X  f, B$ P) m- l4 ~6 C
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
' B5 w/ y) W! j3 n: h. g二、命令使用
# o& q$ `7 w8 L( z) l$ u/ O- I& q& P3 C- f) s. O3 x
1、O(N)命令关注N的数量/ C) E; p; l9 v+ X$ `5 K

0 h+ |  w/ ?4 u! a例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
& [& n  d; d4 v! G: G# w2、禁用命令& B  i% M0 `" x7 ]* Z

% E6 U6 O4 R6 _% f, f9 k: @3 P禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
2 m7 T- n, c, C6 P/ i. i; o" Q3、合理使用select& o: A5 t# t6 i, c% c' q

) [, ?+ C! C  d. f) d( W9 {0 }/ J! _9 M, ~; @/ t
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
5 `$ {4 ~. r8 o, {8 @) E4、使用批量操作提高效率
9 T" S! r3 p" j$ q0 D
3 r5 ?/ F3 y$ \$ M, L
    . }% g% L3 J: E! _( b* e
  • 原生命令:例如mget、mset。) @$ F( c8 M0 [4 S! |9 f
  • 非原生命令:可以使用pipeline提高效率。$ e2 U0 N% Q4 Z2 q8 g! t* D/ q6 c6 o
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。3 W7 f$ _! n$ j
注意两者不同:5 c- T9 f+ s: g6 I6 Z
    ' l$ S7 }; _: Z% S/ m' l
  • 原生是原子操作,pipeline是非原子操作。
    # x, p5 n- f# I
  • pipeline可以打包不同的命令,原生做不到# I; O) x0 c* R* _
  • pipeline需要客户端和服务端同时支持。
    $ O+ Z' J6 P: E- f
5、不建议过多使用Redis事务功能
8 I9 i2 ?1 Y  E
, [% n  i# K) F* e  _6 RRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
. O8 U. A( S' s. l- S6 G6、Redis集群版本在使用Lua上有特殊要求
, R0 o) E9 i; P, K4 u0 w
0 S1 A, ?# [- f1、所有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"
5 f+ F, h& M' x7 \  E# N/ U7、monitor命令# Q% i" I3 N% B

* d& Y/ F% T2 f& y# P4 a* ~6 ?必要情况下使用monitor命令时,要注意不要长时间使用。
2 w9 j0 y: H5 v- x& H  e: {8 ~三、客户端使用
0 G& h, c1 M' z5 \4 h2 A! a
. }% P1 @% G4 ]% z4 E1、避免多个应用使用一个Redis实例
$ i% _" K: X; [, Z6 `) h
7 k8 E& i0 p$ u不相干的业务拆分,公共数据做服务化。
7 D. w( o9 y, F2 m5 z2、使用连接池5 i1 E' y( y; c& R7 Y+ S
1 Q6 g+ y- c) j  T* s, n- G
可以有效控制连接,同时提高效率,标准使用方式:$ w5 k! f5 c0 Z
Jedis jedis = null;
7 D4 |6 R, C- p7 V6 `3 E% w5 `try {4 H1 W  K  d9 P) h2 e% A
jedis = jedisPool.getResource;
0 X3 I$ G* `5 p2 _$ I. f //具体的命令3 D' a' n* K( v1 {
jedis.executeCommand) w# e2 I: f) u; J8 {
} catch (Exception e) {
+ f6 G7 w3 i. S' [; [ logger.error("op key {} error: " + e.getMessage, key, e);; L; j6 |2 j$ a5 ^. a
} finally {! d8 r$ ^+ a% e( l
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
' ]. X6 R# {. N9 ]8 ~" K4 Y if (jedis != null)
  Q# P2 e" M8 } jedis.close;
; F( z0 \. B5 X! B. B8 d) h1 \}4 ^" G4 z+ f- w7 Z
3、熔断功能9 S4 k) f4 F1 ?: Z" L

' C9 h  x& u" h0 j高并发下建议客户端添加熔断功能(例如netflix hystrix)
1 E1 l3 f6 o8 U( d! w% t# u' N$ c4、合理的加密
" }8 _5 Z! s; d6 [7 E: e; \, p3 G8 M, F8 ?+ T; h
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)% A9 w$ b1 q: x2 d6 T
5、淘汰策略
+ P7 j" \6 w% S+ j, {! l. d. u, d' ^* R% f% [# q" A" }) n
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
. Y3 f* n/ ?% P, T/ M% P/ m3 y7 q默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。% f: ]: c1 W% N. m  g3 R2 r
其他策略如下:
; E$ c9 [9 c' b' T

    % c8 F; L' P' n
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
    " q( y& f, b: d5 n/ b' L  I
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。1 G4 ^4 o1 V+ {4 X, y+ ]7 W
  • volatile-random:随机删除过期键,直到腾出足够空间为止。% u# `3 [. h* _- s! j  T
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。8 u# I* w* J+ F# `9 U: C
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。9 L) ?3 }3 i9 \+ `
四、相关工具' R) P# A9 V1 \% B2 v4 x6 f8 u8 A
  I8 ]* v6 c9 O
1、数据同步2 y/ P! [& j2 ^9 w( R' A: @: p/ o3 W- b- G

0 G# M4 U" w$ H1 O3 j) q) p4 `redis间数据同步可以使用:redis-port3 R" G  C) o9 s( E; W( a
2、big key搜索: H2 M" `2 i$ [. K5 I
5 B  |: k5 ?0 t
redis大key搜索工具' c. e$ u9 w. Z5 N$ [
3、热点key寻找! t4 T: a7 L& I% u2 ^/ B. b" L7 L

# Z: \* v) C" S; `! Q; K) p& m4 |内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
  Q* W' G2 r" C& I: D( r. b五、删除bigkey. W" o1 ~, P: b6 }6 j
7 [+ C- y5 y. v8 U( J2 ]" _
    $ T- H) j: c! V) d5 B( d" L
  • 下面操作可以使用pipeline加速。6 e1 l) J  t- X/ z4 U: X1 Y
  • redis 4.0已经支持key的异步删除,欢迎使用。) e1 N2 ~5 H( \$ H, p
1、Hash删除: hscan + hdel
( z4 n* ]+ w2 F5 Q6 W% A
; K  u1 k. V1 \, N8 Opublic void delBigHash(String host, int port, String password, String bigHashKey) {
! q( w, z6 z" G3 d: h Jedis jedis = new Jedis(host, port);
! t# ^, W& _9 w% P9 E2 D3 E& K if (password != null && !"".equals(password)) {
& t0 ~" ?6 m4 g6 S jedis.auth(password);
: K0 `( D( c6 Y) B! t* o }2 c9 j- L6 @7 {% Y8 @+ F0 d$ F
ScanParams scanParams = new ScanParams.count(100);( x3 S6 R+ O. o- a
String cursor = "0";1 m3 q* w; |) ?' w3 U( q
do {
. I/ Y, U4 Y1 T7 w+ a ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
4 V* f) e9 P; m$ V4 P& Y1 M+ y# [, w1 ^ List entryList = scanResult.getResult;
* W8 g9 ]- m4 P2 Y9 o4 D; z& ^/ } if (entryList != null && !entryList.isEmpty) {
* B! e; Y, S3 S3 d for (Entry entry : entryList) {$ t" B- s7 [; s' }8 v" J
jedis.hdel(bigHashKey, entry.getKey);
/ N  j  l7 n% \+ a. j }" |1 X% m+ [" |: m  z' h7 x
}( W+ Y8 s* ^& {$ B: V
cursor = scanResult.getStringCursor;
) M% X8 x. A5 u. D# I } while (!"0".equals(cursor));
* R/ Y  c, q" s. H$ c# \; u) I* l4 B$ g7 D# [  W
//删除bigkey
1 |5 Q( {, A1 A8 k8 d jedis.del(bigHashKey);0 @  ~% R5 {- n, J
}* l1 k8 |7 e) k3 I' _7 [
2、List删除: ltrim
6 B  `% _/ ]; Z# H, K7 @7 b, |, w7 i# v& n( Z) q, F
public void delBigList(String host, int port, String password, String bigListKey) {4 J+ A( A6 W4 _# u+ r! y
Jedis jedis = new Jedis(host, port);
, D( J& l/ i7 P1 u" `5 } if (password != null && !"".equals(password)) {
4 a+ Y& l1 W# Q; O: g: T* R$ p0 T jedis.auth(password);
+ g+ L# X, `. c/ a  `8 a9 Y; u8 B' j }
8 R+ e3 t  A% m; R" V long llen = jedis.llen(bigListKey);
. F/ F1 u) y. ]: q3 z int counter = 0;* T5 s  b% B6 y. t
int left = 100;& @0 N7 K. P9 F. q8 N
while (counter < llen) {
* l: P) ?- s5 l, m7 a" o( c: d //每次从左侧截掉100个
8 w4 V4 i5 l2 b' n) n jedis.ltrim(bigListKey, left, llen);, ]- ]5 ^' J8 f8 R  V
counter += left;
3 X9 N, ?0 Q) } }! X1 d" d5 b! P$ G
//最终删除key
/ c4 V, |) K2 H2 B6 k jedis.del(bigListKey);3 ^9 J0 i: g+ [1 i
}3、Set删除: sscan + srem
4 H+ Y1 W" B# Q3 T6 y6 \2 d9 l6 u  u
: f  M8 }/ [/ [" Q' ^& s8 apublic void delBigSet(String host, int port, String password, String bigSetKey) {' x) e' x7 @! @  v2 e5 v5 ?: L! C( z4 l
Jedis jedis = new Jedis(host, port);
- t1 U( o* }' m, D  V0 Z, S if (password != null && !"".equals(password)) {
8 E* ^) q) L6 d jedis.auth(password);, N7 V2 _* M4 y1 w+ k, r  ]
}
, W( ?* V7 t) H ScanParams scanParams = new ScanParams.count(100);
7 K8 `" c, z. x) `2 H String cursor = "0";
' x  q* N4 f7 }% s: R( @  F0 @ do {
9 s. S5 K' e+ y8 s# N ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);) F0 _: b/ c* s6 v$ i0 b
List memberList = scanResult.getResult;
& L" N- Y4 ~9 U if (memberList != null && !memberList.isEmpty) {
$ R' Q6 o* u0 M1 u1 X for (String member : memberList) {0 `- g; w* R7 a
jedis.srem(bigSetKey, member);
# \  W( I# Z% N, ^% v+ S3 [6 K }5 _' g' w! u$ C
}* l. x1 `( [) Q& z* R; v* _: R2 u
cursor = scanResult.getStringCursor;
+ ~- l& T" X' P5 Q9 L* s! f } while (!"0".equals(cursor));
- Q$ t" i6 l* x" }6 _9 I; e* D4 e" V
//删除bigkey( \; ~4 T: A" Y: d/ h7 |. J3 K
jedis.del(bigSetKey);
6 D0 I* |% T4 @; g# T0 H' ]}
$ Q( U+ o) h+ u9 c. r4 Z4、SortedSet删除: zscan + zrem) Z: e- T% Y9 p. Y" C# T

3 V( f6 D0 T/ w  ^public void delBigZset(String host, int port, String password, String bigZsetKey) {
( @* [' P+ r7 R7 h* S Jedis jedis = new Jedis(host, port);
, U$ ?4 B7 d$ ~  l6 o& T if (password != null && !"".equals(password)) {
- I6 h/ e1 q2 M+ q5 R jedis.auth(password);
+ t/ Y- p1 ^' h: v  x7 f } . T6 V; m4 X( N2 X- C( C# z
ScanParams scanParams = new ScanParams.count(100); 4 F$ S3 u- T$ Y
String cursor = "0"; 7 l1 S9 \4 I* D0 x$ V
do { 4 r: c" |) H$ g+ Q" s
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 7 J! W, t2 n! t  X/ `
ListtupleList = scanResult.getResult; # E" u! |% p6 s( V/ K1 H( }
if (tupleList != null && !tupleList.isEmpty) { ; L. ^* s* K- Z2 U' }
for (Tuple tuple : tupleList) {
* p# N3 u1 |- J$ K8 i5 |% { jedis.zrem(bigZsetKey, tuple.getElement); # Y! z6 x4 N% v  R# W  W" c
} 2 z* z* B: x3 y2 o) _; M' a- O
} 5 d3 {" {4 @  b& \# {: [$ M0 R, J4 z6 |
cursor = scanResult.getStringCursor;
  E( k/ f& t2 o) s8 \+ v- @ } while (!"0".equals(cursor));
& m7 D0 x  w  O8 i
! q9 }1 `$ }% r0 j//删除bigkey
5 X; L! y6 G1 \* w) V5 x1 B jedis.del(bigZsetKey); . K) j  }6 H+ i; C
} 公众号内回复“1”带你进粉丝群" }' g% M2 {2 ]0 j1 \, Y2 O
来源:http://www.yidianzixun.com/article/0LevQm7t
* w# B3 [. U  n) V免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

关闭

站长推荐上一条 /6 下一条

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

GMT+8, 2025-7-9 07:14 , Processed in 0.037225 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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