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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9313|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

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

3 N) g1 Q) ]* Q本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。( D! s9 \* p+ i

    5 s6 ?& g7 Z8 w& n- T- ]7 z, M* s
  • 键值设计
    . O9 {/ L1 l8 D) l- |; C% s! l
  • 命令使用
    ) a+ b- N, M+ x8 n
  • 客户端使用6 E  _$ d( h, p# Z, R# B" A7 H- ]
  • 相关工具
    2 v: G; \! z5 i( P& T
通过本文的介绍可以减少使用Redis过程带来的问题。
: v4 G7 c: H3 d) T- u: m6 J一、键值设计
( h/ e& n4 o/ C. x8 _' p* r; j' t' Y( N' ^8 a& p: G" f/ }4 I3 a
1、key名设计
: p9 ?# W7 G  {' E& g2 ^, X: v# d( ^( L2 E
可读性和可管理性
7 t$ t: g% s2 o% O* s+ o0 L# R3 q/ n3 A5 U
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id7 W/ u. D3 }2 T( e

    5 N& P, x6 M' d$ o  T
  • ugc:video:1( ~% Z( Z2 x2 d' S$ x
简洁性
3 ?" W% E, ]5 \8 m% }4 ^) A2 U% q5 X  j) w" {
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
* ?9 j2 `. a: F' Y& h

    . S* L; C6 T6 J  M% @7 j1 r/ r
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。9 J( Z# v" H6 [. n
不要包含特殊字符7 C0 x* e1 I+ _2 \) F& S3 s4 g' |# F

- X; N" f6 z0 K7 P. j反例:包含空格、换行、单双引号以及其他转义字符
" |% U* I: h4 `+ L" j% @) m/ \& w2、value设计5 i! i8 F- e. b% |: f

) s9 \7 J  U2 l拒绝bigkey1 m; j; S% m( M4 J

% P8 n& b8 {  ^* F7 ?! }  [防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
3 }, @# h* f& K2 b反例:一个包含200万个元素的list。
' g" t7 z: g" R2 ]4 V非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法' g+ S: }1 c1 k6 j  c
选择适合的数据类型& M+ c$ W5 |  A# U8 H% p" l, `

7 {# h+ Z% @: a1 ?$ ^( P例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?1 F! G/ C0 q& \) e
反例:/ y6 p2 F% i+ ]! G$ y  I

    ) _2 s: C' z3 H- H
  • set user:1:name tom: B# O! \0 M, `3 E5 o) b2 _2 X
  • set user:1:age 190 B# i- N1 K5 h( G  P* U+ o% t; |4 ~
  • set user:1:favor football
    ! K  Z* S* S4 A; P4 Q% Q. c
正例:
( f# z  d2 b9 w' g$ r3 y* c$ J
      C+ X0 N& d, f5 g
  • hmset user:1 name tom age 19 favor football
      H* q+ S! D% {3 q
控制key的生命周期- h1 T: J. z, P6 M9 w( d) X2 s+ l% C" \
: R3 D1 v- |" ~; b% a
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。7 a+ R: N/ V2 X
二、命令使用" F' N1 ?5 O) `5 T6 D

+ j6 ^2 U) i% u; o! X1 W( v1、O(N)命令关注N的数量( @- @3 Q* s8 _& Q/ h% c/ y
9 h1 a5 D; B5 r1 Y5 V
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
% X- j$ r! {  n& X) ?/ [3 c2、禁用命令
: p' X: Z# ?4 a$ D" s3 T7 y: O. l. Y# S+ p4 |8 U
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
% Y. ^; i& O# |$ T2 D6 @- Z3、合理使用select" h( I. l" S  g  @6 u+ _

1 x5 \0 \8 o. r+ N- K& q7 e6 [/ _: |
% {+ k  `8 s8 s4 ]3 o! Q( Aredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。/ U0 p% [9 i& A! [1 @
4、使用批量操作提高效率. n1 P/ L2 H  Q: z) ^% D

0 |+ R+ n. w) `' K

    $ ?# \8 C$ O6 C
  • 原生命令:例如mget、mset。
    % ?' [( M1 Q' R
  • 非原生命令:可以使用pipeline提高效率。
    0 h4 D+ ^2 H) @6 B
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
  P# `9 Z) k# s4 X) M  K6 D注意两者不同:$ H7 A; n/ e# w! u2 G8 |1 ?9 F/ U

    0 q2 W6 X+ E/ A$ Z# H4 c( ?
  • 原生是原子操作,pipeline是非原子操作。5 x& @/ _4 A9 ?( a* j6 C, y
  • pipeline可以打包不同的命令,原生做不到) e$ j  M4 z& W* Q) ?* B
  • pipeline需要客户端和服务端同时支持。
    9 J6 W: C) N5 m. n9 r1 |. r
5、不建议过多使用Redis事务功能& x: r* ?2 I( J  \
! X) G) ?" @# Z0 h# C; I* q. L4 g: q6 z
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!* r) ]& S3 S! {$ q, w
6、Redis集群版本在使用Lua上有特殊要求
  U7 F) [3 b$ F7 V5 x
1 R9 P# [* J' X, A1、所有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"
0 J& p" m3 c. ]) j  }7、monitor命令
, i4 j* h% _# `: B4 c; x7 @5 d% x0 D
必要情况下使用monitor命令时,要注意不要长时间使用。& Z  X4 C$ E& Z+ v
三、客户端使用
) O1 G$ _- u$ s" P5 d5 E& j. y
1、避免多个应用使用一个Redis实例
3 B& n1 [' X9 l: ]6 L% F/ r! \" c1 V* o2 U: ?5 Y
不相干的业务拆分,公共数据做服务化。' t# n2 h% M6 ]( W4 W
2、使用连接池  k/ f/ ]/ T0 v1 X
9 F2 w$ Q6 U6 Y" w: h8 F( n
可以有效控制连接,同时提高效率,标准使用方式:1 ~. E0 Q* b: r3 g8 H
Jedis jedis = null;
4 L, t$ C" M; l( ctry {
  l! O3 E$ s% j  j: ?  x3 n jedis = jedisPool.getResource;# V5 w& d& w3 _2 `. i
//具体的命令
0 ]% E( @" N1 L3 H; @ jedis.executeCommand
) x2 o* Z/ i/ ?7 h. v/ T, q} catch (Exception e) {5 S) s6 M: `% Q% }8 Y& j
logger.error("op key {} error: " + e.getMessage, key, e);
# S+ R( i4 m2 J% X$ C3 @( t} finally {
. S" X4 C7 w) C/ [7 I+ ~+ r% Q# R' V //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
9 \. j  \& o7 o2 V/ `5 z9 X' O if (jedis != null) + [( z3 O1 s/ ^0 a) J* G1 u; Z
jedis.close;  l6 V( p3 a7 P) t" j
}. u; ~% T- X4 D7 O, @2 F9 y
3、熔断功能
% G2 n- [! ]; s' m, `" H  V3 V, V" ]; ?7 f7 U$ f' U; U
高并发下建议客户端添加熔断功能(例如netflix hystrix)/ w! w, I3 S+ N
4、合理的加密1 s. ~2 d/ I, J" m. D

