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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9386|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
) r9 l; K: o* K% W
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
/ |- a( V0 N+ V7 p+ E+ m

    0 r& {, b9 `  T! R7 q
  • 键值设计4 J2 c7 w/ Y, Q: _9 f
  • 命令使用
    * W4 G3 Z# G1 ^
  • 客户端使用$ W! p6 e- T1 v' w
  • 相关工具% k) h' f; K8 Y! w
通过本文的介绍可以减少使用Redis过程带来的问题。* K. ]) b4 D$ T& ^
一、键值设计2 ^1 S% ?8 F9 z8 ]
/ R/ {2 q; F  R' d! P  a+ \
1、key名设计
3 K5 ~; x  u* L  q3 ?" d& M; F! K# ~/ N7 h& _- @, }' h' q! `
可读性和可管理性! b7 G9 Z! w6 k2 L5 \3 c) D. e. W

  ~" A; E9 X( B- S2 |以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
" b3 J" V0 H9 U' t
    1 g  {, n# x8 I& [) Z
  • ugc:video:13 t: W; }) h# Q! O/ I
简洁性  \2 s" q0 i8 G: ?" q

# y6 j$ Z1 s' l4 L7 G3 F  e* E保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
8 o2 f6 K% v8 x" ]& F9 V
    ( ^2 @. e6 z4 v2 j2 D, z7 L
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
    % z$ ?6 @3 H2 O( h# W) `3 v& q: x
不要包含特殊字符
9 ~8 K) b$ C8 h& ?; J. c* F# a7 x3 |+ h* r
( x5 f2 e$ b( A' ?反例:包含空格、换行、单双引号以及其他转义字符
; S" K5 d& t# Z/ L, r2、value设计: F, L0 ^+ G  v4 q6 h+ a0 Z# n
2 c/ a  z5 ]9 X" ^0 c4 [
拒绝bigkey" q: x0 B* x* M6 N+ D8 \  E  ^: W( L
+ e) W6 ?8 T1 p" F' r+ T. x" k
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。) f; g$ {6 l' V' C  A8 U2 R
反例:一个包含200万个元素的list。) u) S( W' L! o1 z& v5 c
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
4 X  @. P8 g1 |$ g! w) f选择适合的数据类型
. A# i" X7 d1 ~* S0 {+ W- W7 v
4 d. t5 O( Z) H+ G. B1 Z' M* r例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
* f' {. c8 F) L/ r8 k, J反例:
! q, Y2 P% x/ v! J. p  s  m0 q1 o
    . U$ v6 z. T: W' l& V
  • set user:1:name tom
    ( z0 O! T3 W) a; C1 e' \" n; c! g
  • set user:1:age 19" r& \+ K2 j; Y* @
  • set user:1:favor football
    1 R6 R& Y9 H) R1 D" P. ~6 ^
正例:
5 {4 y5 V. G4 W6 ?7 f+ s5 A4 N

    : u" Y7 J, T4 ~3 ^/ C
  • hmset user:1 name tom age 19 favor football5 [1 P6 o; Z# A/ [2 [( {
控制key的生命周期& C) ~- }4 d# X0 m( ]0 u

# R% `% {6 L" G/ v* X2 \/ Lredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
  U. I- `$ F7 C8 F" o$ _二、命令使用2 X/ k# L$ D. l+ v7 m7 ?

8 p3 d" N! _. P3 S- }& ]0 W1、O(N)命令关注N的数量
2 g3 R2 e0 R/ B  Z$ [  W: Z) C( C, e
2 ]+ `* g4 c! l- o# O9 Y5 v例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
, L  c/ x: J- X2、禁用命令
# E* ^: ]( u3 B+ `7 u) Q  o2 t; s; g. l! t' ^: W
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
- m9 H/ x! A! r3 Q5 X4 c3、合理使用select
' Y& [9 u4 f3 N8 V1 Q& V' K

