Redis为什么慢了(下)

Redis为什么慢了

文件系统

AOF模式

AOF持久化模式主要注重点有两个,第一追加日志写入文件的时机也就是写回策略,第二个就是AOF文件的重写操作。

日志追加写入一般分为两个步骤

  • Redis执行写命令后,将这个命令写入到文件内存中,系统调用write完成。
  • Redis根据不同的刷盘时机,将内存中的数据刷到磁盘,系统调用fsync完成。

不同的刷盘时机其实就是写回策略有三种

  • no:主线程每次写入到内存中就返回,具体刷盘时机由操作系统决定。
  • everysec:主线程每次写入到内存中就返回,Redis每隔1秒调用fsync异步将内存中的数据刷到磁盘中。
  • always:主线程每次执行完写操作后立马调用fsync执行刷盘操作。

写回策略对比如下所示

一般生产上建议折中选择,选取everysec作为写回策略的方案,但这并不是万无一失fsync虽然由子线程操作,但主线程会监控子线程的进度,如果写入过慢,后续主线程再次把新接收的操作写入到内存时(调用write),发现还有fsync操作还未完成,那么将阻塞主线程,造成业务处理延迟,而造成fsync后台子线程写入过慢一般分为两种原因

  • Redis正在执行AOF文件重写操作,这个操作需要消耗大量的IO资源。
  • 其它程序的进程正在进行大量IO操作,磁盘压力大。

对于情况一如果开启了AOF这种情况避免不了,因为AOF无限的文件追加肯定是需要进行文件重写操作的,这种情况可以通过配置实现,在redis.conf文件中配置no-appendfsync-on-rewrite yes表明在AOF重写期间不进行刷盘操作也就是不执行fsync,但这样做的前提是业务允许数据有一定量的丢失,如果业务需要保证高可靠那么不建议使用。

如果是其它程序的进程占用大量IO,一般建议将Redis实例单独部署,避免其它进程对Redis实例的影响。

fork耗时

Redis在进行AOF文件重写时会fork子线程,在生成RDB文件时也会fork子进程,虽然这AOF重写和RDB文件生成都是由子线程完成,但是在fork的一瞬间是阻塞主线程的,也就是说fork时间越长那么阻塞时间也越长。

fork的时间一般取决于Redis实例的大小,fork时主线程会拷贝自己的内存页表给子进程,实例越大那么内存页表越多,所以fork时会消耗大量的CPU资源阻塞主进程,我们可以通过Redis info的latest_fork_usec指标确定,如下所示

### 最近的一次fork耗时 单位微秒  云服务器数据量少仅供参考
[root@zzf993 bin]# ./redis-cli info | grep latest_fork_usec
latest_fork_usec:294

当latest_fork_usec的值突然变的很大时,那么需要注意fork肯定是有问题的。

想要避免这种情况,可以采用如下方案优化

  • fork耗时过长本质就是实例内存过大,我们可以加机器分担内存压力。
  • 采用合理的持久化策略,当业务对Redis中的数据不敏感时甚至可以停用持久化机制。
  • 降低主从全量同步的概率,将主从同步记录偏移量的环形缓冲区容量repl-backlog-size(默认1M)调大,这样主节点的写入覆盖从节点的读取的概率就降低了。

操作系统

swap影响

内存swap是操作系统将内存数据在内存和磁盘中来回换出的机制一般涉及到磁盘的读写,所以当需要读取的数据从内存被交换到磁盘,那么数据速度的速度就相差了几百倍,这对于一个内存数据库是不能容忍的,那如何知道内存数据有多少交换到磁盘呢?可以通过如下步骤查看

获取Rides的进程ID 方法有两种都可以实现

### 方法一
[root@zzf993 bin]# ps -ef | grep redis
root      240367  239193  0 21:51 pts/4    00:00:00 grep --color=auto redis
root     1841352       1  0 May05 ?        00:02:14 ./bin/redis-server 127.0.0.1:6379

### 方法二
[root@zzf993 bin]# ./redis-cli info | grep process_id
process_id:1841352

