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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9389|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

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

. T% c0 L0 Q3 }本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
; t" {2 K  l8 J# r1 i5 j

    . C! |' d' H- \+ U2 r
  • 键值设计
    # c  ~0 N" }+ c& ~# i
  • 命令使用
    : T( K5 |8 I! ?3 B/ K/ ]' X% c8 b
  • 客户端使用
    . Q* \: E& R; p0 S/ p4 }& y" S
  • 相关工具5 d- h$ y) {) O0 N
通过本文的介绍可以减少使用Redis过程带来的问题。3 `" l( ]% |! ]8 z, p4 j9 s9 c
一、键值设计( p# s  j! l6 q" m
. }, L9 A: L2 U6 I/ W- g! ^
1、key名设计
5 C% f- r  r2 P+ A$ \
1 y* _: G# _! p; j* e( H7 f# ?5 ]可读性和可管理性
6 @& t  v3 Y; `% t5 q) m' K
- T- {4 ~) a1 i* Y0 N9 m以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
) E/ l; W( E4 y: |/ N8 q" L
    ( R- T, O, [& g( v: L
  • ugc:video:1: i3 `, f+ ?/ `( R# v$ }& M; {- Q
简洁性
  v- L9 M/ o+ F. Y' }  N2 ]6 k0 V+ i: u- ]& q
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:# F! v+ x) N& z: f

    # A) a# h' S6 |- q/ p7 d
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
    4 B0 s+ m+ f8 J5 ~, h) G
不要包含特殊字符
( t2 N5 O" o2 P: E/ k& ?1 L# g! c& n5 C$ r# \# ]7 p6 d6 [
反例:包含空格、换行、单双引号以及其他转义字符
7 X# \' C; X& ~2、value设计
8 {) Z9 k( t( G
$ z2 z( K0 N9 O0 R6 V+ N% u$ l4 S0 J拒绝bigkey# p3 m3 D" c/ e6 J

2 j/ f# y7 M( x; G: v+ C9 D防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。4 `& H6 x5 M6 R) m, V- l
反例:一个包含200万个元素的list。
! D( ]1 L1 ]5 z/ ^4 c( H) ^非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法+ x+ x0 Y9 E. p- B
选择适合的数据类型
- Z$ ?9 j, U8 e& T$ C& C$ M, r; Z* g1 \
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
( L+ @* [1 q9 e# t! D反例:9 x7 ~) G! ]0 z5 ~' y& N
    # h& s' a. N! f/ \) E) G7 M
  • set user:1:name tom
    8 z3 x' c5 b' W0 m9 v/ V
  • set user:1:age 190 P  ~! y1 J- B+ B4 o1 O% V
  • set user:1:favor football
    " t6 X: @- @7 s
正例:
, L: m, w5 C: C" w+ W) q1 e5 Y" @
    2 g0 H1 W  b' ]% |
  • hmset user:1 name tom age 19 favor football2 [; m) m4 r" j6 _2 c
控制key的生命周期
8 w" e0 U/ f) m, g* x7 y$ n3 \+ d! e" D2 v0 f% x$ _
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
- |6 j; J6 F9 M" H! [  h0 y二、命令使用
/ p4 k0 P/ R- B+ K2 h3 c; o5 F0 a* B, e6 C( T: i
1、O(N)命令关注N的数量
5 A# v- _/ q: m1 Q6 ]0 J7 Y/ X/ k3 o: ]% c8 d4 D
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。$ ?2 l& _7 ^# E2 D" l/ s6 e' W4 R
2、禁用命令
8 _+ {# }5 a. m) `
8 f9 A1 K; P' z) Z禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
. R) Q' m( N6 v- Z0 J2 y: V3、合理使用select; d! p( M9 ~1 h; Q  J

; X' D5 g' c1 [; T  p: D1 C' \2 t2 l9 G' i. T
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。8 E& ?3 i3 M1 y4 g
4、使用批量操作提高效率
0 b. Z1 E" n: [# m5 M* I  g! z8 E6 x) ^

    , f% o2 b0 \# I, R% p
  • 原生命令:例如mget、mset。
    - l8 j$ s* [, a$ g1 s! ^  U
  • 非原生命令:可以使用pipeline提高效率。- U: `, G1 z/ J$ R* T( j% z7 }
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
: i9 _, L! U9 x9 e注意两者不同:; _. D! V  h! ?' |9 h
    5 C" E; Z2 A) c6 W
  • 原生是原子操作,pipeline是非原子操作。3 v3 Y7 F) Y+ I* N
  • pipeline可以打包不同的命令,原生做不到5 C& Q/ g9 {  q1 v( z
  • pipeline需要客户端和服务端同时支持。6 f  H0 a& d0 y* A
5、不建议过多使用Redis事务功能: y7 F5 \" o( r6 E7 B$ A; F
3 w; r5 Y+ q" h% }: a* F* J8 V
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
4 L! f1 [9 I" b' ~! \& l* q; K- E6、Redis集群版本在使用Lua上有特殊要求5 ?, L6 N5 I6 d
8 `7 }4 P# {; B% w% C
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"; G1 _) P% ^% J- t0 q1 ^9 Q; G0 Z
7、monitor命令1 B$ W+ s; g/ K* i. w, R

4 ?3 V5 w4 X% o6 Y必要情况下使用monitor命令时,要注意不要长时间使用。
' U5 R2 F3 D" C" y+ D2 K三、客户端使用
9 o# S, Q6 j, ?9 T- T0 q6 i8 M& X9 a6 P2 S" a
1、避免多个应用使用一个Redis实例
- C3 C3 A4 U0 `$ i4 n5 b
" R. h0 C5 I. W, {+ W不相干的业务拆分,公共数据做服务化。
2 j. L7 _4 _8 S7 D0 n2 u9 F: `- m2、使用连接池; g( {/ P, z# R" ^# W! _, @
& R. m6 Y5 C( j$ {: y( |/ d& k: {1 h
可以有效控制连接,同时提高效率,标准使用方式:
# C4 E+ w9 g. A2 \7 D8 _/ L" @( V5 o' oJedis jedis = null;
& s7 D$ N7 L7 stry {
4 Z9 x- x# t, |8 {; O/ P& f jedis = jedisPool.getResource;
; \4 ]4 z. a; A/ l& ~ //具体的命令
% r7 o. P# F: V" ] jedis.executeCommand
7 P3 `8 H6 S9 s9 P9 V0 f* j) x8 ~; n} catch (Exception e) {
& Z2 {! D$ U$ ~- a8 A, o logger.error("op key {} error: " + e.getMessage, key, e);
/ F3 T$ l9 q. f4 E5 l- U} finally {# q( U1 Y: p2 U3 ^5 \$ Q$ h, h
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。# e( ~4 H( O( J( m4 k
if (jedis != null) 7 r$ U3 O) C- {+ L4 \! _1 H
jedis.close;
) a9 N4 r) K& h7 n$ u5 F8 F}+ a& _9 V! M- H8 E  U
3、熔断功能
, Z7 K0 T) O. G5 [, T) K% b2 @! a9 E/ p: D! _
高并发下建议客户端添加熔断功能(例如netflix hystrix)
* I. ?4 C, t2 O/ `2 y0 w7 E' W4、合理的加密
$ U4 G2 J7 ~& O; L1 _0 r! S
" O1 o! z6 n; o; V% O/ U* A设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)3 d3 D! s0 m2 u% f, Y
5、淘汰策略% H, o4 l8 c( x
5 [8 u) F+ J) r4 r1 r" Y6 `4 A
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。3 ^; |% f& R+ [% Q3 [# H
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。( x1 K) ?. A( @5 ~+ Z! |/ l  M
其他策略如下:4 O9 r$ a" E% [5 J
    , ?3 e! g* s  N
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
    6 z# j# e0 S( Q# a/ |; y
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    ( X8 g2 m$ d( z& s: }! K
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
    / K7 Q0 x, [3 [4 F, _
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    - J$ m' z/ B0 t& c) e2 X- r
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
    9 U' b7 ^" N, w2 l  d8 M9 Q
四、相关工具
, L1 L1 }- J9 D1 y; }7 ^9 j# o5 S* ?- Q7 k7 v
1、数据同步0 _1 Y  ^/ `% ]9 J2 ?5 _" K

  \' m( G8 L- C2 j, `$ u# a2 A' C; t5 M5 Lredis间数据同步可以使用:redis-port
+ v5 p0 ^  U$ M2、big key搜索# |! ~/ G" P& h* t# k* y+ I
4 G9 L0 {8 D5 f7 I
redis大key搜索工具
- M) x9 M  i9 K2 z5 h; A3、热点key寻找
; z; E" _7 W1 t+ ~3 L& D
' A  e5 l+ \- r5 ~6 D: g内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
3 J; u/ z/ W% j- h9 D: x2 I2 R五、删除bigkey
0 T0 j5 |: V' g& o( Q
4 t  e5 `4 D; M" Y- s3 }1 l5 q
    0 [$ u; ^* ~) i! k+ a2 s! [: p
  • 下面操作可以使用pipeline加速。& w9 M4 q0 h6 w) W
  • redis 4.0已经支持key的异步删除,欢迎使用。9 d$ }3 l3 i1 x
1、Hash删除: hscan + hdel# G# `! E6 k1 \" f2 r" I

7 Q$ x/ X% T# ]6 Vpublic void delBigHash(String host, int port, String password, String bigHashKey) {
. p7 ]. t* j8 E# H  H, L Jedis jedis = new Jedis(host, port);
, t. @3 N$ S& ]0 }  Q( Q, K if (password != null && !"".equals(password)) {5 c  ]" }: O, s" q+ [1 K$ l
jedis.auth(password);
9 `4 t% ~" ?  T' T! x4 [' I* K, L: V. X }$ h4 z7 h* n; H9 q
ScanParams scanParams = new ScanParams.count(100);
  m5 ~% l" h8 Y: l& i! o' c( H String cursor = "0";
" a: j8 P5 ]/ f- m5 m+ F do {
( i$ `* o( C6 R ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
9 }6 R$ o5 u# q% Z, d List entryList = scanResult.getResult;% p3 a" G9 z1 [" A; ^* P; m- K% [/ u
if (entryList != null && !entryList.isEmpty) {* q" ]% Y" q. g- N
for (Entry entry : entryList) {& ?$ s* @: o4 Y$ _3 O
jedis.hdel(bigHashKey, entry.getKey);
6 ^8 a- _) |) `- Q6 k }' J  @! O8 e/ ~" ]" a
}
5 o0 d# P- @3 [, q5 D1 D cursor = scanResult.getStringCursor;
. c' g, ~, ~( C$ n } while (!"0".equals(cursor));
% P; X! T, A8 e1 w0 p
- }- @+ l9 A1 c( f1 O/ ^' S$ e1 o( ?//删除bigkey
5 A4 S* I5 ?$ E3 d9 {( _: j jedis.del(bigHashKey);
$ r' D' R/ }& O2 M0 J, Z}
, I  w5 f4 o4 B  D: B" ~2、List删除: ltrim
& K5 d8 [" v2 V2 I- q7 f3 B; T+ l0 t7 L( O/ l! @2 s" X2 O- M
public void delBigList(String host, int port, String password, String bigListKey) {3 S2 x' y0 n8 d" J  d3 P/ d
Jedis jedis = new Jedis(host, port);
7 M) m/ q' B2 R3 O; a4 {# I4 a8 B! R if (password != null && !"".equals(password)) {' @- y: c0 \! ]: B. w7 `6 j' N
jedis.auth(password);
0 Q! c9 B, G/ }( S& n# B$ b# K }
% ], ?! e) z/ b long llen = jedis.llen(bigListKey);
$ s- Y5 F( B# C9 O$ d! Q int counter = 0;
7 p  W. `  P( X4 ~* `7 L2 i int left = 100;
& d$ V5 r7 {/ w9 x' e; H while (counter < llen) {1 d% l! A/ {7 f3 m% ~6 V1 Z7 o
//每次从左侧截掉100个
5 h, l1 j$ x! h5 ], z jedis.ltrim(bigListKey, left, llen);4 H9 r- [& \- B! `
counter += left;
8 b# _( B6 @5 h }6 R$ y' m, I) [( V- G; c* D2 B& t+ \
//最终删除key8 K7 d6 ^0 O. M
jedis.del(bigListKey);
+ j) m( @1 ^% {1 Y}3、Set删除: sscan + srem
0 {& B+ R  @+ S3 z2 h6 ~, Q# r8 ]5 w6 w; T- J
public void delBigSet(String host, int port, String password, String bigSetKey) {
" ?2 Q1 w  M: Q' C! T9 Y2 d. W Jedis jedis = new Jedis(host, port);
& b' W# [; n# e* ]( H) `) F- d) h if (password != null && !"".equals(password)) {
/ P+ f* W* v, |7 a  t' y jedis.auth(password);; m8 b7 @1 E9 b8 [; z
}
* v* z0 Y; y+ ` ScanParams scanParams = new ScanParams.count(100);# i, s& s7 b9 G$ j: L) K( r4 ]
String cursor = "0";
& U6 F2 _+ q' P4 @* G- z) N! _* Q8 m do {
. r' m) s) s% q ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
5 m1 E. X. E1 F  X! c List memberList = scanResult.getResult;% |  K+ w. N6 R" M& v
if (memberList != null && !memberList.isEmpty) {# J) h# ]$ f/ [
for (String member : memberList) {+ k3 k" T. z' i& A" }! v/ ?
jedis.srem(bigSetKey, member);6 Y& s2 F! N# m( }8 _
}; x3 H+ {$ G3 Y" W2 }5 c2 L
}, y. Y( \6 P" D7 x! I, W
cursor = scanResult.getStringCursor;6 w  p; V# O( p1 ?' @1 j$ V
} while (!"0".equals(cursor));
3 c" X, L% {/ \9 l
+ T2 Y& {* d  i* C//删除bigkey. T8 Q! [: p: ?  i. v
jedis.del(bigSetKey);9 g& K% Q0 _& x* a# F1 Q0 ?  P4 u
}
, m4 B, N5 C  T3 G0 \4、SortedSet删除: zscan + zrem
" s8 C, Z% m; B4 C- ]$ a( z/ D  {5 b/ }. _& ~$ A
public void delBigZset(String host, int port, String password, String bigZsetKey) { : z) W7 K" v1 W% k
Jedis jedis = new Jedis(host, port);
$ A4 Y2 F8 R6 t: U: _! Q if (password != null && !"".equals(password)) { " O/ c9 M0 ]: R. r- M
jedis.auth(password);
; P( L) |, d7 a }
1 k! B# _; p, G: D ScanParams scanParams = new ScanParams.count(100);
( C' o0 Y$ d  |, m String cursor = "0";
3 T3 F+ G) u! m. [2 @& ] do {
  X% n0 \7 i$ I' u% G ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); . G0 e( R+ z: p9 T: z4 ]
ListtupleList = scanResult.getResult; & Q# y( j) e% U4 E
if (tupleList != null && !tupleList.isEmpty) { 4 M. Z! m% i1 ~9 q; J) v8 Y7 c' K: D3 U' |
for (Tuple tuple : tupleList) { 0 a0 W& c& \: ^+ Z
jedis.zrem(bigZsetKey, tuple.getElement);
! z4 w2 S9 p2 ~/ W# D7 u }
1 x  M, c9 E: h- e }
8 W8 y0 ]" x& k6 w cursor = scanResult.getStringCursor;
7 M+ V- J6 }1 j- h$ J4 ]; k } while (!"0".equals(cursor));
4 s' ?% `1 i. ]( y6 k3 s9 G( y3 Y  t( N2 q
//删除bigkey
8 S; t' `' X; {) g0 \5 ? jedis.del(bigZsetKey); 9 w, i( t* z- x' L- ^/ B
} 公众号内回复“1”带你进粉丝群
2 q, C6 f8 t) a+ b来源:http://www.yidianzixun.com/article/0LevQm7t5 a7 e" t4 G" L9 Z! s
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-7-30 18:02 , Processed in 0.050770 second(s), 25 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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