Linux

Redis持久化配置不当带来的性能问题分析

一 背景说明

两台机器,server1跑Redis缓存数据库和应用;

server2向server1的应用执行一个Redis查询请求,并持续监控该请求延迟;

server1: 172.16.17.7,4C 8G;

server2: 172.16.11.36,24C 64G;

二 分析步骤和思路

1 server1启动并验证容器正常工作

[root@guoxin7 ~]# docker run --name=redis -itd -p 10000:80 feisky/redis-server
Unable to find image 'feisky/redis-server:latest' locally
...
be617247edfe0e7603e13db07bdf447153ae8bb7d21547465e5dbc44bc33be3b
[root@guoxin7 ~]# docker run --name=app --network=container:redis -itd feisky/redis-app
Unable to find image 'feisky/redis-app:latest' locally
Trying to pull repository docker.io/feisky/redis-app ... 
...
ae8e5b690d0a5719c3bd646de9fac402e8bbb0446b6e9c35c90ac600455cfd70
[root@guoxin7 ~]# docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                             NAMES
ae8e5b690d0a        feisky/redis-app      "python /app.py"         2 minutes ago       Up 2 minutes                                          app
be617247edfe        feisky/redis-server   "docker-entrypoint..."   3 minutes ago       Up 3 minutes        6379/tcp, 0.0.0.0:10000->80/tcp   redis
[root@guoxin7 ~]# curl http://172.16.17.7:10000
hello redis
[root@guoxin7 ~]#

2 server1初始化Redis缓存数据

[root@guoxin7 ~]# time curl http://172.16.17.7:10000/init/10000
{"elapsed_seconds":152.70450973510742,"keys_initialized":10000}
​
real    2m32.714s
user    0m0.007s
sys     0m0.006s
[root@guoxin7 ~]# 

3 server2调用接口访问server1 redis

