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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9192|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
/ c0 M$ P, T) s  [( j0 A! X
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。- d1 N7 h) W) j* k

    ; E+ e6 E2 U7 h6 d1 m6 {/ J2 Z0 M5 }) u
  • 键值设计: C" i+ g- F% b+ `
  • 命令使用7 l4 j, `9 G& u
  • 客户端使用+ I! h' C4 i3 V+ a" Q6 ]- }+ b
  • 相关工具$ I; R; W$ b( |3 F5 t1 O
通过本文的介绍可以减少使用Redis过程带来的问题。. d  w1 |1 m  G7 S3 P
一、键值设计
; Q  y( v0 Q3 Z7 O3 u7 q( [* i0 k& l" i5 n' A0 B2 o
1、key名设计. W# q" r/ i7 W: c  h8 K% A

/ D0 R( j- Y; w4 [; s可读性和可管理性$ D! r+ b/ j- R0 B* Z

" z& Z+ P' [; x, E1 W/ T' ~3 h以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
# {" W. I$ b1 G' c
    $ B  T# O# ?& H& j$ ^: A) C
  • ugc:video:1
    ) i' `- s; t& r' q1 r! y, x
简洁性
$ e3 ^. |7 y$ T. t" W0 i$ X/ K
' l/ ~4 S: X2 `; ^7 T/ a保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:6 W7 e! D: L5 L

    ! x* V( F8 F% y# T7 v
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
    ' p2 E% c( b9 y: e
不要包含特殊字符( \4 ^$ d2 f  ~5 |. N; o

, y# ^  G: M2 E( u( x# |6 |反例:包含空格、换行、单双引号以及其他转义字符, [  f( F4 O; }$ K' f8 p$ g
2、value设计9 i% l  Q5 L# p0 L" S7 e7 o
! H  h, @5 z! E; V/ a' @: O
拒绝bigkey
& X7 o2 c* O4 `& [( n' G" v# d) j6 k6 E5 Z8 y! Z
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
' P6 t  B+ A' w4 L" b$ x. ?; G' q反例:一个包含200万个元素的list。
0 [# J+ z0 l* C" x+ P非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法* C0 E7 m. l8 O, D
选择适合的数据类型0 M+ T9 u0 E& R% Y9 e

8 X2 x9 q7 x, I例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?$ E* ^9 f% p9 s1 p2 l6 _+ X( ^5 x* J
反例:
1 ?) k' @/ E7 C5 N$ m
    & X7 B8 Z1 e$ b: u+ `' m5 T% u& n
  • set user:1:name tom5 G7 @6 a* I% n# A4 U" E8 A
  • set user:1:age 19) f) b) \0 I0 P% a: k/ e& O% \
  • set user:1:favor football
    6 Q) T) _% `! [7 t! F6 X; y
正例:
" u4 g2 E! m6 q" j9 A7 A
    # }2 s0 r) ~6 _) _( k6 U
  • hmset user:1 name tom age 19 favor football
    3 M/ |) }& q8 q& D( u
控制key的生命周期9 F8 u" r! M9 A: o5 r5 o* ~6 h

4 v* r% U5 ^1 a. `* {& |  S1 h& H& wredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。% O$ ]% }3 i: B! b7 a0 f% l
二、命令使用
! z2 M( |/ B1 w0 K# ~. w% n# {4 D* ~! O8 s% t  N6 S
1、O(N)命令关注N的数量3 p7 X1 L* n. s3 h! D. d
4 l9 I- E5 }1 j
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
5 H3 m8 E. v2 `$ |0 G# h2、禁用命令
8 \' U1 n9 V7 O& z; [
9 D: j0 r2 f0 Y0 b4 K" I禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
- x! X' r# d% e4 f. p7 a& J$ h3、合理使用select2 |6 P: A& d3 ~+ D4 g, w4 A

" m( L' p. }! c5 j& L, S
& _/ G: `! J) ~7 R- g* \: ]redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
  k4 t: R3 w, r( Q6 L- \4 r  n4、使用批量操作提高效率4 M/ S1 K3 b, g( a9 q% P! v
0 @, e% j7 s( v
    0 {- z) h; M, n3 T( D# T: l
  • 原生命令:例如mget、mset。
    : L1 d, H8 z- }: t: q4 C- W( U. @
  • 非原生命令:可以使用pipeline提高效率。3 I, {2 k5 N, m5 \  y
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。* y  m, v  V+ }9 u+ t  [7 r
注意两者不同:& K! j2 D- v( Z0 ^; n3 O0 H7 c7 F

    . ^& R/ g; w# h) E& i" R
  • 原生是原子操作,pipeline是非原子操作。% Q0 ~  \6 v. ]  E2 j2 H1 t9 S% m0 }
  • pipeline可以打包不同的命令,原生做不到$ t+ O! L. O1 w9 Q
  • pipeline需要客户端和服务端同时支持。4 M9 [/ p, L) A/ h  B4 B% ]
5、不建议过多使用Redis事务功能0 N. O3 R- b  ?3 n1 g) ?
2 Q% |3 Q/ S2 V2 o, j; c. y4 ?6 w
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!; t" \( T  w' X! H4 x5 m0 a2 ^6 B# H
6、Redis集群版本在使用Lua上有特殊要求
0 i9 d) y% K! O# l, ]* d
' [% }! ^3 S. v% c6 o7 N1、所有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 P9 i+ A2 }1 r7、monitor命令
. g/ Y: @$ H) W- W" x2 H+ T) X' y$ `$ }" h
必要情况下使用monitor命令时,要注意不要长时间使用。7 _0 g. l  m8 b+ _7 o, y
三、客户端使用
, m3 I( P- K8 q: }/ d% `9 K: L5 t1 D2 Z
1、避免多个应用使用一个Redis实例
  Z9 J( S9 ~7 p! |2 E) w% g0 V2 G8 @8 F  Z
不相干的业务拆分,公共数据做服务化。
  P+ O& Z$ T7 _. V2、使用连接池
2 S  G2 t+ x: ^7 ~* [
* }: Q( T" F0 E$ d6 U7 }: F3 n5 S4 @可以有效控制连接,同时提高效率,标准使用方式:
2 d0 z; G  V* ]0 pJedis jedis = null;3 ^% W+ D/ e& n% e0 I! b
try {
: i5 X9 S7 r1 K2 Z3 V$ j& W jedis = jedisPool.getResource;
. |. [$ K1 w7 I //具体的命令9 W1 _, I  C: Z. I. Q+ c# U  [
jedis.executeCommand& j4 g; F& B6 d. }
} catch (Exception e) {
/ [: j' ]; J: r0 t: r logger.error("op key {} error: " + e.getMessage, key, e);2 d: Y; K) a3 _3 @+ o# ?$ c
} finally {- L. M1 L/ m/ @& n
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
- ]6 R9 t& P8 a# K if (jedis != null)
) T4 J) R. t9 Z/ E jedis.close;
( H/ Q( _4 B' I! M9 K2 _/ G' F}6 Y6 e! E' m' G" q! M* Z) h- V) U- q
3、熔断功能0 h1 @6 H! T4 t% ~( t5 F
$ g$ u6 A% w6 v- p
高并发下建议客户端添加熔断功能(例如netflix hystrix)# h6 @0 ~$ S# g( L! K4 f8 c0 F
4、合理的加密, n* b$ c; x3 p+ P( N/ t* a, X

9 Y3 c# s" B0 ]设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)1 z6 Q; w: d2 s$ e2 F2 O: c
5、淘汰策略/ w) t- K6 W5 B9 _' Q7 G
1 u) q- a7 F% p& T& L# d
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
8 z- c3 U5 |6 ]! j默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
! R2 t2 @$ T( b其他策略如下:. r# q8 O0 @7 K: S  b! y5 I

    ( R8 z4 i( o$ K% E
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。: @) O3 E  r3 Q& m( |: E$ X- p
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    ' I' k9 \  d- I
  • volatile-random:随机删除过期键,直到腾出足够空间为止。) A0 j9 q, G! I0 a2 D
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    % d# N& B; T+ e6 i6 C) r* n9 {
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    , @  P( c/ b) c8 }" m
四、相关工具
  \9 s( w' q; G# x4 g5 g) H1 y" o/ o8 S* Q3 O0 e
1、数据同步
8 Y7 c, E$ \8 p! \; \. `1 [4 @1 x4 N$ |/ V$ t+ m
redis间数据同步可以使用:redis-port
# L" u- _  g$ b; n* ]  S3 a: x7 |6 t2、big key搜索8 [1 I" b/ {$ {% Q0 m; h

" e/ c# K& n6 \5 p1 O; m# D( [redis大key搜索工具
$ g0 \3 u" }# a5 V5 Y0 \4 ^! s' {3、热点key寻找% o9 O7 D! @7 Q
+ R8 t! q9 B, _
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
' ~& s& V7 j  K1 ]五、删除bigkey
& q# Y. h8 v0 ^- f$ g4 t% g( g3 R
4 i5 P# l1 p/ ]5 b1 O0 I( Z8 y

    , a- J# ~$ e2 ], ^
  • 下面操作可以使用pipeline加速。$ v" O  X; L; J+ o' C# I0 R
  • redis 4.0已经支持key的异步删除,欢迎使用。. [( `- t8 A$ J; Q
1、Hash删除: hscan + hdel
5 k) D3 {) x+ x" C' _+ P
5 {: J7 R! _; u& O8 _3 bpublic void delBigHash(String host, int port, String password, String bigHashKey) {
% @, Z5 P7 E4 `' S9 O1 u2 l) \ Jedis jedis = new Jedis(host, port);* n0 y6 Z1 K: p: p, H. W$ O
if (password != null && !"".equals(password)) {
8 T8 i0 u0 [- x$ L jedis.auth(password);
) ]+ O, d) e8 Y# ~ }6 ]/ }" C8 N/ m* R+ x4 I
ScanParams scanParams = new ScanParams.count(100);6 C* I  k' ~. J- c2 X7 p
String cursor = "0";9 y3 H6 M% v4 z) u( ]& d
do {" @5 a& P+ G* F9 S
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);( S- @* D7 F/ O- \5 K- e: l
List entryList = scanResult.getResult;
1 f  ?, C: W" Q, E if (entryList != null && !entryList.isEmpty) {
# m$ X; d: `: X for (Entry entry : entryList) {
' K) y8 s" {/ ] jedis.hdel(bigHashKey, entry.getKey);# n  f8 E/ b" D) m' W  b
}" U5 q, j5 L8 P  A( S+ \; J
}
& m6 u: v* ]! S6 r# {% X) p6 A cursor = scanResult.getStringCursor;, Z- L2 N/ `7 a% h2 R7 U
} while (!"0".equals(cursor));
9 T! Z! e- o5 O" I6 s
" {* I% v, ?) U9 j5 \5 S/ D% h//删除bigkey6 O# H1 Y: K  `
jedis.del(bigHashKey);8 |  ~" R- [$ F7 @. ]2 V( p
}7 V: E* ^$ i" F3 J
2、List删除: ltrim- T, m6 X! X0 K! C) c

  Q2 V& M  V! s" e. F, rpublic void delBigList(String host, int port, String password, String bigListKey) {
