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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9265|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
0 J3 p; T& V3 ?2 ]+ T* w# O
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
5 V0 Q$ I) n9 [; `6 Z: N2 S: U( t
    8 `/ ^4 e' X9 A  g1 k9 c/ w
  • 键值设计
      \& G  C# z) v" D, {2 d. P# G
  • 命令使用
    * S  V7 U: G+ L1 K8 G
  • 客户端使用, t  y8 _& }7 G6 j
  • 相关工具
    , r  b. Z) c' o9 S7 `+ z' O) J
通过本文的介绍可以减少使用Redis过程带来的问题。+ X# j+ D3 y  T9 q$ ~) g" U1 E1 t
一、键值设计
( }5 S7 x6 n7 H# K% U- B1 V5 m9 R6 S2 b
1、key名设计7 W; Q! m, P" h6 L- N# U- b3 r

3 L( e  s. C8 ?可读性和可管理性1 B; d& T. p) t' }3 f$ \/ l

! j+ ^) g( d- Q, Q; I! `以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
# {6 L, m: d* {5 @& I
    9 I+ }/ k+ h7 y1 ^$ }1 e
  • ugc:video:1. |$ ^7 c4 T$ w  f% i3 Z" ^3 Y
简洁性
8 Y) \1 W3 M  c& H5 ?- ~0 j4 L
9 Y) `) e' f  q5 V- b" ?保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
* ]) O; n( W+ E& U! X3 l+ F
    + ?! k7 A' R% a! W6 R
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。# C* [6 e1 m1 G. ~
不要包含特殊字符! |6 ]. X6 X# W; m/ N

- N3 q( U0 ]7 b7 g5 D* D反例:包含空格、换行、单双引号以及其他转义字符
" L& w/ i7 ?+ f' }6 J2、value设计$ }- r9 g7 g  ^# d4 Z( |, C
' D5 _7 {9 U- Q5 q4 v# v
拒绝bigkey5 w2 p: D, j% y% W

: k* D; h7 b  J' c( M- d: K5 N防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。9 B8 m& f8 j8 O/ c
反例:一个包含200万个元素的list。
4 K5 q* `2 |/ Z$ v1 W/ N非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
( G1 M6 @# j# g+ B& G4 |9 k  @选择适合的数据类型
. z( @5 g5 z& _8 y" ~; q6 H; J9 v6 b' W6 G- W
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?% T% O5 P( s; m
反例:
, {! [+ _, P5 p9 N* X( Q
    0 P% ~9 R, s7 V9 h
  • set user:1:name tom& P! I% G) m1 |
  • set user:1:age 19
    0 {# H. L& n+ R* m. y3 L# U
  • set user:1:favor football
    + e/ z$ i) X+ k# j- e
正例:
; T# }: ~$ h  |6 k0 A2 D) D

    4 j7 @6 ?2 f. I2 T9 `  H
  • hmset user:1 name tom age 19 favor football
    : G  r* t9 c$ `) G
控制key的生命周期
" O6 [2 [+ a( n# j& E8 w, Y9 h8 a0 _- x8 }" Y
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
! d4 v1 _. O7 [' z! C4 l二、命令使用
% ~7 C) P/ L0 ?7 Q
" B/ p' L" O0 x& Z* V0 s1、O(N)命令关注N的数量
6 M9 N8 ~; V5 f4 x" v! Y+ ]
' q' }5 q2 K5 c# c( k" m6 M5 a. ~例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
' U& l" i4 c; G) k) G2、禁用命令
; {9 m/ L0 v+ `' ^' u% f$ q- b6 ~- A; t* D2 O8 H$ Z; q! a
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。) ^% S% c0 _; X  B# {4 E
3、合理使用select1 ^' U0 I+ f0 A; y9 f

