|
|

# Z# _8 g, k9 |& R本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
' D5 u' G& A/ g6 S$ a6 @( @8 J
$ U, J5 {+ u$ [' l- Y8 o- 键值设计. X6 h! [2 g; l5 ?) w
- 命令使用
: N/ i$ Y9 I4 q% `& Y - 客户端使用
& D3 B) r$ |! c6 m2 L, W x2 Q - 相关工具4 m6 H i* K. T: q$ L% n. n' h6 _
通过本文的介绍可以减少使用Redis过程带来的问题。$ F7 M' N4 c$ L- w1 O
一、键值设计
8 @9 I: K) d9 B6 |' D% W% | Y4 ~; H w4 T6 T1 ~1 E
1、key名设计5 T3 D5 ?/ N! Y. C/ d( ?
( H8 d& r: O1 ?' W& Y( B' G3 I8 [可读性和可管理性 E" V9 V7 D8 h% |) h7 f( ?
2 n) k2 m* Z$ ?5 }- r( I; Z以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id+ p" F; ^0 s1 A7 N2 K
; @1 {( ~- e' T+ J( x- P! R5 w
- ugc:video:1; _2 S2 m5 @! f* g) l% b
简洁性
2 G' j; w6 y! X }& ?& r* b
7 m7 X, @% J; X2 y$ {' ~4 _2 z4 o保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
4 Q- N3 _1 h! Y$ {
3 T# k7 ]! V- d9 G( l% z/ M- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。 M$ S+ H/ Q8 E8 H- a/ I7 i
不要包含特殊字符
% b5 H% g7 d# o* D g" O$ i
. H* C5 G8 t. G' G/ ]' u. t反例:包含空格、换行、单双引号以及其他转义字符
+ ~2 ~1 }' J+ d+ ~; O* i, C2、value设计) _1 l6 {" c7 d) o1 \
* g* x" s$ U ^1 E/ r
拒绝bigkey ]. G0 u6 \- ~2 Y* p
! |. a9 B4 H+ W" h. q6 r3 X0 q
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
7 v: l& |9 i3 g, [( y反例:一个包含200万个元素的list。
6 y' W- }( }$ L* h非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法2 M6 }9 \" ?3 b) l
选择适合的数据类型' [1 a9 M' k# V- n7 C: n3 t5 @
1 `" R, C: H0 Q _5 }$ Q' o
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
7 m0 F/ r1 W2 }9 A8 S反例:* C! H9 D; ?, k
3 G/ H9 x; H2 V- set user:1:name tom
/ Y* m! e; L6 S* y) C - set user:1:age 190 G! Y6 D' N z+ ^) v: _8 k
- set user:1:favor football
; G+ A! \6 |) e+ H- Z9 h0 R$ { 正例:8 x; c- Z# ?$ m+ k4 ]0 l
7 f2 c9 E+ I7 k- I% K: C- hmset user:1 name tom age 19 favor football0 ^1 {. t0 S9 y, W+ j
控制key的生命周期
# ^4 S0 n7 [+ t: }# p
2 {9 A9 A7 P6 s1 |4 L4 p2 z0 R0 Iredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。( H# N6 h5 Q9 |" {# P+ o) B
二、命令使用
8 B7 R: ?4 j0 _3 \ u9 S9 d; `' d8 T" D& M9 ]* Z# ~" H2 g0 ~- m" Y
1、O(N)命令关注N的数量
: J8 w) b- h# o: m8 M* d H2 d
: k3 z: o0 \1 J* X例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。% P/ ?6 P1 I# R
2、禁用命令1 J8 W/ _& R4 m5 q. S( [
. ?. T( m6 e1 r7 o$ q1 e6 t
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
/ G) [, n$ Z- C' `3、合理使用select
4 j1 o, r! q0 M. R+ k) N8 S( w" I" l/ K% z
& s9 t9 Y9 I* `. i7 p+ i& Oredis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。) D- ^1 v- i- L& f/ |
4、使用批量操作提高效率4 u4 z* {* g) Q% B( [% Q) W2 D3 v
; Y, l. U9 g1 V) f
: k8 _% X3 h2 W- g1 ?5 f& u1 f- 原生命令:例如mget、mset。3 k5 S3 Q8 d* V7 ~* n' {4 d+ g
- 非原生命令:可以使用pipeline提高效率。
8 |: @* g" l3 W8 H. p- U 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。! z$ G/ X z( y0 @% [$ F2 w& n- U" M
注意两者不同:9 r* S: P+ a r6 f
5 m+ }/ F+ [7 {' \ ~1 _- 原生是原子操作,pipeline是非原子操作。4 `" l$ w3 Q2 v: v0 c! X0 w1 m
- pipeline可以打包不同的命令,原生做不到: @8 a3 d3 M b' A4 |$ Z
- pipeline需要客户端和服务端同时支持。- b$ r9 t) z# U, x- S6 f
5、不建议过多使用Redis事务功能
$ d* K2 k* H1 g# P* M" m8 J; G7 M+ h+ S
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!& J9 J+ ]8 |' ~. ^
6、Redis集群版本在使用Lua上有特殊要求1 E! f: p8 m3 w7 k$ p j$ l
2 n1 K* l" }# N# L2 T+ J! |& k5 C1、所有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"
+ X6 |$ @0 R6 ?$ F6 M2 v7、monitor命令
) f) o4 q4 y8 L( \4 {. V# [# X% K" ~' g- ]+ d2 }* A. P% e
必要情况下使用monitor命令时,要注意不要长时间使用。
8 Z4 w' `9 O$ c9 H3 ]三、客户端使用* f; W+ Y0 x( A2 P+ w+ {
# p; n& p2 h. {9 [+ v6 I
1、避免多个应用使用一个Redis实例8 m* E' N, l3 ?" y. r+ F9 y. H; j
: R1 m; a4 d* V" B) u5 f& V! o; f不相干的业务拆分,公共数据做服务化。, u6 }" j0 ^" u3 s( Z
2、使用连接池
9 v$ c1 J; w9 u6 |
! C! K: q( `1 N4 ~3 v/ E可以有效控制连接,同时提高效率,标准使用方式:" o6 h4 q" [/ n; ]5 l/ T
Jedis jedis = null;
& {" J% E& E3 R/ E8 j* \: R# X% Stry {
+ t: [/ n$ ^. U' n3 n1 w. i jedis = jedisPool.getResource;/ U0 d' g! z; W8 Z8 Z! }# d8 y
//具体的命令, V Q' p+ T$ k& T) N5 @& K
jedis.executeCommand
1 \; O) W7 S" [3 ^" F& T' c} catch (Exception e) {2 c; ~ Y: u' N* f
logger.error("op key {} error: " + e.getMessage, key, e);
) X* u# n! h5 | z! O" y4 K; H} finally {8 O! H# L- o% A! a0 n* B% V$ N
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
! ?' s1 ?. U% X, S$ W5 W if (jedis != null) 9 j- \0 r0 A$ E! _: S4 J
jedis.close; w5 y+ a/ |* i: d1 `# i+ S
}! B3 ?9 J) p; e! l9 r
3、熔断功能; y# B7 G o- k' K$ d
$ C7 u' G( S3 \3 f9 T3 @高并发下建议客户端添加熔断功能(例如netflix hystrix)
1 z1 e7 X$ b* P) g/ r4、合理的加密2 I8 |3 K- {9 Z7 k3 E
9 Z$ w7 @+ z. e5 ^: W
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
) D: X0 ~4 D0 z8 e5、淘汰策略/ ~. L5 K# h" U
3 E2 f1 u& @; {$ j) @" j, e9 g/ k
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。. N( Y5 H' M3 |) b2 [% ]
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
+ M K5 e9 n/ }# `3 y/ _' Z) P+ q其他策略如下:
: [# {9 _6 P# v4 z* u" E' d" A. ?; ` @! D2 T
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
7 a* v. x; t' O" [ - allkeys-random:随机删除所有键,直到腾出足够空间为止。
( R# @9 r0 x! x/ J2 q - volatile-random:随机删除过期键,直到腾出足够空间为止。* e4 i5 b5 i1 B/ n- U
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
& C; t6 b- h3 ]2 e4 J; g9 q& ] - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
/ f; L4 B$ O+ O0 f 四、相关工具. L1 c6 x1 G4 k$ V N# E# R
3 Z* M6 ~2 q& N0 B6 y5 x+ K& v
1、数据同步
7 M0 C0 E* s5 f: c
( k, \( J o2 ]2 \' c0 ^redis间数据同步可以使用:redis-port
$ h7 a5 a. ^5 H+ p/ B$ K2、big key搜索
; q1 V, _/ \; w3 q7 H! F0 F5 q0 ^7 n" y4 j$ T) f
redis大key搜索工具
: O4 x* T' R" D/ @% }3、热点key寻找3 V7 e+ V" n6 }5 i4 D+ v
6 X a- e; |+ Z r, {/ h! a
内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题
& n7 j" [) T! e" M' T4 ?五、删除bigkey
- e* y8 W6 a+ o) [. M' i
R3 c2 ?0 F9 h1 g* B- f' B, \" G- I
- 下面操作可以使用pipeline加速。
" e/ w; |8 {5 P& S) `3 U! g - redis 4.0已经支持key的异步删除,欢迎使用。6 T& T) U& B: |: v) [( R
1、Hash删除: hscan + hdel9 D3 C/ a% Q+ B
0 w: P1 G7 A$ M | s3 ~public void delBigHash(String host, int port, String password, String bigHashKey) {8 }: q- ?/ r% ~2 i( E' G5 D
Jedis jedis = new Jedis(host, port);% `4 |" X) F% t9 ^
if (password != null && !"".equals(password)) {) `# Y. ]/ Z- ]4 I4 F, h
jedis.auth(password);3 T! M1 H0 [; _ N
}" A( N8 ~# g; O% \; l9 O
ScanParams scanParams = new ScanParams.count(100);) K6 I* J5 F- Q2 n7 r4 y/ U
String cursor = "0";9 h% @6 E( U; \2 T, C9 L- ~
do {" C9 }- Y n/ B- ^8 |( ~7 _
ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);* j, i) Z4 c, V( z" y: _. @
List entryList = scanResult.getResult;6 |/ Q4 R- Z* ]5 [- F& G
if (entryList != null && !entryList.isEmpty) {
$ Y5 a, V& i/ X for (Entry entry : entryList) {
- w! x, W/ h* W. k jedis.hdel(bigHashKey, entry.getKey);
5 H6 T( n: T$ J/ B }
6 l7 J* y6 C- f0 |* U: ?7 } }2 k2 |( a* T( P5 ^
cursor = scanResult.getStringCursor;
: K+ {4 p4 {# S1 \; H: K5 |9 Y } while (!"0".equals(cursor));% X: m! D% d% v, M* k9 C
8 _# f2 R& F: d0 a
//删除bigkey' y5 p1 X' [7 e) m9 ]: i) j8 i
jedis.del(bigHashKey);8 ?. E, x0 z3 k
}
& e6 m8 [" S8 {0 U, T2、List删除: ltrim6 l/ Y/ ^6 y% H$ A5 g
' L" h* @3 a$ c: A% s t7 [, ppublic void delBigList(String host, int port, String password, String bigListKey) {
7 R* X, v# \% Z8 o Jedis jedis = new Jedis(host, port);
0 f/ I- C( Q4 Z- i( g if (password != null && !"".equals(password)) {
. D2 P- ]/ ?2 i jedis.auth(password);3 I- Y0 R _. R
}! j$ L3 E: ^) f3 u6 S
long llen = jedis.llen(bigListKey);7 i: c; |& M! S
int counter = 0;# T6 R, @4 ] I: J b7 o
int left = 100;
S# B) d% t& \+ [ while (counter < llen) {
0 i: i. ~5 {4 t; R# Y3 p5 w //每次从左侧截掉100个* t$ t3 p$ t- I, P
jedis.ltrim(bigListKey, left, llen);
6 ~. t/ x K4 {8 a6 r9 r3 l counter += left;
' }. u3 n# V' C9 `0 C }+ ?5 W3 G `* u5 ?# c4 | ~- T
//最终删除key# O B0 H7 @; R
jedis.del(bigListKey);
% B- h2 A$ s2 h3 r; q1 z% c4 q, e}3、Set删除: sscan + srem
7 a; t1 ^" x- o3 Q g& Y" Q8 w8 S! q6 a$ [- l8 z5 u7 l* n" ?9 Q
public void delBigSet(String host, int port, String password, String bigSetKey) {
, x5 {+ w7 E7 `+ v$ F6 H, \ Jedis jedis = new Jedis(host, port);
) G# t3 v7 m) T. F9 {; S2 B2 } if (password != null && !"".equals(password)) {6 l( h. ?* j9 g4 h2 T! M. `
jedis.auth(password);
/ V7 H2 Z/ c. B6 g* X }8 ~+ t& Y7 v5 @" y, F
ScanParams scanParams = new ScanParams.count(100);
4 M% e7 f; l) k8 z String cursor = "0";9 b+ T ?* s0 I' {! a! R9 s: J
do {8 |& X2 m0 J! d3 Z) w) k
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
% p( T9 h* O) {( j. O7 P/ i List memberList = scanResult.getResult;
$ L# t' |: j* |' o5 b0 ]0 c1 ] if (memberList != null && !memberList.isEmpty) {
8 J2 d" B+ }1 k: ~5 }- z" v for (String member : memberList) {& P. @5 r6 T7 Y+ R; J. w4 G
jedis.srem(bigSetKey, member);6 L2 u# _' Q: M7 Y
}; q+ U- M! k7 x; ~
}/ g7 X' X5 b3 c7 e+ i% Z
cursor = scanResult.getStringCursor;
, [2 L Z" S4 X( p: p } while (!"0".equals(cursor));
6 i3 g; @% ~' ^! n9 l7 R( p1 \3 |; k
//删除bigkey# k) c% G+ \2 }
jedis.del(bigSetKey);+ C% c& I8 b7 N
}) i/ S/ |6 T' x- K' P
4、SortedSet删除: zscan + zrem
) A2 T2 x3 o* ]/ H
* L+ G" B1 `- }/ m7 T; ]5 X7 Zpublic void delBigZset(String host, int port, String password, String bigZsetKey) {
4 @ E( W1 y+ W# x1 ~: m. { Jedis jedis = new Jedis(host, port); ( N& L! E( D( `# l. q" C# {
if (password != null && !"".equals(password)) { - K7 `- x2 @7 U5 f( ~" y
jedis.auth(password);
; G0 R) F: R' `: p2 q' O1 Z9 _ } 9 x% e+ }9 d* t2 `
ScanParams scanParams = new ScanParams.count(100); , \9 k+ W0 F0 U
String cursor = "0"; * J# h; ~7 O0 O! J/ X) B- w
do {
5 Y! v, x1 W# y ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); & D2 G( F5 c |- K) Y* E2 \6 i
ListtupleList = scanResult.getResult;
! \. @+ {& @- _. H* J if (tupleList != null && !tupleList.isEmpty) {
& H! n% x! S& Z# |: v+ L& [2 E for (Tuple tuple : tupleList) {
" P7 G# P* _2 K2 { jedis.zrem(bigZsetKey, tuple.getElement);
2 ~, D& X' Q ~ } * z# J/ H$ K$ i3 h$ q
}
K8 y$ h: \. C, D; X1 v& i cursor = scanResult.getStringCursor; : i P. J6 e( `
} while (!"0".equals(cursor));
+ E U: D; d( N9 B8 S
! k% p' W" p" R4 l; t0 W( h//删除bigkey
: X4 R* @+ A5 K6 U9 G& S) W jedis.del(bigZsetKey);
. Y7 }) @! u, B- e7 v: d} 公众号内回复“1”带你进粉丝群
7 k# @; v6 P' I( h来源:http://www.yidianzixun.com/article/0LevQm7t, l7 X- R' A$ [% _& i7 ?- z, M
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|