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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9247|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
4 z: B, h+ D' E8 a
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
. N# h, ]) l! J- K

    3 }4 c; P% Z( p! S
  • 键值设计
    & v3 O* A: p1 x( U) @* A
  • 命令使用0 V' E7 h& J) D  G* {! j
  • 客户端使用
    " Y* T9 A8 {5 H  N! j
  • 相关工具& F/ k7 P& J) O! T
通过本文的介绍可以减少使用Redis过程带来的问题。
6 u* x4 F3 b3 G/ V- r& i一、键值设计
2 k+ q; ~7 H: ?+ A1 A% ~& Y. f3 M5 \1 b" ], t" J
1、key名设计/ U. a0 n& a! `2 D$ l6 z

$ h7 j. C* c: c可读性和可管理性
5 v9 W; S5 I8 a. s9 L# s5 o; X, V7 d
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
, ]: F! ?) y8 z& H/ T2 D8 r
    . y( B3 b/ `' \* n) A
  • ugc:video:1
    2 Y8 D& @  U' i' X6 a
简洁性
8 d4 G' i0 j6 }& h9 _/ [6 ?. X3 ]& v' c* G5 F9 `1 G0 k
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
! O; r5 t6 _- j
    ; Y8 x0 X0 h  ?5 z2 W! B: H. m
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
    ( `5 L; K# _9 ]! x
不要包含特殊字符* ]2 y7 j: W7 u3 Y: Y0 H& ^7 |
1 I7 u6 A' R" m" x0 K8 T
反例:包含空格、换行、单双引号以及其他转义字符# F2 A6 A) L1 q0 H) J5 ~
2、value设计: C/ O9 `. k: S& W5 V+ O! K9 s

* X0 V% X& O8 \# o$ v) Z7 x拒绝bigkey( t& p# V3 _3 @8 T0 r, x, K8 v% n

* h% r  h' s. ^4 I# J防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。, ^: R" z( |, t: w2 D- B4 D
反例:一个包含200万个元素的list。+ M& r0 j5 x) A( t# R
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法. X# _: f7 F! ~+ s! c8 i
选择适合的数据类型
' W4 ^& F1 @+ e) G" {
) R- ^3 ]! n* _例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
3 N: b' P( b$ C; ^反例:
8 U2 v) \0 p! m0 U" c# g8 g8 V
    * A* J& H: z/ {- |2 }4 L* w
  • set user:1:name tom
    % w9 a0 g8 D2 P. ~6 O- H
  • set user:1:age 19- T/ w4 e! U/ N+ g# ~" A% F
  • set user:1:favor football+ y8 W+ W2 R* [8 w3 a$ `
正例:
$ N  T9 |& R* o' P3 Q" e- M$ Z8 k

    ; P1 b. j9 _3 [- V  z8 K" b
  • hmset user:1 name tom age 19 favor football$ q% I: s1 D; d* v
控制key的生命周期
+ Z0 x8 b8 y8 A( O1 O4 i) ?; B8 c
. B' B7 m& D! [" \8 F& Sredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
# F6 ~8 {2 q- M4 x9 u( N二、命令使用
( N, |" Z. @' x  S+ R4 n5 Z" ?! n; H: G! o
1、O(N)命令关注N的数量
5 k5 K7 Y! _- |, O' d+ B( J0 W: D2 g( B1 Y* {6 g) n; S& ~
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
. |% j. K5 b* k6 ]6 \3 L2、禁用命令
3 {- Y4 z% Z  u2 Q% v0 {% n7 A$ V! a0 ^& Z' o* r1 {7 S
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。. V6 I# T- g. m& v/ R, l# N) u
3、合理使用select) W4 s$ P2 h% G- ?% d6 J9 ]

. x3 l$ B) Z# N3 C* i& ~5 y: x+ }
8 j( ]" a5 I: C  tredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
6 o+ i9 m  n" J+ ]9 G4、使用批量操作提高效率
; i) ~5 ]) B5 t; O$ \
( E* _" S$ E) ^. i1 `1 C/ ?

    6 v, r! B1 P8 V; V6 ?( L/ \
  • 原生命令:例如mget、mset。4 r: f& ~% [) S# B& Y$ K  C% N
  • 非原生命令:可以使用pipeline提高效率。: Q. J0 h7 O9 r4 W) @2 o
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。$ `# I: n, s+ ]! E
注意两者不同:- D0 a4 Z9 W- W! `$ u
    2 b/ I8 ^$ k" p! l1 U$ \! g+ ~; v
  • 原生是原子操作,pipeline是非原子操作。
    % Y, y0 h; N/ r" s
  • pipeline可以打包不同的命令,原生做不到: }/ m& I+ W6 R
  • pipeline需要客户端和服务端同时支持。
    - y9 A( @/ U* l& z  z
5、不建议过多使用Redis事务功能  U* I: @( _3 s% r; r& A0 r
" ]; d/ }% }# d5 i
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
9 j5 P  D& S- h6 t9 v6、Redis集群版本在使用Lua上有特殊要求
* w8 r, g9 H. B: w9 o2 l) j$ q$ T9 l: ^
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"
8 R! S, C8 n, K; ]7、monitor命令
2 d6 y1 G  M* N' }
: r6 ?6 t, X. \% E; V1 T" o5 @必要情况下使用monitor命令时,要注意不要长时间使用。% [# r& `% R* Y1 Z5 r: v
三、客户端使用) Q5 }8 C# H& t

