|
|
+ o" V% r2 ^0 g0 k8 m5 N
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
* s, x, x% K7 D- K2 h* K- i
5 b& G) I ` W' N; g: z0 q/ \7 @% f, @- 键值设计7 W! F" z" d1 a: j
- 命令使用
( Q8 ]% }5 c% O; P* `, Y5 R - 客户端使用
* ?: A, Q0 r2 t' }9 F - 相关工具
& m7 K; j3 y6 G' d, ~! B 通过本文的介绍可以减少使用Redis过程带来的问题。: k# K R3 ^7 f5 u; U, R4 \1 K
一、键值设计! H8 e4 V4 u& f8 F% ]0 x" g* M
" o/ s1 e$ j3 p
1、key名设计
: c# R; r. y e2 ?: d) B7 _
/ U5 e. e# e* O0 w" U5 z! {可读性和可管理性
+ \* g7 Y- J1 `1 u8 C" y% r& }. v
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id9 c7 `# g8 p% H
9 I8 s- E* M# Q# I; [- ugc:video:16 o5 O- W5 h I6 a
简洁性8 `7 q" x7 ?+ W& C* O* G% D( J
6 j' y$ ]4 N. A \" t7 B6 t. H
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:& _7 ?& q% N* D
% H$ K+ U+ E" y3 A0 ~) g2 g( `- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
0 [- ?% p- r1 w/ n) A 不要包含特殊字符, E' d& W3 X& y# ?
1 G( |" D8 L9 ^6 l/ P" V反例:包含空格、换行、单双引号以及其他转义字符0 k) J b9 ~% _. w# Z
2、value设计
% L& X3 V( v& w5 u% W$ S
1 W% h6 O! u+ Q8 r拒绝bigkey( G1 z M8 ]5 c1 `# C
4 P6 P# U0 N' u9 W/ |, M
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。8 Z% E8 y3 E9 }. `% o
反例:一个包含200万个元素的list。
2 q% g) v2 U& }; W! I3 E非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
( V8 J4 `) z/ A! Q0 K% ^( l选择适合的数据类型
. g% I0 q$ R1 D1 T# t, y
$ V B1 ?1 u2 O% E7 c$ D例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?' b6 B; e$ R; X+ G/ \* ~7 Y
反例:1 y2 p. A. y% t: _1 l; n
) _6 c; O9 \% x9 x1 W
- set user:1:name tom
. N, M2 W Z3 n: U [ - set user:1:age 19
! k' N- F, y u( j" L, K& }8 S5 t - set user:1:favor football% [% d K" Z4 @" `# }" j
正例:
p3 R1 d. }- X7 _ L! q* U ^ i+ k/ n4 @9 Z
- hmset user:1 name tom age 19 favor football
; w' B3 X& S* G- b% J0 n 控制key的生命周期" K. c; o) \& n& `7 F- _
! N! D C- I, X* I. `/ ^0 c# fredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。6 Y& w+ M, e/ e6 n* |" b, [
二、命令使用
o! p4 w, G. ~* o9 T" i& S; X7 Y+ y7 p6 [( u) L, p
1、O(N)命令关注N的数量
* u* \% P- }3 u, z* R0 f+ x' J" k2 w
, s* S: z6 S% m3 r, ?" I) m5 d- V, S例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。8 h" S) b$ X) S6 l9 B
2、禁用命令6 M* e' U# H; g, ^) @
5 }4 i8 c2 ~& L- ?9 i0 x3 `禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
1 H' D. ^3 s2 y4 n9 L8 Y4 ^3 B3、合理使用select
' h: h0 h& X2 u% U2 ^* v M0 b' }: }, q, P6 k, e! s! k
6 s5 r8 D5 k; }( w# Qredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
% v& G7 ]: E7 ?4、使用批量操作提高效率( G8 m, O) D2 H* E
9 j' d7 w6 F2 I2 i9 ?6 `
, \: \3 O% ~- A# U- n& ~0 V' B. z- 原生命令:例如mget、mset。' \, b9 N$ g. F
- 非原生命令:可以使用pipeline提高效率。
' R' X& X( d0 i; \) V0 o 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。. h( D+ r* r' n. ~
注意两者不同:- h- \1 b( V3 `: D
" W# `- L- ]! t5 m
- 原生是原子操作,pipeline是非原子操作。
7 q4 j* S& Q8 k2 _ - pipeline可以打包不同的命令,原生做不到
" f0 I/ |# l& J - pipeline需要客户端和服务端同时支持。
2 B# h S& h9 u) g7 M 5、不建议过多使用Redis事务功能4 w) _( X6 J5 F, h& w
& ` `& p2 ]$ G/ q, H( N
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!* R/ x! ^: e: ]. Q5 k; X0 u
6、Redis集群版本在使用Lua上有特殊要求5 [4 b& p+ n7 I* s
7 h: T, o/ D w3 B
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"
0 e" E' e/ D6 L) {7、monitor命令$ O1 d+ |. ~6 L4 Y( s+ M+ o* s
( u1 A# V; `1 d" O) U必要情况下使用monitor命令时,要注意不要长时间使用。
V2 Q: T! E5 |9 [. a1 |: y/ I2 [三、客户端使用- E. U- j* l0 Z4 o% @- l
4 u# ]# I' z. x! ^" K2 H
1、避免多个应用使用一个Redis实例
- ?) p% D' O) I' @/ E
& V! E% ~; [ [( p/ N5 ]不相干的业务拆分,公共数据做服务化。% }5 L5 F M( F
2、使用连接池
$ o% b' K; ^4 {( e* L, d0 W' N* b5 I& y. l# o
可以有效控制连接,同时提高效率,标准使用方式:
- M, Q- j6 e& t2 k" h' P B. yJedis jedis = null;2 v. `2 x! e9 D. {% I$ N
try {, [9 X6 ^# T ~; o/ c
jedis = jedisPool.getResource;
* `/ z4 A& G4 j3 @ //具体的命令
7 B# s: e; Q% u jedis.executeCommand
# \3 q. S! x h" v} catch (Exception e) {+ t! j' S2 A* v, ~7 f( G
logger.error("op key {} error: " + e.getMessage, key, e);
; | e0 y$ A: S) P9 H} finally {( s+ U2 Q+ V7 {% h5 f! y, l
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
: @; J; i$ V/ }7 } if (jedis != null)
0 [* C& I% e' S8 w+ c; h- N jedis.close;1 g4 j! ^. l! C( Y) B* x3 f
}2 e9 d0 F. I* ]5 G9 v2 H0 Z
3、熔断功能
% [5 Z' ^6 U2 K4 B) ?; Q4 p/ e+ o; O6 C% C) d$ X2 D. w
高并发下建议客户端添加熔断功能(例如netflix hystrix)
0 o- B, p" ]5 p9 x8 \) u4、合理的加密
; {+ ^ ]. t% _' s' Y; H- k/ {& v. Z: A4 D" ~
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
* J k* k4 g, n5、淘汰策略
( d6 D) C& K/ P `/ R+ H$ [. ]2 v1 p/ ^( S0 A
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。+ k4 z' Y/ x: O$ C
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。: {7 m1 [# M- d, i9 d4 J
其他策略如下:5 e e' q4 Y# v) x, `! b
' B: {4 ]; s5 X, H- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。7 C% T" h7 h/ o
- allkeys-random:随机删除所有键,直到腾出足够空间为止。8 r/ y* T0 a8 u$ y
- volatile-random:随机删除过期键,直到腾出足够空间为止。6 X' ^% k4 e8 \1 d8 _
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
) R2 l- @& y. F- S& O7 R5 d( B - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
! y9 N/ n' R2 a9 M+ t* R 四、相关工具
8 M/ I& N. f( K' M. v+ L" U
- [; E$ g7 [% N: _8 D; j) ?) J1、数据同步
6 X% I! Q j- I( F& M& e0 |" A6 a+ T6 k- C+ Y( D, _
redis间数据同步可以使用:redis-port; Z3 s% W4 p$ `, }5 G0 h/ }
2、big key搜索
, g# G2 N- Y p5 i. g0 M' g. t) T+ J: M* U( K7 S7 J0 P
redis大key搜索工具4 K& C9 [ j& ]/ Q, \- [
3、热点key寻找
2 `: i' S# O9 a4 c9 C: A) r( A. u- a. o! m5 ?0 |# G5 L5 j2 D
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题9 z* _/ G1 z3 j9 `+ j f. T* Z
五、删除bigkey2 D0 v- z) G, P* _7 I9 y5 S Z' L
0 b! d# r, o. Z. B3 N3 E
+ [) I0 }+ T# }( E- 下面操作可以使用pipeline加速。: Z6 n, p, \& _, |
- redis 4.0已经支持key的异步删除,欢迎使用。6 A5 }# i# W$ Q
1、Hash删除: hscan + hdel }+ E8 z$ z, X' v6 m
J8 } z7 H! B' \; o9 n9 t
public void delBigHash(String host, int port, String password, String bigHashKey) {: }" z' t' \4 X: E
Jedis jedis = new Jedis(host, port);
( r" T! { v+ g* R if (password != null && !"".equals(password)) {
+ U( j7 x5 K0 A) q7 S& M jedis.auth(password);
0 r% ^/ ~$ g, m8 q) G }. x; W$ m$ O- h A4 V S1 n
ScanParams scanParams = new ScanParams.count(100);
" d; ]$ o* X) N, ^5 |9 ^ String cursor = "0";
0 i- |9 \! a" k) I+ V0 t( r do {
" b9 D b# L* v7 T4 Z ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);% c( p/ _2 x9 F" E+ Y$ v
List entryList = scanResult.getResult;' J; {+ {7 g# m8 f
if (entryList != null && !entryList.isEmpty) {
7 C2 f. \# I# A4 X/ ? for (Entry entry : entryList) {" ]) a# c9 ]! ^" Y7 b
jedis.hdel(bigHashKey, entry.getKey);& q2 F0 p }! h' ?9 d
}' Q! v4 f1 R0 d. D% ]+ R3 g7 t
}2 T& L- c G# _* J
cursor = scanResult.getStringCursor;
% R6 s6 O* t$ k6 y, x } while (!"0".equals(cursor));
- }. u: b! W5 {; C9 u! r: `8 i8 _, x M/ |/ G0 v6 U c# a! d
//删除bigkey
: m3 A8 k. W0 ^( {+ ^* @ jedis.del(bigHashKey);
# l4 c$ C+ i! N& Y( _4 z}/ k3 B% ]' z4 u- l6 x$ ?, z3 B
2、List删除: ltrim
- {$ Z5 v1 n& C& H
/ S2 B' t6 ]2 l3 |0 C6 ^! @% W+ l" cpublic void delBigList(String host, int port, String password, String bigListKey) {( ?! H! i$ H5 p; ^, N+ ~- C& ~
Jedis jedis = new Jedis(host, port);9 y( j0 a6 u* ^5 t1 u/ O; w
if (password != null && !"".equals(password)) {/ O5 q0 p1 g: [# v
jedis.auth(password);; N* D, t6 z# A! s# D& j6 N) `
}
2 B8 g4 J4 {" U% M- r long llen = jedis.llen(bigListKey);1 g9 ]) O! g8 S$ \/ c6 \; M
int counter = 0;* p* {1 _- l& z, }! Q
int left = 100;4 O. ]. n; e2 y: P' V$ Q- J
while (counter < llen) {6 c" [/ T( x& f- m# {
//每次从左侧截掉100个
1 s) q+ \; S# \, h1 J7 | jedis.ltrim(bigListKey, left, llen);
& z& F! ^% u$ } j9 p) l counter += left;
* w) H6 I* k; N l3 J5 @ }0 L8 G' _+ {" _
//最终删除key
% ^" B; v4 z9 [) [ jedis.del(bigListKey);' D$ F4 j C: d! x
}3、Set删除: sscan + srem
; w5 {: r) }6 _* o! _9 Y; L. j1 P8 `- p& c3 S2 y5 Z
public void delBigSet(String host, int port, String password, String bigSetKey) {
8 I/ [* U8 U) }1 o4 H2 j Jedis jedis = new Jedis(host, port);
; C5 r4 V! w: [. O2 _) \0 S if (password != null && !"".equals(password)) {
; F0 q; x/ N$ X% ^9 U jedis.auth(password);% c b0 t) Z: C! ^& u# ?
}& |) c. D/ a) H7 h7 z
ScanParams scanParams = new ScanParams.count(100);" E' x3 L0 g7 l0 l8 S
String cursor = "0"; [$ c, @1 ^* p3 y u R3 q
do {
% }2 u4 s* C. M4 P2 }( o$ C ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);1 g6 ^* j) x& E- R: S5 D# e- g
List memberList = scanResult.getResult;& @: @9 Q; F9 B4 v! F W
if (memberList != null && !memberList.isEmpty) {8 t9 F) q. l! Z; K) d$ D) N
for (String member : memberList) {5 H5 \ j: B0 x1 m' F* D; R5 t
jedis.srem(bigSetKey, member);2 G4 i/ n0 n/ ~
}, g# z( t& |2 s- E# K6 t: F& x
}5 T' U( v. n9 q9 _1 b
cursor = scanResult.getStringCursor;) N$ Q& ^. D- D- Y
} while (!"0".equals(cursor));
& |/ L" N4 }* B1 C. l0 y9 k
9 [- x; m8 j; W5 y+ P- K! G( x% H6 N- F: W//删除bigkey
2 t. z' K: x' L/ \& [0 o jedis.del(bigSetKey);9 P: \- n* z- Q/ R: i! g2 A6 Z
}
8 E0 S! t6 k0 \3 U4、SortedSet删除: zscan + zrem
# r" O& ]1 p7 m4 ^8 w& l& h& D6 g4 j' p' m
public void delBigZset(String host, int port, String password, String bigZsetKey) {
7 B5 e0 d9 r% |" ^ Jedis jedis = new Jedis(host, port);
$ ~# k8 c3 Z2 E8 o8 I% Z/ _ if (password != null && !"".equals(password)) { l! W6 Y0 S. w# V$ f1 x$ |
jedis.auth(password);
# W @, c T- V; o+ p! ~4 F5 Y1 A } 2 z" I; ]1 h& N* \% S! m) S4 k
ScanParams scanParams = new ScanParams.count(100); ; j" L# O# d5 F
String cursor = "0";
: A: h4 D$ q4 Q# e S- P- P1 d do {
3 v' g7 C& [$ @; U ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
( I/ {& F M1 A' J( ~ ListtupleList = scanResult.getResult; ) _; n2 ~ `/ M3 F* ]" S/ f- ?% A
if (tupleList != null && !tupleList.isEmpty) {
! T3 Z4 z1 T5 `+ w6 S W for (Tuple tuple : tupleList) {
! D( Y$ ~5 o4 n" N1 v jedis.zrem(bigZsetKey, tuple.getElement);
1 d4 W) _) T6 {; ` }
V; u% q5 d; I P" c0 G }
' }! t9 e7 D& z' w cursor = scanResult.getStringCursor;
5 S' m5 N3 A0 a! M( I" } } while (!"0".equals(cursor));9 F3 b) \1 j# o6 u
/ b3 K- Z! C' O' G
//删除bigkey / r! q; k% c* Z7 C! W5 c7 s5 a
jedis.del(bigZsetKey); & l# r* x# b& u$ A
} 公众号内回复“1”带你进粉丝群
" h. z/ h2 M, ~; ]6 [来源:http://www.yidianzixun.com/article/0LevQm7t
% g- @- [* H" ]: Z9 L& Z免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|