Redis运维经验一二三

本文主要介绍一些列简单的redis运维经验,帮助大家对redis的运维有个初步的了解,条目不分先后,后续还会持续添加。本文档预期读者为:redis使用者。

计算延迟时间

这里的延迟时间指的是指令请求的等待时间(单位为毫秒),包含网络延时以及单线程的命令等待,命令很简单:

1
redis-cli --latency- history --i <interval> -h `host` -p `port`

这里我们来测试下
场景1:本地空闲的redis实例,延时几乎为0

1
2
3
4
5
[root@lxj tmp]# /usr/local/redis/bin/redis-cli --latency-history -i 5 -h 10.59.xx.xx -p 6379
min: 0, max: 1, avg: 0.00 (455 samples) -- 5.01 seconds range
min: 0, max: 0, avg: 0.00 (423 samples) -- 5.00 seconds range
min: 0, max: 1, avg: 0.00 (455 samples) -- 5.01 seconds range
min: 0, max: 1, avg: 0.28 (455 samples) -- 5.01 seconds range

场景2:模拟网络延时严重的场景(client),由于客户端出口带宽被打满,导致请求延时,平均在30毫秒+

1
2
3
4
5
6
7
8
[root@lxj ~]# /usr/local/redis/bin/redis-cli --latency-history -i 5 -h 10.59.xx.xx -p 20010 -a 'xxxxx'
min: 9, max: 640, avg: 48.21 (87 samples) -- 5.17 seconds range
min: 9, max: 639, avg: 38.48 (101 samples) -- 5.02 seconds range
min: 9, max: 1472, avg: 54.92 (78 samples) -- 5.14 seconds range
min: 9, max: 219, avg: 26.30 (135 samples) -- 5.01 seconds range
min: 9, max: 637, avg: 30.42 (119 samples) -- 5.01 seconds range
min: 9, max: 639, avg: 21.83 (150 samples) -- 5.01 seconds range
min: 9, max: 636, avg: 45.84 (91 samples) -- 5.20 seconds range

场景3:生产redis实例1,该实例平均延时较高,很多时候平均延时几乎到1毫秒,最大延时到了13毫秒,这样的请求跟负载注定实例的qps以及吞吐量不会高

1
2
3
4
5
6
7
8
9
[root@lxj ~]# /usr/local/redis/bin/redis-cli --latency-history -i 5 -h 10.59.xx.xx -p 20010 -a 'xxxxx'
min: 1, max: 2, avg: 1.03 (453 samples) -- 5.01 seconds range
min: 0, max: 1, avg: 1.00 (445 samples) -- 5.01 seconds range
min: 0, max: 1, avg: 0.98 (417 samples) -- 5.00 seconds range
min: 0, max: 2, avg: 0.96 (422 samples) -- 5.01 seconds range
min: 0, max: 13, avg: 0.81 (448 samples) -- 5.00 seconds range
min: 0, max: 2, avg: 0.49 (447 samples) -- 5.01 seconds range
min: 0, max: 2, avg: 0.17 (418 samples) -- 5.00 seconds range
min: 0, max: 1, avg: 0.12 (418 samples) -- 5.01 seconds range

场景4:生产redis实例2,该实例比起实例1平均延时较低,平均延时在0.01~0.13毫秒,算是比较健康的实例

1
2
3
4
5
6
[root@lxj tmp]# /usr/local/redis/bin/redis-cli --latency-history -i 5 -h 10.59.xx.xx -p 6379 -a 'xxxx'
min: 0, max: 2, avg: 0.13 (418 samples) -- 5.01 seconds range
min: 0, max: 1, avg: 0.03 (443 samples) -- 5.00 seconds range
min: 0, max: 1, avg: 0.01 (454 samples) -- 5.00 seconds range
min: 0, max: 1, avg: 0.01 (447 samples) -- 5.01 seconds range
min: 0, max: 1, avg: 0.00 (418 samples) -- 5.01 seconds range

注意产生的流量

很多时候我们也需要注意下redis服务器的流量负载情况,由于redis本身的高性能服务,我们的生产服务就出现过直接把服务器出口带宽打满的现象。这点只需要注意下即可,正常出现概率不高。

注意请求的时间复杂度

时间复杂度是指redis指令执行的时间复杂度,从O(1)到O(N)甚至更复杂不等。我们建议将一些复杂度O(N)甚至是更复杂的操作,比如:排序,多键操作等等指令转化或者是放在redis slave上,达到效率提升的目的,说白了有点读写分离的意思。

案例1:某项目推送模块的redis优化,其中有一个是:将复杂度为O(log(N)+M)的ZREVRANGE操作转换为list的O(1)操作,qps从5000上升到6500。

案例2:某项目的一个在线用户统计模块,包含了大量的keys的遍历操作(大约6%的请求占了90%+的请求时间),导致redis吞吐大大降低,取消掉keys操作以后,吞吐量开始回升。

而这些信息我们可以通过一个redis-faina.py的脚本或获取redis的运行状态以及信息,从中我们可以发现一些优化点,具体信息见下。

