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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9366|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

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

9 I2 _# R: i, c* K本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
/ s# y% I* p, p7 |" b/ d

    9 B  ]4 l/ r  h$ S  ^3 x% S
  • 键值设计
    - \. M7 Y6 t5 F
  • 命令使用6 I9 D( @+ W3 ^5 u4 @2 h" N
  • 客户端使用
    ) x! W/ C9 E5 [% W% n
  • 相关工具
    # q- h) T& d& f" I
通过本文的介绍可以减少使用Redis过程带来的问题。6 u6 d) _- b( I* U
一、键值设计1 D4 T2 t! ]0 i6 E9 J: y' q$ `

- N9 O% |/ f2 P1、key名设计: l1 S) @0 h% j/ H

) E% p7 s  d# O) ]4 r. x可读性和可管理性4 G0 g6 y) Q0 E, M7 G0 k
. Y$ J6 g9 ]+ C# B
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
' g: v: A$ p' ~7 Y  R0 |

    : [0 c- q# T  S- J4 b# E6 V
  • ugc:video:1
    9 L6 F) `8 w9 ?0 S
简洁性* G- L5 B+ S: J

* L! s' ~5 S  `; s0 V' f" c2 K7 g保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:8 a( q" V+ X( L6 p3 {

    8 ^. p- t' E' {
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
    " F1 W9 m' h# t9 o2 W" }
