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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9367|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
  T" [/ G% {, H) k; e# i
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。( C! G$ S- J$ {6 L

    ) X1 e# a4 G* D( H0 i# h
  • 键值设计
    $ `. ]7 O& S9 J5 }! \: g  h
  • 命令使用4 l; z* c- U; E, m2 x. P% H6 o
  • 客户端使用% \' f0 k$ ^6 @$ \! a# ?( Y5 C
  • 相关工具2 ?% B& W& w" j; s* d
通过本文的介绍可以减少使用Redis过程带来的问题。
% l9 I0 r0 g( _% P) I9 d一、键值设计
. S: ]- b! f7 B0 Y3 ^# I0 W! H! O  z( Y- q. B
1、key名设计
, V# E" M; h) j1 D& d, U6 k" g4 m8 X- X7 u( e& e
可读性和可管理性% V- Y. Y/ C+ ?. T+ F, b

& ?5 `  c% ]9 u' a3 ]以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
" Q7 t! u! I) ^( `, d/ V& G# l- L
    / Z0 j- l+ `+ F. |7 `3 d' ?
  • ugc:video:11 {. j1 g. d3 N* @# ]
简洁性
3 i" f3 E& I% B7 m8 S$ p2 T' g' S9 {, z0 Q
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
. A9 e! ~; b- g: _) d
    ' |3 N  ~2 {. M% G9 E1 d
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。$ [) y8 [( n2 E9 s  G4 c* R$ \  v
不要包含特殊字符. Q0 S& t: J: p! G% m; p# V4 t
+ z9 n1 P: ?+ ]) |; I0 L0 n$ s, a
反例:包含空格、换行、单双引号以及其他转义字符! _! e* N2 Y: V, `- L
2、value设计/ E- w2 W2 N. J7 y- \. L

9 C4 k. S: k- m0 d6 Y拒绝bigkey# E. x- W8 K" E8 X3 {. W' y

* `) A' O( p5 j* m" w防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
+ _- V: f- R0 o: G+ i" B反例:一个包含200万个元素的list。: ]4 k. [% C( Z  M  m, ~
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
+ k. x# F& P8 N8 v7 |2 [; ~* M选择适合的数据类型5 Q- {5 A$ v; g' [8 e) d8 G5 O
' r7 [3 b5 @0 {/ g" I" H7 e3 Y
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
0 @7 I; ]9 e! i  K- s8 ^反例:
' j6 ]( R! Y: M7 J
    ' c! x! c' h$ p7 }. ?) o0 [2 ]* S  ~
  • set user:1:name tom
    " y0 ~! l9 \' M/ B9 V3 Y
  • set user:1:age 19* ^( n( {" t$ f( M
  • set user:1:favor football
    7 @1 [9 j2 P& c9 j" G6 }9 k8 p
正例:% y. n9 F  F$ j5 u

    7 K1 ]; s& n- x. T; U5 |: H
  • hmset user:1 name tom age 19 favor football5 E# M( @  }* R6 q
控制key的生命周期
- g" k, [/ e1 ^) T) t
" r8 \* }) C7 g. Jredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
" J: ]  k, T4 [7 M! K2 Y+ V4 k: Z二、命令使用7 S, ~# L$ I7 K7 R* V
' `3 M3 g, d. R2 |/ i0 X
1、O(N)命令关注N的数量! M0 v6 h' E: @. _0 K

1 I, a2 _! s( |: f例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。4 J  W/ }6 a. |8 f" R7 t
2、禁用命令. h+ h. Q) X5 m7 T+ E, J
7 k5 f0 u; T; T( M- H
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。% e5 S+ v( u9 m7 f6 r) J7 ^4 P
3、合理使用select) J  y8 K" R6 Y# ?: }

4 u+ i: x% ~6 r* X' l& c9 E/ U# ~, y& z8 U7 U2 _  h2 s
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
& O$ B. o+ l7 c' [  ?: Z& ]& _4、使用批量操作提高效率: d* ~! \  A6 |& g
1 e& ]: g9 t- F3 `. d$ G; K$ B
    4 D# E* F5 T  `2 [( _( X- _+ T4 k! m  }
  • 原生命令:例如mget、mset。* t* I. r+ K3 ~/ U6 J" |8 T
  • 非原生命令:可以使用pipeline提高效率。0 }; q$ T9 i" [) Z/ F
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。2 ?3 c; ^6 |! h4 F9 \; N$ ^, ?
注意两者不同:
( S7 x- a( \& t' h! ~$ S

      I9 C: F, y! C! L/ Y# e
  • 原生是原子操作,pipeline是非原子操作。' G! M4 j9 [; I/ R# @% S% z
  • pipeline可以打包不同的命令,原生做不到0 \" o8 g: V2 _* S
  • pipeline需要客户端和服务端同时支持。& B( J! f3 s1 d9 K& _4 b3 }' p( M
5、不建议过多使用Redis事务功能( v2 y! |) H; x
6 ~: b6 G5 P9 s) P3 @
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
# c7 P* X. s! a8 }% w6、Redis集群版本在使用Lua上有特殊要求1 {! Y9 z2 r5 G
( X& g7 {0 y- s$ k; I$ u( L, l: W* B$ }
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"
! A, k- Q; k7 g& g& i+ a7、monitor命令: h& E3 m' J6 ]  i8 |
! |5 D, U/ a8 d5 f4 C4 i( H
必要情况下使用monitor命令时,要注意不要长时间使用。% ^/ R. B9 I: _. X5 t" ~3 R% i  ^
三、客户端使用
: L! q7 s% X4 c8 }& w7 V+ F8 d6 f* Y3 N& j/ ~, C1 [2 W6 W4 T. G
1、避免多个应用使用一个Redis实例: a" v: |. M6 u  B$ h
2 l$ U+ I9 X. ]  d1 U$ n+ v0 a% t" ?
不相干的业务拆分,公共数据做服务化。
( f/ U: D" b, f# p* E2、使用连接池: Z3 T1 A) C; O; k6 ~0 O" R

5 Q3 ?% v1 l. t# c! g) c. @可以有效控制连接,同时提高效率,标准使用方式:' n% ]3 C" m6 K& a1 H1 p  w% F$ x
Jedis jedis = null;8 |: q1 ?! b: [/ _
try {
' I8 M+ J' |2 N6 ?0 M jedis = jedisPool.getResource;# `9 R- y  r2 N4 X1 |/ H1 j! ]$ e+ N
//具体的命令% M' {- c; a, r% H% ]1 B9 X) `& K
jedis.executeCommand) s3 t; L1 q2 D
} catch (Exception e) {1 s1 `2 z( G% F% Y
logger.error("op key {} error: " + e.getMessage, key, e);, ?- t0 E1 q9 t, q/ g; G3 o; Q* t, {: S
} finally {& u+ [$ R# v+ j4 Z# d
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。+ Y# ]/ ~: d8 K
if (jedis != null) 3 b- {6 e* F; P1 ~
jedis.close;
7 @/ M8 x. ]. h4 n/ \/ i}
2 `$ `4 u5 ^8 b3、熔断功能! f3 Z7 F* }! H& q7 `2 t) ?# ]

& o4 {3 I+ k" W; ?( L高并发下建议客户端添加熔断功能(例如netflix hystrix)) o$ ~6 r  R3 I+ Z
4、合理的加密
/ N3 Z5 S" z; X7 {4 _9 k0 w% |- ]$ m- }! r$ G. g, z
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
0 B- k/ S, u8 F, d, C+ e5、淘汰策略
9 G* k: L: B' t0 D6 ?* [' g7 g) ?% a% P  X3 F
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
6 S% B6 N3 Z  N' }  E% Y  n4 a* D  \默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
. @+ z5 f* K0 @( A5 _其他策略如下:
4 u, `8 y& R1 ~( Q$ C/ \" z
    - q) v  O& M" Y4 m
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
    9 T' s1 \- [' C0 Z7 o. N
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。1 \0 }) V1 o9 a' M0 G$ f# t
  • volatile-random:随机删除过期键,直到腾出足够空间为止。0 y# q9 x6 I1 m( @: n$ E+ A
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    " X% X4 W3 \2 [0 X* T" o
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    1 N5 S% Y9 _0 W0 U4 X7 U0 p
四、相关工具( n/ A# Q3 C5 [) d+ j9 _& }

( [5 H4 o/ J2 f% D, u. `1、数据同步& C& _  q! L5 L8 {
( D0 S! f& D1 O" D! l/ `6 V
redis间数据同步可以使用:redis-port" @9 ^+ i- |4 l- k2 u
2、big key搜索& C) H2 A/ N* i  ^! p/ U

) O2 ]" H8 q. m' U' ^redis大key搜索工具! m* Y" x7 j) U9 a3 Y
3、热点key寻找
0 w/ f2 D. D1 K, w' b& h+ I0 S! J4 I# z+ e$ B
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
2 {+ s2 @* H5 y" |: t0 U$ ^五、删除bigkey
0 Q: \1 Z1 [2 F& x' Q5 I0 r# ~# f0 a( J; d
    ; ]3 ]% z$ S+ i! i# G) D& u! v
  • 下面操作可以使用pipeline加速。7 @7 j/ W9 y- s$ p6 e  q
  • redis 4.0已经支持key的异步删除,欢迎使用。
    " \4 D+ h( L, O. n. }
1、Hash删除: hscan + hdel
3 d0 {3 F3 Q1 H; H; ?- ~+ y' ~# [; T9 x( O4 F8 x% a$ M0 i# m
public void delBigHash(String host, int port, String password, String bigHashKey) {% X5 S- w8 T, Z' V0 C5 q
Jedis jedis = new Jedis(host, port);! r$ e; ]+ a& D2 m) J4 `+ S3 Q
if (password != null && !"".equals(password)) {
' |1 M1 Q* [! K0 ~ jedis.auth(password);
( \  f# @  G8 z- g }
5 e$ v7 A4 v2 P# K7 r* v ScanParams scanParams = new ScanParams.count(100);
3 @2 d/ Q& Y+ N String cursor = "0";
5 U6 J: \2 r( d9 k# v do {
* Y0 D& C5 A8 B6 v9 |' c$ W2 F4 S1 D ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);+ l2 A3 d; y8 c# Y5 c  W
List entryList = scanResult.getResult;
6 M3 G9 _/ ?1 r! |' [6 M; x* X/ T9 Z# G) M if (entryList != null && !entryList.isEmpty) {
1 N* s' h+ B' H0 P9 g for (Entry entry : entryList) {
% m' l; F2 D* O$ l7 D jedis.hdel(bigHashKey, entry.getKey);, o1 t; N, n2 I5 P
}
" T- z  s+ e1 _ }
) N  \% S% O' ^5 A9 [" B cursor = scanResult.getStringCursor;- \" r2 G4 e4 A0 i" ]$ ]4 k! |
} while (!"0".equals(cursor));
1 E; ^" j) \8 {
5 L; Q/ ^& t# {. T//删除bigkey' B# N. O8 i) m/ ?6 w
jedis.del(bigHashKey);
( M0 Y- ~5 v: B' q! H* n0 h}) h& Q  A& E& y+ N
2、List删除: ltrim
0 l2 z3 v* m" [* L8 _4 D5 K
/ n5 ]' X- p, ~public void delBigList(String host, int port, String password, String bigListKey) {& R3 K5 E" Y" `$ k0 `2 ]4 u9 u" C
Jedis jedis = new Jedis(host, port);
, I4 o; ]7 o8 k$ K$ c7 p if (password != null && !"".equals(password)) {8 G* z- `$ {/ l/ p: R
jedis.auth(password);4 V( \% W* O3 J' U2 v
}
; x: j, k. k6 d2 ]6 l, y2 F long llen = jedis.llen(bigListKey);5 H# r* B0 f0 \
int counter = 0;! l2 }. M+ s% ]- T
int left = 100;
  `5 K3 b3 {$ ]$ v while (counter < llen) {0 P' U$ l, G" `  Z
//每次从左侧截掉100个
) U6 y0 Z9 m. E8 T; i7 w( U jedis.ltrim(bigListKey, left, llen);
" z# D% J( @& S$ U# y8 j: O) U9 q counter += left;4 h, ~, u  A$ v. q+ _
}
7 t; O( w- C1 v //最终删除key
8 h/ E4 G* _3 C jedis.del(bigListKey);
1 G( `' h* b+ n: j+ f4 h}3、Set删除: sscan + srem, }7 e. x5 `0 d* p

8 Z. ?! F( y9 |" d0 hpublic void delBigSet(String host, int port, String password, String bigSetKey) {% q  P% e/ M: J' h+ z6 [: r
Jedis jedis = new Jedis(host, port);- W% ~* r/ d- Y  w6 e/ U3 L- s4 _
if (password != null && !"".equals(password)) {3 h7 p" ]$ r  o4 e7 e/ @) q
jedis.auth(password);
$ b! O+ v6 O7 M, m8 {( m" V }
- _3 l3 E& W7 n" s% w( @+ u8 P ScanParams scanParams = new ScanParams.count(100);, Y3 ^3 P/ t* p$ ]
String cursor = "0";
" k7 t& h: S* [+ ~4 g# E; k do {: T9 G6 D1 U2 m+ t; S+ x
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);6 Z8 o4 C( t; q. r& B' X8 P
List memberList = scanResult.getResult;& L, @! }4 A% V; C9 ~/ o1 c
if (memberList != null && !memberList.isEmpty) {3 {2 T4 }+ v$ M
for (String member : memberList) {
# J1 X- a8 l9 A/ s7 ? jedis.srem(bigSetKey, member);" ]& z7 o$ u+ u' f2 a3 L
}& N5 K7 ~$ |+ ]+ s
}
# z' b" K# c! d) A cursor = scanResult.getStringCursor;5 C4 w4 o% t) w# S5 V
} while (!"0".equals(cursor));$ W4 l" x( |# _' Y  e( Y: t( N! e6 R9 b
! j- X; a2 e/ e! B4 W; ?
//删除bigkey4 _4 u- ]: ~7 m  i
jedis.del(bigSetKey);
# C# s% y2 W0 z% ~0 l9 e}2 q& ^1 u0 P6 o% ^" b, t
4、SortedSet删除: zscan + zrem" I* d, H. c/ T- N
: Y( x2 ]- F% e
public void delBigZset(String host, int port, String password, String bigZsetKey) { ( s& B* Y4 b1 T& v7 `) P
Jedis jedis = new Jedis(host, port);
" M) C5 f2 M+ S! U5 }% s/ W if (password != null && !"".equals(password)) {
1 M# q1 i2 J# y% M3 d( `/ {: y jedis.auth(password); ! a! {( c% }3 W  x, d, R0 }2 B+ [1 Y
}
- @! B9 |+ s! ], u( ] ScanParams scanParams = new ScanParams.count(100); / G% E. K7 c$ d2 u
String cursor = "0";
: W7 P- _$ a. k2 b) q! A  k do { 0 i( M; d9 e% Y
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
9 c% g; p  J$ i+ H, q ListtupleList = scanResult.getResult; # Y8 m: D5 `0 a/ Y* Y) Z
if (tupleList != null && !tupleList.isEmpty) { - r4 S  p; v4 y. g5 `$ M
for (Tuple tuple : tupleList) {
+ [8 {/ t  c- J6 \! l* z6 [+ ]. Q  V jedis.zrem(bigZsetKey, tuple.getElement); ! Y; \3 ?* z# v3 a' ]; o
} + M/ C/ M* q3 J8 \4 b' E
}
( _! j) J5 ]8 X+ d' }7 H cursor = scanResult.getStringCursor; % C2 g# `2 j  E5 n1 M# [: M3 T
} while (!"0".equals(cursor));
+ Z4 U5 x  Q* n2 ^6 f+ k& ~' L4 N2 P0 V+ D  w1 ~5 c( z0 Y9 U
//删除bigkey 4 u4 d0 ~4 l; v0 H
jedis.del(bigZsetKey); ! O4 E# Q5 D# t' b
} 公众号内回复“1”带你进粉丝群% v6 C9 q7 D8 p) _( ^
来源:http://www.yidianzixun.com/article/0LevQm7t
* L6 Q3 `; `" X8 N  ?/ I: H! `- F免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-7-16 17:02 , Processed in 0.044411 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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