通过进程文件查找swap的使用情况

### 进入进程文件 每个进程id都对应一个进程文件
[root@zzf993 bin]# cd /proc/1841352/
### 查看进程文件中的swap交换信息 egrep是过滤文件中的内容
[root@zzf993 1841352]# cat smaps | egrep '^(Swap|Size)'
Size:               1728 kB
Swap:                  0 kB
SwapPss:               0 kB
Size:                  8 kB
Swap:                  0 kB
SwapPss:               0 kB
......

swap信息中每个Size就代表Redis所使用的一块内存大小,Size下面对应的Swap对应的就是交换到磁盘的大小,如果Swap的大小和Size的大小相等或者相差不大,那么就需要注意Redis的性能肯定是急剧下降的,延迟自然高。

Swap的根源是内存不足可以有如下解决方案

  • 最粗暴的办法就是增加内存,如果是Cluster模式下还能通过加实例的方式分摊内存压力。
  • 如果Redis实例机器上还有其它应用程序在运行,建议将Redis实例单独部署。
  • 还可以让Redis释放自身实例的内存,重启实例可以做到。

内存大页

内存大页的概念是从Linux 内核2.6.38 开始支持,应用程序申请内存都是按照内存页申请,普通内存页是4KB,而内存大页是2MB,内存大页就会比普通内存的申请时间长,那么如何影响Redis呢?

Redis在重写AOF文件或者持久化RDB文件时会fork子进程,这个fork的过程主线程就会复制自身内存的虚拟页表给子线程

主线程可以同时处理写入的数据,这时不能直接修改某个内存页上的值因为这可能影响重写AOF文件或者RDB持久化,系统会利用COW机制,拷贝一份新的内存页给主进程修改数据,这样保证了主线程和子线程的操作都不会受影响,效果图如下。

如果是内存大页那么即使修改了1kb的数据也需要拷贝2MB的数据,如果修改过于频繁那么将会拷贝大量的内存页,这些内存页都需要向系统申请,申请时间延长自然导致写入延迟。

那么如何解决内存大页问题呢?我们先查询下Redis实例所在机器是否开启内存大页

#### 查看是否开启内存大页
[root@zzf993 1841352]# cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

always表示已经开启内存大页,一般都会建议关闭内存大页,可以执行如下命令

### 关闭内存大页
[root@zzf993 1841352]# echo never > /sys/kernel/mm/transparent_hugepage/enabled

### 再次查看 显示never内存大页关闭
[root@zzf993 1841352]# cat /sys/kernel/mm/transparent_hugepage/enabled
always madvise [never]

CPU绑定

我在之前的文章中有聊到CPU的架构对Redis的性能有影响,无论是CPU多核架构还是多CPU的NUMA非统一内存访问架构,都需要绑定CPU内核,不然会有如下影响

  • CPU多核架构:一个CPU包含多个物理核心,运行过程中如果出现上下文切换,到一个新的CPU核又需要加载如栈指针、CPU寄存器的值等信息到一级缓存和二级缓存中去,这个加载是消耗时间的,所以需要将Redis实例和CPU核进行绑定。
  • 多CPU的NUMA架构:多个CPU环境下,位于不同的CPU的线程如果想通信必须依赖总线,这属于远程内存访问,这种跨CPU的访问消耗时间较多,就如Redis实例和网络中断程序,解决办法就是将Redis实例和网络中断程序绑定到一个CPU的物理核上,注意不能是逻辑核,因为Redis实例还存在子线程的情况,如果只是绑定在一个逻辑核上那么将会有子线程和主线程竞争资源的情况,显然是不可取的。

在NUMA架构下有可能出现绑错核的情况,因为在这个架构下CPU的逻辑核编号稍有不同,这是先将所有CPU的第一个逻辑核先编完号才会给第二个逻辑核编号,示例图如下所示

绑核命令如下

### 为实例绑定0和12号逻辑核,也就是0号物理核
[root@zzf993 1841352]# taskset -c 0,12 ./redis-server
举报
评论 0