|
1 L" w- Y6 O6 C5 b3 b; A
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。
6 s8 h/ b2 K8 B
, ]$ c4 E* G, Y' Q6 \/ V! n- 键值设计
: F& a' g) M. s4 F5 F - 命令使用
7 d' M; E S3 I8 E( W - 客户端使用
1 Q4 T( j# t2 @ R3 v) o% A - 相关工具
+ P& X' N4 J' d$ a- G; l7 ~0 b/ ? 通过本文的介绍可以减少使用Redis过程带来的问题。( r5 _' W4 ~ {
一、键值设计) K4 z e; k& s: D E
+ S+ I+ T+ C; a/ O; f1、key名设计& y i4 |- ~5 A; t7 W& S% k$ Z
4 [5 {& A3 U1 A" [; s3 v
可读性和可管理性
& p/ c, _+ X7 V C/ ~$ l
8 l/ E4 M. F; e" Y8 ~) z8 z6 m, t以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id( t4 x1 y* t/ R8 J
# C% ]1 ~* r4 T: S2 f d
- ugc:video:1
0 o: U" e' g2 r [4 R# Y. ?& s1 x 简洁性
8 H, x( X9 f+ @1 ]5 Y' i' L8 z- ?9 a; q9 i3 B
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
/ R1 Q" c$ s- x! z# _0 x6 x5 g! J2 g/ y
3 O; A: z0 _9 g& c' c- user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
( Y4 P& }# p; V4 k; x, X 不要包含特殊字符
) C) L {5 _6 N2 a# y- ~' q, u2 [1 K' `; n
反例:包含空格、换行、单双引号以及其他转义字符
; D0 n( G S+ r+ }$ b3 ]# d2、value设计
7 J2 L4 x* h9 A) u
7 z1 s/ h9 [3 l- p拒绝bigkey
) C9 q" _* L4 x7 ]: ] w( W) p! s7 I* i
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
8 g! a# y+ _: L& h2 \# u% R% w反例:一个包含200万个元素的list。
) p; x* h/ v% K0 Q9 ~非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
' D& @4 z3 V+ B4 P) u选择适合的数据类型3 v3 V( P- a" W) ]
0 i8 I! @% @: L1 B例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?4 ^2 M4 s" ?: _# p* q4 k. u# `1 _
反例:
) F* I2 I6 C6 W/ f8 g6 R5 L9 U2 l
* M8 R) }1 b. J4 g- set user:1:name tom; s' I: A: y4 p( V4 g$ G5 B5 m
- set user:1:age 19* h6 R# ?7 r. J- }8 H. v
- set user:1:favor football
) }2 s' U8 T0 ], u1 M! K# D; V. U. F+ s1 Y 正例:/ F+ U5 g- n5 i: z5 R
7 q P9 b6 |) c7 X+ F- hmset user:1 name tom age 19 favor football' g6 l( r4 `8 R# H/ a8 v% O
控制key的生命周期
v4 U1 s$ S( M, W% a
+ x3 }: N& U8 m* m( E6 vredis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。7 S$ C2 j# S0 O
二、命令使用
+ ^8 p3 W U* H$ @, G' g3 F' w6 K6 ~1 E" J. P
1、O(N)命令关注N的数量$ B) S4 A' n! q# ^8 M) f3 w. u
( B# A9 c1 ^- W4 I- i1 h. V3 `
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
5 s i3 ~+ I; M2、禁用命令, ^( e2 o" d: m5 J
, F. f, W4 _3 L* S5 J禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。& {6 ^4 a* v) d: x
3、合理使用select
3 R! h0 d( M. G& ~' p8 [# q2 Y$ o0 P: A# e2 m7 |- r
% w; T% \& \4 S- H' M
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
4 e0 W5 }/ j1 G7 m4、使用批量操作提高效率
$ v( Y0 O8 x% E6 ?" }) X3 e3 o% {, ~, {) h. t: z6 N% n/ v( C# `
/ ]) m; f( Y- \; i- 原生命令:例如mget、mset。
. E q. z' X9 R f - 非原生命令:可以使用pipeline提高效率。
$ ]: L" Y9 W, l/ r, r9 [' t$ j 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
. @+ _% d* |2 o: q1 l! F0 C注意两者不同:
8 D# h4 `! F7 F/ k+ n2 N+ W: _3 d% |' x' J6 L7 u: K
- 原生是原子操作,pipeline是非原子操作。
5 R, N S! u9 f; c& ^ - pipeline可以打包不同的命令,原生做不到9 O5 X$ n$ G% o" x
- pipeline需要客户端和服务端同时支持。" T- `: M2 f: @' f- u
5、不建议过多使用Redis事务功能
" ^# o; D- y; p! Q5 l; `/ ~0 d( \, T) U8 H/ i w i6 B( _% p; u
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。分布式事务不理解?一次给你讲清楚!
- h9 o5 W, U7 j3 ]+ G" F6、Redis集群版本在使用Lua上有特殊要求
: E" ?& w$ ^ z$ B6 G! x6 w/ x0 c1 r% k% V7 e0 M: {
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"
* A' _/ V5 Z4 x" X, E6 _/ A7、monitor命令
% [2 L/ K4 |) A
$ N+ ^7 v& M" Y1 A! y P% A必要情况下使用monitor命令时,要注意不要长时间使用。; _( l# [- L! y/ E. i3 f
三、客户端使用
: g. J+ Y+ W8 l. I& g
+ _ Q, u8 J k; Z1、避免多个应用使用一个Redis实例
; f& ]5 X) Z2 I2 `
8 I- W9 t; o% Z" p. ]不相干的业务拆分,公共数据做服务化。
8 M: _' R$ m; L N7 U, A9 U: h, N2、使用连接池
$ N0 \4 X* y8 r0 `! J0 d
; {1 c8 J' o8 I/ p/ E可以有效控制连接,同时提高效率,标准使用方式:/ c" D& }- i4 O u
Jedis jedis = null;
( S& k& M. V+ l3 `$ |try {
0 w/ k8 ?6 f( f) u2 T jedis = jedisPool.getResource;$ s1 L7 j* {6 v7 g0 A
//具体的命令
: U8 z/ W+ {8 l. T1 A, e! I jedis.executeCommand
+ c, n3 t8 Q8 z} catch (Exception e) {
. E/ e/ _. m. F4 |" B logger.error("op key {} error: " + e.getMessage, key, e);
- t2 K, x2 W: d4 e+ t" g} finally {
% l8 J& s: q: v! a; A //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。4 }: ]/ Y$ O; H: h
if (jedis != null)
, x6 X; I% N+ P, ] jedis.close;
- P. m) v" D9 m" z' y+ w4 y}
* ~$ T) [; i* W- y# U4 L3、熔断功能
: P) g0 d( F2 x# e, Y+ k) z+ J
$ `( t( K/ e5 S/ E高并发下建议客户端添加熔断功能(例如netflix hystrix)8 r1 x4 G& `( C9 B
4、合理的加密: K2 H. [" ]* R' q+ S# g
+ y: P& J- i7 a4 m
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)6 A- ~) V0 _0 ^; O5 l" n
5、淘汰策略; Y" s- z5 z4 r/ ^% a* T' ]( {- R# x
2 k: L6 p2 u2 _* k2 Q
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
; h" N/ F% {: J3 q, k8 K; t默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。- W2 d3 d$ l. { ~- H; k; V0 I
其他策略如下:
* | {4 d; B E' r, s9 z1 v! J& V* u; N1 ]: {, D4 I- [
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
. j" Y$ J, r5 W0 s1 m6 @( T, l - allkeys-random:随机删除所有键,直到腾出足够空间为止。
. d& N6 X* V" j' ^4 f: ~ - volatile-random:随机删除过期键,直到腾出足够空间为止。, F; J8 H+ B) _- a
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
9 _9 F4 Q' h, B, b `, ? - noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。3 W6 N, g& F! l8 M3 W6 s
四、相关工具) M3 _5 M! J# m& u, f
- u& s6 c& ^ c m1 `1 [1、数据同步9 F; j; j# a' q
# s5 r3 P7 g+ q0 l! N
redis间数据同步可以使用:redis-port! ?" A# p- [+ k) p; Y1 F; ^7 t
2、big key搜索
7 j0 E& N/ a5 i/ R2 {
) b% w, J7 j' r! V% W$ ` c' Eredis大key搜索工具
3 o* o. C$ C0 Q( `5 z! F& s3、热点key寻找
8 W* \+ z: R4 B9 [! \# @
' E7 g: \6 R- Q) ~内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题4 B# C. W1 A. B' o: X- D
五、删除bigkey8 K& X+ Z+ d/ l) v6 B/ X
* z2 ]* `# A7 m% {1 I# L% _5 \' Y) j1 P/ a( g# l2 B& t
- 下面操作可以使用pipeline加速。7 k& P4 g+ X. ^6 K
- redis 4.0已经支持key的异步删除,欢迎使用。
C, \, I& Z) @5 } 1、Hash删除: hscan + hdel! P# y2 g4 K4 ~2 [* \
/ X2 d7 V* f: \$ i9 V$ }
public void delBigHash(String host, int port, String password, String bigHashKey) {
0 H: h0 |& c: }3 F2 Z1 k/ K# Z4 E Jedis jedis = new Jedis(host, port);1 L# p! s }2 y2 @
if (password != null && !"".equals(password)) {
# i% T* c R5 ~ B" p* D& o4 t jedis.auth(password);
+ D) Q8 p8 T, D0 D: U }! r% `# s4 ^% O% U6 i1 N$ o
ScanParams scanParams = new ScanParams.count(100);$ P5 X; v4 x# v2 P6 G
String cursor = "0";/ d. i) v: R+ I+ u: ?' c
do {
9 X4 }" J6 v* S ScanResult scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
6 {6 m6 X! }6 m( N List entryList = scanResult.getResult;
6 u( w- R* ]. S1 Y8 s0 Q: y* |3 J* c9 T if (entryList != null && !entryList.isEmpty) {
, ^; G6 o/ r0 L4 F6 V g" s3 o+ v for (Entry entry : entryList) {
! \" Z9 k- _$ F4 } jedis.hdel(bigHashKey, entry.getKey);, O& t# S4 v) Y; E
}
, b( L4 m& W: @ R5 }$ | }
2 \. _* q8 ]0 s, k( b cursor = scanResult.getStringCursor;
& K0 B, p9 S% x2 p } while (!"0".equals(cursor));
9 R6 i, C+ V/ c8 \
8 J' ]8 f% q9 J8 t; i0 A3 T; T//删除bigkey
! D+ ]; ?, e# y d& _# b* m jedis.del(bigHashKey);* e7 M7 H: s9 [0 |7 L% @
}( o1 y k0 a6 @1 {9 a3 Z1 s% A' b
2、List删除: ltrim
8 N5 S, b6 Y4 I# [# m
" ?7 @6 _9 b& f# @3 Npublic void delBigList(String host, int port, String password, String bigListKey) {
8 ^& X6 Y" p$ D, a2 v' P Jedis jedis = new Jedis(host, port);' L* V; f) d/ y7 w4 I" S3 X8 \: W
if (password != null && !"".equals(password)) {
" j9 L9 ]" m/ k$ u5 `: n jedis.auth(password);7 j1 ?+ i) t: f! A
}
0 v/ w: @. G6 s( Z0 W long llen = jedis.llen(bigListKey);. u8 f, w# T( `) j& n+ g6 M5 x- l
int counter = 0;2 J) b8 U: y5 ^8 A4 U& |) |
int left = 100;
+ u7 G$ C" f# m# } while (counter < llen) {' y/ T1 ?, |$ c/ {9 F
//每次从左侧截掉100个
- R& y$ o" Z" W# I1 @ jedis.ltrim(bigListKey, left, llen);# P6 _: ?5 ~2 t x7 f# |5 B: T
counter += left;& Z4 Q9 ]; }+ f7 v
}! l! q. D+ z4 J' z* k1 f
//最终删除key
$ ?% ^: B# q) g jedis.del(bigListKey);
. U, i/ Y( c; W}3、Set删除: sscan + srem
8 d* J- |3 T) C- [: \9 S1 P* B, V( b% r6 V$ f4 `# `
public void delBigSet(String host, int port, String password, String bigSetKey) {
4 k. D5 n% k8 N5 }' t. F Jedis jedis = new Jedis(host, port);
9 A2 S6 X5 d+ j1 }# Z* m& u6 Q6 x if (password != null && !"".equals(password)) {
7 ^! M) Y" }% `) Q jedis.auth(password);1 w& ~) b0 y( o* @8 n, S9 H% f
}
. Z$ F8 d" ^: o$ F- ]& ~4 A/ v ScanParams scanParams = new ScanParams.count(100);
; [& ?4 B: |# ?1 K( E- I5 U) @ String cursor = "0";
3 L* x+ k* \7 V3 E do {
4 |& I5 O5 D: F$ g# C( U4 k/ V+ N ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
; u/ C) V; Y# P9 O/ r List memberList = scanResult.getResult;
X2 f. c; L; m% o+ M8 W if (memberList != null && !memberList.isEmpty) {# |1 X# H4 H; m) o
for (String member : memberList) {2 u& G+ u- R0 O! L
jedis.srem(bigSetKey, member);$ t! H; x9 Y/ ?
}8 [% T" P$ J% Q0 N8 F; D
}6 k0 Q0 r1 q1 J2 Q9 I N: E
cursor = scanResult.getStringCursor;
: J# E( F3 s2 N2 l& @! Y } while (!"0".equals(cursor));
; K( x1 }/ `0 g# x7 S" W( F' z
( E" L9 D2 O! H$ Q//删除bigkey3 z1 @! a( @: F ?4 `
jedis.del(bigSetKey);: _) T |" K. {* y I$ A! e
}
. \& F0 j, H6 F8 [4、SortedSet删除: zscan + zrem( s- S2 _! `0 d( Q' @0 o1 q
9 n$ ] T& y/ n; w/ t1 @
public void delBigZset(String host, int port, String password, String bigZsetKey) {
! T) m6 e# X3 r. x% y Jedis jedis = new Jedis(host, port); * }6 J/ g' N5 P1 s+ F
if (password != null && !"".equals(password)) { 6 b$ {/ }; c- W6 C( d
jedis.auth(password);
& l$ Q, Z+ X) W, x: J- l/ X }
- o1 ^5 X. V6 z" f% T' }5 i) e ScanParams scanParams = new ScanParams.count(100);
6 r7 @1 F8 J! V+ |# l8 r) @ String cursor = "0";
: v% v( L/ V/ H8 x: t% W5 a do { 5 O+ F) K9 v# N5 V/ [1 l) i) v% f
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams); + |/ T; G! C' ]
ListtupleList = scanResult.getResult;
# I3 n( e9 i5 v8 ^% u if (tupleList != null && !tupleList.isEmpty) { 3 G* @8 S" x q5 r7 k1 m
for (Tuple tuple : tupleList) {
! I7 k, |1 X8 Q- S- ~ jedis.zrem(bigZsetKey, tuple.getElement); / t6 h7 F0 p5 L+ h+ @
}
1 V5 y, X5 ]3 t# U- e }
: d: k3 B" ~, c cursor = scanResult.getStringCursor;
- L* n( u. q5 r* a } while (!"0".equals(cursor));
, o) d8 J5 X" }9 ]4 o4 Q; \, j
/ @' z9 t& b9 S, [//删除bigkey
3 t7 ]% h, Y+ Z; t4 u. C/ o jedis.del(bigZsetKey); - V( i! R% s% ~' s$ m
} 公众号内回复“1”带你进粉丝群
- V/ g7 c. I* E& s* w来源:http://www.yidianzixun.com/article/0LevQm7t- b2 w3 ` `, [, \: e/ g* p
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|