5 ^; @9 |4 U7 _5 c# k7 _; N! j' T
( p. z: `. H; Wredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
7 A+ H- W$ f% e2 l! P# P4、使用批量操作提高效率  H! q& Y( \; c: o0 h

' x4 k. ?# x+ F% ^& d

      W( X8 Y" o: q+ e) Q
  • 原生命令:例如mget、mset。# o3 t! j( T0 c  Q4 d+ Q# h
  • 非原生命令:可以使用pipeline提高效率。
    4 W  Y2 e. T1 f9 g" {& y  m
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
2 D2 @" a  I% a3 `2 H/ N* x% V注意两者不同:+ Q8 E2 e8 g: T/ v1 N) k( y

    * T" x7 [+ X7 [0 j- b4 s
  • 原生是原子操作,pipeline是非原子操作。  V0 f* A( W* h
  • pipeline可以打包不同的命令,原生做不到
      E/ \+ m2 x/ c7 Y1 Z2 f
  • pipeline需要客户端和服务端同时支持。6 o" E. R6 a# p2 e6 p
5、不建议过多使用Redis事务功能
  ?! F  x- a5 x7 a- F. Y4 |7 O
7 y2 V. @0 b% [! h9 C& @Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!: M7 g) z; Q  T' i) N9 P4 j3 S
6、Redis集群版本在使用Lua上有特殊要求; E# y- C' [5 d+ s

$ I2 c9 M) X) P! R* w/ p5 q6 [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"4 m6 ~9 _  M/ s1 |5 u$ z
7、monitor命令/ q: P* E  h7 y7 |6 \3 N
" }; x- |- Z# o$ L5 r( r
必要情况下使用monitor命令时,要注意不要长时间使用。: J$ U: O& A( V# D, o2 H: e+ \2 n! f
三、客户端使用# |; N( p2 g7 ?5 Z& a! n! w5 A6 \

% n: D1 e' e, d# `3 `$ q1 B* [# ~1 G1、避免多个应用使用一个Redis实例5 o- ^7 S2 L" d4 Q

1 V9 N) L6 [/ ]0 o2 M: g不相干的业务拆分,公共数据做服务化。: Z. K( R( ^' [* Y
2、使用连接池
, b$ j8 k0 J  a5 e- H* E3 ~6 {# n0 g
% g9 A& M4 a9 i可以有效控制连接,同时提高效率,标准使用方式:- u) I8 p& Z( x
Jedis jedis = null;/ W" C5 \% f+ U5 o# B3 T
try {6 Q" z* m; a- Y
jedis = jedisPool.getResource;1 [) C( q- G& Q
//具体的命令
7 Z2 V$ x' ~8 J jedis.executeCommand
/ a5 l& M* q4 l. V. X} catch (Exception e) {/ A% c) X/ h0 e* e' t  |
logger.error("op key {} error: " + e.getMessage, key, e);  u) c) v, T" d  `$ i  C
} finally {- Y% e8 j9 V, M% f' H1 e
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。3 N* r- ]; Z/ j! e( X5 x- i
if (jedis != null) ) R+ c$ {' b* I
jedis.close;& a, ~2 R0 \* k" k
}
9 G* O3 j5 Y' C; C; N3、熔断功能: W2 ^7 X  i2 E% j) F4 a

6 P% l" s0 c  K& Z3 J$ I1 S; D! @$ m- E/ Y高并发下建议客户端添加熔断功能(例如netflix hystrix)
2 O4 |2 C! W2 t2 O6 J7 u1 I( H4、合理的加密- O7 o7 t9 a! o9 d% r& k) A
: D  }4 H2 I# B$ v2 ^
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
2 `0 n2 W+ r1 t( ^5、淘汰策略5 O. A: R' u; ^" @2 t
3 W: C" V4 w1 n' b: K
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
; h6 T9 a9 `4 K2 ?0 U# E默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。. t! E' I" h# }& [$ c- R8 n
其他策略如下:
3 v" b4 m8 a2 W6 K
    % ?- Z+ E6 C. @3 @4 [5 d- s; {
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。0 A1 o- f- @4 X* {' H1 ^
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    $ [5 o% I7 u9 w  J) o# I
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
    ' H/ {: a& Y/ _# V. K4 L' J1 {2 G
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。+ z7 ?4 W* c: S
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。( \/ H8 Q5 Z9 P% J
四、相关工具% c0 e. f, {' l# i% a7 n; }) b- Q

1 J& \2 r* U- j! \# ?1、数据同步
5 j/ q" f( O; n- [
/ ^2 {% x/ b" Q9 [7 oredis间数据同步可以使用:redis-port2 @& P  [" j7 ~
2、big key搜索" t, S$ r$ E3 @1 d' Z" k: t: P1 ^/ \

. U. _4 r& U* Nredis大key搜索工具
- ^; b2 t4 x. V) u' ?3、热点key寻找2 r/ ?; l2 H1 @. A
, I( S" e5 H2 |7 ~
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题6 Q: c5 X% m- t) v  F0 U: j
五、删除bigkey# Y0 Y+ h2 @) O/ G; V7 w- f) w
# v% e4 K& F, P3 I9 r
    7 B) s  x& X) V. u5 ?
  • 下面操作可以使用pipeline加速。# g: c4 n( M- j' [( i
  • redis 4.0已经支持key的异步删除,欢迎使用。
    6 Z! E4 M' ^( u% t
1、Hash删除: hscan + hdel: c4 O* W8 N+ u5 G  t% S0 q

! e% n0 Z& C. {' vpublic void delBigHash(String host, int port, String password, String bigHashKey) {" t2 p+ E9 y' f' g( [/ a
Jedis jedis = new Jedis(host, port);
+ _' {+ j9 o$ ]' R/ K- t( _6 w if (password != null && !"".equals(password)) {  z. ^4 K5 r& P0 z+ j
jedis.auth(password);2 f! V$ Y' w$ ?& F! u! G5 _/ z
}4 ?. E4 w) B9 ~7 i, z. V3 N; [
ScanParams scanParams = new ScanParams.count(100);* W1 Q  ?% V8 A7 R: Q
String cursor = "0";
: E* T2 W* l7 W$ I& Z0 p3 C% K2 @( m* T do {2 c) A; E' b! S
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);; s9 Y3 u9 g3 v* [9 W! M. G% x
List entryList = scanResult.getResult;
' \6 q8 Z# Q+ t0 ^& |0 c0 P' U8 P if (entryList != null && !entryList.isEmpty) {
- M8 r- J: x* p3 r! l for (Entry entry : entryList) {5 s7 v# R3 M$ x7 H; h+ l! h7 B1 n
jedis.hdel(bigHashKey, entry.getKey);, B9 r3 l- P+ S; p2 C+ Q
}
% X# D1 l) w" a; `- Y7 W }4 L( k% g; y4 q" O9 v3 j
cursor = scanResult.getStringCursor;
" X# ^+ W/ }& }+ M } while (!"0".equals(cursor));
2 D+ s% H7 c+ S3 K% }2 W$ a0 w$ U! l$ c; F# ~) O4 g
//删除bigkey
. I! i- f4 q: I- t' Y' e$ n jedis.del(bigHashKey);2 j3 R1 ^3 ]% n% a
}
/ ]; D) f: q. C2、List删除: ltrim
$ P7 j8 j, h  H: ~! s1 z2 V1 C  Q+ z/ `0 I3 E( \# P1 h
public void delBigList(String host, int port, String password, String bigListKey) {; b' E) x0 d" s+ r( A
Jedis jedis = new Jedis(host, port);
( `3 P; m- n4 L' F# U if (password != null && !"".equals(password)) {
+ B) w$ Y" o! [5 c1 O$ g jedis.auth(password);/ U2 n) w: f* x. S3 x
}# C" Z! Q" [  j  M
long llen = jedis.llen(bigListKey);1 J+ |6 `5 Y( ?+ s) p
int counter = 0;
$ N& M! g9 K% Q8 N) o- `: y! Z' ^" p2 k int left = 100;" Q. ]( Q( e% h3 P
while (counter < llen) {
" U( m) {5 Y3 o4 u //每次从左侧截掉100个. a$ \' `9 E  D8 `* Y+ }6 Y: k
jedis.ltrim(bigListKey, left, llen);
* t: g. v( E, d: w9 W counter += left;# A: Z9 R& T9 l2 `4 e4 k9 U+ t! J
}( k" b: V4 A. E
//最终删除key
9 Q. ]0 q5 N- g9 Y jedis.del(bigListKey);
4 ^( B- T1 h- \}3、Set删除: sscan + srem
8 ], F4 n) T3 o( F7 m% n' U
/ L8 F' m% T5 U  Opublic void delBigSet(String host, int port, String password, String bigSetKey) {6 w% Z6 @! W$ ~9 f
Jedis jedis = new Jedis(host, port);
" M$ Q  X9 d5 S$ Y4 g- j1 Q if (password != null && !"".equals(password)) {* ]9 s4 E. @. R$ m6 i/ |
jedis.auth(password);
  s& @' R4 @" C- i7 m }
( d; R) k: K, y7 Y3 {+ F ScanParams scanParams = new ScanParams.count(100);/ z) ?+ b( z; j8 w8 x& d& E
String cursor = "0";& A% n  x4 e, ]. P  F' A
do {
, \: \* n& Z$ M9 u ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);1 J* _  B! g6 p( I/ X( i4 ?
List memberList = scanResult.getResult;" _( h$ g- X/ {0 Q; ^2 U$ i0 I
if (memberList != null && !memberList.isEmpty) {9 y9 |: J  Y5 u8 V
for (String member : memberList) {6 F- x& k1 `: {
jedis.srem(bigSetKey, member);9 c" k* A5 S7 D' w- w
}
+ O: d3 W- P8 ]3 w& p8 Q: X }- G/ r; F+ v) e$ Q/ D
cursor = scanResult.getStringCursor;2 a8 U9 @; `! }8 _- M5 U
} while (!"0".equals(cursor));3 Q' Z6 z! i8 V" F4 v5 A  m

+ m/ D! c2 b4 H0 Y+ J+ Q//删除bigkey
' `( P4 E: H3 ^8 k6 j* b jedis.del(bigSetKey);* w5 N0 O. \* q+ H" j& h! n1 W
}
7 `7 ?( `2 B! Q" _4、SortedSet删除: zscan + zrem
6 |0 A& v$ S7 s
( c' M& T8 _! kpublic void delBigZset(String host, int port, String password, String bigZsetKey) { ' o5 \( [8 b4 \! B4 P( n6 A9 @
Jedis jedis = new Jedis(host, port);   V5 t4 k3 f8 l) k3 o8 |4 {$ I' K
if (password != null && !"".equals(password)) { ) J; `3 w& Z9 e& X4 P
jedis.auth(password);
5 w5 O  n' @2 C/ e  {2 t } 6 A" c6 p( o* z+ W. l
ScanParams scanParams = new ScanParams.count(100);
% R4 U, X+ M# K& T& t9 r String cursor = "0"; 3 |( b. r' R* ?, Z. t1 d8 G2 b
do {
% c% Z7 a4 q! a6 D ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
6 ?7 r1 s* v' {6 T" Z* j7 f; x ListtupleList = scanResult.getResult;   W% b' `3 v; b* ?3 H
if (tupleList != null && !tupleList.isEmpty) { * @. E9 \; w+ [7 V' L' R
for (Tuple tuple : tupleList) { 6 u7 m8 Z/ g- g
jedis.zrem(bigZsetKey, tuple.getElement); 3 w9 S) Y9 D% }- f1 A+ C" h3 _
} ) P( l& {' D" j+ c: w: I& q- t
} 3 A3 I3 x( S- o+ r0 s+ C
cursor = scanResult.getStringCursor;
" h3 M9 i7 B) T" q/ k% F$ t } while (!"0".equals(cursor));
7 V( J# f2 H7 i3 H( C/ c6 A0 \" G# u' `: A, E+ r" A
//删除bigkey / @# |7 {$ Q0 S& q+ [
jedis.del(bigZsetKey); , f, l; x7 s& D6 a7 C
} 公众号内回复“1”带你进粉丝群
8 b8 |1 o! P" }# Z" A来源:http://www.yidianzixun.com/article/0LevQm7t
5 x) i' A  g8 U( O% ?* u免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-7-30 17:08 , Processed in 0.049400 second(s), 25 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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