9 A( |8 @4 q; ?: C6 d4 w: ]1、避免多个应用使用一个Redis实例6 A' }; A* D- V7 R
0 F9 n3 q4 W, `" f& J
不相干的业务拆分,公共数据做服务化。2 x% a0 Q3 A+ V/ W  _$ C8 `6 g% K$ l: U
2、使用连接池5 m! |0 E. W& v3 N
: T! Q- a0 y' r8 b8 Q; i
可以有效控制连接,同时提高效率,标准使用方式:# m& }# K2 X6 Y
Jedis jedis = null;" ^; d9 J# J6 R
try {# T/ K  d' U( x* P6 s1 C3 P
jedis = jedisPool.getResource;  k8 j) i* k7 I! b5 u
//具体的命令& n* g, `* V+ U) C, k: y
jedis.executeCommand* u, E* f" o6 y) G1 v
} catch (Exception e) {
$ N+ t9 x% K3 F/ n) E: T logger.error("op key {} error: " + e.getMessage, key, e);
' Z( \, ^" ]$ {3 |5 l/ G# C} finally {
4 G5 [- Y4 C" s- L! t/ f, i4 l! N/ q //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
4 s6 ?# U% C' S% u& \1 q+ |  m if (jedis != null) % H$ F: K+ R3 h7 k6 v  m
jedis.close;- d% r4 J# G% f1 ~
}( r/ C3 J+ \4 F3 N3 ?8 C5 e
3、熔断功能0 R3 y( }8 p( x) \0 U8 e- K

. d" g: u' |+ c: z高并发下建议客户端添加熔断功能(例如netflix hystrix)
5 {9 \( b9 X- R3 e. D% u4、合理的加密
, t0 ~6 d! Q& H( k+ D+ h, @; {& s6 w7 W4 O( }; f3 k
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
1 k# [$ S( \9 v- ]) E1 i/ ]5、淘汰策略1 i9 O8 F2 O6 g3 E5 O

/ d, l! \6 L9 U0 G; A2 w$ S根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。' Y* u$ j9 g6 s4 n
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。( V; n, w. U! {& B" z9 d$ l( J
其他策略如下:
* _; g/ N6 E" I( V: |1 g

    : y: {# F. t# G
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。1 d- k% y7 a* a# T
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。; F# M2 U0 ~% r" Z; \
  • volatile-random:随机删除过期键,直到腾出足够空间为止。  H2 c' b: e, n; P! `7 G
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    ; `8 T0 H# u: r4 N1 |. k% v
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    6 x: A& V0 s% n4 z( ^7 d/ j
四、相关工具
5 N' L1 u, g$ m+ b# m; N9 s4 v5 a) ]
1、数据同步0 ~" g) _! n9 I, B) P2 ~  h. {
0 I; p! I; F8 G; @$ r, [8 D3 P6 U
redis间数据同步可以使用:redis-port6 G9 [) Q" Q4 V$ p! [" E3 {" z
2、big key搜索
3 V9 H# q+ c: l! w( w
& w, G7 k) U6 B5 l) F/ V5 X; bredis大key搜索工具
' z7 o# p: L6 n3、热点key寻找6 f+ J4 ]  b; M: |
( W3 i8 d& B  p/ y4 ]5 H" i2 ^
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
; L/ A4 X- a& r7 R7 v' P五、删除bigkey
0 V3 G8 H5 p* l9 T, [( ~" |
+ v$ {# A7 W0 L+ t9 H
    $ T7 ^* w2 S' {9 b) t
  • 下面操作可以使用pipeline加速。0 U) O9 S. p! N' F1 p% P; {/ Q
  • redis 4.0已经支持key的异步删除,欢迎使用。6 @. E: {. r, B7 M- w5 {6 Y2 S
1、Hash删除: hscan + hdel
2 i+ Q6 T, S* G' X# R% m
- z% ~9 b8 U1 Y  w( _* x$ X, w/ wpublic void delBigHash(String host, int port, String password, String bigHashKey) {$ c$ ~* R0 e* W+ ~5 ?5 l
Jedis jedis = new Jedis(host, port);
2 c; ~9 e! q) a1 p) t if (password != null && !"".equals(password)) {0 o+ z5 ^6 ?) q5 B, R
jedis.auth(password);
" q' j6 g7 T$ w, D }2 F! G8 R8 _( N9 q8 h" m# z+ m7 x/ I
ScanParams scanParams = new ScanParams.count(100);
2 J2 h" k$ T& x" t& M String cursor = "0";1 x: [  t8 i2 {5 E  Z3 U. ?
do {' c) a: m- a( b& z
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);5 n, U- Y6 v) d5 g+ f
List entryList = scanResult.getResult;0 n  z6 j/ w7 V' I+ K: \, ]
if (entryList != null && !entryList.isEmpty) {; T) K! g" p' A1 ^0 e% J
for (Entry entry : entryList) {
; Z3 v% u& ?( M% s9 ` jedis.hdel(bigHashKey, entry.getKey);- S' ^* [* p0 Q2 ^' X2 K. k
}
  |7 E2 @! r4 ?. C }6 u$ Z7 ^5 Q+ K" i$ a0 l3 U
cursor = scanResult.getStringCursor;
6 O8 j: o& a7 A3 c8 E0 p5 i } while (!"0".equals(cursor));
7 Y8 i! l4 G$ \  @
6 @* S9 k: I/ r- a  N4 S' o//删除bigkey+ B, |$ C& l3 o2 h# _
jedis.del(bigHashKey);& a' h3 Y3 j+ {$ @: `( I9 \
}+ s. s( \8 x+ t1 l3 w
2、List删除: ltrim+ x" P7 C- y! q. n! l" y+ z$ w
+ G/ E! _; n3 j" x# j
public void delBigList(String host, int port, String password, String bigListKey) {
& B5 {0 d& }3 w Jedis jedis = new Jedis(host, port);
% a  U0 ]" j# T# ]8 A if (password != null && !"".equals(password)) {
/ i+ `  t' z. v  I- a) @, z: ` jedis.auth(password);
3 `8 w; s5 \. }! V; N% J" I }
& Q/ \1 r  `! n7 R' e, f long llen = jedis.llen(bigListKey);
0 Y5 j- h: A0 A) c( ]( q int counter = 0;
4 K- b7 J% p4 W, f( o/ l int left = 100;
) M0 d$ V+ L$ L. s0 z9 P while (counter < llen) {0 U( g% `$ N* {
//每次从左侧截掉100个0 c; Y1 w! v) X; t" S% O% [$ O
jedis.ltrim(bigListKey, left, llen);- [1 ?4 E5 ^' N6 Q7 Z7 }, K
counter += left;
; e0 V6 \( I, H* F  y }4 d3 |. p6 c5 W& T6 f1 P: f7 i
//最终删除key
& m" e- M( `* h( j jedis.del(bigListKey);
  f( T3 s3 t5 C6 Z9 o3 i( u}3、Set删除: sscan + srem- g$ V$ o  a7 w8 x
2 W" Z9 @$ M: [
public void delBigSet(String host, int port, String password, String bigSetKey) {
3 k  G6 p' R4 K Jedis jedis = new Jedis(host, port);
# i5 }3 ~5 x+ }7 y9 g8 x& M0 {6 f if (password != null && !"".equals(password)) {8 n  j* c0 k. H  o7 I& K
jedis.auth(password);5 G; i$ V& g: [( M( O
}6 @3 j8 V! B* g, X- `( {4 J' |
ScanParams scanParams = new ScanParams.count(100);
! ~6 n2 y6 T. |8 H- I String cursor = "0";& J% `5 S6 _; r; J/ y, R5 a
do {
# E% N- s) K! r ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
* d; W* L7 Y) D' D List memberList = scanResult.getResult;
& n: S7 d6 |7 R9 e) l if (memberList != null && !memberList.isEmpty) {
* J* }/ A7 w, P& O! W for (String member : memberList) {' S+ ^+ |; \; ^& g" \) }
jedis.srem(bigSetKey, member);
2 m. l. v8 Z) N- k: T' G6 t }! c1 ^; z/ e' v0 Z' s  Q4 t
}
+ d/ ^( V0 L" n$ Z* g cursor = scanResult.getStringCursor;
5 m: T1 f2 j- o' i9 Y" F } while (!"0".equals(cursor));7 A! ]- P: L( _6 F* U

9 q7 g2 v$ ]$ x6 C2 `9 `8 M$ i//删除bigkey# L5 ^# Y& R8 d! t& m5 q
jedis.del(bigSetKey);
: [5 G2 ?4 i4 g: {8 n}; K7 f- }1 b$ h9 X
4、SortedSet删除: zscan + zrem
& I" Z2 ]- ^# Y; {2 E
* ~! g7 A. j2 M/ C: Tpublic void delBigZset(String host, int port, String password, String bigZsetKey) { 1 \. n% D* H- a9 ~0 _0 G( ?: f$ ^
Jedis jedis = new Jedis(host, port); ' m; A1 Y( u+ X: i! a
if (password != null && !"".equals(password)) { * g7 H! Q% `) ^1 I8 H
jedis.auth(password);
+ ]( K" r, C! _" {1 _  ? } / f) P, i  r4 @5 E
ScanParams scanParams = new ScanParams.count(100); & J2 N' Y( R1 U" B) K9 B
String cursor = "0"; # v" s: c* A6 A4 R- A8 g7 k+ l
do {
' u5 d0 `3 H* G( B ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); - g( z- D" c" D
ListtupleList = scanResult.getResult;
4 _+ a5 `) w! j- v( n7 B if (tupleList != null && !tupleList.isEmpty) {
+ [, ^3 S& L% t. n for (Tuple tuple : tupleList) { 7 n' t$ ~* j- p" O- x# R+ n
jedis.zrem(bigZsetKey, tuple.getElement);
9 i- H$ u2 f. M }
6 r4 X% u7 \( q6 ]6 b( a } 2 W& b% Z/ L. B$ z
cursor = scanResult.getStringCursor; ' G. v( `4 s  \( ]$ N$ w& h
} while (!"0".equals(cursor));: H  w1 v0 R6 s: Z- c% G
1 ]# t$ x" C7 C- f
//删除bigkey ) P; x) M1 K5 m% Q
jedis.del(bigZsetKey); # ]7 f# P2 A' y& @! c
} 公众号内回复“1”带你进粉丝群. h0 m% M% O" O0 U. |9 e
来源:http://www.yidianzixun.com/article/0LevQm7t8 v# L. H1 O' j
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-11-9 15:15 , Processed in 0.054935 second(s), 23 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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