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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9329|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
  Q# D7 m0 b: s2 d9 ~$ ^$ K  \5 u& A8 e
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。& ^8 Q5 o/ H- N! p, H
    6 y" k7 o+ q' s
  • 键值设计3 q( ~6 U) v8 K" ?/ Q5 c
  • 命令使用
    , C  C; u7 E7 d1 F2 P/ Y+ }: p
  • 客户端使用
      a; X5 S. Z6 ]' A8 \. E' n1 f' x
  • 相关工具
    4 U6 X0 |5 S; {8 J- N, B
通过本文的介绍可以减少使用Redis过程带来的问题。' p% d3 V) A% W& Q+ ~7 a
一、键值设计7 V( z1 N1 |, u; Y" R
2 `& l5 N) z& {! g8 e2 Z: ]* A5 v: S
1、key名设计# N( S) l2 r7 Q7 T+ i

/ Q  h0 o0 h5 P( P可读性和可管理性8 ^. g+ z0 L5 M7 b5 t9 h* f0 F1 y* C8 N

5 p* x% s: E1 y5 Z1 h/ I以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
+ r: t" E8 n  q& @  s* A9 A

    8 n6 |2 l- E# Q/ Q$ i3 C% w) W
  • ugc:video:1* c+ N* q# _3 v# A: T( N8 ?4 P
简洁性6 I0 y3 E& e7 Z2 @1 K
& c+ q6 O9 |. M2 L8 m; ^: F
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:, U% Y- O# e& G  I
    & o6 O8 u0 o% n7 N. \. N
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。) N8 Y+ t/ v$ T$ a& a2 C1 ^/ z
不要包含特殊字符! X0 g) O* r: v9 H

$ w# L, T4 v# m4 ^反例:包含空格、换行、单双引号以及其他转义字符% h9 G! L8 ?3 n; X  V
2、value设计/ R2 g7 B" l. W7 b+ U  X/ C3 j

3 n( t$ a# G6 o. ~5 w$ M拒绝bigkey
+ v/ R0 M! r$ V0 [0 V! G! q/ W/ I: q0 @6 Q2 y+ ]0 N
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
- J' t1 b* ]6 M$ U( K; F* ]反例:一个包含200万个元素的list。
4 i3 a  M: w2 X9 R, Z, A* ]非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
9 H) J' `4 Q( w7 ~+ Q选择适合的数据类型0 y! t9 q% k( n2 Y8 B% f- b/ B
( c* N+ w+ b% d7 R- Q
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?1 M: s2 K. _/ J* |( K
反例:+ d$ c8 x  r6 l4 Y! b
    / d& U- M+ }- k! ?+ S
  • set user:1:name tom
    * [" S. ^6 h# Y; v' ]
  • set user:1:age 19
    3 q4 M. h8 n3 ^6 x' Q; U# g4 J
  • set user:1:favor football9 {# F5 u( c% V
正例:
8 p4 z  J( [! F* k& S+ `

    6 V; i6 Q& N- k& T5 A2 c
  • hmset user:1 name tom age 19 favor football6 D: d) L7 Z; ?  A
控制key的生命周期
* J, Q# v. {* W6 b2 ^1 E9 P: L
6 r: [5 ~: {5 p* Hredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。5 I3 a! v5 C" N; F5 l' z
二、命令使用( Y$ @) P' i1 O3 f) x
9 C1 H9 B* n$ V4 B' }5 k9 d
1、O(N)命令关注N的数量6 }9 m+ G# O1 a" V

( `/ u* P2 L. @5 D3 g- V! R8 Y例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。9 W) z6 m. B8 ]  A3 l$ f- e+ Y
2、禁用命令
* s7 o6 h# p4 b* B" d( _3 a' H) K
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。8 `$ k8 r* ?3 s4 f5 G6 \4 e
3、合理使用select
; p) r" Z, N  ^" r  C, t
$ ~6 i, T$ }9 |
) A. V/ x) n8 I( L4 y7 ]
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。% @, U# A) S: f
4、使用批量操作提高效率
( _  \& D: s* y' }% B% G
6 U- P' |; s' ]
    2 U: Z2 D( U9 R/ x) E5 t
  • 原生命令:例如mget、mset。
    , U" R( N' k% @5 ?
  • 非原生命令:可以使用pipeline提高效率。
    % ?$ c+ n2 j6 A. c* N7 w# |
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
' \: w7 c/ R$ H$ h+ d注意两者不同:
7 {5 @( {; _7 O  f

    7 m3 r. Z7 D+ H! V
  • 原生是原子操作,pipeline是非原子操作。; q% G. n( S  n  I. b
  • pipeline可以打包不同的命令,原生做不到; V* w# D* h) ^! u
  • pipeline需要客户端和服务端同时支持。
    2 z0 f( Y! A, x* t2 m6 U
5、不建议过多使用Redis事务功能. J2 K; \/ I7 t8 \* Y7 I. H7 K& x

, b3 d- v# w  r2 a" e/ K7 QRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
4 A7 T2 R% ?8 j6、Redis集群版本在使用Lua上有特殊要求
' X+ m1 u, K# w( o$ V! O# r) r1 O5 G8 z: J1 }. K: F: P
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"' i! l2 p2 \" i0 p' n
7、monitor命令+ x8 k' L+ x6 y  V' J: k
& u; q+ _) f" V' w5 t2 R1 _
必要情况下使用monitor命令时,要注意不要长时间使用。
1 W' M' z% C8 x# Z" Z; x0 O三、客户端使用
+ E$ t0 a- B! f4 i$ l
% c; \, {4 @5 ~  Q1、避免多个应用使用一个Redis实例7 n& u* _3 H6 D! f

9 x. x# G5 G2 s1 A) W+ y; \' X' u不相干的业务拆分,公共数据做服务化。/ F+ J; l; I! N
2、使用连接池
+ V  P* L$ ]& \0 j. G; s! T, J6 t
可以有效控制连接,同时提高效率,标准使用方式:- B7 b9 y# a+ B
Jedis jedis = null;
0 U6 \7 D3 M; |6 c# @( ytry {
3 @! B4 S9 w7 Q2 V jedis = jedisPool.getResource;+ F  a0 p0 a! ?; W% k
//具体的命令- S# P$ ~& @6 M0 j& i& k9 v
jedis.executeCommand% ~2 t" i! b2 `" Y4 y( w
} catch (Exception e) {
& e7 `# N6 q) v logger.error("op key {} error: " + e.getMessage, key, e);
. b# {9 j4 t9 C. O0 `} finally {: I+ s: H( \/ y) O% K" f% c, z/ ?0 U
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。& Q+ l, d$ J" b9 v% B$ P9 X$ F
if (jedis != null)
8 Q5 ~: T9 U3 C  m! Z4 O5 Q% w jedis.close;* ?3 m* q4 [: \0 u' [& g
}
. V' f9 Q* g  A, d7 g3、熔断功能
' `; R3 a4 C3 {0 O2 a3 E* d3 N6 b6 q1 z# Y
高并发下建议客户端添加熔断功能(例如netflix hystrix)
* F$ Z1 b! F5 V* j0 e4 G1 L4、合理的加密
- F( t' W4 J' i+ l, p. ]* S% c: r2 ^0 Y& N2 V
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)4 M. z/ u0 g( E* m. [1 O3 K! @
5、淘汰策略6 u/ C4 k# j& A
0 O1 y% Y# ]" {1 T
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。7 L  W8 ~2 V7 M' z3 x+ x9 W
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。7 O: A5 w( m: I# {2 @- e
其他策略如下:
6 Z3 S: E! i" I6 B

    # b5 Y8 j1 j2 V
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。7 [4 @2 i( W) t. i8 ~* W' D
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    ; ]( \! F4 @$ l8 K: q
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
    ; g: }8 z: }& }% k6 v
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    2 z, j3 n: w' {/ b5 _
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。! ]+ v. ^. K+ t4 x) |$ p1 [
四、相关工具
( t- S6 I  D& t9 A- e- W9 _5 V5 n4 d7 V% s* F
1、数据同步5 C6 ]# m6 B7 s2 y3 ?2 I. z  I

* Z5 a3 V, \0 E% Mredis间数据同步可以使用:redis-port
( A7 l/ d& ^3 A* Q9 F; H4 J2、big key搜索
* s# }8 q3 ?  r) u
; o. X) J1 ^  w8 jredis大key搜索工具+ _6 @5 n4 I% ]; @9 G' J+ w
3、热点key寻找
/ B( Q* v' r& h$ I  A! `1 O% h3 w7 e: C! L, x
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题: D+ g( T' N% `; n" P+ `9 n
五、删除bigkey
, n3 o0 b. G4 a2 d1 B
* H3 j6 n) ]4 k
      H- b5 b- T! _' b
  • 下面操作可以使用pipeline加速。4 h# Z: @: d( h7 o( w6 e
  • redis 4.0已经支持key的异步删除,欢迎使用。4 }( G! U: ~, _# E, j
1、Hash删除: hscan + hdel$ H2 h) y0 X8 H$ i5 C& H
3 Z0 I1 D" G6 c" I1 I1 f
public void delBigHash(String host, int port, String password, String bigHashKey) {
; G0 w6 p6 d' x" S/ ^% r Jedis jedis = new Jedis(host, port);# o& [; Z# h. I2 W: e
if (password != null && !"".equals(password)) {0 b  l+ E9 r0 r! G# T
jedis.auth(password);
8 c$ [1 Z( y9 \1 r) a7 X }4 c/ }- Q$ T! \4 ]
ScanParams scanParams = new ScanParams.count(100);
) B" G, a% n3 f: K& b5 S String cursor = "0";
9 z* _2 l8 A4 i' P8 F do {8 d0 }; a6 n) c/ D1 J" C
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
) W: ~4 R/ p( `) ?' c1 I List entryList = scanResult.getResult;. Q) q, g) I" x( {/ N/ E) U& C
if (entryList != null && !entryList.isEmpty) {" e2 C; K! E+ j& G; G# `' m
for (Entry entry : entryList) {
8 ]1 D/ K) D0 _) ]8 |; ] jedis.hdel(bigHashKey, entry.getKey);
1 s! ?6 ]  v- [5 u$ J. j2 ^ }0 F9 f' `: F% f# }+ l- e4 e- Y
}0 Z2 G4 H  I3 b) e1 v
cursor = scanResult.getStringCursor;
2 C' b  d" ~4 C* i2 | } while (!"0".equals(cursor));; @, i9 i' D' S7 p( f5 R$ A1 c