[root@centos-master ~]# curl http://172.16.17.7:10000/get_cache
{"count":3364,"data":["3d6bc5c4-aa48-11ec-abe1-0242ac110002",
....
"elapsed_seconds":62.205448150634766,"type":"good"}
[root@centos-master ~]# 

发现,通过接口调用serve1的Redis需要62秒,1分多钟,这太慢了。

4 使用top和iostat工具对server1资源使用情况分析和监测

server2对server1持续进行接口访问请求:

[root@centos-master ~]# while true; do time curl http://172.16.17.7:10000/get_cache ;sleep 10 ;done
...
10002"],"elapsed_seconds":46.78778529167175,"type":"good"}
​
real    0m46.806s
user    0m0.007s
sys     0m0.006s
​

然后再在server1上分别使用top监控资源使用情况:

[root@guoxin7 ~]# top
top - 15:02:47 up 2 days,  4:07,  4 users,  load average: 0.52, 0.39, 0.63
Tasks: 229 total,   1 running, 228 sleeping,   0 stopped,   0 zombie
%Cpu0  :  2.7 us,  1.8 sy,  0.0 ni, 51.6 id, 43.6 wa,  0.0 hi,  0.4 si,  0.0 st
%Cpu1  :  0.0 us,  0.0 sy,  0.0 ni, 99.6 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
%Cpu2  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.4 us,  0.4 sy,  0.0 ni, 99.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 41037092 total, 35626136 free,  1150372 used,  4260584 buff/cache
KiB Swap:  8257532 total,  8257532 free,        0 used. 36866508 avail Mem 
​
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                           
13627 root      20   0  267236  25456   5148 S   3.5  0.1   8:34.22 python /app.py                                                                                                    
13375 100       20   0   30900   9896   1140 D   1.8  0.0   4:39.50 redis-server /etc/redis.conf                                                                                      
  429 root       0 -20       0      0      0 S   0.4  0.0   0:05.36 [kworker/0:1H]                                                                                                    
 9388 postgres  20   0 3435712   2180   1292 S   0.4  0.0   0:11.54 postgres: autovacuum launcher process                                                                             
15286 root      20   0  162248   2504   1692 R   0.4  0.0   0:52.09 top                                        

看到服务器上CPU资源并没有被严重消耗,同时内存资源也充足,机器整体负载也不高。但是,客户端请求Redis响应就是慢。

使用iostat -dx 1;监测系统磁盘IO情况:

[root@guoxin7 ~]# iostat -dx 1
Linux 3.10.0-1160.59.1.el7.x86_64 (guoxin7)     2022年03月23日  _x86_64_        (4 CPU)
​
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00     0.08   38.43   16.61 16146.82  1170.35   629.19     2.44   44.28    9.76  124.13   3.03  16.66
scd0              0.00     0.00    0.00    0.00     0.01     0.00   114.22     0.00    6.89    6.89    0.00   6.67   0.00
dm-0              0.00     0.00   38.43   16.70 16146.71  1170.26   628.25     2.47   44.73    9.76  125.23   3.02  16.66
dm-1              0.00     0.00    0.00    0.00     0.01     0.00    50.09     0.00    1.77    1.77    0.00   1.57   0.00
​
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00     0.00    0.00  135.00     0.00   340.50     5.04     0.94    6.93    0.00    6.93   6.99  94.30
scd0              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
dm-0              0.00     0.00    0.00  135.00     0.00   343.50     5.09     0.94    6.93    0.00    6.93   6.99  94.40
dm-1              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
​
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00     0.00    0.00   96.00     0.00   244.00     5.08     0.96   10.11    0.00   10.11  10.03  96.30
scd0              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
dm-0              0.00     0.00    0.00   96.00     0.00   244.00     5.08     0.96   10.11    0.00   10.11  10.03  96.30
dm-1              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
​
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00     0.00    0.00  160.00     0.00   421.50     5.27     0.93    5.83    0.00    5.83   5.83  93.20
scd0              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
dm-0              0.00     0.00    0.00  160.00     0.00   421.50     5.27     0.93    5.83    0.00    5.83   5.83  93.20
dm-1              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
​
​
^C
[root@guoxin7 ~]# 

系统的IO资源使用率偏高,随着时间的推移,当客户的请求结束时,磁盘使用率%util下降。同时,也看到随着磁盘使用率的上升和下降,磁盘的写次数w/s和wkB/s指标也响应的上升和下降。

这里有个疑问出现:客户端请求服务端的Redis,为啥服务端出现比较明显的磁盘写,而不是磁盘读?通常来讲,Redis作为缓存数据库服务来用,应该读大于写才符合常理的啊。

5 pidstat来跟踪Redis进程的磁盘IO

在server2上,通过pidstat -d -l -p redis_pid 1;命令来检测Redis进程的磁盘IO情况:

[root@guoxin7 ~]# pidstat -d -l -p 13375 1
Linux 3.10.0-1160.59.1.el7.x86_64 (guoxin7)     2022年03月23日  _x86_64_        (4 CPU)
​
15时18分24秒   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
15时18分25秒   100     13375      0.00    212.00      0.00  redis-server /etc/redis.conf 
15时18分26秒   100     13375      0.00    224.00      0.00  redis-server /etc/redis.conf 
15时18分27秒   100     13375      0.00    172.00      0.00  redis-server /etc/redis.conf 
15时18分28秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分29秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分30秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分31秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分32秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分33秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分34秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分35秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分36秒   100     13375      0.00      0.00      0.00  redis-server /etc/redis.conf 
15时18分37秒   100     13375      0.00     40.00      0.00  redis-server /etc/redis.conf 
15时18分38秒   100     13375      0.00    272.00      0.00  redis-server /etc/redis.conf 
15时18分39秒   100     13375      0.00    336.00      0.00  redis-server /etc/redis.conf 
15时18分40秒   100     13375      0.00    204.00      0.00  redis-server /etc/redis.conf 
15时18分41秒   100     13375      0.00    284.00      0.00  redis-server /etc/redis.conf 
15时18分42秒   100     13375      0.00    252.00      0.00  redis-server /etc/redis.conf 
^C
平均时间:   100     13375      0.00    110.89      0.00  redis-server /etc/redis.conf 
[root@guoxin7 ~]# 

明显可以看到,Redis后台进程的13375磁盘IO也主要是写kB_wr/s,读操作kB_rd/s为0。更为重要的是,跟随客户端调用Redis请求时,13375开始写磁盘,客户端请求停止时,看到磁盘写的值为0.

到这里,基本可以判断出客户端请求Redis响应慢的问题,基本上可以认定是Redis自身的配置或者是使用的问题了。服务器上的资源充足够用,CPU消耗不高,内存够用,缓存也稳定,几乎没有太大抖动,系统负载也不高。

6 跟踪查看Redis进程在内核的系统调用和打开文件信息

strace跟踪进程的内核调用:strace -fp 13375

[root@guoxin7 ~]# strace -fp 13375
...
[pid 13375] read(8, "*3\r\n$4\r\nSADD\r\n$4\r\ngood\r\n$36\r\n597"..., 16384) = 67
[pid 13375] read(3, 0x7ffd8bb6edf7, 1)  = -1 EAGAIN (资源暂时不可用)
[pid 13375] write(18, "*3\r\n$4\r\nSADD\r\n$4\r\ngood\r\n$36\r\n597"..., 67) = 67
[pid 13375] fdatasync(18)               = 0
[pid 13375] write(8, ":1\r\n", 4)       = 4
[pid 13375] epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 51, NULL, 8) = 1
[pid 13375] read(8, "*2\r\n$3\r\nGET\r\n$41\r\nuuid:3f57d508-"..., 16384) = 61
[pid 13375] read(3, 0x7ffd8bb6edf7, 1)  = -1 EAGAIN (资源暂时不可用)
[pid 13375] write(8, "$3\r\nbad\r\n", 9) = 9
[pid 13375] epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 50, NULL, 8) = 1
[pid 13375] read(8, "*4\r\n$4\r\nSCAN\r\n$5\r\n13396\r\n$5\r\nMAT"..., 16384) = 48
[pid 13375] read(3, 0x7ffd8bb6edf7, 1)  = -1 EAGAIN (资源暂时不可用)
[pid 13375] write(8, "*2\r\n$5\r\n14932\r\n*10\r\n$41\r\nuuid:53"..., 500) = 500
[pid 13375] epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 50, NULL, 8) = 1
[pid 13375] read(8, "*2\r\n$3\r\nGET\r\n$41\r\nuuid:53e5509a-"..., 16384) = 61
[pid 13375] read(3, 0x7ffd8bb6edf7, 1)  = -1 EAGAIN (资源暂时不可用)
[pid 13375] write(8, "$4\r\ngood\r\n", 10) = 10
[pid 13375] epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 49, NULL, 8) = 1
[pid 13375] read(8, "*3\r\n$4\r\nSADD\r\n$4\r\ngood\r\n$36\r\n53e"..., 16384) = 67
[pid 13375] read(3, 0x7ffd8bb6edf7, 1)  = -1 EAGAIN (资源暂时不可用)
[pid 13375] write(18, "*3\r\n$4\r\nSADD\r\n$4\r\ngood\r\n$36\r\n53e"..., 67) = 67
[pid 13375] fdatasync(18^Cstrace: Process 13375 detached
 <detached ...>
strace: Process 13410 detached
strace: Process 13411 detached
strace: Process 13412 detached
[root@guoxin7 ~]# 

Redis进程主要系统调用是write(),read(),和fdatasync()以及epoll_pwait()等操作。

查看进程打开文件信息:lsof -p 13375

[root@guoxin7 ~]# lsof -p 13375
...
COMMAND     PID     USER   FD      TYPE DEVICE SIZE/OFF      NODE NAME
redis-ser 13375      100    3r     FIFO    0,9      0t0   6374739 pipe
lsof: no pwd entry for UID 100
redis-ser 13375      100    4w     FIFO    0,9      0t0   6374739 pipe
lsof: no pwd entry for UID 100
redis-ser 13375      100    5u  a_inode   0,10        0      7418 [eventpoll]
lsof: no pwd entry for UID 100
redis-ser 13375      100    6u     sock    0,7      0t0   6374741 protocol: TCP
lsof: no pwd entry for UID 100
redis-ser 13375      100    8u     sock    0,7      0t0   6389057 protocol: TCP
lsof: no pwd entry for UID 100
redis-ser 13375      100    9u     sock    0,7      0t0   6598637 protocol: TCP
lsof: no pwd entry for UID 100
redis-ser 13375      100   18w      REG  253,0  1125462 210213898 /data/appendonly.aof
[root@guoxin7 ~]# 

Redis进程在写文件描述符为18号的文件,读取3号文件是pipe,以及8号文件的TCP Socket文件。

7 调整Redis的持久化配置参数

进入Redis container里,查看Redis的持久化配置:

#1 先进入Redis container
[root@guoxin7 ~]# docker exec -it redis /bin/sh
#2 redis-cli连接到Redis
/data # redis-cli 
#3 查看持久化配置
127.0.0.1:6379> CONFIG GET 'append*'
1) "appendfsync"
2) "always"
3) "appendonly"
4) "yes"
127.0.0.1:6379> 