不要包含特殊字符1 }# {! f1 X9 L/ j
) x1 Y5 s! J! E: b+ E! B
反例:包含空格、换行、单双引号以及其他转义字符
% c1 u: }5 e' O0 k1 `/ f; z2、value设计
- ~& i% y4 m, g1 @) I2 B+ {) {% K+ V( U3 l& ]" ]0 \' S* {1 ?
拒绝bigkey$ h- A2 q- R6 }* m& j1 _* c- }% E
1 d; j, N/ W& ^# E" e
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
7 D3 D. w* Q* j/ Q2 M: y" e反例:一个包含200万个元素的list。
7 e3 W2 |& R0 x6 z非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法/ t7 R4 _8 [6 U0 P" ~0 I9 Z" ^2 I" k
选择适合的数据类型# h* i4 y$ R. X/ o- i) E1 t
7 D- Y' v( E6 m1 U6 O% }
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
$ r$ f% S: ?' o& b7 Z/ w. V反例:
2 t9 R- `( t/ M( l1 R5 h
    * y: h) t! G3 a. |% y; `" `
  • set user:1:name tom
    9 [1 F; `; [! W  L
  • set user:1:age 19
    " j/ L9 {& k  C  M6 l
  • set user:1:favor football
    ( `- J6 o6 f9 F% ~& A) G% O
正例:
' O  W& W6 ^& ]9 q

    ( z* C2 n& Q7 U3 t
  • hmset user:1 name tom age 19 favor football
    9 T/ Q5 Q+ i* z) r8 [  \8 N# ~7 d- U% _
控制key的生命周期
9 Z) Y' r5 ^& W" S' @: T6 r: h/ Z( l1 I! O+ ^. F8 y! K3 d8 J; k0 t, w
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
& A& ]1 x" q! u# r二、命令使用) g( ?1 S  |& I; b# }7 h
1 R0 h! ~  v8 n
1、O(N)命令关注N的数量
$ o* A1 M( s4 T6 a* H) T- l) w, t
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。5 J% G+ s; }( {7 z- h3 S) B
2、禁用命令- q. a0 p" r4 O" z7 o6 k

, W3 k4 G- e# Y1 [禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。3 |; ^1 C9 l. E% H: j6 A2 M
3、合理使用select
3 p9 o/ p8 ]6 {( \
1 z4 x( S9 H' _! A

. {5 @1 j. T! V  j) tredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
# F4 P9 U6 D( J& d4、使用批量操作提高效率
' Z# Q- _1 F1 H/ V( o
6 y; T3 q" A+ o- a

    8 W' n+ j5 k, v' E( G
  • 原生命令:例如mget、mset。
    4 B" y) }$ I0 }
  • 非原生命令:可以使用pipeline提高效率。$ x# v( @; i# N( D$ {8 I) \
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
. T8 e* ^% Y" x9 `( \! F6 l$ e注意两者不同:
- ?& A, F8 b8 j* Y: k
    * d) Y2 z' N+ q, a' J2 B* y
  • 原生是原子操作,pipeline是非原子操作。2 D6 U* L" b4 [% X6 U
  • pipeline可以打包不同的命令,原生做不到
    9 s" ~5 @: q0 V8 p7 M
  • pipeline需要客户端和服务端同时支持。
    1 p: U3 w4 q% t' @8 D1 k6 W  w7 x- d5 I
5、不建议过多使用Redis事务功能+ s0 }! N) \) q# W/ Q% n# M3 R

) d1 |9 D; [2 a% O! S1 y  Z$ HRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
/ g! ?8 s- a/ M) I' W3 o7 \4 I, Z1 K. d/ g6、Redis集群版本在使用Lua上有特殊要求$ y( f# \9 `- `/ ]- s
3 k6 S- t' Y& r3 a2 l  V
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"* U/ z; |; J# f& W
7、monitor命令
1 P2 P& A- y  Z" w( d1 j! ?9 b& {6 N: k$ i
必要情况下使用monitor命令时,要注意不要长时间使用。& _1 b5 H( o* f- m+ I
三、客户端使用) b/ R0 W7 ~, o8 y5 F. I9 D' d
4 L# F4 L* E1 `
1、避免多个应用使用一个Redis实例
* R- |- h9 b- i5 y* t- N1 D4 I% d# v( M5 a* A  f9 t
不相干的业务拆分,公共数据做服务化。( \! F/ a2 R2 m  j" V# J- L
2、使用连接池
  c$ J; G0 g2 j5 b" @2 U$ _' S% ^5 I
' I- N3 G% m1 Y可以有效控制连接,同时提高效率,标准使用方式:
. F4 \8 O- W! }/ p$ L' ]( WJedis jedis = null;$ T% Q  k& L- n8 l' g: S4 p
try {& m1 d; a5 j; G# F$ {  T
jedis = jedisPool.getResource;
6 B/ \# S! M0 `1 j, Y //具体的命令
' z; p& E2 e# N& f0 @, H6 c jedis.executeCommand
( I* ^" h. @+ _2 T} catch (Exception e) {
3 A4 ?. w* M1 ~3 Y$ x. L* t logger.error("op key {} error: " + e.getMessage, key, e);
4 }' R$ H) d0 i} finally {
) {; u& h( R- ?- j7 t0 p$ a //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
% V- q) C/ p0 V2 B$ l; C" j if (jedis != null) # M* j7 I- U3 H( f6 H  y3 F2 U3 c: |
jedis.close;5 g3 w' q4 g, H  l; L, o
}% {2 o1 b1 y* G: S! J4 e7 f4 m
3、熔断功能: d3 D0 a; r  @. r! ^7 u: w

/ |; }7 u2 l* S- _2 o) P6 b8 }高并发下建议客户端添加熔断功能(例如netflix hystrix)
6 i9 U, A3 m. d, H$ u1 C8 U4、合理的加密* q* Y, U8 D/ c7 O( Q$ z0 e$ J, I; r

2 c! c" G8 W0 H% S( w设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)$ r7 U2 a: U9 \( j: f
5、淘汰策略. a( [7 Z- F4 e/ u2 }
$ x! C3 Q5 a( L, v# V  ~/ Z
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
9 A& J* A& X! X默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
9 k: }0 a/ n0 U2 B5 V* v( ~1 y其他策略如下:
( t$ w) [5 {2 h1 p2 t" R% [9 W: V
    1 }( f0 P( a" `. g9 t
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。- ~! {7 i' E5 e+ R- i+ f4 L. t0 w' W: d/ \% D
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    " H6 ~0 t! O4 L# D7 N
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
    1 y5 P8 t. T8 W  [) J. R
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    ' r2 ]: I9 \+ {2 ], t+ O
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。9 b( b$ t( l  ]& ?( {
四、相关工具- `" h# c  p7 z) }
" O; \5 _/ y7 n8 l/ t
1、数据同步
: R  E4 C8 p5 C7 ^. T6 O0 Z: Z8 {2 ?4 O" E+ o. ?/ y9 L2 V
redis间数据同步可以使用:redis-port/ }. }, l+ P& o! c
2、big key搜索
; v( o& w' @' h; _1 q- s6 x* H: P* `, U
redis大key搜索工具
# A4 Y: z( r& e6 `6 P- V3、热点key寻找
  U  j* `+ [5 f
% [& T0 |* w) M7 W6 ~" B4 c内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题& f, o0 N5 N+ N! h; r
五、删除bigkey' h5 ?. \/ O' v4 ?8 P- q
# ]$ d/ l, e4 T' Z' E/ |3 L

    " Y2 b; L* s. e6 U* _+ _
  • 下面操作可以使用pipeline加速。
    & w: e: y! J$ r0 `! o0 B& r& q
  • redis 4.0已经支持key的异步删除,欢迎使用。
    % P( ~4 P. D+ C* v$ `: D
1、Hash删除: hscan + hdel
' X& g( b$ r9 k1 k8 a) ^. I# ^& |6 K( K, ~* r) e5 M
public void delBigHash(String host, int port, String password, String bigHashKey) {
/ N- X! f3 A7 f7 L, ^7 t: O4 n3 B Jedis jedis = new Jedis(host, port);. i9 w; Q2 Q2 M3 G' J- e
if (password != null && !"".equals(password)) {
8 ^+ P- x6 z+ T$ z1 | jedis.auth(password);
5 s) i" \$ M/ ]: W }
0 V& ~2 r/ h/ }( h9 ] ScanParams scanParams = new ScanParams.count(100);$ j* l1 ]% I* T( {0 d8 `
String cursor = "0";
1 r5 T0 R% H0 p+ Z5 W1 L do {0 l7 P4 n. `! z5 m
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
( P- J  _& u( y4 f- Q( p List entryList = scanResult.getResult;
5 x& Y. L/ g9 k+ N5 b: ]# W: ~ if (entryList != null && !entryList.isEmpty) {" J- s  l2 D7 n1 y1 l) Q
for (Entry entry : entryList) {3 @/ N2 y/ t6 o( I: z/ Q
jedis.hdel(bigHashKey, entry.getKey);
5 H  p0 D/ f; {1 ]5 y* @ }
+ ~6 I5 Y/ u$ _- H' @ }
' ?0 |( i- b7 ~  o cursor = scanResult.getStringCursor;' [" ]6 k7 c$ J3 f/ G. j# z% d; [$ q
} while (!"0".equals(cursor));
4 x7 H+ q$ y( p' `7 W0 ~* q; F* D; d" d5 p1 ?
//删除bigkey& H5 B" X4 Q1 Z4 D1 C
jedis.del(bigHashKey);. F: V, [2 \# y. Z0 v
}
4 d1 ~6 K5 T# V$ a2、List删除: ltrim
6 C9 E4 ^9 U6 X6 ?. Y" j( [4 T% [5 T# c5 q! h: b
public void delBigList(String host, int port, String password, String bigListKey) {  _- f& u& R' t# \3 u1 d
Jedis jedis = new Jedis(host, port);
9 c- N( p7 q, X- O  ] if (password != null && !"".equals(password)) {- R- g+ C- G# \
jedis.auth(password);
# }$ ]: H7 w/ M }
; T6 _8 M# U; ~9 Z9 b# @0 Z long llen = jedis.llen(bigListKey);
) U/ ~# ]7 K' Z5 ~ int counter = 0;
# M; q+ D1 k/ Z7 v% T" B1 M4 w# h, s int left = 100;; x+ l& X! d/ J9 y% t( g
while (counter < llen) {( M# r/ G/ v1 t& s( R, g- A1 \
//每次从左侧截掉100个" D/ a9 Y/ M7 z
jedis.ltrim(bigListKey, left, llen);+ \9 m' o7 t% }- p/ u( X
counter += left;% |5 @5 Q/ Q0 W: D  C; B
}% C! g. ~+ j& R4 s
//最终删除key
. s8 V" y. W, O% u3 z jedis.del(bigListKey);1 G7 q6 p- h# a# }2 U
}3、Set删除: sscan + srem4 e2 H) c4 c# d6 I; n
5 }2 M9 Z6 E9 A8 N8 _# C/ l6 @( y# e
public void delBigSet(String host, int port, String password, String bigSetKey) {
! l! r! r3 B& f' { Jedis jedis = new Jedis(host, port);7 f* ~, e$ ~  U4 l5 |% z
if (password != null && !"".equals(password)) {/ k" D1 c- A# `$ t
jedis.auth(password);
0 K& x  \8 b( ^. B }: z8 l9 b5 p" I) ^7 q' S7 g
ScanParams scanParams = new ScanParams.count(100);
1 p: D$ b' S- H$ n$ U9 G1 d String cursor = "0";
* o5 c- J, {; s5 P: n do {
1 v, i$ ]  q2 ?1 @ ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);: `& A* k2 A  n1 S5 l+ a
List memberList = scanResult.getResult;3 A9 {+ ~) B1 w1 ]  M& q( W/ s
if (memberList != null && !memberList.isEmpty) {: @& x# X2 V5 M$ ~7 `3 N
for (String member : memberList) {9 x. |- A, E; W
jedis.srem(bigSetKey, member);
$ ?' _) N  T/ ]  s }
9 R0 C. c! m" E% ]$ ^! o- y  n# V }  J; E0 j: n' \0 K2 w* c
cursor = scanResult.getStringCursor;
. y: a) {, y/ F/ K7 v: h3 p } while (!"0".equals(cursor));& c* z  Q2 {1 e9 h$ W

; ~+ e1 v4 F. s# e6 R9 q  D! k; n: Y$ g//删除bigkey
# T0 M+ h* q' g) P' ?3 O3 E jedis.del(bigSetKey);
# w- k% U1 X4 E3 V7 F}
8 H( i* P- v5 Y: `( p5 M) s0 j4、SortedSet删除: zscan + zrem
* X8 Q- ^  @. Z3 _+ g/ j+ i+ u- _8 j7 ^5 Z' B1 ^% J- \8 |, ^7 t8 @
public void delBigZset(String host, int port, String password, String bigZsetKey) { . u  I8 T, ~  c" L- A/ W! q
Jedis jedis = new Jedis(host, port); + f7 B# t: N5 F: V. M
if (password != null && !"".equals(password)) {
2 X; Y" {+ a, |7 y jedis.auth(password); $ N/ A! I8 ^) p& O" ^! n
} * h. A0 g1 a2 \  M) U- v. w
ScanParams scanParams = new ScanParams.count(100); . u) ?! s" Q$ d9 L' `0 Y8 @! Y
String cursor = "0"; 1 T& S6 p* j/ D( N- m+ z: m+ o, [
do {
- c: K! M% V! |+ v  ~- `: p$ B ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); , b. ?, `) b$ H: x" t  F
ListtupleList = scanResult.getResult;
% D% R) P$ v! a7 x5 G! O if (tupleList != null && !tupleList.isEmpty) {
) }" X. a$ A0 @; [3 v6 V for (Tuple tuple : tupleList) { # J! r) p+ o! r; [: e  M5 X
jedis.zrem(bigZsetKey, tuple.getElement);
+ b4 v# B6 }& Z0 t: W5 E; z4 Z% M  \ }
% E" m( D+ w- h; C, P } ! [: n7 ]" i2 _, x; x
cursor = scanResult.getStringCursor; 2 ?) t$ [6 ?* y1 |. e+ k  ]
} while (!"0".equals(cursor));5 _" k( c3 |6 C9 B4 k3 t  ~

" ~! S8 A1 k/ f% b9 y. V//删除bigkey
2 D- i; H" h7 F" Y$ U% D jedis.del(bigZsetKey);
0 Z5 F( U4 y1 {# F- \! o5 z} 公众号内回复“1”带你进粉丝群
- j% D5 O/ J! O" I5 ]来源:http://www.yidianzixun.com/article/0LevQm7t
1 e, a$ R$ x+ Y2 b免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-7-16 16:10 , Processed in 0.038136 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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