Redis变慢?深入浅出Redis性能诊断系列文章(四)
(本文首发于“数据库架构师”公号,订阅“数据库架构师”公号,一起学习数据库技术,助力职业发展)
本篇为Redis性能问题诊断系列的第四篇,也是最后一篇,主要从应用程序、系统、服务器硬件及网络系统等层面上进行讲解,重点分享了哪些配置需要重点关注和调整优化,才能最大程度的发挥Redis的处理能力;
一、服务器预留足够内存,监控SWAP使用
Swap是操作系统层面行为,指当服务器内存不足时,会将原本在内存中的一部分数据拿出放入磁盘,如果再次访问这部分数据就会响应很慢,因为磁盘的访问速度是远远不如内存的。
Redis作为内存数据库,有个常识一定要记住:所有的数据默认都是在内存中,不存在一部分在内存一部分在磁盘中的情况,除非被迫发生了SWAP。
说明:Redis在2.6版本之前有个VM【虚拟内存】特性,可以支持数据存放在内存和磁盘中,不过带来的性能波动影响太大,就被废弃了。但现在网上还有不少人在传抄相关VM特性的文章,太有误导性!
可以通过以下方式来查看 Redis 进程是否使用到了 Swap:
1.获取redis对应的进程id
shell> redis-cli info | grep process_id
2.查看 Swap 使用情况
shell>cat /proc/$pid/smaps | egrep '^(Swap|Size)'
输出结果如下:
Size: 1492 kB
Swap: 0 kB
Size: 32 kB
Swap: 0 kB
Size: 2196 kB
Swap: 0 kB
Size: 2048 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 1576 kB
上图中size代表Redis进程占用的一块内存空间大小,并对应一个Swap。
Swap后的数字表示该内存空间有多少已经被换到磁盘上了,如果两者相等,则代表这块内存空间的数据全都被换到了上了。
针对使用swap的解决方案可以参考如下:
a.提高Redis所在服务器的内存并预留可用内存,建议剩余可用内存至少保留20%以上;
b.Redis单独部署或降低单机部署实例数量,不和其他应用程序混合部署,避免多服务争用内存导致Redis数据被swap到磁盘上。
平时对Redis所在服务器的剩余可用内存及Swap 使用情况进行监控,在内存不足或使用到 Swap 时报警出来,及时干预处理。
二、.使用万兆网卡,避免网络带宽打满
Redis 的高性能,除了数据都在内存之外,就在于网络 IO 了,如果网络存在瓶颈,那么也会严重影响 Redis 的性能。
网络带宽过载的情况下,比如带宽被打满,那么服务器在 TCP 层和网络层就会出现数据包发送延迟、丢包等情况。
如果确实出现这种情况,我们需要及时核对原因,主要有以下几个:
a.某个Redis服务访问量过大,可能QPS高叠加操作的Key过大,导致网络满载;
b.所在服务器网卡上限过小,如千兆网卡或者虚拟机限速200MB等;
c.服务器网卡/网线/驱动等问题,导致万兆的网卡降频为千兆或者被限流。
查看网卡速度:ethtool eth0
针对网络过载可以采用以下方案来解决:
a.降低单机部署Redis实例个数,打散重度使用网络带宽的Redis服务到多台服务器;
b.对Redis服务使用的网络带宽进行监控,可以关注性能指标:instantaneous_input_kbps、instantaneous_output_kbps
c.使用万兆网卡的服务器,并添加对带宽上限【警惕网卡从万兆降为千兆】、网络带宽使用、丢包情况的监控;
d.遵守Redis使用规范,比如控制写入Redis中的VALUE大小、限制使用smembers或hgetall等操作的集合成员个数等。
三、根据场景选择是否使用SSD磁盘
大家要根据自己的实际场景判断,比如使用单节点且用于缓存服务的情形,就不需要使用SSD磁盘。
但是如果希望使用Redis的持久化能力来保证数据安全,那么磁盘IO能力就不得不重视了。
这里对于Redis的持久化不做详细介绍,具体可以参考上篇文章。
Redis中对IO比较敏感的操作主要有下面几类:
a.AOF持久化,相关磁盘操作有: AOF命令落盘、AOF文件重写;
b.RDB持久化,相关磁盘操作有:主从复制主节点RDB生成快照、从节点加载RDB文件、备份触发RDB快照、配置触发自动RDB快照
上面列出的都会严重依赖磁盘IO能力,特别是单机部署多Redis实例的情况,如果磁盘IO能力一般,那么就会严重影响Redis的性能。
四、系统参数配置
1.内存分配策略参数vm.overcommit_memory
Redis启动给出Warning提示:
WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.
这里首先解答下什么是Overcommit?
Linux对大部分申请内存的请求都回复"yes",以便能跑更多更大占用内存的程序。因为申请内存后,并不会马上使用内存。这种技术叫做Overcommit。
当linux发现内存不足时,会发生OOM killer(OOM=out-of-memory)。它会选择杀死一些进程(用户态进程,不是内核线程),以便释放内存。
overcommit_memory的几个主要值的说明:
0:表⽰内核将检查是否有⾜够的可⽤内存供应⽤进程使⽤;如果有⾜够的可⽤内存,内存申请允许;否则,内存申请失败,并把错误返回给应⽤进程;
1: 表⽰内核允许分配所有的物理内存,⽽不管当前的内存状态如何;
2: 表⽰内核允许分配超过所有物理内存和交换空间总和的内存。
这里建议调整为1,相关调整方式:
永久生效:
编辑vim /etc/sysctl.conf ,改vm.overcommit_memory=1,然后sysctl -p 使配置文件生效
临时生效:
echo 1 > /proc/sys/vm/overcommit_memory
上述日志中的Background save代表的是bgsave和bgrewriteaof, 如果当前可用内存不足, 操作系统应该如何处理fork操作呢?
如果vm.overcommit_memory=0, 代表如果没有可用内存, 就申请内存失败, 对应到Redis就是执行fork失败, 在Redis的日志会出现:
Cannot allocate memory
Redis建议把这个值设置为1, 是为了让fork操作能够在低内存下也执行成功。
2.操作系统内存大页参数配置
Redis启动给出Warning提示:
WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
意思是:你的Redis所在服务器当前使用的是内存大页机制,可能导致Redis访问延迟和内存使用问题。
那什么是内存大页呢?
应用程序向操作系统申请内存空间时,是按内存页为单位进行申请的,默认大小是4KB。不过Linux从 2.6.38内核版本开始,支持了内存大页机制,可以允许向操作系统一次申请 2MB 大小的内存。由于申请的内存单位变大,也意味着申请耗时相对变长。
那对于 Redis服务会有什么影响呢?
当 Redis 在执行后台 RDB 和 AOF rewrite 时,采用 fork 子进程的方式来处理。但主进程 fork 子进程后,此时的主进程依旧是可以接收写请求的,而进来的写请求,会采用 Copy On Write(写时复制)的方式操作内存数据。
也就是说,主进程一旦有数据需要修改,Redis 并不会直接修改现有内存中的数据,而是先将这块内存数据拷贝出来,再修改这块新内存的数据,这就是所谓的「写时复制」。
写时复制可以理解为:需要发生写操作哪个Key,就需要先拷贝这个Key,然后再修改。
这里注意,主进程在修改拷贝内存数据时,这个阶段就涉及到新内存的申请。如果此时操作系统开启了内存大页,那么在此期间,应用程序即便只修改 10B 的数据,Redis 在申请内存时也会以 2MB 为单位向操作系统申请,申请内存的耗时变长,进而导致每个写请求的延迟增加,影响到 Redis 性能。
所以为了避免过多的内存申请,我们需要关闭内存大页机制:
cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
如果输出选项是 always,就表示目前开启了内存大页机制,我们需要关掉它:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
其实,操作系统提供的内存大页机制,其优势是可以在一定程序上降低应用程序申请内存的次数。
比如针对大数据、对象存储相关的服务来说可能会更好,但是对于 Redis 这种对性能和延迟极其敏感的数据库来说,我们希望 Redis 在每次申请内存时,耗时尽量短,建议关闭这个参数。
五、其他影响访问Redis的性能的因素
1.应用程序配置不合理
a.应配置合理的连接数等相关参数,比如jedis,默认MaxActive最大连接数只有8个,在高QPS时就会出现无法获取新连接的提示:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool … Caused by: java.util.NoSuchElementException: Timeout waiting for idle object at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
b.驱动版本过低。低版本的Driver连接高版本Redis,除了无法使用最新的特性外,还会经常出现连接不释放、内存泄露、访问缓慢等问题。
2.使用连接池,避免使用短连接模式.特别是使用PHP的应用,频繁的连接创建与销毁,在高QPS访问时网络开销巨大;
3.CPU绑核及主频影响
Redis是单线程模型处理处理用户需求,那么处理的吞吐、效率就会极度依赖CPU的处理能力,所以选型CPU时,如果部署的Redis平时QPS较高,可以采购主频高些的CPU.
另外现在的CPU都是多核处理,为了提高服务性能,降低应用程序在多个 CPU 核心之间的上下文切换带来的性能损耗,通常采用的方案是进程绑定 CPU 的方式提高性能。
但是Redis的绑核操作过于复杂,对于单机多实例的管理挑战过高,再加上Redis 的性能已经足够优秀,不建议绑定 CPU来处理,这里也不做深入说明。
最后总结:
本篇为Redis性能诊断的完结篇,通过总结常见的可能导致访问响应延迟、甚至阻塞的问题的各种场景,以及如何定位及分析针对性地提供了解决方案。
但是由于篇幅限制,关于 Redis 的很多细节也无法全部展开,后续也会对Redis使用的各种技巧、架构及内部的工作原理深入分享,欢迎保持关注。
如果这篇文章对你有帮助,还请帮忙点赞、在看、转发 一下,你的支持会激励我们输出更多高质量的文章,非常感谢!
如果你还想看更多优质文章,欢迎关注我的公众号「数据库架构师」,提升数据库技能。