1 G' J& l! X# k/ c! D+ v设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
8 ^$ Q& c2 _7 k5、淘汰策略
7 h! C, U5 h+ R6 W
" D/ \0 V# E$ P$ S# F% ?根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。7 j; {, V) Q, `
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
* F0 d4 I9 B: q' ^) c6 U其他策略如下:8 F8 E# s9 Z( ?; ?

    ' Z6 d) c0 E. c4 k9 U: S$ ~/ h. m+ e' f
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
    1 n1 m" T7 s9 g9 U5 E4 e7 D+ o4 Z9 k
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。4 q& m0 s  i0 q+ j
  • volatile-random:随机删除过期键,直到腾出足够空间为止。: z: x- S7 o( a: P) W% Z: Y3 M
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。2 F/ H  M9 ?$ P& j
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    # d. ]6 {! v! m" s! ?+ F0 k
四、相关工具
3 @4 Q. m4 x# x  ^
3 ~7 ^4 j/ S7 ?0 ~3 B9 \1、数据同步
  ^$ \. ]+ `- c& Y  P' s* z
. ^! p( Z& C3 P" |4 o: R& Aredis间数据同步可以使用:redis-port$ e4 T6 y4 L; [( T% n
2、big key搜索
( g$ f4 h) G7 Q! U' D* S! {7 ?: b6 O3 j: d. T4 m9 n
redis大key搜索工具6 N2 b) L9 T; P. p9 M2 c
3、热点key寻找
; R. ~3 ~* e: F" g) v1 _/ U$ I- v& ~( U1 X' t, H1 H
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题# ^9 s$ Y& b) Y7 E$ R* ^
五、删除bigkey* Q6 n3 |9 G4 `

