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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9193|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

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

( G2 e4 M5 B2 F" I8 f1 ?/ G4 \本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。7 p6 L/ c* H, z' ]7 ~) m
    5 y5 |4 h; O! t, x! J: F" w" \
  • 键值设计6 @9 M! ~% \! }4 z/ x! t/ @( k
  • 命令使用5 U- E8 r& X- n! s5 h- r6 R! H
  • 客户端使用# H" [% [) K% T) a
  • 相关工具" e; ^' e! T: F5 X. Y6 w) i) u
通过本文的介绍可以减少使用Redis过程带来的问题。
8 Q  l0 q( `% }, h) P) x一、键值设计
6 Y( c3 ]9 o. X5 k% B0 ~2 Y
+ u- S3 K' j* f1、key名设计
7 T5 b, p  ^- S2 b; [# L) T4 q/ l' h% j) z$ {) `9 D
可读性和可管理性
3 ?8 `0 n' ~2 X( J0 i" _: A% p% E0 X$ f4 F& Q9 @  d
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id4 P7 u% h" ]+ Q. h7 Z% K
    6 D/ _$ c+ v, h# f: j
  • ugc:video:1
    + O) C0 Z2 `( E2 Z2 x. u8 t3 a
简洁性6 s8 ~: ^# v3 F$ r0 O" w
3 B; @/ i7 G4 j+ ~! K; l7 |" W
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:# B7 t; N- Y/ F1 C1 n# _

    6 q1 C1 t3 H- R# d
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。: h3 B. F6 N5 Q
不要包含特殊字符! _& M& M$ |& d

- Y! O) ~- L' d8 Q0 g  w0 L反例:包含空格、换行、单双引号以及其他转义字符& }! Q& w- K. d6 V$ O& O# H
2、value设计
' [0 q3 i8 g7 r
2 W) w; I; G  R拒绝bigkey) R: Y% x* i- N/ X4 a; H  l& F7 s
9 K' [! K! ^! o; o' D
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。4 R6 w, c; Q2 v& e" `% z
反例:一个包含200万个元素的list。
( Z3 K& F3 U, q% d0 X  @! S非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法* L5 k8 y9 m) o! h& c  ?% ?
选择适合的数据类型
; t6 j* Y4 c$ G$ P3 u
2 Q; p7 b  E' v' b例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?) b! p0 Q9 i; y/ a
反例:6 e) w5 b2 Y& _$ Z1 q

    ( _1 e! I0 E9 E) m$ m2 D
  • set user:1:name tom8 G3 {( [' g" n( q  R
  • set user:1:age 191 @( T" _/ k- s4 H& z" f/ j
  • set user:1:favor football
    - H  `, l) w3 i  m: q( v9 B
正例:
% t4 A2 t' r' Q) F
    % a) ~' ^( C! y/ C; T) \
  • hmset user:1 name tom age 19 favor football* L& X8 ]) V6 a7 d7 d
控制key的生命周期, a6 o) z: M: S  d  i. k1 O: o
+ g$ I; m) Z9 J; T
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
. R2 W1 C* p7 ]0 f; S) `! W2 ]3 g二、命令使用
1 H& l0 K9 }+ b2 j" F( L
8 F% j7 K: H5 A* j" N$ x" N1、O(N)命令关注N的数量' R$ h+ K5 C* H/ M1 G0 m5 I: @
* {# ~7 s' y+ G6 z1 G
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。% o/ _# g3 \/ e2 m$ a
2、禁用命令
7 N- O1 {/ u& i4 L0 a* ]( j# k1 e$ L! a$ J  I8 r6 u
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
+ n3 ~( e+ E- e( Z: S: z: \3、合理使用select
1 d/ m0 _6 q* g
8 I% s6 {8 V* r: r
1 T  p( P' }6 N/ s
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
* o8 y3 S8 a6 p: G) Q$ D8 t4、使用批量操作提高效率
1 n# M* b) I* T# K, P1 p) \0 Y" d1 v  M' q7 G7 m& s

    & b/ a0 Y1 N% L& t5 @% U1 A8 {
  • 原生命令:例如mget、mset。
    7 h: r3 z  _) ], T2 U+ H2 ^% {" l! s
  • 非原生命令:可以使用pipeline提高效率。- E+ S, _0 J+ V  I4 t, l
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。- Y6 W" w6 n$ C. F: S9 I
注意两者不同:' K" o* `" K$ ]8 {. S0 N* i
    , b! b- n) V' O( m* I
  • 原生是原子操作,pipeline是非原子操作。* K8 p7 U- y* L  C8 ^
  • pipeline可以打包不同的命令,原生做不到
    * Z2 O! v0 b4 D5 Q
  • pipeline需要客户端和服务端同时支持。
    7 i$ u9 x/ Y) F* ]1 k8 g( E' ?
5、不建议过多使用Redis事务功能
* Y# v  H8 S9 D! B9 l; C) A/ t0 E: O$ \0 U
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!8 U9 p. }6 O0 g% L" s
6、Redis集群版本在使用Lua上有特殊要求
" x6 W. n4 E5 z! w5 z* \( Y! O6 K# _( d! k
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"9 ~* F6 i% E/ z" N3 r
7、monitor命令
  W9 r7 W% k8 n# J6 `' k( |
, O) B8 C8 D% q/ J! g3 }; [3 m4 M; G必要情况下使用monitor命令时,要注意不要长时间使用。; V6 r- a7 Z; C! C: s/ w8 \
三、客户端使用/ o+ h* [1 f, U& X

' Q5 \! }9 t( w2 n$ l1、避免多个应用使用一个Redis实例
) U3 p/ X4 |% p( f) M% w) }8 n) S" u7 @& \
不相干的业务拆分,公共数据做服务化。
$ F+ ]/ B, ], L' f! N/ F2、使用连接池% l0 ?! f2 P; R

