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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9466|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
$ j3 E$ A8 I# u& W% p: B
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
  ~: `8 R2 n3 L) H7 c2 \; l$ N# `3 B
    9 V1 W1 Z& {3 y
  • 键值设计- Y4 a; y2 X6 e% j" P" q0 d0 K! T# z# z
  • 命令使用+ c# {3 V1 ]5 g7 d1 F
  • 客户端使用! f# X1 i$ V1 p
  • 相关工具
    8 E2 O8 B1 `# |1 p1 ^# Y
通过本文的介绍可以减少使用Redis过程带来的问题。2 M. w: i, {9 x& u9 E) t3 ]# @/ V
一、键值设计
) c3 q) O3 M+ t/ A7 D& u4 W2 U; L( J
1、key名设计3 W6 k' Z# V7 A* w9 N* D
" f3 a2 A7 I1 Z. u! a4 }
可读性和可管理性
/ E( W8 ~) b; k2 G8 s3 v9 J  `
+ C( t+ O2 {) W! `$ w. g* E: q) r8 u以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id# H7 ~  V1 B3 ]$ q
    : K4 l5 u% `8 Z: ], Y( S( \
  • ugc:video:1
    - M3 r- B% q( a- V. W& D
简洁性
8 }! t4 ]9 c/ J" @! m
! h9 E- D1 F( g, Q保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
2 w/ k/ r- G( A& e
    1 N9 x- v/ f" O+ i9 H9 ]0 z- b
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。9 ]. d- N5 a/ _: H$ V/ ~# U* z
不要包含特殊字符0 }0 d; A$ V* }7 j  C# N- [

# {% z" H6 N+ o, u反例:包含空格、换行、单双引号以及其他转义字符
4 _2 p* r3 N  c2、value设计
/ i) V: q, W5 ~/ ]" M1 c
" g3 z: P6 }" j, f+ z6 m" R拒绝bigkey1 t6 W4 S6 b; W( q% F( l4 J0 T% t

