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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9284|回复: 0

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

[复制链接]

8

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2019-4-5 01:19:59 | 显示全部楼层 |阅读模式 来自 中国
1 B+ I/ q- Q; X  F
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
  o4 p! p1 Q0 n6 d, M; h4 o

    ) m( C9 c& G- S; {2 k
  • 键值设计6 R- {. A8 k( |) s% h8 M& W
  • 命令使用
    $ a' K. |6 A5 y; S3 u8 Y' A
  • 客户端使用
    + c/ G# I1 `' A* C
  • 相关工具
      _2 d) U" }2 _! j
通过本文的介绍可以减少使用Redis过程带来的问题。
' w' B$ m$ l- V" a6 v7 s. `一、键值设计
% a) J/ p1 g. p
! }% A1 r! \4 I4 K1 x& j1、key名设计) l% r) _4 B( C% A! r
2 J+ K1 y: X! x# L1 ^
可读性和可管理性( O# d4 M9 N7 y' q3 Z3 o, f9 T
% i- [: Q1 w7 s8 l: N3 J- c: o. D3 t( I
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id. A1 e; L. m9 @! E% ?6 l* ?. f$ |

    ( ^, g( X  D! i, W# z
  • ugc:video:1) Y0 i/ D; S3 Y# C/ p3 g
简洁性% X! w" e1 Q. d$ j+ |, T
% ~3 w9 s! x" s6 n' H. }5 q
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
* K7 p2 t; ~5 N$ n+ x
    ) p2 S$ {; p' ]1 i3 M: ]/ G8 \" \
  • user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。/ O# |& k; v: F
不要包含特殊字符
4 l7 C% [1 d# ]9 p2 _6 |; `
6 [. p7 X$ W% j' [0 u9 ?3 V反例:包含空格、换行、单双引号以及其他转义字符0 K3 X* E  H1 r# b7 M& ]" Y/ \& B; N
2、value设计& p" R9 Q0 X9 n6 E! A  ]$ D
- K0 K8 ^4 O; n( H9 g9 {  b
拒绝bigkey
# n5 a' J$ d# N- V; w- p% B% \3 x2 G) D) `# f" A8 r+ r
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
8 L: N* x! m/ Z5 F反例:一个包含200万个元素的list。
* l# B; K) R  K9 p( \. [非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
. k' d; [6 \: o; g- J7 A* d1 L$ s* J7 c* [选择适合的数据类型
" V6 i  W$ m' B
. x$ Q' B1 u  C- d7 F" e- \& L例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?; W# \; u) H; j' ~# n# Z( l; j5 J
反例:
$ E; z1 g0 z5 _! p1 Y

    ; x1 e; o" v( y+ z
  • set user:1:name tom
    2 t) T9 x+ e$ Z! \8 s2 v6 n8 y0 y
  • set user:1:age 19
    : [$ O- @7 i: i9 L7 t7 f
  • set user:1:favor football
    + A# I1 `8 K$ i$ B3 E
正例:7 o0 D; P+ c4 e9 c
    / r7 w" }# l* n4 [  u" [, m1 m
  • hmset user:1 name tom age 19 favor football+ j5 n7 ]" c( X7 J; J" x
控制key的生命周期& k2 ?+ r, t2 y
# V$ L9 x) ~/ g
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
. l1 g/ \+ @" r: d! X二、命令使用
0 @; u2 G, z! T" d. V
& |* y1 }3 s! a% I  y% [# @$ J8 ?1、O(N)命令关注N的数量
3 ?( g9 E# `! i7 F- b# a4 m) q* l6 n6 z/ e5 A  J2 A) K/ Q
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
) k9 w2 I, {+ M) `5 u% `2、禁用命令
% E/ X/ p% l9 n8 e0 Q7 M
# Q5 Q" B/ J( R, P6 S3 \2 ?禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
# }$ r4 c. o5 |, h# A2 m- X3、合理使用select. b+ o1 o8 ]! l1 Z; d% g
* E/ W$ I" J+ p3 E, ?; J: l

2 l8 Y* _" X( U, qredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。+ S$ b" h* U2 k0 B: Z- s
4、使用批量操作提高效率
3 s3 H7 ~7 f/ l* Y# {- G3 t/ Y/ j4 g2 r- a- A8 D1 ?
    & ?0 M9 q5 F1 v9 k- j
  • 原生命令:例如mget、mset。
    5 A9 X; J  [8 r' f+ K* Y# _
  • 非原生命令:可以使用pipeline提高效率。  p& Y) n! |- V( n* t  X" w  K5 t( W
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。9 H/ j* E+ S- ]4 B3 P
注意两者不同:
; s' T  }- y, ]% H

    & D6 `2 e0 r+ S5 C+ P3 }5 j
  • 原生是原子操作,pipeline是非原子操作。
    3 B: B4 E9 _0 f- ]+ ?
  • pipeline可以打包不同的命令,原生做不到
    8 Y- W+ h4 f, W3 N2 M
  • pipeline需要客户端和服务端同时支持。! b$ E1 D* z" _; r+ g
5、不建议过多使用Redis事务功能1 L7 u' p& C1 `5 ?
/ h3 w; U6 \4 m$ S3 n
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
& x- c, V( l$ W% e4 ~: X6、Redis集群版本在使用Lua上有特殊要求  k* ~2 {% P7 N$ M/ x0 B$ ?

) R+ `2 o; U2 V, X: q, {$ S1、所有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"6 C8 _. v! d5 a& @: ^  [
7、monitor命令
: a. i, G! U) g( C
! J- ?. p+ k) F8 C# t5 m  e) L必要情况下使用monitor命令时,要注意不要长时间使用。, r- p% U! U  _( U0 @5 Y
三、客户端使用
6 f2 `+ f7 s: P8 m$ m" _
* m1 g' i4 {6 }# {" t1 l# ?+ w, o6 }2 f1、避免多个应用使用一个Redis实例
4 \6 U9 A$ K- M2 q
( J5 x6 _1 a' w不相干的业务拆分,公共数据做服务化。
+ W, w4 K- D" S( q5 r( X2、使用连接池
$ z$ [/ E6 X% l; v! W9 l! t1 R/ M
可以有效控制连接,同时提高效率,标准使用方式:( }9 v7 i& |8 F! b
Jedis jedis = null;
( [! ^1 j" F, {5 ~try {
2 U* Z) i5 P, m jedis = jedisPool.getResource;6 D( L) o4 b0 O  D8 ^6 F) s9 p
//具体的命令
( R, g9 C8 n, ]; w) B$ p jedis.executeCommand
$ w1 S( A" ]1 ?} catch (Exception e) {1 V  ~" @- a" L* p
logger.error("op key {} error: " + e.getMessage, key, e);
# m% q# {6 c7 V5 Y3 I} finally {! Q' U$ t; n7 @7 B, K2 w% u
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
0 l- Q6 f$ d% c+ p# `) } if (jedis != null) , R2 r3 k/ E' y9 b, j2 f2 r
jedis.close;
4 D* b- I8 x" Y0 ^}% [' F1 }  f3 j3 K" o& }
3、熔断功能
& R8 K! F( e2 m% B, l  L8 ~+ c, y8 l7 u1 q% M5 a+ R
高并发下建议客户端添加熔断功能(例如netflix hystrix)
7 M2 c4 o* @0 P0 z  T. S: C4、合理的加密8 H" n" n: _) v  S
) y% s" p$ |) D* D
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
& q. L2 [# p* ?" P4 X5 F% G; }3 p* ]5、淘汰策略
5 ?0 K7 q" @' n8 U+ a/ z! ]. ]
, }9 p  H+ ?) w! ]9 `7 b根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
& _$ ~' N; d4 A; E# \8 X7 @4 h默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
+ w5 P# a% T8 n9 D) J( J7 E4 @: P+ _其他策略如下:
" g( o3 G8 K- h) n: {6 L) Q

    , I2 Z9 p4 z9 E' a
  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。5 h" O& k1 z5 O& n# ]
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    ; X; g' k9 D5 A, y! L7 A1 H! h2 n8 _+ u
  • volatile-random:随机删除过期键,直到腾出足够空间为止。% g. r# S5 ^8 |1 ?# G7 j9 [- [" a
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    $ O7 s3 @8 {- L* z( S- j
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。" y. v8 V6 ]1 V; g) g
四、相关工具5 G- b  j3 O' f+ A; E2 K, \$ s- P% y
; r3 ^& i! `4 D: m4 K! p
1、数据同步
$ m! _3 ]; S% j+ O/ _1 C6 Q6 v  N( y& m' ~- {2 C" ~
redis间数据同步可以使用:redis-port
$ K) r) f! S6 }7 D5 Z; [( l2、big key搜索
$ S8 y/ ^7 V$ v; D, ^# R3 j( D. O! S: A8 N3 f& F1 W
redis大key搜索工具. k5 k  |/ T; g- R7 u
3、热点key寻找
! l$ f$ _! }' ]7 G7 T" G6 W2 A3 Z  S% v
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
) S- k+ c4 J! X- w4 F+ R5 x7 B4 b五、删除bigkey: D3 q  d$ e) w* p; }, A" _
8 W+ r; v+ V3 j- j9 D  E
    9 K+ Z: M, u% t- [
  • 下面操作可以使用pipeline加速。6 J4 J8 T: u8 P# O( W% |
  • redis 4.0已经支持key的异步删除,欢迎使用。+ F1 ?8 H* k$ ~6 L8 d
1、Hash删除: hscan + hdel
  i4 u/ {9 z$ |+ L! x- S" W8 w6 ?6 E; \, a1 u
public void delBigHash(String host, int port, String password, String bigHashKey) {
! n/ K1 j& S  X7 a Jedis jedis = new Jedis(host, port);
" e' Q/ i) C/ U5 M if (password != null && !"".equals(password)) {$ W0 f5 o8 U& L9 |4 d6 q' k
jedis.auth(password);8 A$ K: n+ I: Q3 D& S! B7 ~
}
3 d2 F, I* @' t+ {. y3 W" B/ L5 K ScanParams scanParams = new ScanParams.count(100);  Z) ?$ N% L" U0 i; j% O& R
String cursor = "0";, }' y* }" M6 F( s) Q: M8 v# `1 E5 h. z
do {3 j2 Y4 S# n7 x7 C5 _+ @6 \( b) R
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);( n: g; x, B7 f5 O" `
List entryList = scanResult.getResult;
0 q, M  L- ^& q( ^ if (entryList != null && !entryList.isEmpty) {
6 n2 h- c2 L) B, D0 K/ ]2 [, q: c* B for (Entry entry : entryList) {
$ l5 X+ ~) J% n' _& t) w! q jedis.hdel(bigHashKey, entry.getKey);% _/ u! ?$ a0 [: V, F
}
" O, q: k& ?% M$ e2 ? }
6 S5 ~/ Z7 E# x6 ^. s5 D cursor = scanResult.getStringCursor;0 d/ M; K! S4 J% Q
} while (!"0".equals(cursor));$ r6 Q0 b5 {# h4 j) l- a
  u5 A1 k0 G, F# o" t
//删除bigkey
0 Q% W8 [( ~' R# J: `9 b! K jedis.del(bigHashKey);
& i0 o( k% Q" b5 H. ?! U}4 Y* p8 c$ |0 @
2、List删除: ltrim
: U( Q# o* q0 g0 D. d( C# s9 G9 H; u( ]+ O1 K1 I- G
public void delBigList(String host, int port, String password, String bigListKey) {
8 I) W' M8 R, f" U% E0 R Jedis jedis = new Jedis(host, port);
! q8 p" P: V6 ^% n if (password != null && !"".equals(password)) {: Q; [3 t! n9 ~) N4 K( e
jedis.auth(password);
) O/ B4 J) _& g6 @* v9 V }
0 ]1 j( J; B+ I3 ]# l* ]3 V long llen = jedis.llen(bigListKey);- F" m: O4 k/ G. e+ X1 ~. f
int counter = 0;
; f9 v- ]5 [' z* K. o# c int left = 100;! ]5 H* q- C# X$ A: p
while (counter < llen) {
1 X: p- A$ c( `& L8 a9 j) t //每次从左侧截掉100个
: T, E* I5 s5 W- O% x. K jedis.ltrim(bigListKey, left, llen);, i' Q/ q9 V& y' g: F& G" N, \9 j/ t6 C
counter += left;# c; |0 V. l$ p3 S" N+ g' ^
}
9 T8 I. `) W, e' z6 y4 |, ^ //最终删除key
& r! H* A7 B. g; g( u4 E jedis.del(bigListKey);4 {& `" D8 Z4 E6 U4 L) f
}3、Set删除: sscan + srem; u9 h, B) O% P4 i
. r4 |) N2 u' S4 ~* S
public void delBigSet(String host, int port, String password, String bigSetKey) {& y8 N* r( M6 n( V, P% Q- p
Jedis jedis = new Jedis(host, port);6 c  l8 K* S0 t& w# e  _- d5 ]
if (password != null && !"".equals(password)) {, x, e( N" \0 E" J
jedis.auth(password);# u8 u( y2 i2 U: i6 _8 q+ j9 P* Z2 C
}
# Y, \3 w4 A8 Q" f4 l9 e! x7 z ScanParams scanParams = new ScanParams.count(100);8 w6 M0 F* S# T4 Z! A2 t$ i+ ]
String cursor = "0";
3 b: n1 q4 _: @" ~6 [( l do {2 i0 q7 Z' a. ]5 l* l9 K
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);1 y9 Z1 j' p  }, T$ P' A8 a! [
List memberList = scanResult.getResult;
& }+ s: ^0 f8 I- `+ a0 l. M% b& L+ E if (memberList != null && !memberList.isEmpty) {; |  F) b$ l# g! @2 j1 F& V/ O
for (String member : memberList) {
9 y( R: m. ]$ r1 h+ x jedis.srem(bigSetKey, member);6 e( E# U; j) g" M/ V. k
}
& o& y0 Z, a, x" d: ~* b8 E1 T5 i }" u9 h) [0 E2 h; n& a( Y8 O7 P
cursor = scanResult.getStringCursor;* B6 a8 ^+ _0 M) R! n! |
} while (!"0".equals(cursor));$ B4 t( A+ e) r2 b* i

2 L3 e" G3 V, d; c//删除bigkey* y8 i. Z% O# L5 F* l
jedis.del(bigSetKey);5 h! n  L' K7 c' P
}
/ L5 f5 u1 R* Q/ e9 ]+ F4、SortedSet删除: zscan + zrem. `: ]8 e1 H* b9 a/ y

4 c; u: N& M5 h& T& ~public void delBigZset(String host, int port, String password, String bigZsetKey) {
" G9 f% n8 A0 G Jedis jedis = new Jedis(host, port);
* x) v0 m3 `- Z- L  x6 n5 v if (password != null && !"".equals(password)) { 0 `6 }2 \* g8 _6 c3 \
jedis.auth(password); 4 k) E3 x% z1 _4 R8 d* r' {6 L
} % J- p5 @; A% l& t
ScanParams scanParams = new ScanParams.count(100); : `1 i9 [! |  _$ W
String cursor = "0"; ' M% b" U* c- n( Q$ O2 A
do {
+ @" N' w$ a+ A/ ~ ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); % R  a( l5 ]2 [7 Z& A+ b1 w; [
ListtupleList = scanResult.getResult;
5 P0 ^( t( Y0 q$ z9 G# C if (tupleList != null && !tupleList.isEmpty) { 5 a( `* u  @/ f  Z* v+ R
for (Tuple tuple : tupleList) { # o9 M$ E$ \. e1 O4 X8 K& X0 \
jedis.zrem(bigZsetKey, tuple.getElement); 2 R  Q" K4 m* z! I
}
% H9 d+ \/ \7 G) z } 6 q- X/ v4 i  J, T( o, w# K
cursor = scanResult.getStringCursor; % |6 z8 W9 x& r  f+ Z
} while (!"0".equals(cursor));
# W; u# Q0 ?; ]  D5 Y. V3 i& J6 ~3 ^
//删除bigkey   `: p, q# E- h
jedis.del(bigZsetKey); # t) d$ ~! q- y( i& _7 [  y
} 公众号内回复“1”带你进粉丝群
5 k( \! J5 Z* M% w# {8 }来源:http://www.yidianzixun.com/article/0LevQm7t
% S) q: j, W% x! q) ]免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

×

帖子地址: 

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-3-3 01:52 , Processed in 0.053362 second(s), 24 queries .

Powered by Mxzdjyxk! X3.5

© 2001-2026 Discuz! Team.

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