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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9443|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

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

: `1 \. c& N) z8 U* p本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
0 P* j& E! A- p& o; t7 ?
    " T) O. U- [5 D) P; {, F+ E
  • 键值设计: _- J, S! d! t; d  t& m2 r; I
  • 命令使用$ E- I, K8 ?6 i; ~& m. N( @
  • 客户端使用
    - ~' F; A8 a: e6 q! R, A
  • 相关工具
    + B% X. y7 I5 Z/ I8 \. M3 u
通过本文的介绍可以减少使用Redis过程带来的问题。
% {0 {  b$ |0 ?/ x一、键值设计
- W. h, z7 j- T' E1 q+ K+ h' H8 L5 {3 B; V8 H' ?- [$ t) z9 z
1、key名设计3 V: z6 O/ M8 E5 e$ i) P; ]

) J2 u2 Z# n8 f, Z- F2 }可读性和可管理性6 t8 E- a% X( G' |

# C' V- }5 w; P; j0 ^6 @, Y以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id  J: ]* h* e! ^: N! r; L
    & ?( {5 t0 X; p- V
  • ugc:video:10 F% M( u) Q! E' a6 `, L
简洁性
# b) g/ A% T' J- n; P' E
+ J. D. H! x( d+ A7 w* v保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:6 u% [& g! L- n- a. q2 j% g( R
    7 \; t' [" i& [, c7 Y# u
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
    9 ^; q( k- J1 ]
不要包含特殊字符1 U, N' e2 p7 h7 Q( h' \
  K! ]) O- F; \
反例:包含空格、换行、单双引号以及其他转义字符. M+ l# o' d/ C" B/ J7 Q" u
2、value设计, P6 Z7 u$ G1 h1 g

- \" Z$ V9 C7 B( \$ w拒绝bigkey1 C6 h: C+ `; i

# ]* h4 V  C( A& `- a# Y防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
3 R+ x. y: S. a8 O2 j! X& h反例:一个包含200万个元素的list。& I8 M' p( c4 c: _% X  C6 }
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法" f3 ]( S. E) T7 |
选择适合的数据类型. w/ A# Z2 l, A1 ?  C' q

: _, y) W; C5 w" A, `例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
* D$ F4 q  C1 l1 \3 [7 @2 s# Q% e. ~反例:
- {( `) U* G+ f* `3 X9 K
    5 x! g( [  y# h
  • set user:1:name tom
    ) I2 }, T, Q9 a+ n8 t
  • set user:1:age 19
    " ~/ e4 E( S0 C8 ~
  • set user:1:favor football
    ; L2 ~, S' v3 _- u# b
正例:; v- l5 l2 H% |6 t) j
    0 Z: a. }* k6 d# ^0 {+ t4 b% \
  • hmset user:1 name tom age 19 favor football
    . [$ `3 s. Q. O7 P
控制key的生命周期* E; m. N9 C. m1 i5 l8 b8 Q: A
7 C- R* Q$ I* @& m" e: Q/ C
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。& \6 M& B+ M  S' E& _! n4 t! E; Z
二、命令使用
$ C, G9 j& t! `; U2 X" r) [/ a) U6 A8 u7 a5 f8 k* A: X/ \
1、O(N)命令关注N的数量
2 [  Y7 X# r1 ^* A
- j7 z$ s/ |9 I) B/ u' b例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。. F: ~1 l, N; S1 e2 h8 A# S5 A: n( ~
2、禁用命令
1 F/ H' G7 y, c: p$ V' p5 }- x% {" P, E5 `) q* {% G
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
8 }0 s  F; @& E3 \3、合理使用select" i6 h0 y: Z9 U* a. x/ T
9 m. Q+ Y2 o, f) G% Y

  X$ T+ O& r: @1 b* {3 c* _redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。5 j2 ?$ O; \+ |" ?
4、使用批量操作提高效率
8 L2 N) I9 F8 c0 ^$ c1 l- [4 x: k: D+ N) g: _
    ) j9 `8 N% \, s( o
  • 原生命令:例如mget、mset。
    " M" ]; k0 Y+ G% R, D* l8 u
  • 非原生命令:可以使用pipeline提高效率。% l8 J! L, ~3 W5 ^9 ^
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
+ F( J% s5 c+ r* P, u( F+ \5 s+ y注意两者不同:
9 I8 R' `2 L' f

    / X* j2 B9 Z8 j/ \! w
  • 原生是原子操作,pipeline是非原子操作。
    6 {4 w8 x$ A1 E
  • pipeline可以打包不同的命令,原生做不到/ T% H( b( U& G4 p# o7 q  L/ H1 R  u
  • pipeline需要客户端和服务端同时支持。. H0 J' D1 j# _+ x9 }3 Q5 J% L
5、不建议过多使用Redis事务功能
5 D+ k  w) x* o6 H" P. ?6 v7 a
' V/ c* d8 V& E8 qRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
9 M* r+ u  |& \9 X. m) x& ~6、Redis集群版本在使用Lua上有特殊要求
3 o) J8 S! |, G1 A8 v1 O; Z
2 [# L8 [+ a6 ?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"
$ o$ {% K: s( h& _( H7、monitor命令
+ P' v/ m! h% K7 K# d, m6 D9 k+ }+ O& I. B  i
必要情况下使用monitor命令时,要注意不要长时间使用。
& {9 z% h6 `8 a  [" G三、客户端使用# O9 w- N6 ], o9 t8 ?& H( l% r* y

7 ^# U3 Q- `; f. H# x4 v1、避免多个应用使用一个Redis实例
. I9 c  G1 i# M8 o
* |, t! V  D7 V: F+ V2 E2 m不相干的业务拆分,公共数据做服务化。4 i$ m2 Q$ b  ], J* B9 V
2、使用连接池
- o% m" p2 A$ o. \' s/ q! J7 A: J" j, I0 t& A! h
可以有效控制连接,同时提高效率,标准使用方式:
1 @: z% c( P2 |. l2 y/ C4 PJedis jedis = null;
5 Y$ S. [0 q7 u2 h9 Gtry {
" a1 Q# L, Y& S8 W6 F jedis = jedisPool.getResource;  I2 K  k/ f4 R4 S( d; Q  i
//具体的命令( ~. M' j+ p+ y' E0 W( [
jedis.executeCommand
0 s- M6 O( }+ }* r8 d} catch (Exception e) {7 J4 z( F0 x% E. |/ ]( E
logger.error("op key {} error: " + e.getMessage, key, e);
0 M0 J8 E3 Q; t} finally {
, I4 h0 |% F# [, F! N8 m: v //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
/ U! r* T3 @0 m% G" ]% U if (jedis != null)
+ s% P! N- N+ y jedis.close;
5 G5 \  ]& L4 D}
% n# o7 r; H& d; {3、熔断功能
! q& }/ l2 O  m  z! Z  Q( O" h# {: Y2 D$ L
高并发下建议客户端添加熔断功能(例如netflix hystrix)
" L/ j( |5 A8 q! W0 R, V  u2 I4、合理的加密9 j# d2 f1 z$ A' R+ `* H  P3 z8 T+ S

; H2 P+ h! U* E9 Q% Z$ J/ k: _设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
9 j+ I& Z3 {, S1 F+ t; G5、淘汰策略
) Y; I' q% ]! C
. [- I/ P* R, d/ {6 z1 p根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。% e3 I! `: i- w+ L! Y4 ~4 G
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。4 I- H; G% ]! `5 {% w  i
其他策略如下:$ }; @( \9 |# A
    % l2 Q* Y& C; W: r  P6 B" [
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。. V% X$ }0 Z7 S# I
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。; B. ?$ @) r8 s0 L8 E( F5 o$ t
  • volatile-random:随机删除过期键,直到腾出足够空间为止。. H; l( w( d3 I4 x+ I
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    ! `! N: f: L' t7 W
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    1 ], s! r5 v5 \9 ]! ~$ o: E3 i3 s
四、相关工具
' _& ?9 }4 V5 a- J+ m1 T; V' `$ \* T0 O
1、数据同步
+ o; d  A2 n6 t, S' A3 p
0 _5 G$ ~+ e! I* o0 A$ Hredis间数据同步可以使用:redis-port* X# o( l7 y8 W, N0 Q
2、big key搜索
, J  l6 w+ i8 r$ R. I+ }$ Q0 _
; v5 z  g; Q6 ]% `% Yredis大key搜索工具
; c. V  _" R5 N5 c( u* P; k3、热点key寻找/ F* U$ |2 H9 k+ H4 L5 v* o
; }3 F* |5 T& {: o' k" |5 M
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
8 \1 m9 Y0 k  \五、删除bigkey
8 ]9 f2 S. w3 s1 ?$ N$ `, G- X7 x5 l) }0 G+ h1 E- |7 X- R) Q7 F
    / ~$ c+ _5 R# e9 r
  • 下面操作可以使用pipeline加速。3 o. B" X  s( Y0 c+ x
  • redis 4.0已经支持key的异步删除,欢迎使用。
    1 l( I; f5 J9 F/ X: T
1、Hash删除: hscan + hdel$ f0 p* p6 y" ^# H+ s8 ]1 N
( ]. p6 u3 t+ H: Z4 R
public void delBigHash(String host, int port, String password, String bigHashKey) {. T/ P# J! e3 R
Jedis jedis = new Jedis(host, port);" ]1 y; x7 \& X- @, P, h/ V
if (password != null && !"".equals(password)) {
/ g" ^% U+ A: [# i  `: n6 e* ?9 X5 q jedis.auth(password);
! [  l+ O, I# d$ e3 D }* B# q& d* M/ ?8 L2 A* B1 k
ScanParams scanParams = new ScanParams.count(100);
' k" C  \* Y6 R/ `* o' d String cursor = "0";- v5 q2 Q1 e# v. l! Z
do {
7 L( R) |1 V0 }$ z ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);! |' X5 z# {5 Z# F& V
List entryList = scanResult.getResult;
- \. U, h5 X7 S' w4 t. u if (entryList != null && !entryList.isEmpty) {2 B7 ~# \# S) a, G: f9 h8 {8 F
for (Entry entry : entryList) {
$ g! e. s0 [1 Y& F  K5 `  K jedis.hdel(bigHashKey, entry.getKey);
) K8 e$ }  R2 N }  A  J8 ]. d9 c. R
}+ `9 M( ^4 I0 l9 ~  j
cursor = scanResult.getStringCursor;  a% C& e2 v- ?' O) w: y4 |/ v' P
} while (!"0".equals(cursor));
9 F# u1 U9 W. B* o6 L
" [# \1 f/ c7 D( T6 J/ U* K" U8 O//删除bigkey
  |/ R) e/ |2 n; [( q* w1 @- s jedis.del(bigHashKey);' T) L6 y4 b7 j  B" ~
}
/ g1 o+ z+ Z  r2、List删除: ltrim
, f9 P- n* r( d- `; v/ M, e1 J, k5 g% L# y" T3 s
public void delBigList(String host, int port, String password, String bigListKey) {+ k0 {- J) X; k& ~# B
Jedis jedis = new Jedis(host, port);* v2 A- g' g: K' t
if (password != null && !"".equals(password)) {* i) ]% Y5 v. |5 q$ {* j6 ]7 ?
jedis.auth(password);3 E* |% B* ]! M& X3 s: w
}
" d$ P  X4 }- X  `& P; K1 i; g: B! s long llen = jedis.llen(bigListKey);1 e* S- w! e9 n3 G' m) ?+ {2 i
int counter = 0;% X8 n" ~+ N# n4 z* G% t
int left = 100;  x5 |. o9 i% T/ o. S5 |: \% R
while (counter < llen) {/ |, C8 t6 X* R
//每次从左侧截掉100个
, p; Z) z; u# n+ _6 v5 m jedis.ltrim(bigListKey, left, llen);1 y9 X# j% \: |! ]" E7 d& t
counter += left;
6 m+ Y+ l% _% l }  e! m. `, T" n& K+ a3 @) w
//最终删除key
# `4 D+ q) a& E1 ]$ { jedis.del(bigListKey);- z" t6 U3 N8 `4 d
}3、Set删除: sscan + srem
; }+ r& J, l  a0 w8 d
" p/ V* m  @3 w  \( {9 z2 Q* I; C# [7 Gpublic void delBigSet(String host, int port, String password, String bigSetKey) {
9 E! k  `" |; T" f6 H Jedis jedis = new Jedis(host, port);% M& k% |# ]9 R
if (password != null && !"".equals(password)) {
5 }0 t) z, Y$ w: q6 @( a jedis.auth(password);/ x2 q, \/ X5 t$ P7 r
}
1 [* U' c# y! V ScanParams scanParams = new ScanParams.count(100);1 |" S0 f9 H* h- h8 ^
String cursor = "0";
4 B5 f; E0 Q. A, g do {
: C; ^" C/ @$ z  _% y! }, V ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);6 m% J2 v' b- W# d% T+ Z
List memberList = scanResult.getResult;7 u4 k9 L1 k; ]9 t/ j* x
if (memberList != null && !memberList.isEmpty) {
! n$ d# C6 s0 s2 H for (String member : memberList) {% g. K# g8 R+ V- \( J. w8 P, M: F
jedis.srem(bigSetKey, member);5 }5 P: l# J  [0 d0 s0 o
}6 B0 `- k; H1 V, ~
}
( _1 M5 O9 Q  x- i+ E" f cursor = scanResult.getStringCursor;
# q0 L% i' K$ d9 D } while (!"0".equals(cursor));
! a" D1 r' Z- B
( r) E) O- Y0 ?$ _; d5 y//删除bigkey0 a$ T1 L: r0 y# i# p
jedis.del(bigSetKey);
. x7 B/ v+ V' l1 N" O% p2 S2 T}
) p4 A, |  [; U  P2 e; L4、SortedSet删除: zscan + zrem
! V3 @( x; C5 I1 b& P3 n1 `3 e7 q
public void delBigZset(String host, int port, String password, String bigZsetKey) {
1 e  L" c2 n" d3 [( F  o+ O Jedis jedis = new Jedis(host, port);
' D8 H  b# T; @4 p9 {0 G if (password != null && !"".equals(password)) { 7 Z8 Q" l$ ]. |
jedis.auth(password); - m- l, r1 \# n+ c3 ]1 M: ?$ S; ^
}
' e) L# f7 m/ I ScanParams scanParams = new ScanParams.count(100); 6 _, l8 W0 H( W+ H" f% h$ @
String cursor = "0";
# z+ U& b* w1 k- `2 O) p' r do { 4 I* Z) O+ F4 y" N" o; y; v
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
, {, I- E$ X4 O* S& h ListtupleList = scanResult.getResult; : x( t& [4 D0 e
if (tupleList != null && !tupleList.isEmpty) {   j+ }" D' E7 y/ b5 D: M
for (Tuple tuple : tupleList) {
% `. \  Q- h, D* ]! Z' D jedis.zrem(bigZsetKey, tuple.getElement); * t" V& t2 i7 A" ?: v* I. _
}
+ N% w. X4 }: S# B) L0 r } $ f8 w2 ~# S" v* q5 p8 G
cursor = scanResult.getStringCursor;
# K. V' [3 v1 X } while (!"0".equals(cursor));
. g7 H6 w' w- L5 F8 [5 B
4 B& \9 @5 S0 H. ~! ^5 o' H4 d  p2 a//删除bigkey 8 f6 ~' k0 l' U1 O6 J) f
jedis.del(bigZsetKey);
; N5 A6 C- `6 z} 公众号内回复“1”带你进粉丝群
' H1 k; t9 ]$ f来源:http://www.yidianzixun.com/article/0LevQm7t
9 u- W3 C2 G' K) _6 U4 A) j免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-9-15 04:23 , Processed in 0.054328 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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