$ w  V1 z# e+ K  t5 d$ d
    1 o7 Y6 g& x) B* q. T
  • 下面操作可以使用pipeline加速。
    6 Y3 V8 I) u, V! B7 D3 H6 D
  • redis 4.0已经支持key的异步删除,欢迎使用。
    . ?: _( x+ z3 y" n- x
1、Hash删除: hscan + hdel
: _  t2 q; r1 `5 B# z7 }- F, J  c& v1 B# D
public void delBigHash(String host, int port, String password, String bigHashKey) {
4 u1 s  q; V2 n# N% I: c Jedis jedis = new Jedis(host, port);
% v. r/ p) c! h( L+ Q if (password != null && !"".equals(password)) {
6 ]' Q% m, F- S7 D& }) s/ v( b+ h. R jedis.auth(password);
; ]( _5 ]7 K% q& T) {' U: q" G: B- I }* Q% E$ ~6 d9 C; [
ScanParams scanParams = new ScanParams.count(100);
7 v  Q, d# W9 ?9 s6 p String cursor = "0";% D+ Q4 H' K7 c5 L8 y* a
do {' m" W$ t8 }% I2 k& M
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);" K4 S& H3 ], Y. n
List entryList = scanResult.getResult;) b6 V' p" a! }# G5 u3 g- h! E0 r
if (entryList != null && !entryList.isEmpty) {3 p1 b' i; T0 H
for (Entry entry : entryList) {4 h$ g; d$ R, Y; G/ _
jedis.hdel(bigHashKey, entry.getKey);% ]& U' t% C8 Q; M: R
}
- H0 W( K8 @! c% ~0 Z0 u5 E }( J% k8 ^2 l  o  X- @# n
cursor = scanResult.getStringCursor;
& M: d* S% ?, G' }; l. Z } while (!"0".equals(cursor));- l: D' P* ?* [
. Z0 o8 U' Z. ]1 q
//删除bigkey- P( W9 Q3 W  D: D) [. q& T  V6 W
jedis.del(bigHashKey);4 V2 B6 F9 |8 ^
}
) E& h! W6 e; v% F0 S/ d2、List删除: ltrim- b7 r, g; R+ u) g6 _, J
- r5 E. i& J! f8 E8 w/ z1 B/ b
public void delBigList(String host, int port, String password, String bigListKey) {; L% H; I5 ~) ~& M8 ^) `
Jedis jedis = new Jedis(host, port);2 X0 X0 `1 x" J7 f; `
if (password != null && !"".equals(password)) {3 i) l+ t$ D* {, ]
jedis.auth(password);
2 p2 l0 b4 f  T4 N" F, B( p+ ~ }
' ~- x+ L/ j; t  \2 A  `0 |& l long llen = jedis.llen(bigListKey);$ Q  `& m$ h! O4 y+ Z
int counter = 0;
3 l$ i0 |. P# l9 c& \ int left = 100;
8 A9 X  t7 ]5 C& z3 n3 s while (counter < llen) {
! i: K6 i+ Q9 j; T$ F //每次从左侧截掉100个
. T1 d0 o+ b, V4 }8 Q jedis.ltrim(bigListKey, left, llen);2 S! W/ q3 R9 c
counter += left;
$ A0 J. o8 j9 a- D8 |  x }; r  Z5 Q/ J! y8 ~, [
//最终删除key* `1 e" P: O" z* o, p
jedis.del(bigListKey);9 U2 X/ _  M/ z% o0 S' W
}3、Set删除: sscan + srem7 H" R! H, @5 u
7 e, E: H' p: w9 G6 U/ m2 L* T  ~
public void delBigSet(String host, int port, String password, String bigSetKey) {
7 y5 w" z, Q: V0 u Jedis jedis = new Jedis(host, port);
  |! e+ c2 h) F/ Y6 \' D' F if (password != null && !"".equals(password)) {1 Y. P  L) A) E3 H9 `& P! M
jedis.auth(password);
% [: M: q  m5 j, [9 D }
+ x0 ?$ |3 B/ |8 p/ F& _& |* ~ ScanParams scanParams = new ScanParams.count(100);
' M4 _0 q! N7 h1 {+ A String cursor = "0";$ k. {) s# g2 k0 N1 u4 f6 u3 v
do {
8 O% I; I, }1 d, ^8 a, A ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
3 x) }" F6 }2 a# [/ x) e% |1 p9 C List memberList = scanResult.getResult;
; t( d. V: t/ D# ? if (memberList != null && !memberList.isEmpty) {, v! f3 Y, n& O, ?
for (String member : memberList) {
* U& \7 y" O7 B) h1 M# C3 O/ B jedis.srem(bigSetKey, member);0 ]% x& v8 d+ R/ ?9 E# `- Q* `
}
9 `) b, B, A5 m3 j2 N6 |& o9 b }3 v% L2 R" p, w8 J
cursor = scanResult.getStringCursor;3 n6 |+ D  n$ Y+ C
} while (!"0".equals(cursor));/ A7 L3 B2 y4 l