( L  w/ l% ?' Y- F; F" Q8 V/ i$ Y可以有效控制连接,同时提高效率,标准使用方式:
2 }) u) [: A0 ?' u8 N, F4 S- R* l# aJedis jedis = null;
& h5 J: k* N' g. I* K5 ~& ytry {9 ?  `" H( O0 W4 [
jedis = jedisPool.getResource;
- y# g. e, w% O2 V  ?7 V //具体的命令! z0 S1 |2 z0 T. O  c
jedis.executeCommand
4 E  p/ D, g0 m  K% l} catch (Exception e) {
* A- ?+ J; U! e. t logger.error("op key {} error: " + e.getMessage, key, e);
# w- o$ }6 P6 B; R) u} finally {
) T/ A9 E0 X' [2 u+ W //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。: a1 H# \9 y4 S  i  ?/ w
if (jedis != null)
8 L; [  R2 [! L" H; I jedis.close;: ?& n2 @. O5 w) `1 Z$ X
}" ?- F5 r  t4 {0 H/ Z" m
3、熔断功能
) k/ u" R) k5 A0 y7 m# U! ^& A: V5 H5 z6 b& Q) d" Z, P8 h
高并发下建议客户端添加熔断功能(例如netflix hystrix)
* V3 U- ?7 V6 K; D( Q1 {4、合理的加密8 n  R" [+ N+ M# R/ a% O
! }4 U8 m5 ?( Y6 p6 z
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)& \  z# ~7 M3 ^5 b1 v# D* n
5、淘汰策略
8 k& @# g- M4 x0 v! t6 A# n9 z3 {3 S
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。3 P% C$ x9 y9 h# ~. i% V
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
6 H$ @! U: W' `- V# w8 q其他策略如下:
1 T% C# X6 a- u1 J. u3 }

    : ~2 w  A5 n! Q5 k" Y5 o% f, f/ n
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。, R$ p; _/ d  b3 O
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    6 X. L% {* h# I8 J
  • volatile-random:随机删除过期键,直到腾出足够空间为止。5 J, y" A% c$ E
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。. ?' X# o. j+ u* u( s2 a9 B
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    6 F! z; n" v: L+ [
四、相关工具. J( ]3 i" d! N( H

% q# X6 U; E3 v* M1 U1、数据同步
* r! B/ W1 W9 V' ?: T7 W' w
& J0 J, h5 e% [( S/ Yredis间数据同步可以使用:redis-port* Z1 E7 J) |3 D! i
2、big key搜索$ Q. I2 B* ]9 N9 f2 T3 c" b' d# U
) F8 I9 a4 ]$ @$ n
redis大key搜索工具, R7 K5 B0 F1 S: D
3、热点key寻找$ ~, d. r* e- s+ B0 R2 ~