0 b9 o; K' t( d//删除bigkey
* j: k# ~9 R, ]! c  @/ j jedis.del(bigHashKey);- x  I1 q: V8 ]  E: |
}
0 B4 H3 L9 R& |5 i# }4 n2、List删除: ltrim
0 F9 n" a7 s2 r& b, o* y2 N# s. `: E; S/ m0 f
public void delBigList(String host, int port, String password, String bigListKey) {
% x: c" Y( G* Q4 J$ O Jedis jedis = new Jedis(host, port);
, D) V( A/ _  h: ~( D& B if (password != null && !"".equals(password)) {" o" v0 w# x9 t1 f0 S
jedis.auth(password);
& V/ X8 r9 |5 q, C# c6 d }7 B2 l9 E/ P3 E8 S
long llen = jedis.llen(bigListKey);
  ^2 Y- Y1 L1 \. B1 C1 V int counter = 0;
+ {: ~/ Y' x$ d2 U7 X) Y; ]+ V int left = 100;1 F7 m5 U+ s1 Z2 J1 x
while (counter < llen) {" b) `9 a' ]: |' e
//每次从左侧截掉100个/ U6 _, k8 d' J, _+ [
jedis.ltrim(bigListKey, left, llen);
. q0 p% H$ c, m( z3 g5 i1 t- ]. p counter += left;
" s% H) T! G3 J$ ~8 S% z1 \ }# e5 F' r2 t9 _9 [5 Q- z; F
//最终删除key
6 V7 R2 z) n" g! W$ s' L2 K jedis.del(bigListKey);2 G# f$ J6 D0 \  K4 N: O
}3、Set删除: sscan + srem1 y' s0 V2 j# N4 w3 r" T; _

: a, |" h( v  K( D: S# p- ppublic void delBigSet(String host, int port, String password, String bigSetKey) {4 B; H# N9 q. h
Jedis jedis = new Jedis(host, port);
( K  z& y- v" G" u+ E if (password != null && !"".equals(password)) {3 z. |- W# }. i. A4 P9 ]5 H
jedis.auth(password);/ `. h7 `4 Y/ m$ V8 H9 o" }; F3 W
}- P+ I0 j) Q4 O) h- F  l
ScanParams scanParams = new ScanParams.count(100);
0 a( u1 f: x/ h8 o) ^! X8 F- l String cursor = "0";
$ Y, ^8 j' q) {# f do {
1 I" ]# |$ s3 W) W/ S8 t ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);1 H  D& a$ \: _* x7 N7 n5 B' V
List memberList = scanResult.getResult;" l0 b" N8 x& I1 l+ |
if (memberList != null && !memberList.isEmpty) {
* a7 T/ S6 W" M# A: T; j, c for (String member : memberList) {
  W* g* [4 x, J2 n. Y jedis.srem(bigSetKey, member);5 g$ {2 J" G1 ~2 l& Y
}
" F5 r9 K9 D3 s, x8 f. |6 v0 j }
& i0 H3 X& i7 b" J( k! D4 H$ a cursor = scanResult.getStringCursor;% z5 ?; J, S4 T' C- |
} while (!"0".equals(cursor));8 L9 j* V/ u+ I( \1 h
7 @5 L1 ~/ \! f$ ~0 x4 T3 Q* Q
//删除bigkey
  A' z4 c. a7 D7 X jedis.del(bigSetKey);* K9 O7 ?6 F+ _; Z6 s
}! |% Z0 x1 K* @; w
4、SortedSet删除: zscan + zrem, X$ m3 z7 T% L7 k$ ]! k5 K- Z

1 `( A5 R* D+ p3 D2 L( ~3 Kpublic void delBigZset(String host, int port, String password, String bigZsetKey) { / O. q" Y. q- k
Jedis jedis = new Jedis(host, port);   u2 N' U. ]" Z2 a4 O4 p) E& R3 M
if (password != null && !"".equals(password)) { : s% c9 m+ T  m6 r1 i: ]4 Z3 V8 R7 ^
jedis.auth(password); ; v; Y+ U' Y2 y3 [
}
, [3 L) B, W9 Z& y8 x ScanParams scanParams = new ScanParams.count(100);
4 l  j+ N) B- \! |* r String cursor = "0";
  h+ [. `7 U& K, n# p+ }. p do {
- h3 q7 y0 a8 J% d/ m9 ^: A ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); : Z9 [+ v8 @8 ?/ \* E- V( ^
ListtupleList = scanResult.getResult;
1 ?' r5 P. h- o, X if (tupleList != null && !tupleList.isEmpty) { 1 v) D' l7 P7 v' y; u9 c
for (Tuple tuple : tupleList) {
  [" M8 B7 f, }8 V( u' B( n) p4 z jedis.zrem(bigZsetKey, tuple.getElement); : i, h& }5 m$ z& |5 ~* J
}
# q; F. J& ^* _3 H. \% _% f) c }
- v, C5 o9 q# E3 V  d! p cursor = scanResult.getStringCursor; : ^# K+ L7 R) S* i
} while (!"0".equals(cursor));$ A2 _  Q. W  @8 ^" s. {9 ^
( M& F! z0 o8 S4 G
//删除bigkey
* F. C3 O4 j$ p! L% s$ n+ Z6 N9 b5 g jedis.del(bigZsetKey); # g8 V, G; R0 P0 O5 s
} 公众号内回复“1”带你进粉丝群1 t8 x1 O* f1 E# ]! F' C" P% k! T
来源:http://www.yidianzixun.com/article/0LevQm7t* g6 d+ ], C4 T6 Y/ I  i" u/ M+ z
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-6-15 10:30 , Processed in 0.042094 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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