' O- {2 H  g5 q) \% G, G$ k Jedis jedis = new Jedis(host, port);$ E  ?7 d" }! ]/ L& C
if (password != null && !"".equals(password)) {) C! j& ^% q1 t8 S& @, H
jedis.auth(password);
# [' ?+ r2 G9 [* ^6 r }
$ p% }4 U0 J  o+ U5 [& p long llen = jedis.llen(bigListKey);/ w- w  D3 ]5 D$ d4 c
int counter = 0;' H: G9 j  L0 J
int left = 100;3 W, D5 H( o" [8 u
while (counter < llen) {, d' q6 x/ \+ z1 C8 e* Q
//每次从左侧截掉100个
5 S% G; l: a' Y& T7 X jedis.ltrim(bigListKey, left, llen);. g9 U4 \7 h& L( O( L9 U
counter += left;
  E3 o# h- d( H5 g, p3 T) d& n }  `* L& g+ h& h( p  Q
//最终删除key  Y$ |  o: u* d
jedis.del(bigListKey);
/ o. \. l1 `- j" R& g5 k}3、Set删除: sscan + srem
! M' @! w/ f2 Y" ^9 K* _+ P- t' A% p& b4 t8 O1 b
public void delBigSet(String host, int port, String password, String bigSetKey) {- T* m2 Z2 R! y+ B* Z+ g& r
Jedis jedis = new Jedis(host, port);
( m0 S  z: e4 P9 O' U1 c if (password != null && !"".equals(password)) {
* Q$ y4 ]% a5 W5 w$ F jedis.auth(password);
6 f* ?3 e% J1 C% A }
) o4 ?8 ?% A+ n: _9 n0 y ScanParams scanParams = new ScanParams.count(100);
2 E$ n2 B- j: m* O7 x& K String cursor = "0";
  I7 S' K  g5 R  M- T do {
4 p- @" [5 P9 m7 @1 `* A) Q ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);. E+ A9 q% |  H# c& F# L
List memberList = scanResult.getResult;6 O+ V& O& A9 P' @5 Y7 F) D
if (memberList != null && !memberList.isEmpty) {
; @0 |* B2 ^3 N" l4 j: p for (String member : memberList) {
# B% V& Z. U: o+ e% a jedis.srem(bigSetKey, member);3 \3 |6 S. O- Y
}1 @% R7 s1 A8 b: p
}
8 `. w* `7 f3 K cursor = scanResult.getStringCursor;6 g6 S7 j( U$ M; r+ `( I* o! L
} while (!"0".equals(cursor));7 d/ `- K6 P( H2 g4 X

2 b; n  J) @9 l/ m, ^//删除bigkey
9 }$ @% Y- u* l) d' f6 Y% p* ?$ K jedis.del(bigSetKey);" r, g8 J5 a( y: u
}* H5 t, ?, Z  `$ d6 N' F
4、SortedSet删除: zscan + zrem7 F4 \( |4 ?% J/ h& e

8 Q$ b! S  K% ]8 G) D  Qpublic void delBigZset(String host, int port, String password, String bigZsetKey) {
" ]5 j0 g0 k& B$ L% i Jedis jedis = new Jedis(host, port); * X8 l- C5 v4 W9 c& p3 N
if (password != null && !"".equals(password)) { ; K0 K# d' C" N$ Z8 d$ r/ I
jedis.auth(password);
" }6 l7 W- c, Z6 r" m } 9 `, M8 z* ^4 |0 F. W& e0 |2 F
ScanParams scanParams = new ScanParams.count(100);
8 b  Y  \) p  l+ C String cursor = "0";
8 X4 `( W) G. p' {3 ^* Y do { 5 T4 [, c+ m5 E  l( {0 T
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); $ \. g4 A' B* Q6 M! l) Z
ListtupleList = scanResult.getResult;
' @' q# K% a- B5 l$ d. j& A if (tupleList != null && !tupleList.isEmpty) {
/ n% _0 ]# N$ a0 O; s for (Tuple tuple : tupleList) {
8 j/ D% l3 Z3 n0 V jedis.zrem(bigZsetKey, tuple.getElement); $ V* X0 J% X( z) ^1 J- s0 J/ ?
} 7 y0 e0 e* d& P' {8 n/ @
} ' s$ Z  l$ ?  {. Q! h
cursor = scanResult.getStringCursor; % W9 D$ }/ X8 p  u- B0 _8 J- d
} while (!"0".equals(cursor));
# U" m3 p2 c  W$ I( p3 d
. M9 n% n. U3 R# r5 l6 u8 R3 q//删除bigkey
* r9 V" [& h: b0 B5 `( y jedis.del(bigZsetKey); % T2 B9 M$ v& p5 D/ Q5 X
} 公众号内回复“1”带你进粉丝群5 W3 X8 k5 J  u
来源:http://www.yidianzixun.com/article/0LevQm7t
, i5 S2 u/ J, n. d免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

关闭

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

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

GMT+8, 2025-7-9 06:12 , Processed in 0.040055 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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