* c" d' B% d6 U- h防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
  I( @! x% p, C( |. N9 ?! `0 d反例:一个包含200万个元素的list。$ ?! s6 x6 S; B: N5 N$ Q7 `. V
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
# W1 a: r4 a% G& @/ H+ e; O选择适合的数据类型
- J. F8 L4 _# y: }( B
" \! Q/ X5 A; X  ]5 `例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?& L: b% a: ^/ g: Q2 G' h8 [6 b
反例:
. m. _6 x( m0 o

    1 D' o6 }; N) w& a3 F& s, z( D& ^
  • set user:1:name tom( t) k* Y* H. G4 ^$ S
  • set user:1:age 19
    6 o1 g) t0 }2 S. Q( K1 N0 i
  • set user:1:favor football3 z. e- O6 Z& S
正例:
2 B! F( ?1 a, J0 @

    . ^) |6 b9 g9 k  o0 j
  • hmset user:1 name tom age 19 favor football0 B/ C! B6 n$ q5 {7 P' ~0 u
控制key的生命周期
/ t' \3 ]/ `; ]1 I( [8 e/ \# a8 r+ [9 ?& K$ K5 u, O3 K
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。* o4 E+ O+ U% Y$ g- g% H; }, G
二、命令使用
4 F7 P& }; K" ?6 V9 W
+ v( W) O( x3 f# D$ @1、O(N)命令关注N的数量
& p3 X9 a% e$ m" J7 t
" b( Q6 U- ]# k! x, Q例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。$ E, E' r0 T3 C& k; d$ s
2、禁用命令2 @6 q( c' _$ e" I7 D- ^

2 J8 S" S" |* K5 Z* O禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
* N+ H1 J. ~/ O7 g' m3、合理使用select
4 B" n( `0 z# Y2 ^

* I+ h- [7 @+ X* @; ^* x) ?3 `, _6 C  L: z
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
# {1 s- z9 @! X1 ^$ J) r4、使用批量操作提高效率
, T+ a2 c2 Q. T: C- M( t/ B$ {2 h

    4 |- q' P& d- ~- x! x8 V
  • 原生命令:例如mget、mset。
    1 n2 K% x1 z3 G6 C3 `) j! @! x9 N0 w
  • 非原生命令:可以使用pipeline提高效率。, O' a- k6 p; Z" ~
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。9 Y* R# R1 g- ^; |5 B* |  W
注意两者不同:( h* R3 f9 D9 A( @% P$ ?8 C& P- T
    * [* @6 `. A& L# A* @7 K
  • 原生是原子操作,pipeline是非原子操作。1 q/ C/ Z: K/ A9 m5 W
  • pipeline可以打包不同的命令,原生做不到( s9 Y/ d& k) d$ R+ |
  • pipeline需要客户端和服务端同时支持。
    ; y: b, J3 B; ^( t5 x" x
5、不建议过多使用Redis事务功能& L9 U$ A- q3 n% b* r
; `, t: f! o# S, Z' v
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
# ^! H! n2 t, p. y. r' X1 L% H6、Redis集群版本在使用Lua上有特殊要求
' t& Y! V& A0 K. s: [
! u0 I2 h" _6 j- D1、所有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"
: h* e) G1 J6 s# R8 j3 Z7、monitor命令: P! V! \: g/ T9 U; i

8 h8 e; z- F. S; h: y  W必要情况下使用monitor命令时,要注意不要长时间使用。8 \' G5 A# l" N3 q4 E
三、客户端使用
. x% _4 l7 m3 g- g. W$ `
  W( s; O/ @* K9 {, L1、避免多个应用使用一个Redis实例
1 y; o3 w: d! J/ \  `- M, z4 ~( f  l2 j2 _4 E
不相干的业务拆分,公共数据做服务化。
) [1 U4 F1 U; ]' J6 R: R( e& w2、使用连接池% G4 h; d3 C) v: {; g0 `: M

* o' m1 S6 I- F7 ^5 U6 U6 ]可以有效控制连接,同时提高效率,标准使用方式:9 N# K! u1 E# N* y* g: U
Jedis jedis = null;
) Q+ W; e# U6 o: I4 y9 btry {7 \- T* D4 s5 @1 H' b
jedis = jedisPool.getResource;
% I# Y' b+ u/ ~# w0 k //具体的命令* Q/ |1 r/ N4 j# c% \. _4 n# y
jedis.executeCommand
, r" z0 u3 i) r1 A& z} catch (Exception e) {6 V7 G) Z& \* p& ]+ k5 _8 O
logger.error("op key {} error: " + e.getMessage, key, e);
9 T* X/ F/ s* d( X} finally {
6 V' d( r- Y% E& ~ //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。9 Z0 E8 D. G: j2 `2 u0 j
if (jedis != null) 7 P+ B: W- |4 p9 a9 c$ s
jedis.close;
5 p& D. m) Q9 o  d+ J4 h3 P}
' K" M* ~  x+ Q; t5 {7 t3、熔断功能6 |; K3 V) a6 \1 p: Q( z. v2 O) j
* r3 H# S" ^+ I. w+ n
高并发下建议客户端添加熔断功能(例如netflix hystrix)) @( N4 }' O2 g
4、合理的加密
7 e/ z0 }  Q) \( D/ p/ v% K
* O1 ]5 P8 O: F* _( X- f- \( N设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
* Z) h1 I0 E$ Y* I5、淘汰策略3 i( o8 U* p  F: q+ {+ X% `# i3 D! o7 n

1 e& M" x+ O3 [2 K1 q, z1 k8 w根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。/ L  ?& b  L) ?/ y  Y
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
7 A& H* i6 O& }. l其他策略如下:- }- \2 @' P3 ~

    # p% B( _) H( v9 J4 R
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
    2 E" B& ?: `5 F) s4 O
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。: k( i0 B; J/ X* r: f2 m( d
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
    * g3 s9 c  v: w/ M5 O1 D
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。7 z; S" V% N: R7 {5 u
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。5 o: U5 Y7 a! H# E
四、相关工具5 [0 G. ?9 c  P6 R& N1 J/ v

5 e' c8 F4 N) O! N1 K/ g5 j1、数据同步
) a! t. z6 o/ z. S0 z5 n# f& H  X$ J, n6 Q) P) d
redis间数据同步可以使用:redis-port& g! M4 b" {; C( [/ R+ `) B2 C4 v
2、big key搜索: x% g" B% Z8 |8 ]; l
! b- X0 f+ ^0 N7 K
redis大key搜索工具
) ~6 z, r" \1 u# }* a3、热点key寻找
8 a# h8 f2 n2 s$ o+ z2 d" n
& ~( |0 I: M/ ~0 r! r  o内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题% t0 I' `# D* T* N1 \; |  |* j
五、删除bigkey
/ m) d1 K  v2 Z! L8 ~' G- o/ `
7 \+ a  D/ n& K5 _' V* f2 G/ p
    3 a6 ^$ z/ q, Z/ S' C% n
  • 下面操作可以使用pipeline加速。' ?6 X* [' o' `2 x( Q; G
  • redis 4.0已经支持key的异步删除,欢迎使用。
    * ^0 P, K( m2 H, l# l
1、Hash删除: hscan + hdel
* ^1 u, f" A! C7 ^/ U2 _' k, X
) K; q( F# a7 Y) a0 h" f  Ppublic void delBigHash(String host, int port, String password, String bigHashKey) {. @1 `% T" n6 N: C: ~8 l8 U$ H! [
Jedis jedis = new Jedis(host, port);
& [+ L7 J8 {8 A+ ] if (password != null && !"".equals(password)) {: v8 T  K3 n  G( O6 w- V
jedis.auth(password);
0 Y& F/ T" G  h) f }/ D+ B, c6 A! d0 t
ScanParams scanParams = new ScanParams.count(100);1 N1 W% _* n# g  q2 ^; G
String cursor = "0";; I* c* v: {  l( p
do {
* P+ l  q& H  M4 d$ U, k ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);8 O$ _! V  J+ C6 b: n; K
List entryList = scanResult.getResult;: v0 q- J* e7 A: [  r* x
if (entryList != null && !entryList.isEmpty) {
0 h* N9 w! s/ W$ H# n! H) ^, V for (Entry entry : entryList) {
5 R1 ?+ Q2 G7 z" G% O jedis.hdel(bigHashKey, entry.getKey);
4 V" q) o) x7 J7 Z2 z }8 t3 c. k+ ?& G, L0 p' M
}3 n6 {2 _$ L$ s/ o
cursor = scanResult.getStringCursor;& |: O/ P4 j* ^% T5 ?6 n* F- P4 S
} while (!"0".equals(cursor));
5 Y2 r1 Q4 j6 _0 P0 }) ]' M, o/ d7 A
//删除bigkey! J* Z+ q& G# O+ w# c& J- j
jedis.del(bigHashKey);9 p$ ]0 e, K- l+ b
}
' V# O$ ^: l. E6 t& i2、List删除: ltrim9 P4 V, m  s7 Z1 r
9 ~. k! e& J/ E* C$ U4 q: n
public void delBigList(String host, int port, String password, String bigListKey) {
# H# \% [3 j7 j8 Q8 ?8 s Jedis jedis = new Jedis(host, port);7 g  i! m3 l: X% |: z8 O" j
if (password != null && !"".equals(password)) {1 F% K: n+ Y; \+ T/ m) ^
jedis.auth(password);. o' v  m/ P& R3 n, j6 U
}
9 C$ F6 n; J0 s& n long llen = jedis.llen(bigListKey);1 d6 m( l- i$ M
int counter = 0;
' K7 N# ]  X: D8 ]: ^6 p  y( ~" J$ e$ S/ J int left = 100;2 \5 i* U! E, v! p
while (counter < llen) {
; v/ p( \4 ?8 i$ ~. i0 o //每次从左侧截掉100个
9 x6 }6 z( D% ]" l6 L1 V. n jedis.ltrim(bigListKey, left, llen);/ j! r, v1 H5 l, d% F& e
counter += left;# I$ l) V0 m- |" g, C& I6 m
}
2 j% e3 T1 P" \8 H8 ] //最终删除key
! m& K4 O% }+ B; i jedis.del(bigListKey);& b! }7 I7 A7 k- @! g. p
}3、Set删除: sscan + srem. S' m4 ~, P2 w( |, |8 \

6 E& t2 A0 q, l  Cpublic void delBigSet(String host, int port, String password, String bigSetKey) {
: ~; B% n  E, }2 `0 G Jedis jedis = new Jedis(host, port);& @9 v, J0 e* t+ s% |
if (password != null && !"".equals(password)) {
* F8 S4 i  |# O5 J: W4 v  q7 O jedis.auth(password);
- Q. Q" n. R3 z! N0 F- | }1 h, i: D, w7 Q7 W  }8 R
ScanParams scanParams = new ScanParams.count(100);
3 `! K; O3 s; A/ j String cursor = "0";
( r" Q8 f; y0 _8 s5 d9 ? do {' Z# G4 Y1 ~2 E, ?$ h; b$ _4 r/ F
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
/ L0 A; y: e/ q7 c% ] List memberList = scanResult.getResult;' ~# E% e+ P+ O* Q1 K+ }
if (memberList != null && !memberList.isEmpty) {: h2 R! ^& v$ B9 x" k
for (String member : memberList) {+ T% X3 X" n5 `, x  q1 `& P; m
jedis.srem(bigSetKey, member);
/ b- o! z: N5 V7 s, \9 } }
% z9 F3 Q/ K0 C7 U3 z5 A5 e }
8 i9 o( d% d- I1 Y! F  j cursor = scanResult.getStringCursor;
" X8 F7 _8 D' M; E6 B7 H& b& [ } while (!"0".equals(cursor));' r: X8 G4 m' x& v6 S

& ?* V# A. I" k0 H; i, ~//删除bigkey
& H  H! Q7 B1 o2 P, @! l, ~ jedis.del(bigSetKey);
5 x, h2 n) H/ r8 A1 }# k}- ?" w) H! \% _
4、SortedSet删除: zscan + zrem
+ O/ R5 Y4 b6 N- I& T5 Y* M( x
" v5 i' S5 c* vpublic void delBigZset(String host, int port, String password, String bigZsetKey) { 9 u. I0 e" y: h3 K, c, h7 o# O6 K
Jedis jedis = new Jedis(host, port); " e# M) r8 h) }* Z
if (password != null && !"".equals(password)) {
) ?, J3 r! P+ }1 l" O$ s  o jedis.auth(password); # m2 v9 o  Y6 f6 Z9 `- k+ H2 d
} 0 R7 b; ^7 i  z" A* U
ScanParams scanParams = new ScanParams.count(100);
' f, m7 L4 m  m: I String cursor = "0";
9 R. D9 G2 P+ I do {
) ?& x; V3 D& s- N" F+ h' q ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
: }8 M; q9 X6 l% I ListtupleList = scanResult.getResult;
4 Z4 s, e2 n8 Y' j if (tupleList != null && !tupleList.isEmpty) {
9 P# Y* d# T- G3 ?9 d for (Tuple tuple : tupleList) {
4 K# U$ V2 x+ ^3 l3 G# o jedis.zrem(bigZsetKey, tuple.getElement); 0 ^% e' M9 I9 L$ g
}
+ l: N" D! v3 r9 P* s( Z }
. a8 h4 X. L+ ~4 U cursor = scanResult.getStringCursor; 7 Y. V3 Y9 l  e
} while (!"0".equals(cursor));3 x- A8 c8 h3 H! J6 D

$ x6 \0 |: ~* z( K4 H//删除bigkey
7 }% |( U" ]4 u( \4 C4 Z( m* z jedis.del(bigZsetKey);
" r& n. {( z  c8 s# @- Q0 G} 公众号内回复“1”带你进粉丝群
! F  G: `& K* V4 X( P& J来源:http://www.yidianzixun.com/article/0LevQm7t! v, [6 a3 V1 K' T
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

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

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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