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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9198|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
+ X: t) ~% w2 Q1 I
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。7 v* @; ~9 ?" K

    ! v( `9 J- l3 M
  • 键值设计
    % S. ]! {9 l4 V. f% H. L% k7 J
  • 命令使用; Z, J* n( v* I* K& `- N3 M: r4 H5 [
  • 客户端使用" J# r& x. v% ]4 H2 M1 a- O
  • 相关工具
    4 s: D$ k! z2 G) t! J
通过本文的介绍可以减少使用Redis过程带来的问题。! m# e0 `5 p. m+ u- L$ N
一、键值设计( X2 P& D2 C" R( |. Z& n
" y' _, G1 v" g4 n( |( Y
1、key名设计! f/ x7 B) O+ y  e" P
+ ]$ G3 @: L  W4 [& D
可读性和可管理性
& {* \- e9 Z3 G  W& ]' G% k3 @2 S" U' f: u" j' S
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id4 \0 H# R8 d3 X2 @! q) G4 D6 Q# w
    * ~+ }+ ~9 b4 Y) d& W4 \
  • ugc:video:1
    # i( B. W7 [' R
简洁性- P0 T6 [1 F0 Q. i

' H" u& z0 Y2 J: Q保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
# L& w$ q+ a, J6 s. i" p; ~

    5 j, m0 H* w' X& r, x. p6 w
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。7 C$ W4 @1 o# I3 m
不要包含特殊字符8 [* h8 g# L5 H  v/ {% ^" i, S
# m% Y+ V( l* K( K! t) @
反例:包含空格、换行、单双引号以及其他转义字符
9 n+ b1 S/ E$ G. N2、value设计
7 q+ A- i" c9 i/ u
" h4 t8 W8 F. M8 }7 U5 d拒绝bigkey! ]) D2 Q3 v6 ~" h( u

, E& b- _2 t- ^5 u2 ]: V防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
8 d) U  `! F8 K2 g  |反例:一个包含200万个元素的list。
% v1 }3 Z8 G* i! l7 X非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
/ C- Z, X  q6 o# E选择适合的数据类型
/ D8 B3 M) t8 h6 F& h( o- i9 k* S8 c& w2 r" ^9 G
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?/ {" G4 O; I3 v) X
反例:
7 d% L4 _- v# ?+ @* M

    ) g7 V  C, {) N) u
  • set user:1:name tom
    6 z6 d( I" T( q1 o
  • set user:1:age 192 ]" H" O9 ]( ]4 V( W
  • set user:1:favor football% `% D7 B9 u3 i% K7 G4 E9 {6 Q/ _  p
正例:
+ c  e4 c; @) U; z* Q

    6 b0 @5 A# |4 P: @
  • hmset user:1 name tom age 19 favor football0 Q/ F2 u' z8 p% \
控制key的生命周期
: ^0 W  J4 V+ p" k; m5 S( e  v  ^' ?, f' f
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
: X$ v/ V& w/ K/ \& F1 U二、命令使用
6 M9 C! [6 Z" F/ g9 z' K8 `7 `- ~
$ M5 E0 P7 P& ]0 }5 T2 q1、O(N)命令关注N的数量$ Y7 b' H% K: ^

0 p' ~; H7 X3 m1 K0 G3 }1 p+ v8 c例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。4 J( h" h' t& y# d! A& a, j9 f* B( Q
2、禁用命令" y" z2 H) y) s" B9 w) L% X2 Y; f3 t
3 d! Y' U* i% Z$ s. e6 `" K$ @1 L( |
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。* b- ^8 B0 _( h4 Q/ O# v
3、合理使用select
" W5 H9 G  t8 u+ j1 o& P

3 W' A6 t9 W6 P
. s  w, [5 B7 |( d) Predis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。, F, D3 M; Z8 _/ V3 O; N3 {
4、使用批量操作提高效率# s4 q' F) h- L8 F

0 u$ H# ~: N& Y, G

    ' x  z. c) ~% f3 G& t* x# q
  • 原生命令:例如mget、mset。
    ( _4 k. O. _% [
  • 非原生命令:可以使用pipeline提高效率。
    1 c. H& J: I4 R+ D5 Y
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
3 [  G' L: `. j/ R0 \注意两者不同:
! p) g) i% b; F. P3 D# g! X& }

    9 Q- |9 k2 r9 i1 V+ p; Q
  • 原生是原子操作,pipeline是非原子操作。$ I2 A1 p! M5 R' \  a
  • pipeline可以打包不同的命令,原生做不到
    & F( }, ^4 W& B9 o, \
  • pipeline需要客户端和服务端同时支持。
    $ t# Y# C" }  l% [# |
5、不建议过多使用Redis事务功能( m) X. V. d4 u: K* t

& g" G: [  ~8 c2 C; ]+ a+ k0 dRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
2 v* D/ B; R! h: v6、Redis集群版本在使用Lua上有特殊要求
7 L4 R1 Y6 q* s7 Y: q6 A8 z7 s+ W5 u, h4 i
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": h8 m1 m4 c& V  K7 T! |. ?) r
7、monitor命令
! _# I1 V; O& C4 s6 H0 N: e1 |- z: J1 B3 H6 l2 X7 o4 L6 t
必要情况下使用monitor命令时,要注意不要长时间使用。
( @6 J  m* `7 f7 J2 N+ t% k$ B7 D三、客户端使用% u* o' ]" P; C% s
  h# |5 w: W# q
1、避免多个应用使用一个Redis实例
! P5 r2 S1 @4 X( s" j* X! V' y
" {" ~) D+ y4 \0 E1 e6 R& w+ R不相干的业务拆分,公共数据做服务化。# F" E+ F* a- |! h+ I- v* R
2、使用连接池5 g, l+ k* t- m& _2 s( c( ~1 q

* B5 H6 I, n6 x. L! p可以有效控制连接,同时提高效率,标准使用方式:1 s8 ]) K& p8 T- z# C2 C( P3 r
Jedis jedis = null;) \" O) e) J# }2 x1 }2 R0 R
try {  [# f7 S2 P- p0 j8 H, l
jedis = jedisPool.getResource;1 Q  P8 V# @  w: t
//具体的命令7 G5 N& t/ V. j, Z! u: b
jedis.executeCommand* @* f( \3 Y0 i6 G4 e
} catch (Exception e) {
% o. B1 Q. }& j6 Y3 t( M3 P+ O1 t logger.error("op key {} error: " + e.getMessage, key, e);
+ @+ j6 n2 P; D2 n( `} finally {
" I$ l+ e5 V  M; p% v/ X //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
6 c+ b, C  D) I if (jedis != null) ( K% B+ b" z$ X# _* ~! h  [
jedis.close;
; M! e3 W. m- A, F}
" e& V; F& G/ c% F% Q3、熔断功能8 u/ h& T9 _- K
( S& E# \. C- H* ?
高并发下建议客户端添加熔断功能(例如netflix hystrix); n7 |& Q! I( y4 o
4、合理的加密
- l9 Z6 d: E: F+ f4 k! C& e. P/ M. y$ n" l, A: r6 ]
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持); ~* C* z4 M& A( w! |, a
5、淘汰策略, ~# U5 H* Y- K3 z

: u# X3 y: O6 ]2 Q- V根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
; }6 E" [, N1 E& K5 n! K5 f默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。  ]/ Z3 c+ J; E' k% _
其他策略如下:! I- Y2 m" j' H+ |0 H% U) U% R2 H0 K
    # a/ ~& F, x6 y6 y
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
    & M3 Q2 ?2 L. t" V( Y
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    4 e% i6 N: c4 P4 t; b2 w) F
  • volatile-random:随机删除过期键,直到腾出足够空间为止。* c7 \6 r+ l! s/ @
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。, t1 j7 B0 N$ g+ T5 O9 V' ~+ a0 Z* x
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    6 {! Q; q( [  q* r% n+ P2 Q! R+ ?
四、相关工具7 c$ u3 M$ G6 v! `% _

; G2 P+ Y* D) k4 V5 p  a1、数据同步0 {0 I: L. }9 X0 N( n* `

! f- p( k( _! e. l" l9 kredis间数据同步可以使用:redis-port
3 J1 o+ }+ F/ u* @5 k; o2 ^2、big key搜索
$ f& L1 ^7 E' z  V7 d
( q' q9 j2 X* I/ L, z7 T4 Lredis大key搜索工具; }" a. q, r6 |8 j
3、热点key寻找
& g0 s0 r5 z8 ^$ `% K0 t/ ^
; h; B2 w+ L- T: t. z) A% H3 t内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题8 h) M  D' `% z) x% d
五、删除bigkey/ C' s+ f  |: F" X

( K' M% _; S( P5 \8 E7 c

    6 N6 i- F$ D' I
  • 下面操作可以使用pipeline加速。
    2 Y- R" \  R  V2 C- @
  • redis 4.0已经支持key的异步删除,欢迎使用。* J0 O- I8 m% s: G. Q
1、Hash删除: hscan + hdel* p+ i6 w2 E' L% a2 d; i! W4 m

- o% `: i& Q, q8 D  Zpublic void delBigHash(String host, int port, String password, String bigHashKey) {
, q9 A* ~3 @5 t6 t Jedis jedis = new Jedis(host, port);4 w: Y; n2 V6 X3 i( J
if (password != null && !"".equals(password)) {2 h+ W7 A, _: ?4 ^1 [8 v
jedis.auth(password);
8 p" j0 l( \( t. V$ u }
" X, T2 G" g1 g- ] ScanParams scanParams = new ScanParams.count(100);
. ~5 e$ v, O' J+ k1 ]3 o String cursor = "0";
# s9 |  }" k* N( Y+ I. y# D! S do {
1 m# @" m$ e6 Q$ V) L9 W' U ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);) k7 J+ b: D  t0 b
List entryList = scanResult.getResult;0 N, s- B5 Q3 G# {7 |
if (entryList != null && !entryList.isEmpty) {3 m# T- A, ]$ r  h6 \6 l. E
for (Entry entry : entryList) {2 p$ ?3 n# u% C1 N) e) F
jedis.hdel(bigHashKey, entry.getKey);+ M( Z( E' @5 o6 V5 \6 y; c$ t2 W* t
}
' _, P" _& a: c3 c# r2 V1 y }
- y9 b" F" B' i2 l3 o1 X1 T1 L" @ cursor = scanResult.getStringCursor;
+ W  s# h, S; x- o8 N } while (!"0".equals(cursor));# }& Y% Y9 ^0 U( R% j; F% Y! s

0 Q+ A8 E+ E! v) e, C& C1 \3 _//删除bigkey8 }1 O7 C; b2 u) B
jedis.del(bigHashKey);) R+ E. k+ s; c- y
}
7 H/ N9 b- D- M% t2 s2、List删除: ltrim$ k7 ^; M7 _' O
+ u$ ?  Y4 a  F; }0 O% g
public void delBigList(String host, int port, String password, String bigListKey) {
" X* I1 @5 d$ O* a! [5 n Jedis jedis = new Jedis(host, port);/ P  e) c1 ~7 E# R( m7 s, M
if (password != null && !"".equals(password)) {; Y: A+ w6 r4 k+ d' s6 C- G
jedis.auth(password);
0 [  H- }. F8 k }
6 O/ }) G# M2 _$ [ long llen = jedis.llen(bigListKey);8 E8 h$ s' m3 ]
int counter = 0;
; ~) z+ l$ w  Z1 B  q int left = 100;
  n; h/ P- z6 I+ {: ]3 t while (counter < llen) {
& X* \- f* U# p. w5 a4 U //每次从左侧截掉100个( r+ M3 g" H/ y, r5 z
jedis.ltrim(bigListKey, left, llen);$ z7 t  P2 S( h, w- x
counter += left;
+ j, R/ H4 m' U }( j$ P' T. @: P% K1 M9 t, _
//最终删除key' N7 w, Y  a3 ^9 M  e* }4 y0 M
jedis.del(bigListKey);; ]8 k; ^& d# s* t5 n0 E" C8 S3 C4 r
}3、Set删除: sscan + srem
6 d% ~! o  \' @( }3 I' N/ F7 h- @6 H; ?9 ?
public void delBigSet(String host, int port, String password, String bigSetKey) {3 [& }* y4 N- j! z' Z/ J
Jedis jedis = new Jedis(host, port);" \6 I6 {4 F7 h. G+ I
if (password != null && !"".equals(password)) {
" m( o- p8 u; d5 ]: L jedis.auth(password);
, D8 t' K: O' q# } }
7 k1 R  ^) S3 t+ q$ q4 E& E ScanParams scanParams = new ScanParams.count(100);  [+ \/ p5 @0 T, M; i  u
String cursor = "0";# F' M5 f1 T1 A2 X$ m4 T' C0 F% E+ o
do {
" r$ ~' ^' b! B3 N4 N ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);* i( g& Q' c/ g" k3 q, [% f  P5 c7 s
List memberList = scanResult.getResult;
& r# O) n. C) U, m4 a! K* t if (memberList != null && !memberList.isEmpty) {
2 L% c% g% m- j. z: Z for (String member : memberList) {
) r# f, e' `9 Y0 F8 o; D+ A# h jedis.srem(bigSetKey, member);
+ Y% D5 n1 A" D1 p/ y( }) l }2 u4 w- q# p3 x  \, b
}
0 u2 W0 u- J# ?0 ]- N cursor = scanResult.getStringCursor;
- c! b, [( }2 T* L } while (!"0".equals(cursor));3 [- A4 R3 W& [2 V

1 p5 q5 A, e9 s0 E1 T//删除bigkey
; l# F/ A+ W9 b( k4 m5 J" T& Z jedis.del(bigSetKey);2 c. Q& g& \3 ~, G1 k: D6 _  i
}; D7 O! \: b' m1 G9 @/ Y- J
4、SortedSet删除: zscan + zrem
# J# S  \% S7 z0 L& ]/ s5 x
' b# ~1 N/ ?2 m4 z  @2 l" Y% @+ n% ?public void delBigZset(String host, int port, String password, String bigZsetKey) { - f" J: W, b) N9 O/ Q
Jedis jedis = new Jedis(host, port); , J5 M6 d0 Q7 w+ K
if (password != null && !"".equals(password)) { - L4 E' o# J! s; Y
jedis.auth(password);
0 h! O/ R; @# X: I4 `" x& E }
2 @4 c( J! R) U' i! ?% A" ^ ScanParams scanParams = new ScanParams.count(100);
" o; a) m/ ~7 r: R' N String cursor = "0"; 0 K4 J, o8 l6 N7 Z+ f
do {
  A. I9 ]' b, ~, b; b ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
8 Y3 z2 n) z& V/ z3 O ListtupleList = scanResult.getResult;
) H5 g3 s& V# s% V  G# e4 Z7 Q3 { if (tupleList != null && !tupleList.isEmpty) { ' F. R/ U, W& ]' t# H; ]
for (Tuple tuple : tupleList) { ! T1 x& y* e1 ]
jedis.zrem(bigZsetKey, tuple.getElement); " a) d: |; }8 F; D6 u
} + E1 W) M4 H. h. z! c
}
# ^3 ^+ k- {( B0 F% l8 F* V cursor = scanResult.getStringCursor; , Y3 a$ L6 L2 d& Y3 N) ?* \
} while (!"0".equals(cursor));
) u  K* L0 S& X* a5 r  ^
, d# |4 @2 x0 J5 u//删除bigkey
+ u3 Q6 y/ S' P) `- D jedis.del(bigZsetKey);
$ X  s. S+ I4 c) |, T: a- Z} 公众号内回复“1”带你进粉丝群2 u2 z: D7 d$ K1 m4 j* i1 U( O
来源:http://www.yidianzixun.com/article/0LevQm7t) N+ z# t: C$ p  Z: M! J: N
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

关闭

站长推荐上一条 /6 下一条

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

GMT+8, 2025-7-16 16:30 , Processed in 0.041493 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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