9 d9 g- h# J$ s9 l1 ^" L' h" o
8 J, h% [' h, Y' J' N- Yredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
; A7 t) Q6 K, \, L4、使用批量操作提高效率  q( ~+ V. X! y& K$ l

" b6 u) v" x7 C0 B

    0 `7 ^  Z' P5 E9 w" d  L$ {$ Y
  • 原生命令:例如mget、mset。
    # h8 q0 N/ W/ Y8 |5 }+ s. g  d2 s) G
  • 非原生命令:可以使用pipeline提高效率。
    " h; ~, x# V. b+ B2 A) ]
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。0 A4 [" v" H$ f8 f  P# s
注意两者不同:
3 y7 h1 i$ U5 O7 |' k; H5 }& ?

    , `0 ]8 |' w6 U+ X6 c6 f! B, {+ d
  • 原生是原子操作,pipeline是非原子操作。
    & [" a2 l3 ]0 p6 ^
  • pipeline可以打包不同的命令,原生做不到8 |$ c0 U, L% z6 s+ [6 u6 |: o, Z
  • pipeline需要客户端和服务端同时支持。
    7 _$ r% N0 X, n* k  H, X
5、不建议过多使用Redis事务功能! |  y8 ?( @8 k4 L/ ]9 [% H

& E7 h! O# p+ i- H# c+ v, lRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!1 h) B4 ]. j9 m) U7 B, s* ~0 y" y
6、Redis集群版本在使用Lua上有特殊要求  ]; i% X& F! r: i! ^6 V9 o# n

$ l! ~7 j# T3 h( A  G1、所有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"" X* _% W3 C0 v' t
7、monitor命令
: O8 C% o0 f6 O# u9 J
- S6 g# u* j6 P8 a' [' W0 ~2 c: D必要情况下使用monitor命令时,要注意不要长时间使用。
4 k+ y, X1 o/ e+ o- J三、客户端使用
. O0 |2 |3 f. y8 P/ a8 W8 j4 l1 O. l4 u& F$ U9 P
1、避免多个应用使用一个Redis实例
) v$ ]/ l9 |: n( {! N1 J0 P2 P! K+ g! f- F- N/ s8 ]& ~# I: {' l
不相干的业务拆分,公共数据做服务化。/ n7 m& n5 F# Y) H  ?! e
2、使用连接池
2 K  g5 Y/ u! B: c0 x; J$ [6 o" A9 E) _( U8 |* S8 G9 `$ S
可以有效控制连接,同时提高效率,标准使用方式:
- k) `: I4 B$ \' B( F8 LJedis jedis = null;& B% A$ O" {$ ~
try {
0 c' M8 C/ u; _ jedis = jedisPool.getResource;
5 A  j" n. U7 s1 T( k //具体的命令" }! i$ ]$ {9 _3 R6 e! D5 M0 ]
jedis.executeCommand
# S- V0 N) }8 K: ~( v} catch (Exception e) {: X% H/ L4 [. O
logger.error("op key {} error: " + e.getMessage, key, e);9 z6 o' _4 |( X: @* a' Q( @
} finally {) ^4 R* t' z% Q+ o9 J
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。6 V5 x0 F2 L! n+ o0 J
if (jedis != null)   e' c; ]! u, N$ `' B% Z$ n. H" x
jedis.close;. ]% j' r0 X  G0 @) n" N
}
" ~% f4 e4 D/ W3、熔断功能
* b$ E$ w( C& _2 j5 H+ Z
  p9 H5 ~: q1 I' q# B/ J高并发下建议客户端添加熔断功能(例如netflix hystrix)
  L6 B# h* `4 u  `* O1 u4、合理的加密8 Y6 p( e* z" o) L
) @7 H' s! c1 F1 w: S7 e
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)4 d0 c  w  G" t3 d' c9 {; P
5、淘汰策略
; ]4 ~& o4 T& m0 q7 _6 [5 d- P$ G. f8 r
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。& Q2 h* c* c5 Z- w) t, b' m7 V5 O
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。9 d/ L1 J( Z: ?% i" m1 J/ v2 e5 t
其他策略如下:
/ O5 `4 g; f9 u& B( n0 ^, v
    2 c& k) J! o+ p
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
    3 x) G) G' K7 @7 e
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    2 w5 |5 H/ h: k
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
    0 U( X: V, q) R$ `) l' N; i
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。0 c( M0 M6 g" {" F' r
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。9 D" ~5 U5 e( r: q3 o8 J" q
四、相关工具" W; M: W1 \& G6 J

5 r) a* k, I. r- b7 K% R1、数据同步
; b2 s% p1 T. G7 ^- E0 i6 K7 N8 [
; _: t0 S6 p0 S! o3 l4 {redis间数据同步可以使用:redis-port
" `% _6 L7 I# `) X/ ?) \1 A2、big key搜索
$ b$ V: z! K2 n. R2 n2 I1 t
/ n& h9 H9 M+ @$ ^redis大key搜索工具
9 w, ~0 E8 v8 ^$ \6 S7 o2 N3、热点key寻找
) @6 Z. `( G' L! H7 ]( p. b6 {- e9 o  A% k+ y) M
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
6 U$ ]( G. C9 X% T4 {. ]2 x/ b4 r五、删除bigkey
7 X% a0 ?0 {# R* M  k- u. T) k  Q4 F& G6 G" f" A

    # Q0 @5 Y# e  f+ w' f  k" w7 K
  • 下面操作可以使用pipeline加速。
    8 v+ C; G8 N1 i" V- _0 _
  • redis 4.0已经支持key的异步删除,欢迎使用。& k) e$ E3 G0 E) K5 N
1、Hash删除: hscan + hdel( B0 @4 Y6 {6 m5 \* }# F
" ?" T; _4 a  J, N* s/ x3 o) ^
public void delBigHash(String host, int port, String password, String bigHashKey) {8 V8 I$ n7 F/ @4 p
Jedis jedis = new Jedis(host, port);' l0 U- `9 A) W2 r# x8 }
if (password != null && !"".equals(password)) {
: x& u3 f3 _# q% y9 T jedis.auth(password);
9 }" K; q. e' n( P9 R! r( E' i  H }0 V. h7 j' R0 y% q
ScanParams scanParams = new ScanParams.count(100);
) y4 V/ p; M/ y+ v0 F0 S String cursor = "0";8 l) u% @2 K. W
do {
. B/ A3 x" |$ m' F# d1 Y ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);  {3 f5 _8 {& r" L
List entryList = scanResult.getResult;$ A5 u: o. f2 x  |# J/ T3 n5 J
if (entryList != null && !entryList.isEmpty) {! F- H3 Q/ N- b- M* G
for (Entry entry : entryList) {
& }0 x; E. `2 ~9 a: o! ~. G jedis.hdel(bigHashKey, entry.getKey);% |* F0 F! l- K( k0 ?: ]0 {& D2 T
}
: `4 z" [: G9 x9 F5 M# C }
7 s) r6 Q* [5 l) X2 d cursor = scanResult.getStringCursor;+ w, k% _2 h" _4 P( v
} while (!"0".equals(cursor));9 x% z$ I2 R( o& m5 k9 O8 `' c5 S

) _' ?. w! d" C//删除bigkey' u) `* n3 d( l/ ^( C% d
jedis.del(bigHashKey);' P7 ], g* C- B
}
5 ~2 C+ ^2 }+ {! j6 n8 L) A2、List删除: ltrim% B0 C6 e4 j5 M" j' Y& @
: u3 _1 T2 b6 A
public void delBigList(String host, int port, String password, String bigListKey) {
% {2 a9 A  ]: Q) i Jedis jedis = new Jedis(host, port);* q( Q; `: F+ a! @" r$ H" C- m
if (password != null && !"".equals(password)) {
' [& N9 Y( ]0 z jedis.auth(password);
5 S% I0 ?( o  z: m! `3 J; B }: ^+ \% Y4 J& |5 y6 {6 k1 A1 X2 q8 @2 W
long llen = jedis.llen(bigListKey);6 E' o7 H- j" J
int counter = 0;9 {* P, ]' Q/ r6 f
int left = 100;1 s5 k) z4 v2 V- O1 j% \
while (counter < llen) {
! Z( _; p. l) U2 }, e! e //每次从左侧截掉100个
/ U% C" Q( g( u jedis.ltrim(bigListKey, left, llen);
; n$ c9 @. F) M. O: [$ _; |5 P counter += left;; V& [' p3 v3 q$ h
}
5 D( ^, f1 y4 t( l) g //最终删除key
& ~* g' d' d+ F jedis.del(bigListKey);. {# T) C8 U. N. D) h6 Q
}3、Set删除: sscan + srem
" K- C* ~# ~5 G" f# |
# w9 W9 p3 C' z; [- }1 ~) ], Zpublic void delBigSet(String host, int port, String password, String bigSetKey) {. `: f; @: B1 U) N
Jedis jedis = new Jedis(host, port);1 T  L# u+ r0 a; L8 g% Q5 M
if (password != null && !"".equals(password)) {
3 }+ a) y/ X) g, k" v jedis.auth(password);
8 V( _( a- P* X" g }
2 Z5 v5 Y; f" z& x  f/ l ScanParams scanParams = new ScanParams.count(100);; ^+ S3 }* _" a; D: b* N  r
String cursor = "0";% p9 T, j' O$ E; E& u- ?
do {* P1 Y; A, r9 l
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
2 w( \" }7 k8 M; [3 Q% p: a List memberList = scanResult.getResult;  t2 x; F$ t- M8 m& ^# n( ^* L, P
if (memberList != null && !memberList.isEmpty) {! C1 \. @* ?1 c1 D, n% Q4 c
for (String member : memberList) {
2 \: z8 j+ Y; H. g8 {' J jedis.srem(bigSetKey, member);
. P, r* c9 e$ c. _) E8 j }
; |  @3 M/ ^* u8 Q8 ~+ C7 {! Z  k1 G, @ }
7 @+ D0 v: U1 x3 u1 X5 ` cursor = scanResult.getStringCursor;
& T" i  o" O( ^: E; [9 @% H/ b8 a' M6 V } while (!"0".equals(cursor));
8 j1 w5 C' n' Q0 |  d5 s9 J1 Q0 F6 ]
//删除bigkey
# x; i. v' H  z# o8 `2 x jedis.del(bigSetKey);+ s6 e: ~& B6 S
}
9 D) X" z9 t/ S8 O1 s3 d- x- g8 b4、SortedSet删除: zscan + zrem
; j5 r% R, Z6 n1 V' k' s) p( @9 e. @( p/ _5 E5 T
public void delBigZset(String host, int port, String password, String bigZsetKey) {
" _" b3 l. n& p* U! F Jedis jedis = new Jedis(host, port); + a3 A, `' e% G8 p9 [4 u! ]: W
if (password != null && !"".equals(password)) {
% D& i: N! P2 r/ O' ? jedis.auth(password);
: M* o4 [* |1 q2 X" g3 q$ p } ; b- H' ^1 ~! z+ P
ScanParams scanParams = new ScanParams.count(100);
( ~* K5 g, {  Y String cursor = "0";
7 C( I( y7 I* s  a8 q do { ! @, P2 H8 \, @* H
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); 7 s3 H' @: a+ g- g, l; S
ListtupleList = scanResult.getResult;
: K8 Q8 ]+ B$ k. H( g' @. r if (tupleList != null && !tupleList.isEmpty) {
/ D5 V  R2 E$ f2 d for (Tuple tuple : tupleList) { ( g  l1 I) M: i, L' Y- @+ H7 U' e
jedis.zrem(bigZsetKey, tuple.getElement); / w9 n  B4 @: p0 d
} 6 V& V4 v# X( s7 t' k) a9 w; Q* f
} 9 q" M& I$ V2 W* Q
cursor = scanResult.getStringCursor;
  r& q( ^) O" V9 I3 J3 @. g: V6 P } while (!"0".equals(cursor));
8 X% @# I' {! I0 n. Q- `/ H4 v3 {" V- |3 k9 F
//删除bigkey ! Y1 D" M1 p6 ^# o9 g9 T
jedis.del(bigZsetKey); " t/ N: F3 N: o. K2 p
} 公众号内回复“1”带你进粉丝群
- B& H! `( |  B) K2 B2 F* e来源:http://www.yidianzixun.com/article/0LevQm7t
, g2 [; P4 R" l7 Z免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-1-16 10:31 , Processed in 0.038771 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2025 Discuz! Team.

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