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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9310|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

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

8 f, Z' Q8 o+ o本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。) O" u4 Q& B" Z4 s2 ]
    7 E" v7 h5 C! [& t" S" e
  • 键值设计9 X+ A* @( ~1 y7 x" |
  • 命令使用1 B) W" R8 k$ v# L% g; c
  • 客户端使用% w8 Z6 ~7 [" \  W
  • 相关工具
      \: T6 `- K# i* ^5 {: \& P( E
通过本文的介绍可以减少使用Redis过程带来的问题。4 T- k3 M2 o5 q8 b) U, u2 d1 C
一、键值设计
, i; J5 a1 v# M+ w  M$ ]/ P. i. q3 q/ {" Q
1、key名设计' v9 a! o' j  J: |% H+ R

# ^. O4 V2 S1 f8 {可读性和可管理性
" B- k* A/ O( Y8 D* {% j
7 S$ i: `! ^- s# n, T以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
2 ~% _  X- C# `1 @3 `8 H. j0 U
    3 N4 _) H8 Z+ q0 E& K6 |" E
  • ugc:video:1
    # K) [/ {# Y' `; p4 V
简洁性7 k5 o' h3 V5 [: M2 X- k
) a' J! k# p6 D0 V: B& s1 J
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:/ f( W& ?( \5 n3 y# N3 }

    / h6 _+ q3 R- J6 f* A
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
    0 h4 m( I: R( g" o7 j0 q& t
不要包含特殊字符
; K) b" f5 s, f( E
. Y, q* B7 d8 C7 T& H; Z/ \2 u反例:包含空格、换行、单双引号以及其他转义字符
  P( F! j. Q4 W# {! R! J2、value设计
: @4 ~, l* r+ l- z! K! a7 v1 h  [3 k1 J8 z; I! T7 B5 ]0 @& S7 H& f
拒绝bigkey
  t5 e. [% @4 R/ w  t" D
# {" ~  q' u2 k0 }" @; O1 w  T防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。# ?, V- F) T# |  {9 R
反例:一个包含200万个元素的list。" g5 x8 M- c0 d( |% `
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
1 U5 `& Z2 J0 _" p选择适合的数据类型
: b' Z! h7 i6 V, D! e+ {) j( t0 L5 c! b" X9 K
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?$ x/ I" u$ ]0 t: ~7 [
反例:
; }! J. o+ z) E; h8 U: W, {
    5 |# f$ e( |4 B
  • set user:1:name tom
    7 s3 J. z  J! ?0 C7 C* ^* ^) G5 O. ~8 r
  • set user:1:age 19
    . u- t. N4 a) Z
  • set user:1:favor football
    , ?$ J- j! ^# q; }* ~( j
正例:
; K2 D- V( j1 s
    ; h. ]$ b" `9 h- o3 ]; O
  • hmset user:1 name tom age 19 favor football% \1 c! Y1 R1 d" s1 L. ?
控制key的生命周期" r) k; \6 X0 h3 ]: c

! Z1 J6 y: L: ~" h6 X0 f" Bredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。" ~3 @& k1 M7 b  ~. z
二、命令使用, {" C: S' U/ R4 \. G! R

( ?$ p  }$ X' U/ _1、O(N)命令关注N的数量
& ~5 }" l. {- r; c  f& b$ @  n: z! X
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。  b! O8 \$ j$ I2 G5 C
2、禁用命令
2 r: ^; z' l& V( B- x! {2 n
2 @/ O+ V  M7 `4 g3 c# d$ s禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。0 B- o/ ~* e1 W3 ~* w6 W3 C
3、合理使用select7 K- @+ m8 _) K8 H
3 Q# k/ R+ U( z+ ?# O- s

: q- D. w( l: I. ^& ~* }8 Eredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。, a* N; i* B% Y$ A6 a. o1 I
4、使用批量操作提高效率1 |6 G# h) b0 h& ^; F$ C" `

2 y# r2 \; h5 Q- c2 c$ k4 P8 C/ _

    0 N3 F3 |- J! r1 A% w4 `; |
  • 原生命令:例如mget、mset。1 O4 N2 C6 a" w5 j8 q
  • 非原生命令:可以使用pipeline提高效率。
    ) Q0 ]5 Z* F4 x# e
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。0 @9 X( T6 n! V$ V8 k
注意两者不同:5 Y) c' @5 E+ Z1 A' `: v
    ' f8 N* x% v+ Z6 u" R& C
  • 原生是原子操作,pipeline是非原子操作。% l2 d/ K" s1 K( c6 G
  • pipeline可以打包不同的命令,原生做不到
    2 R+ C; C8 N$ s5 H# e
  • pipeline需要客户端和服务端同时支持。
    / R, m; W! j5 ]/ r0 F# b) T/ {
5、不建议过多使用Redis事务功能( ^/ ?& o( c- z% ^
+ V! u1 {: f/ @, ?* p) N  ^
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!/ d2 j3 _: l! _( z9 M/ `
6、Redis集群版本在使用Lua上有特殊要求6 I( z. \( Z2 I9 k. k2 ]

5 T" J6 l( S: Q( i1、所有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"% m+ z; p/ R' |" \* h  {: J! X
7、monitor命令
6 @1 z1 t3 c2 x
: O$ E& T5 ]; ^) h8 ?必要情况下使用monitor命令时,要注意不要长时间使用。7 m; I/ E8 k. E: l
三、客户端使用
1 A: g4 @0 S" G" i- y6 Z6 j
; O) e) x0 N) V8 Z) z* k$ q1、避免多个应用使用一个Redis实例
, D2 {: O3 Y; H) b. s6 x
: e: E. R$ J! q不相干的业务拆分,公共数据做服务化。
. b% C/ I/ r# z: o; S6 U5 G6 c2、使用连接池
  w" U4 H0 n8 r1 u7 q4 o* ]
! L, R% D6 _- ?2 @" y$ E9 I# f可以有效控制连接,同时提高效率,标准使用方式:
# F, B4 o3 ?) I' Z/ dJedis jedis = null;: T$ P1 ~+ B) h4 M. \% O# G0 o9 }
try {7 P3 Z! L5 G1 ]& t* r7 `
jedis = jedisPool.getResource;! g# a2 g) G  x% r
//具体的命令
3 _/ X5 N3 B3 y3 b jedis.executeCommand
9 I3 x1 D5 h% F} catch (Exception e) {3 @2 l* S) P8 h- x! m
logger.error("op key {} error: " + e.getMessage, key, e);% j) ~6 G0 m" v
} finally {
) H2 O* p- O! A: b7 A% q //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。; l! a* S4 [9 H/ Z* o5 R
if (jedis != null)
8 f% h5 a( k' k' O9 N2 |1 F( L jedis.close;( L. J* J& Q: L6 d, R
}) v! e: b. E! Z7 W( _% S
3、熔断功能# f0 P9 Y" n# k( x
0 `; c5 E: L; P+ E8 i, p3 k
高并发下建议客户端添加熔断功能(例如netflix hystrix)" S9 y8 u! W9 |4 |2 w) C
4、合理的加密
" r# t  G! [1 N; g9 g+ N2 M. o$ @2 o: o. m. c4 r2 f. c8 J
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)& ~; d( k# S" Q1 x) U1 B
5、淘汰策略# T: L6 [+ @5 E, f- ~: ?6 Z5 D
7 }; ~$ G( Y2 Y, f* J0 w5 w
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
5 r: X  ?" b( L. R  m( N) {: w默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。" A% _( I' o2 B, n& M8 e) c. C3 ?
其他策略如下:
0 \3 n2 D& k) m$ N8 r

      Y; U$ t" r3 M! Y( u8 _! R! p
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。9 {/ H0 ^5 ]$ X# u6 ^5 d% U$ Z3 d
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。1 Y: a' N5 }: Q* {, }
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
    " V# l/ L( j8 V
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    ; x* \$ }1 {2 o" p0 R" J; b
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。$ E+ Q! b5 x+ I/ P+ w
四、相关工具
; |9 l6 X: _4 V1 s  d- W4 U
/ o' i4 y; W% D( Y1、数据同步
. K; B1 ^1 x" k( N
2 C* e. c7 v% r/ d6 tredis间数据同步可以使用:redis-port; ~5 ^9 w2 x9 e3 H+ E7 ~
2、big key搜索' O# p* E4 z/ n9 t2 U

3 W( T( a2 r& f( M* |0 Dredis大key搜索工具* \8 s- q2 K7 f, Y
3、热点key寻找8 X# t3 p; R2 b# R' G

1 i0 N7 c0 M7 l9 c" Q3 |内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
5 F. E7 }. v+ {+ I- C) `五、删除bigkey
( C  M" }' P! \' G, T, u; U# j/ R8 M; x* [7 O/ E

    + b% m. j8 {0 ]; ^: P+ V
  • 下面操作可以使用pipeline加速。: R2 }, m( v; j5 k4 Y
  • redis 4.0已经支持key的异步删除,欢迎使用。' W  Z) H% `4 ^+ X
1、Hash删除: hscan + hdel3 P& Z4 r. i. B+ o, t# s) ~7 [9 ]
3 w6 W- _% V$ j4 K
public void delBigHash(String host, int port, String password, String bigHashKey) {
( `5 i% @9 s( m$ ? Jedis jedis = new Jedis(host, port);
  a; ^, G9 R" F+ ?. B# `9 B& @- d if (password != null && !"".equals(password)) {# H( @3 F/ R  u' h# v8 a) }
jedis.auth(password);
9 D+ U) T: u9 q; i4 F& I+ E- F }
' a* m2 s7 ]4 r. Q% [6 X ScanParams scanParams = new ScanParams.count(100);
5 M1 o6 }% T" {( {/ Q String cursor = "0";
2 ~. k; S$ {. d- B5 S. g5 T( t do {8 r0 t4 p9 [+ N$ `" q# Q
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);8 t8 g7 r& z! W1 _4 @# p: X
List entryList = scanResult.getResult;
/ F# ^7 t. x' o if (entryList != null && !entryList.isEmpty) {
# g% Q# [+ D/ F9 \ for (Entry entry : entryList) {
% d7 j8 a" W" l# {' y jedis.hdel(bigHashKey, entry.getKey);$ o, c9 o7 J6 ~! L0 ]
}
& T$ S: ]' B: Y& { }
5 i2 y1 O. [2 ^ cursor = scanResult.getStringCursor;
' L. q" C$ Z3 K& w } while (!"0".equals(cursor));; ~: }, [* k; z* @- B/ X; W" _& C" \
/ k2 M. C6 \$ F: ?/ b) d4 k
//删除bigkey9 X. F: X2 l7 Q# o) \8 R
jedis.del(bigHashKey);
7 I- N8 D" W5 Y0 X( D}" s$ D7 o/ S  {% R4 D4 @
2、List删除: ltrim
+ B1 @) O/ z/ V$ S) v! v
3 h. T! f; A, ]. k2 _  {9 apublic void delBigList(String host, int port, String password, String bigListKey) {
. ]% F4 G' f1 ]& v3 }7 k Jedis jedis = new Jedis(host, port);7 y# Z* S& J) m3 S2 |+ T
if (password != null && !"".equals(password)) {
% [! q% V/ e8 i" C4 f7 ^) H4 ^ jedis.auth(password);
& H) }0 U0 j$ i4 O% v }
. n* ?  i# M: G2 ^, s long llen = jedis.llen(bigListKey);
, D1 h% T/ ^" a int counter = 0;5 ?+ O/ @. G/ e$ C2 q: b
int left = 100;
. b; n/ o0 r2 y5 M1 T while (counter < llen) {1 v! u% w- ^1 v' F7 ~
//每次从左侧截掉100个6 P) ~% ?8 t4 R
jedis.ltrim(bigListKey, left, llen);  [: ?6 K  j- m, h
counter += left;8 x: N* X+ S9 z
}+ o$ c' G$ C; C6 Z/ m# m* I. e
//最终删除key, \- `( c( X) v& z- d1 h: O: o  r
jedis.del(bigListKey);6 U% h6 f! `$ \; I( P
}3、Set删除: sscan + srem
6 [  M- O, e" u  p# b, v! m7 u5 K
( M8 ~( `, k  Wpublic void delBigSet(String host, int port, String password, String bigSetKey) {$ j& x" H  Z6 I0 |  Z" V
Jedis jedis = new Jedis(host, port);
0 U# D! N" e5 w, Z" ? if (password != null && !"".equals(password)) {8 t: }+ G/ b: U, r& z( }! o" n, d
jedis.auth(password);0 M5 k' q  X1 A% K7 \' `- m
}
. H+ _( v  l! w4 n ScanParams scanParams = new ScanParams.count(100);
  q; I# u1 P3 I  ~& R String cursor = "0";
2 o9 T- Y( L. p do {0 Q% X+ n/ ], E& [! S" g& g
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
7 a% m- K4 J: O( B6 `7 R List memberList = scanResult.getResult;$ w1 l% s. g+ p8 ^% @0 \
if (memberList != null && !memberList.isEmpty) {: ?0 e# Q$ ]  @
for (String member : memberList) {6 w5 t, ?9 K2 G
jedis.srem(bigSetKey, member);. A+ J, ]. U4 V4 X+ b6 o
}
* h0 ~, J6 Q3 ^7 R1 w* a }
* _, u- I3 f# b& f1 U& S cursor = scanResult.getStringCursor;
9 H+ K% b/ V2 s) M- E3 s- l6 F } while (!"0".equals(cursor));
# t) G; E% S& \3 V; ]8 L: D3 ]5 o+ U& l( @5 k0 R1 l7 r* a
//删除bigkey3 z( d" ]- I4 q% ^2 U
jedis.del(bigSetKey);
) B4 K0 A9 F! O( i- g. Y4 O# N  B}& D% V$ P0 }% {7 S
4、SortedSet删除: zscan + zrem
# _7 y- g; P2 w" V; S; z
4 a3 E2 z; p3 ^: {) }public void delBigZset(String host, int port, String password, String bigZsetKey) {
' H8 U0 Z" `) [7 f8 g0 P Jedis jedis = new Jedis(host, port); # E1 V# {: ^" P! L6 q/ A/ P: m
if (password != null && !"".equals(password)) { % J2 z7 e  @! }& ~. {5 j4 W3 v1 C
jedis.auth(password);
0 |+ g: l. Z  h1 B% C }   ~- S; Z% w( G. k0 O, U4 _
ScanParams scanParams = new ScanParams.count(100);
& Z: h( s8 X7 o7 } String cursor = "0"; . Z: o  e& r4 h$ ~! `& _
do { 7 Q/ i5 r) A' s4 Y9 a
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); # s! D0 s1 e3 e( x2 t# c) x
ListtupleList = scanResult.getResult; ( k8 U' M6 S  T4 n5 M% [
if (tupleList != null && !tupleList.isEmpty) { / X* a! U% c: H- A3 ~6 B, `
for (Tuple tuple : tupleList) { & m. f7 X2 R% s2 H2 T
jedis.zrem(bigZsetKey, tuple.getElement);
6 W" u$ c0 k! [# p; K* B } + D! _4 \) B4 ^& V# H" v2 R
}   r+ C6 {8 x; C0 n% N' ~1 i
cursor = scanResult.getStringCursor;
# N1 g, T" X5 i$ G9 }6 B- u } while (!"0".equals(cursor));
5 \+ U0 q# \5 L, G) ?8 g9 e% M' |, C
//删除bigkey
( e; u' r3 x" Q6 M. ~ jedis.del(bigZsetKey); % B, a; `6 I* ]" e4 q2 ^1 l
} 公众号内回复“1”带你进粉丝群
. g: t. v3 b  S来源:http://www.yidianzixun.com/article/0LevQm7t
+ t& b) Y0 E4 [免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-4-19 00:08 , Processed in 0.036860 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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