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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9465|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

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

6 A6 T8 r1 e: q% |+ A本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。. S" Z- E. ]" o! R# F8 K$ {

    / a% N$ B. Z# }0 R. m8 e
  • 键值设计
    ; Q) K# d" F: O. ?
  • 命令使用
    2 T3 v, v0 ], M% A
  • 客户端使用
    : P! I) u1 D& t, Q; x1 r
  • 相关工具+ I: s. J- }" V, _- n3 d
通过本文的介绍可以减少使用Redis过程带来的问题。; V6 M: Z* H# J1 a+ ?, w5 b1 r
一、键值设计7 n" k0 Y. v8 ~* U# }* _
. R: k' T: O. B2 O# J) _
1、key名设计
. y; J$ U$ q* l' p9 F- T, _9 |
  ~# }: B6 J) V+ p' q6 t' @2 B可读性和可管理性
. m% T; L  c/ N5 h4 z, _" y0 |% ?5 [# w* C" I1 m
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id' I3 Q6 l: f( T2 z2 T
      |9 T0 i" S4 B* z& Q
  • ugc:video:1) o# n) i( A* {; H+ E: p: B) L
简洁性
1 H6 W2 y7 n* t& x/ q0 C: q* f) m. \
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
# n. _6 s! |7 X- x

    . a, X: `: j/ q) R4 z/ ?
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。! G1 b9 ]0 f. Y! F  I
不要包含特殊字符
) W5 q" F. D% ?( E2 L6 o5 X& z- c( W- t" I
反例:包含空格、换行、单双引号以及其他转义字符; E, o7 M& j3 h9 X3 B& D
2、value设计
& w+ L" {8 K( |" }$ b
( y8 z+ n- Z8 F拒绝bigkey
- v# a- a5 d7 a
4 L' s. x* q: G" H, L$ h8 i防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
' P3 M! z2 E  R1 C反例:一个包含200万个元素的list。
4 h0 O7 U& P# F  w+ c非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
* i6 k9 J$ O. B7 P选择适合的数据类型
( n6 `, \$ V  I
7 I: k2 J: q( M例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?0 S& ?. F& q! O- [% V
反例:
7 f9 p8 X& }8 k1 Y! J
    ) s7 }% B: Z% `1 _/ p0 U8 J
  • set user:1:name tom
    2 m& J5 h; c4 A) S
  • set user:1:age 19
    # g% q' w9 W! ?+ z' Z/ Y
  • set user:1:favor football
    6 o2 y* s# ^6 I
正例:
# c: S" A5 i; g- Q
    7 D, [5 z5 x) t& J# e" t3 z
  • hmset user:1 name tom age 19 favor football
    4 d) C  j9 I4 n# R1 P* h2 l
控制key的生命周期
* O! B" ?. ?# {- J9 O# Z
! u2 T6 \- e+ V0 Y# J4 Fredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。, I. X5 V7 v6 |
二、命令使用+ v2 H' b9 @) z

7 l7 o8 r2 [; u) h1、O(N)命令关注N的数量
7 D2 @0 P' r: @( l, j
) D: C. n+ y8 n. G) m例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
8 h/ y$ @2 v% F) i1 B0 b! f0 [2、禁用命令. s, B6 _9 `. d2 n4 x. K# R+ q

/ Y8 i& g. Z# A: J" H禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
2 p1 ]! f4 c7 S# @9 ]3、合理使用select) v  O6 P4 u3 {# j' H! Z: l8 s