# G: x& X) l* G% _# F) `0 V6 J内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
- r; j- a; H; ?' N五、删除bigkey
% j& N- f! j1 d) b8 Y. H/ n% Z0 }2 R7 Q
    ! E8 j0 _2 g: `, o
  • 下面操作可以使用pipeline加速。
    % z; P# C0 z3 g( ^
  • redis 4.0已经支持key的异步删除,欢迎使用。
    ; l/ r5 Q0 @$ f" }
1、Hash删除: hscan + hdel# ^5 ~" C( l: p) t" i

4 {9 K2 c7 J1 n/ @  I+ b' A; {public void delBigHash(String host, int port, String password, String bigHashKey) {
9 R4 }% t, d' D0 I Jedis jedis = new Jedis(host, port);
8 K5 Q! Y+ D( I4 ~5 ^/ O if (password != null && !"".equals(password)) {
4 d* i( v4 F7 Q, e/ `- k jedis.auth(password);
  ~" q6 A6 X# e0 E6 P8 j: r1 m }, s2 c. ]1 K. F( b
ScanParams scanParams = new ScanParams.count(100);  v. U+ e! D. Z, K
String cursor = "0";
1 `! s7 o  k+ B+ o do {
7 E& Z5 q5 c8 R ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
% ?9 J* X! u' t List entryList = scanResult.getResult;
0 Q) J& n8 w3 Q6 h7 F) V if (entryList != null && !entryList.isEmpty) {
8 Q# O! K8 g: ?9 R for (Entry entry : entryList) {5 [% L& s) k  _3 n+ }; U) o; K
jedis.hdel(bigHashKey, entry.getKey);
4 U' Q9 R9 ]/ Z  v8 ~6 @" J1 X* M }
9 o" L( S3 _# ~) q6 j! x5 C4 @8 F }
" i: b+ d! F0 I+ K7 H" Y cursor = scanResult.getStringCursor;
0 B  Z: Q: C% M& \8 I7 c6 J( Z } while (!"0".equals(cursor));& j* l! a  a4 V1 L3 T/ d

7 h0 a: ?5 z. J$ C//删除bigkey* {* r. B" i% x  }/ T; }
jedis.del(bigHashKey);6 d0 B4 U- b  _
}
( w* m  G7 ?9 ]3 y2、List删除: ltrim
6 `- m2 S3 `5 ?/ v- g8 P
6 a/ n0 {; s* e9 l" Ypublic void delBigList(String host, int port, String password, String bigListKey) {
, X* m' E% M  @- B( c) c Jedis jedis = new Jedis(host, port);
% \4 T+ c0 w# \2 S' f if (password != null && !"".equals(password)) {
& l7 B$ H* W9 j1 \* k' n2 Y jedis.auth(password);) E8 M4 }  H% s3 b# N' }" o
}" N" S; h/ q+ l7 ?2 _* K
long llen = jedis.llen(bigListKey);
/ l( E1 V7 {* J$ Q9 e* A, l+ M int counter = 0;# o' s2 m/ z* Q# O- ^& \# {
int left = 100;
7 x% y" f. h, M while (counter < llen) {
; r3 l( V: Q* d& a+ @ //每次从左侧截掉100个6 Q% A* L9 i; m" ~) ^8 [
jedis.ltrim(bigListKey, left, llen);. D, V9 t: x2 C3 U1 N5 a* ?7 i/ [' r
counter += left;
% G1 p6 K# R( ~8 t% f9 e/ w: m% y4 D }
" D  x' F; Z% g: Y //最终删除key1 b- Y7 u  H1 I7 H: k
jedis.del(bigListKey);, |8 _! `* W* @0 `* x
}3、Set删除: sscan + srem2 A2 V1 Y" I8 q! |

  y( t" I3 x: C4 R6 g7 epublic void delBigSet(String host, int port, String password, String bigSetKey) {
! k+ |, S. ~3 a Jedis jedis = new Jedis(host, port);5 _: W/ [% Y; l: f1 c
if (password != null && !"".equals(password)) {
; U- {; R# [; n. T3 ?* f5 { jedis.auth(password);) C. j- E% q4 z; N5 Q6 [
}
1 _1 A! M* y6 o! w  e5 T) C( l, e ScanParams scanParams = new ScanParams.count(100);
. L& J# F8 O' ?" h% J String cursor = "0";: |% s" E& ]' |- ^0 Q! R$ I( z
do {
/ |, K( O$ n, c+ @0 h% u% C' b) V ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
# u( W( D6 b4 I) d. D! D List memberList = scanResult.getResult;2 H* d1 Q0 d! L4 s  N6 O
if (memberList != null && !memberList.isEmpty) {
9 I! v. w' ^6 w for (String member : memberList) {9 A7 i6 q9 p1 n/ Q
jedis.srem(bigSetKey, member);( N, X2 i) F* T3 Q% W
}# V" t, |, O) S3 b
}
; ^9 I) Y* ]8 t' T  v4 e cursor = scanResult.getStringCursor;
' i' n* T9 W' h } while (!"0".equals(cursor));
% D, M  e% v0 q( O
" G: [: X1 c0 B+ l3 X+ A0 b//删除bigkey+ F( F) ?& h; {  l, J  S
jedis.del(bigSetKey);( A& \5 [2 m6 m5 O
}
) d1 y3 \" m, l; L0 O4、SortedSet删除: zscan + zrem
2 _1 n' j1 R7 O. r: b/ D, H4 I! M
public void delBigZset(String host, int port, String password, String bigZsetKey) {
# m8 {$ y, C2 G7 C& e8 T Jedis jedis = new Jedis(host, port);
; F5 C* t$ |0 a% l" ~  W# Y if (password != null && !"".equals(password)) {
$ e% i+ ^3 l+ z' r" {9 S& s jedis.auth(password);
2 E/ N& C0 G# ], F" U# |! s) Q }
, @9 L' C0 w8 g1 z6 H ScanParams scanParams = new ScanParams.count(100); 1 m- Y. d8 j5 M  Z2 D1 v
String cursor = "0";
; ~2 J3 {. A9 q2 X7 G7 K; o do {
+ U. }& O" I* b* R( ? ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
1 ]; N/ G) u1 @2 L0 Q) A, {: F7 y ListtupleList = scanResult.getResult;
1 G. O, \6 x6 E- d" R if (tupleList != null && !tupleList.isEmpty) { $ }( p5 {, [$ ^1 D
for (Tuple tuple : tupleList) {
: o0 `9 |5 z5 ~+ D2 P* }0 \ jedis.zrem(bigZsetKey, tuple.getElement); " m4 }# H( B+ {' l9 p
}
# P% B+ I1 D  i9 p; G% p3 O }
% i( k  j) x+ P0 T- N" R- y cursor = scanResult.getStringCursor; $ S2 d) v/ K; P4 p! Y" n( C- M
} while (!"0".equals(cursor));
! m- K. {! }3 t: Z3 r/ d* i* g! w- f% [% f& q% S
//删除bigkey , y8 t5 x5 _$ S7 v' H* x: H
jedis.del(bigZsetKey); * L  U) r' G. J! B1 ^, ?4 X$ i! M
} 公众号内回复“1”带你进粉丝群. A; t7 D. F: x; k
来源:http://www.yidianzixun.com/article/0LevQm7t, |6 M, h1 n# w/ p
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

关闭

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

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

GMT+8, 2025-7-9 07:01 , Processed in 0.038903 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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