这里,看到当前的Redis持久化存储方式采用的是AOF (Append Only File),即追加文件的方式。并且配置(“appendfsync”=”always”)的是每新增一个key-value,就把它持久化写入到文件里,防止内存中的这个key-value丢失。

我们这里尝试把Redis的持久化方式改为”appendfsync”=”no”,即不是每次都要实时写出追加到文件,而是交给文件系统去处理的方式。

127.0.0.1:6379> CONFIG SET appendfsync=no
(error) ERR Unknown subcommand or wrong number of arguments for 'SET'. Try CONFIG HELP.
127.0.0.1:6379> CONFIG SET "appendfsync"="no"
Invalid argument(s)
127.0.0.1:6379> CONFIG SET appendfsync no
OK
127.0.0.1:6379> CONFIG GET appendfsync
1) "appendfsync"
2) "no"
127.0.0.1:6379> CONFIG GET 'append*'
1) "appendfsync"
2) "no"
3) "appendonly"
4) "yes"
127.0.0.1:6379> 

然后,观察客户端发送的Redis请求从之前的耗时60秒左右,降低到1-2秒左右。

[root@centos-master ~]# while true; do time curl http://172.16.17.7:10000/get_cache ;sleep 10 ;done
... "elapsed_seconds":2.073829174041748,"type":"good"}
​
real    0m2.089s
user    0m0.003s
sys     0m0.009s

并且,服务端也不再看到Redis进程频繁的磁盘写操作了。

三 小结和收获

1 思路

客户端发起Redis调用请求耗时偏高,服务端监控到CPU、内存、负载、系统缓存都比较正常;此时,不能判断没有性能问题,客户端请求延迟很高,继续跟踪Redis进程的磁盘IO情况(pidstat -d -l -p redis_pid 1;),strace -fp redis_pid;和lsof -p redis_pid;查看Redis进程在内核中的系统调用和打开文件信息;判断问题基本上是Redis自身的配置和使用不对;

2 学习了Redis的持久化不同配置方案

详细的信息,可以从Redis官网查看:https://redis.io/topics/persistence

3 主机资源正常不代表没有性能问题

此案例中,服务端监控到CPU、内存、负载、系统缓存都比较正常;此时,不能判断没有性能问题,

4 Redis的异常写操作

Redis的异常写操作和当前的场景不太吻合,客户端发起Redis调用请求,查询缓存数据库,不应该在服务器端看到明显的Redis写请求操作。

四 参考和链接

倪朋飞老师的Linux性能优化实战,29 | 案例篇:Redis响应严重延迟,如何解决?

Redis官网关于持久化配置查看:https://redis.io/topics/persistence