5 [/ r2 I$ h/ A) Z* M  J
6 n9 K: c% u+ K. }redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。. |+ V$ m8 z5 Q5 ]% K5 s
4、使用批量操作提高效率9 N1 {" D' e  Y% G) t# D2 K9 U3 [) r

3 Q+ q5 s9 F( y) N! }

    # R: D$ E: o0 M9 R
  • 原生命令:例如mget、mset。3 b$ X+ Z7 ]3 R2 j/ W
  • 非原生命令:可以使用pipeline提高效率。. U* z) V( b/ D8 i0 w, j$ O
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。' N6 t+ W' ^0 F0 O" q0 s1 u* g
注意两者不同:2 T' `3 U) u( q$ F/ v8 T4 g0 X

    * s& F! r% d0 H/ U" @5 s
  • 原生是原子操作,pipeline是非原子操作。
    - w6 P( b. Z9 Q* ]% Q4 y
  • pipeline可以打包不同的命令,原生做不到- V# s' a( d1 S& W+ I
  • pipeline需要客户端和服务端同时支持。
    3 A6 T2 U' Q5 l' T+ n
5、不建议过多使用Redis事务功能
7 X% T0 k- I, Y* J' m$ V7 ^
3 m+ n' W2 x# Z8 DRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!* n& I+ E- Y1 c! Z* {6 R, ~5 W( {2 x2 G
6、Redis集群版本在使用Lua上有特殊要求
$ X& m' r$ Q% S' P# Y$ w
+ K" Q' Z& z; G: {. ]* c1、所有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"
* B. k: j  m/ l. ?1 o" [- N7、monitor命令7 K" ]0 d) [: [  i" a' O. Z
7 M/ k: K1 e( ]" Z) O% }( C
必要情况下使用monitor命令时,要注意不要长时间使用。' S  [) f7 ~: X( Q9 V+ R# f
三、客户端使用
. x. L3 t5 ~7 T. |# l9 F2 E, i$ r! W7 ?& e+ [3 }7 h
1、避免多个应用使用一个Redis实例
3 r" N6 }, I2 B$ y2 t3 Z
" b) u3 P' a7 h$ B不相干的业务拆分,公共数据做服务化。, ]- p8 o" c, f- f# w1 m& o! L
2、使用连接池0 A: S( p! a/ w& ?; O9 B: y2 H
3 f% x1 w: C1 B. O# o! L
可以有效控制连接,同时提高效率,标准使用方式:
/ x: t$ @5 @) d# v' OJedis jedis = null;
. V1 s+ Q; G& N# T/ {! ^try {
3 M, i1 W* {0 |8 u jedis = jedisPool.getResource;: E: c: W+ ~! n" D, B* s& G2 q
//具体的命令1 s: w/ _9 v3 [" Z+ f$ c
jedis.executeCommand
# N8 Z1 ?# Q/ Q  F$ e1 \0 u/ g8 D8 |} catch (Exception e) {- G" B4 }9 y7 X6 I
logger.error("op key {} error: " + e.getMessage, key, e);
+ ^8 x7 f8 r- C/ Q} finally {1 d+ ?7 p$ O0 j+ X. ?* X& M
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
7 _1 u  Z- i  J' ?8 o4 d if (jedis != null)
; A; U7 C! H5 G9 m* e, m6 l jedis.close;
  d8 D- X$ \& N0 f}% v$ I; x* R3 T( h7 R% R' u, @
3、熔断功能
& a) C4 d% N1 M" i+ O+ F1 ^0 w) }* ?% ^0 ~/ x: m$ X: `' j/ V1 t
高并发下建议客户端添加熔断功能(例如netflix hystrix)
! W8 z: ?$ S1 Q  c& X- j* \4、合理的加密8 F) H8 w: W) t+ c, b; W

) h9 Y5 e; O$ F1 J设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)! b6 Y3 S7 y) W; j* U% X
5、淘汰策略
6 W" F" r0 D! d2 \0 N8 @: k* n4 O7 F; m5 _7 A
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。) G7 W- s4 N9 c5 m% Y7 G
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。* {9 \7 p9 u+ d& b7 [
其他策略如下:
+ t0 `2 h- x! L+ j7 L5 j

    7 @  C1 k# J* G3 v" M9 j* u9 ]7 K
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。3 X- q3 R& l0 E4 K
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    * T* q, [7 s  ~. ?0 m
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
    0 k& V, ~5 y8 p7 P4 E5 E
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    ) B" R" ]  H6 T
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    2 h: }, _4 w$ }, m% R% E7 C
四、相关工具
# W% Y+ Q. Q  H1 C4 C. y8 ^
: `) s* G% ?9 @3 h; f  O1、数据同步' Q9 `' [- Q- F$ ~: D( A

, D: I1 g% ?. R: \* X% uredis间数据同步可以使用:redis-port
/ ]  G* J6 }: k9 I2、big key搜索
) C! i$ G8 F; v! [( B
( _8 u3 v( t( M  q& M9 I& Z5 iredis大key搜索工具
5 U0 p& O# e: q) X" v( l3、热点key寻找0 b! {5 c  R$ G
- R: F. V- k( f
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
4 N/ h6 F3 b1 P0 @6 A五、删除bigkey
! R2 p, k/ W! Z4 M+ t/ {
& V4 ?& ]  P, S. i3 l7 v/ P  @
    9 ]$ K' m3 D1 R3 @4 g1 [
  • 下面操作可以使用pipeline加速。7 Y( n; z2 e/ c; D
  • redis 4.0已经支持key的异步删除,欢迎使用。" v$ H8 l7 D5 d
1、Hash删除: hscan + hdel
! C3 v, X2 r: S: r! }* `2 s% Y, ^) N6 z" E4 a: `
public void delBigHash(String host, int port, String password, String bigHashKey) {# E2 ?9 D! U- ~4 T2 b9 E
Jedis jedis = new Jedis(host, port);# ]+ R: W5 i' p7 ]" n- K5 g
if (password != null && !"".equals(password)) {
- J5 J4 M; U# f9 S* l) k+ u7 T0 |7 N, t jedis.auth(password);; l( K1 z9 V6 u8 x* }# ]) g9 r
}
6 N, M; j* i% G# d7 J! |" x ScanParams scanParams = new ScanParams.count(100);; D% P# e& j$ j1 Y
String cursor = "0";
4 g9 u7 L, O) E+ k" @/ f do {- S& ^  U& T; |; i8 _5 n3 H, D! A' [' N
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
$ `6 Z4 L7 q- I. W! H# A1 u1 M& m List entryList = scanResult.getResult;& T  h' r9 w1 |: S. M8 j
if (entryList != null && !entryList.isEmpty) {4 w* N6 P1 _! v0 C- Q
for (Entry entry : entryList) {
; O3 n; I5 ^) {# @0 I jedis.hdel(bigHashKey, entry.getKey);
* w$ `7 c) |; U# b  r8 l) z$ G2 M4 O }
  p: N; O) `* m4 w! L }
- a& B1 T, Y5 O9 g cursor = scanResult.getStringCursor;& K, l' Y/ C) H: f- Z
} while (!"0".equals(cursor));
7 u& P5 N1 O. g! o. x4 L
/ W8 q% Y# t$ X- k$ K//删除bigkey
- D1 @$ T% ]  Z+ f jedis.del(bigHashKey);
# y5 q: e* Y- l8 n}
. G: f5 l8 P8 {! [. m/ z2、List删除: ltrim1 q* o3 \" k2 W& O6 T) {+ r1 |

( ]4 m$ G4 q; Y2 lpublic void delBigList(String host, int port, String password, String bigListKey) {6 [* v0 H4 D4 g. h/ Z
Jedis jedis = new Jedis(host, port);" d6 S: B9 V3 i/ N% H& _4 g
if (password != null && !"".equals(password)) {
8 f2 n5 g- ~# m/ E, c7 W! Q jedis.auth(password);" U  z! y. I% |
}
. \2 y- p  P3 V; v  _* }7 ] long llen = jedis.llen(bigListKey);
% l! N% f8 \2 }! z* c0 `8 G( O4 |! z int counter = 0;
. n8 t6 ], O* y) @ int left = 100;
% }% r9 w) g# C% E while (counter < llen) {
, ?+ G0 ~9 c, d. {, ~2 S //每次从左侧截掉100个6 ]. x+ ?1 S& L! A: @5 ^, z
jedis.ltrim(bigListKey, left, llen);
; j( M) r& E+ ^" N counter += left;0 }" K) M, ^1 g; J
}0 J2 s8 c$ f. }% B$ d
//最终删除key' L  m8 N: r; g
jedis.del(bigListKey);
4 L: P" ?0 t/ C8 @* p: N- _}3、Set删除: sscan + srem
$ N; f2 A" U3 u4 {9 S+ O$ M" ], q  G" `, [2 R
public void delBigSet(String host, int port, String password, String bigSetKey) {5 N" r3 H5 |/ L! r# }* n
Jedis jedis = new Jedis(host, port);
1 V: H+ J* u/ C* _( ?- H! u7 a; W if (password != null && !"".equals(password)) {8 x, S8 m# g% i1 l
jedis.auth(password);1 Q: d3 [  s0 m( s0 G9 h6 \* a- S
}$ \3 X5 `0 }( ?! B: F
ScanParams scanParams = new ScanParams.count(100);
( E3 r# s4 r  M- k" R! a7 z String cursor = "0";8 @! P7 t$ f3 d9 H# z
do {
- ~2 X0 V9 N" M) c4 Z" ~ ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
1 P$ B8 f( @2 M: h8 @1 U List memberList = scanResult.getResult;$ w! }5 S+ c/ t6 S' K
if (memberList != null && !memberList.isEmpty) {$ {, i% E8 q2 N
for (String member : memberList) {# i7 f7 U1 N& J
jedis.srem(bigSetKey, member);5 [7 D! H! \0 c" J. p/ @
}
3 q0 K; ^7 J2 a8 B% L- y }+ M- i) c3 \5 d: U6 e6 ?
cursor = scanResult.getStringCursor;
5 G: }6 m9 E% R) X } while (!"0".equals(cursor));7 m; g2 g0 h, ~4 Z, Y
9 q1 o( w% s8 B1 D! r" j
//删除bigkey
' r+ \! `5 s" k- ^) J- A, U jedis.del(bigSetKey);
& O  d7 R& o, w% u5 g# U% K, @+ n7 [  |}: ~, O" X) M' _6 e6 T/ `
4、SortedSet删除: zscan + zrem
, U. r/ y0 S4 J2 ]  @+ p, L5 S) f; r8 R( b) D
public void delBigZset(String host, int port, String password, String bigZsetKey) {
$ ^  e3 v) C$ T1 g# V) D; w Jedis jedis = new Jedis(host, port); 5 ]9 i$ `- N& Y
if (password != null && !"".equals(password)) {
# V. n5 g  e6 q7 R- l jedis.auth(password); 6 h. V) e! I6 _9 f7 U2 M2 n" s
}
' `! B6 b, _& r ScanParams scanParams = new ScanParams.count(100); ( F+ ]; ?2 \  M8 u6 P- D8 z9 p
String cursor = "0";
) b3 B* ^3 x8 K' \% e# x, B do {
8 E/ k% W5 O6 f' e  q* Y' X ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
* ^& f1 z) Q- M# s& o- R ListtupleList = scanResult.getResult; ; X7 C' k: s( B) Z0 J
if (tupleList != null && !tupleList.isEmpty) { 8 G! e* R; u* B( x! n
for (Tuple tuple : tupleList) {
. Y; V  `' }1 [7 M- q$ `( @" v5 @: ? jedis.zrem(bigZsetKey, tuple.getElement);
/ ^' o$ Y- O7 j% ]- ?' q+ R- o }
) ]2 c6 a% X, R* M  ~, Y: t }
+ n& V  p3 D! B* Q4 L) N0 G cursor = scanResult.getStringCursor;
+ B8 A; i; s7 \7 L! L* Z& b! t4 X) c } while (!"0".equals(cursor));
+ L3 \! l8 \% d7 T. W: U; M# R% V6 S! N. O% O( h
//删除bigkey " @* h) X) P  e  \
jedis.del(bigZsetKey);
9 ?2 @2 Z  ]% C} 公众号内回复“1”带你进粉丝群
" f" z7 P  F2 y! Q6 P8 P2 L$ g4 o, R& N来源:http://www.yidianzixun.com/article/0LevQm7t
- _+ P1 ^) e/ B3 F; ]  p4 n4 b免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-10-14 16:58 , Processed in 0.038755 second(s), 25 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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