' E% x. b. K- o/ t' z$ z//删除bigkey
( o( {) X8 y. M( O: U jedis.del(bigSetKey);
+ P8 d6 `) ^! J; p}+ K5 ~8 R; b7 Q- z/ ]$ k
4、SortedSet删除: zscan + zrem
) n9 W6 V  e! R7 Z/ P3 C8 p
6 F3 y& O8 R& epublic void delBigZset(String host, int port, String password, String bigZsetKey) { 0 t1 f/ A! T9 _7 T* t& r: m! p+ ?0 q
Jedis jedis = new Jedis(host, port); ' M6 Y4 a7 \7 t% b1 M
if (password != null && !"".equals(password)) {
% Q& ]$ a& F8 n jedis.auth(password); . z7 V9 Y) {1 U! }
} $ C  l0 D9 a- Z
ScanParams scanParams = new ScanParams.count(100);
/ k" f7 w; y1 @) _ String cursor = "0"; * ?* J9 z9 _( A0 Q" s+ ]2 k* G
do {
- ~* r0 N5 f# J5 l3 |+ I- Q, [+ N! T ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
5 _/ w) u* e, b  a! D ListtupleList = scanResult.getResult; ( t# A( f( m( J4 o; ?1 M$ @
if (tupleList != null && !tupleList.isEmpty) {
4 Y, o& E' c6 I. N3 C3 S, S9 E2 A1 f for (Tuple tuple : tupleList) {
0 H  ~. Y1 e' X2 m' i% A& f jedis.zrem(bigZsetKey, tuple.getElement);
9 t, i2 N/ w; d: v/ L  ^ } 2 M  c3 l/ q% o" o0 c. L
}
- m  p2 x. I! [' y# }$ s  v cursor = scanResult.getStringCursor;
5 b% [) ?0 b3 i& l+ q } while (!"0".equals(cursor));
7 W2 d: h2 B4 n5 _" I
+ R5 Y9 @- z# B//删除bigkey
1 l# D7 i7 Y$ b: }: z jedis.del(bigZsetKey); ) H/ p+ N( [+ |. ~# B7 O$ o
} 公众号内回复“1”带你进粉丝群3 x* x/ v" m4 W/ l8 L# T
来源:http://www.yidianzixun.com/article/0LevQm7t
. S) H: N% u, O. C8 g8 V0 G免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-4-19 02:55 , Processed in 0.039269 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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