|
|

( U& g' B% }/ N, ~9 E- l本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
% j4 m& `9 p: R7 ?& M, p7 x, C3 W ~& N7 H2 M- y( y
- 键值设计5 Y _1 D- g" ^; w
- 命令使用5 v- `- Q1 D' G4 q* d% n+ T2 y' J
- 客户端使用! W) l& y& \* z1 {9 S
- 相关工具' t$ U9 N2 U0 k- j
通过本文的介绍可以减少使用Redis过程带来的问题。& i8 J- ^1 C8 g1 k
一、键值设计/ R" j m# ~3 P$ L5 i+ O5 Z# j
$ B7 c% Y6 l! I3 {/ `1、key名设计8 g! U( [5 q* H. i
' m0 K+ V# b9 m) f/ a- \可读性和可管理性; C; N g4 ~9 Y1 z
1 u- V7 r4 }1 A
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
9 f9 O6 {1 ?8 m
: g3 \5 c3 u$ |# V9 G- ugc:video:1$ ?' A% N9 p2 b, O% o) U( C
简洁性; G$ r9 i3 n9 c# E
. @( j8 A. V' @; g" _/ V2 Q$ {保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:/ `% S: O) m1 j# K" e0 w* m% ^
% ^; u+ U- R; @+ k
- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
& H; L p2 ]1 ^/ v! m: p 不要包含特殊字符" @: p6 i5 S5 p* U8 f/ W6 ?3 m0 d
3 D: ~5 G5 s) l, \5 x反例:包含空格、换行、单双引号以及其他转义字符
2 Y: w. R- m$ {. W$ v5 U' A2、value设计
. [& C$ B0 ]# r, |- O A5 K3 V- h. ]1 [8 Q0 k
拒绝bigkey
, a3 R7 E) j8 n( k" @: B: v3 h0 `. k4 p
+ v g8 d9 Z7 u# a9 j) u0 `4 A防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。/ d# @) k) o h9 o ^& d* k
反例:一个包含200万个元素的list。
7 a8 e9 U9 c! [8 T非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
Z' M; B( b+ l; D' E$ g选择适合的数据类型, W6 F; u; }5 _1 j% w, v4 U
3 |2 A; {4 ~5 d( b" x% G. ?例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
- }1 u: v. a0 |# |) L' @, r4 X1 i反例:' C9 q8 p; P; I3 I2 D
5 W+ \4 Z% E/ M1 @+ i7 d- set user:1:name tom$ J7 }. J! {# k @2 c5 h4 g
- set user:1:age 19
9 y- ]0 e! d/ C; z6 o. ^ - set user:1:favor football
2 U. ?% \& G+ X- n4 D- f: \ 正例:8 T' S% b# d! `7 b+ G3 L. T
0 K, q4 @6 I: b! z
- hmset user:1 name tom age 19 favor football
; C/ Y8 U& i4 s" T 控制key的生命周期% f9 y/ z! {! N3 v ^. H- m
/ Q. c7 a3 j% \& n3 p& s0 W
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
8 b% f k! B- C' _: y& ]二、命令使用/ R0 Y: a5 ^4 w; J/ o/ {
, R" h6 U' @* E$ Y* L& v" Q1、O(N)命令关注N的数量
/ r, S% R" C, E
. C! Z" {7 R4 R3 j7 B例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
0 _4 h! }/ ]: C. e2 g6 R2、禁用命令
& t0 r& N' ?7 d, f4 `$ D& c4 r2 T" m# P( k& {; y2 P
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。7 G% k7 ]' L2 w
3、合理使用select
8 U. D& g- J+ }; E" ]9 W8 c
$ @: g4 o6 E4 M/ e7 ?- z
3 w" F P% O* uredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。% {2 K8 K- ~# Y
4、使用批量操作提高效率
7 o5 U& O4 L" n& ]) M( f4 O
1 k1 v/ A$ } U$ U+ g# v* ~: k+ d7 Q& D
- 原生命令:例如mget、mset。
2 U! p8 ~5 g+ {8 U! \+ Q2 Z) t5 Y3 h - 非原生命令:可以使用pipeline提高效率。( F1 u6 w1 t0 q
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。" ?# T/ Z& \! G( j s0 t- n$ m
注意两者不同:
, `$ t6 ?" y# n. ]( u" l2 I2 @. }4 m% a4 \/ i
- 原生是原子操作,pipeline是非原子操作。1 m8 f6 g- i; M% A9 g# P
- pipeline可以打包不同的命令,原生做不到
8 u6 i1 p, j) k$ q4 }" |% ?; D - pipeline需要客户端和服务端同时支持。
- Q. v& b" t2 M. ~8 y' x P 5、不建议过多使用Redis事务功能
' \! `* m# H2 W* U* B
1 X' c o% F# f6 t& j QRedis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
+ h% {* W+ S' B+ w8 h6、Redis集群版本在使用Lua上有特殊要求
1 ]4 [* Q* \8 K5 {# _* `/ z2 [, Z' k0 y& j
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"
3 v6 k8 B3 ?) m7、monitor命令
! J; S, S0 ^1 h% Q. Z- K: l( C5 O6 [
必要情况下使用monitor命令时,要注意不要长时间使用。
* o7 R6 L J/ H1 ^( K三、客户端使用
9 [" D. ] ]+ z( W' b4 X
, O; d9 e4 ^ s% R/ Z0 @ ]1、避免多个应用使用一个Redis实例7 e: ^: t) b# j0 ?2 e
, U- c0 Z! D4 {8 J不相干的业务拆分,公共数据做服务化。
9 K; n! O- N- ?- h" k2、使用连接池
" Q: Z. n% v8 C8 }( v
* B* p/ Z) \' h! F可以有效控制连接,同时提高效率,标准使用方式:9 t" h) l; i$ M/ [8 p
Jedis jedis = null;
: @7 n& ?2 E* I ztry {
: y1 F) P- b, z2 {7 ^- q jedis = jedisPool.getResource;
% J8 e+ q0 T: ] //具体的命令5 ^ T* f+ B. g8 ]4 R" J; ^
jedis.executeCommand
; [& F( V, w2 c# {1 F5 |} catch (Exception e) {1 n b( D: ~" `. b0 C
logger.error("op key {} error: " + e.getMessage, key, e);) H% I; S3 N# a- L" k! L; G- x& p
} finally {
h& _3 a7 U1 f0 K //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
: C2 W+ O- u8 h3 ]% y9 Z% u# c if (jedis != null)
. G+ o& }* J$ h% m, w* j: J jedis.close;
- h) z8 G/ G" N+ s9 L$ ~}
7 Q. [- d' N+ z( W3 {* u8 t$ p- H3、熔断功能
: x8 b; Y# I/ B1 w6 O( g' Y% k0 B. g; K$ G& H
高并发下建议客户端添加熔断功能(例如netflix hystrix), U" e4 ^# l N# p* R* a" a
4、合理的加密
+ B. w$ H& l' S8 e( H5 r I+ R& p }& S9 }. b, |0 D/ Q9 g# H* R5 G
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
8 V5 b9 x9 ~1 l$ I, T5、淘汰策略- A, G9 F3 c% }1 }' |
# o" p! D& ^1 ]8 ]% Y" n根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。0 E1 s" E* w# G9 l( ]6 p
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
) D' {$ s7 ?9 @7 m4 l其他策略如下:
+ d# v4 s, |0 M$ P# r9 W7 \; n% ?) Z: s+ D5 }
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。" U8 o+ P K7 a) v. a( z, U
- allkeys-random:随机删除所有键,直到腾出足够空间为止。; i( [4 Y! n2 a7 [, S! C
- volatile-random:随机删除过期键,直到腾出足够空间为止。
5 ]% l# q: w5 S2 Y5 h W - volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
. x8 T4 h# X' Z$ @ V - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
7 M8 R+ Z L6 D* Q 四、相关工具& H) B/ Y7 K$ R% F5 y' U1 d
- E; j/ E/ X7 k) G4 ^
1、数据同步
# P: i. v# Q) ^; n/ v& Y. F4 ~
6 ~1 O4 w3 O; Credis间数据同步可以使用:redis-port2 T7 R7 q0 `- ^
2、big key搜索
6 Y5 d' i: ? U; ^. O: e# A! [2 \% x' l5 K! l
redis大key搜索工具
- t# c- e- c" g6 G9 G3、热点key寻找
" ~! m# S" Z% g& A J
' {% G' i* q% h3 o内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
, Y1 e8 P' O, X4 F. @, o2 H2 [( \五、删除bigkey- v# b% {7 U+ F% r: U9 y
9 g7 N, s f, Q& _2 f$ U( w: _! M" [8 \, y$ t$ |4 t0 x
- 下面操作可以使用pipeline加速。2 f. `* V/ d- i" z3 u5 E" h2 j
- redis 4.0已经支持key的异步删除,欢迎使用。) z/ F9 i4 H% Z/ ?( E
1、Hash删除: hscan + hdel9 n+ a6 u: A* K, O# M
* Z: v* W' R9 M$ t: Ipublic void delBigHash(String host, int port, String password, String bigHashKey) {" _' ?$ h0 h6 g& n" C% o
Jedis jedis = new Jedis(host, port);
3 f% }* W8 z! e* p4 L1 m4 q1 K if (password != null && !"".equals(password)) {
2 R6 M; T1 \+ `2 g9 k+ a( ] jedis.auth(password);
$ v5 K3 i; F$ B7 H }, U" z! X }/ b1 l5 {% V3 }$ }
ScanParams scanParams = new ScanParams.count(100);
; J) e& }, k. w+ M String cursor = "0";
# R b' `; g& M do {
$ t/ q. ^( L' I' V" w' H ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);0 d2 v$ M8 Q$ {" v2 j0 S+ X4 `
List entryList = scanResult.getResult;4 n( F3 ?2 W5 O5 K( V
if (entryList != null && !entryList.isEmpty) {4 H4 F1 j0 Y; R& I
for (Entry entry : entryList) {
% U. `" |2 ^( o% _ jedis.hdel(bigHashKey, entry.getKey);" f5 I# G) g* D2 F
}
7 X1 J* s3 {8 `* j3 ?( s }
+ g' T( Y& D$ V! e( ^ cursor = scanResult.getStringCursor;
) @+ s, b b7 c" d } while (!"0".equals(cursor));1 _% m) O0 l( a+ ~; Q
5 } B/ M% O' w/ C: E9 I
//删除bigkey
. z/ f0 i, U; t( v, L jedis.del(bigHashKey);, f9 D0 g# S6 m. X. F8 N
} y' v* `# `% ~4 s
2、List删除: ltrim
& M6 b6 Y" z7 A& R4 [
+ ^$ g- n4 {4 I* X4 r7 I$ ^7 z; C5 spublic void delBigList(String host, int port, String password, String bigListKey) {5 w' N& L9 P6 n! x7 [
Jedis jedis = new Jedis(host, port);
- ]! U5 a9 o/ c* x2 P6 Z0 s if (password != null && !"".equals(password)) {
& E4 l) K" N+ [8 } jedis.auth(password);. @, x& z$ [ t4 G9 H- d0 g
}
. k" d- s k) b S4 ~) k& e: U long llen = jedis.llen(bigListKey); y1 Q; p( E: g3 a0 b
int counter = 0;
6 I/ M, L5 u; d) H- d' m0 y, B+ G int left = 100;0 V8 N9 |: p! h V) Z2 ~4 J
while (counter < llen) {0 u- b" J2 W- \: c% N$ q
//每次从左侧截掉100个2 y# n# t& c8 ~7 C6 u. |
jedis.ltrim(bigListKey, left, llen);
- B$ n! x1 P7 [ counter += left;
9 i7 |/ M! a( M7 e5 E }* J, k" v- H4 R: u0 U
//最终删除key$ i5 f' ` Y& B0 J2 z
jedis.del(bigListKey);7 U7 ~6 F+ S8 s& t
}3、Set删除: sscan + srem1 w1 X+ a' B9 R* I2 n8 d
- U: X( n, W# g7 ~! z
public void delBigSet(String host, int port, String password, String bigSetKey) {. g) h c& ?" O9 k7 r+ \& j) K4 C( K1 ]
Jedis jedis = new Jedis(host, port);) I/ w0 s6 }) k# y. z! [
if (password != null && !"".equals(password)) {* B( q4 |: ?. I, C5 }6 Q' W
jedis.auth(password);
) R) p: @4 f0 F( H: N }
' _9 x% j0 q) f ]" X. l8 u ScanParams scanParams = new ScanParams.count(100);
8 |+ S- W: i3 Y% @! R8 N& F! r String cursor = "0";
. t% p( s1 K6 W6 P8 D# l0 R" \ do {
! p, b9 _2 j# P ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
/ i7 h1 L7 D/ H" B8 b* @9 _! g8 h. k List memberList = scanResult.getResult;; q& v! F: \% m E9 H2 @9 L- H
if (memberList != null && !memberList.isEmpty) {2 X( |( L& H- n
for (String member : memberList) {
/ b8 ?% a# N' }4 Y6 G jedis.srem(bigSetKey, member);
1 h$ ]7 I& I/ \1 C }
7 U$ L+ F' _: U, c2 T# B }8 \: i" s& s, f; }# r: T
cursor = scanResult.getStringCursor;- F) M6 P9 W. l/ z% t
} while (!"0".equals(cursor));
$ J5 k3 ~, R8 f* `4 X" D, w( S9 A) l) G2 j' |' v
//删除bigkey' o0 H5 K) p: H- i
jedis.del(bigSetKey);
4 Q$ @9 {6 e: i* }( ^' C}- v3 t m; Q2 V Z9 \; G. F
4、SortedSet删除: zscan + zrem/ V+ t9 r( ?$ p" U
+ j% s1 u! m4 N! Upublic void delBigZset(String host, int port, String password, String bigZsetKey) { V8 t9 S: @# H3 j+ s& O% I
Jedis jedis = new Jedis(host, port);
, ]. l |2 C- A8 M* v if (password != null && !"".equals(password)) {
# q4 j/ k1 c! e! l, B5 y- z) {& i jedis.auth(password);
; F0 g6 b, G( {4 f) @1 G } : c2 B3 R" A0 t7 i0 K
ScanParams scanParams = new ScanParams.count(100); / \* t/ f5 e) `" E+ l* v
String cursor = "0";
) T8 M" r% q% F! ~ do { 7 F) G# Q+ v5 `2 \9 O
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
4 a: E! ]; T; R4 H7 a8 v ListtupleList = scanResult.getResult;
& E' Q: I( ?% x. c if (tupleList != null && !tupleList.isEmpty) { 5 e( q9 I9 l5 v8 w ?5 d _% D8 z9 P9 X
for (Tuple tuple : tupleList) { , K/ z# k) H. t) E
jedis.zrem(bigZsetKey, tuple.getElement);
# l2 C2 l. q3 _5 c }
! ?' B7 j e6 L' J }
9 m p% d6 s, Z. l3 Y0 {; K4 j: m cursor = scanResult.getStringCursor; 1 |8 F) g8 q; ^( D+ k
} while (!"0".equals(cursor));9 [9 Z. K# Z# ^9 ]6 U
. _" O* @" c" F- Q# L+ [ N
//删除bigkey S0 v$ S" p6 h- a3 u
jedis.del(bigZsetKey);
2 {5 V+ W* e+ H D- y" A} 公众号内回复“1”带你进粉丝群
4 \. J# G! h5 } r- a2 @来源:http://www.yidianzixun.com/article/0LevQm7t+ i0 o- d. y5 Z* h' A
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|