注意事项1:该脚本运行环境为python2.7,当监控redis2.4的实例时需要加上—redis-version=2.4的参数。
注意事项2:该脚本的的执行统计时长不是绝对准确,在繁忙的环境下可以作为参考,在限时数据不尽准确(执行时长是以两个指令开始执行的时间差作为第一个指令执行的时长,其中可能包含空闲等待的时间)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
[root@lxj tmp]# redis-cli -h 10.59.xx.xx -p 20010 -a 'xxxxx' MONITOR |head -n 10000 | python redis-faina.py // -n 10000是取样10000次请求的意思
Overall Stats
========================================
Lines Processed 10000
Commands/Sec 2125.30 //QPS一个很重要的性能指标
--------------------------------------------------------------------
Top Prefixes
========================================
push 8067 (80.67%)
--------------------------------------------------------------------
Top Keys //请求最多的KEY
========================================
4474d2d3406340ccb39e4cb7103f5a79 1932 (19.32%)
push:onlinedevice:3:1 1930 (19.30%)
push:messagelist:android:3 1620 (16.20%)
push:onlineuser:3:1 321 (3.21%)
push:user:3:1:166457 4 (0.04%)
push:user:3:1:69651 4 (0.04%)
push:user:3:1:145696 4 (0.04%)
push:user:3:1:188873 4 (0.04%)
--------------------------------------------------------------------
Top Commands //请求最多的指令
========================================
ZADD 2252 (22.52%)
AUTH 1932 (19.32%)
SETEX 1930 (19.30%)
LPOP 1621 (16.21%)
LRANGE 1620 (16.20%)
SADD 321 (3.21%)
EXPIRE 321 (3.21%)
SET 2 (0.02%)
--------------------------------------------------------------------
Command Time (microsecs) //响应时间的正态分布
========================================
Median 167.75
75% 349.75
90% 1109.0
99% 5474.25
--------------------------------------------------------------------
Heaviest Commands (microsecs) //指令的总体耗时
========================================
LRANGE 2370628.25
SETEX 1413046.0
AUTH 472047.75
LPOP 281701.75
ZADD 153476.25
SADD 7956.75
EXPIRE 5963.75
SET 388.5
--------------------------------------------------------------------
Slowest Calls //慢查询
========================================
17460.25 "LRANGE" "push:messagelist:android:3" "0" "-1"
16738.25 "SETEX" "push:detail:3:1:be461ce1a982c30771b0fa90496fd492" "1800" "a:2:{s:6:\"app_id\";i:3;s:11:\"device_type\";s:1:\"1\";}"
16026.0 "LRANGE" "push:messagelist:android:3" "0" "-1"
15986.0 "LRANGE" "push:messagelist:android:3" "0" "-1"
15124.0 "LRANGE" "push:messagelist:android:3" "0" "-1"
14983.75 "LRANGE" "push:messagelist:android:3" "0" "-1"
13885.0 "LRANGE" "push:messagelist:android:3" "0" "-1"
13792.0 "LRANGE" "push:messagelist:android:3" "0" "-1"

不必要的操作

不需要的操作是指类型一些EXISTS、DEL这样的操作,在很多场景下是完全没有必要的,在QPS不变的情况下,取消这些不必要的请求可以达到提升有效用户请求的数量。

案例1:XX项目点播业务的redis实例,平均约存在37.34%的EXISTS请求,而EXISTS是用在每次对key做操作(GET,ZRANGE ,HMGET…)前都执行,用于判断该key是否存在,其实redis这些api对于不存在的key都会返回提示,因此可以直接取消EXISTS的请求,在QPS不变的情况下,有效用户请求可以提升约37.34%。

案例2:XX项目推送业务的redis实例中存在25.5%的DEL操作,占据约40%的请求时间,后将key直接设置过期时间,在同样的QPS下,用户有效请求得到提升。

但是并不代表EXISTS、DEL这类型的操作都是不必要的请求,具体案例还需具体分析,存在必有其道理。

管道操作

Redis提供一个pipeline的管道操作模式,将多个指令汇总到队列中批量执行,可以减少tcp交互产生的时间,一般情况下能够有10%~30%不等的性能提升。
但是需要注意的是,pipeline与multi不同,无法保证请求之间的原子性,因此需要考虑使用场景。如果业务场景允许,这也是一个性能提升的点。

IO优化

IO优化主要是redis快照或者aof产生的,一般我们需要根据redis中上数据的重要程度来设置快照频率,或者是快照只在slave上发生。

注意CPU使用率

Redis是单线程作业,因此需要注意CPU使用率,除了分离业务,读写分离以外,twemproxy也能有效的提升redis的CPU利用率。

copy-on-write使用的内存可能真的会是2倍内存

redis在触发COW的时候并不是说写多少数据用多少内存的,实际上在cow的过程中并不是修改多少数据使用多少内存,而是每次修改一个数据加载的最小单位是page,所以当你更新请求比较频繁且分散的话,double memory是可能的。

关服务的时候要注意

有的人备份数据是通过直接关闭redis服务使数据进行落地操作,但是但是但是你首先要确认你的save配置是啥,万一你的配置是save “”呢?那就嗝屁了。