【Redis】《Redis 开发与运维》笔记-Chapter7-Redis的噩梦:阻塞

本文详细探讨了Redis在高并发场景下的阻塞问题,包括慢查询、大对象、CPU饱和、持久化阻塞等内在原因,以及CPU竞争、内存交换、网络问题等外在因素。作者提供了异常检测方法、优化建议和实用工具,助你有效诊断和提升Redis性能。
七、Redis的噩梦:阻塞

1、发现阻塞

  • 常见的做法是在应用方加入异常统计并通过邮件/短信/微信报警,以便及时发现通知问题。
  • 在实现异常统计时要注意,由于Redis调用API会分散在项目的多个地方,每个地方都监听异常并加入监控代码必然难以维护。这时可以借助于日志系统,如Java语言可以使用logback或log4j 。
  • 借助日志系统统计异常的前提是,需要项目必须使用日志API进行异常统一输出,比如所有的异常都通过logger.error打印,这应该作为开发规范推广。其他编程语言也可以采用类似的日志系统实现异常统计报警。

2、内在原因-API或数据结构使用不合理

对于高并发的场景我们应该尽量避免在大对象上执行算法复杂度超过O(n)的命令。

1)如何发现慢查询

  • Redis原生提供慢查询统计功能,执行slowlog get{n}命令可以获取最近的n条慢查询命令,默认对于执行超过10毫秒的命令都会记录到一个定长队列中,线上实例建议设置为1毫秒便于及时发现毫秒级以上的命令。
  • 如果命令执行时间在毫秒级,则实例实际OPS只有1000左右。
  • 慢查询队列长度默认128,可适当调大。

发现慢查询后,可以按照以下两个方向去调整:

  • 修改为低算法度的命令,如hgetall改为hmget等,禁用keys、sort等命令。
  • 调整大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。大对象拆分过程需要视具体的业务决定,如用户好友集合存储在Redis中,有些热点用户会关注大量好友,这时可以按时间或其他维度拆分到多个集合中。

2)如何发现大对象

  • Redis本身提供发现大对象的工具,对应命令: redis-cli -h{ip} -p{port} bigkeys 。内部原理采用分段进行scan操作,把历史扫描过的最大对象统计出来便于分析优化。

3、内在原因-CPU饱和

  • 单线程的Redis处理命令时只能使用一个CPU。CPU饱和是指Redis把单核CPU使用率跑到接近100%。
  • 使用top命令很容易识别出对应Redis进程的CPU使用率。
  • 对于这种情况,首先判断当前Redis的并发量是否达到极限,建议使用统计命令redis-cli -h{ip} -p{port} --stat获取当前Redis使用情况,该命令每秒输出一行统计信息。
  • 还有一种情况是过度的内存优化,这种情况有些隐蔽,需要我们根据info commandstats统计信息分析出命令不合理开销时间。

4、内在原因-持久化阻塞

1)fork阻塞

  • fork操作发生在RDB和AOF重写时,Redis主线程调用fork操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如果fork操作本身耗时过长,必然会导致主线程的阻塞。
  • 可以执行info stats命令获取到latest_fork_usec指标,表示Redis最近一次fork操作耗时,如果耗时很大,比如超过1秒,则需要做出优化调整,如避免使用过大的内存实例和规避fork缓慢的操作系统等。

2)AOF刷盘阻塞

  • 当我们开启AOF持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对AOF文件做fsync操作。
  • 当硬盘压力过大时,fsync操作需要等待,直到写入完成。如果主线程发现距离上一次的fsync成功超过2秒,为了数据安全性它会阻塞直到后台线程执行fsync操作完成。这种阻塞行为主要是硬盘压力引起,可以查看Redis日志识别出这种情况,当发生这种阻塞行为时,会打印如下日志:
    Asynchronous AOF fsync is taking too long (disk is busy) . Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis .
  • 也可以查看info persistence统计中的aof_delayed_fsync指标,每次发生fdatasync阻塞主线程时会累加。
  • 硬盘压力可能是Redis进程引起的,也可能是其他进程引起的,可以使用iotop查看具体是哪个进程消耗过多的硬盘资源。

3)HugePage写操作阻塞

  • 子进程在执行重写期间利用Linux写时复制技术降低内存开销,因此只有写操作时Redis才复制要修改的内存页。
  • 对于开启Transparent HugePages的操作系统,每次写命令引起的复制内存页单位由4K变为2MB,放大了512倍,会拖慢写操作的执行时间,导致大量写操作慢查询。

5、外在原因-CPU竞争

CPU竞争问题如下:

  • 进程竞争:Redis是典型的CPU密集型应用,不建议和其他多核CPU密集型服务部署在一起。当其他进程过度消耗CPU时,将严重影响Redis吞吐量。可以通过top、sar等命令定位到CPU消耗的时间点和具体进程。
  • 绑定CPU:部署Redis时为了充分利用多核CPU,通常一台机器部署多个实例。常见的一种优化是把Redis进程绑定到CPU上,用于降低CPU频繁上下文切换的开销。这个优化技巧正常情况下没有问题,但是存在例外情况,
  • 当Redis父进程创建子进程进行RDB/AOF重写时,如果做了CPU绑定,会与父进程共享使用一个CPU。子进程重写时对单核CPU使用率通常在90%以上,父进程与子进程将产生激烈CPU竞争,极大影响Redis稳定性。因此对于开启了持久化或参与复制的主节点不建议绑定CPU。

6、外在原因-内存交换

内存交换(swap)对于Redis来说是非常致命的,Redis保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把Redis使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的Redis性能急剧下降。

识别Redis内存交换的检查方法如下:

1、查询Redis进程号:
# redis cli p 6383 info server | grep process_id
process_id :4476

2、根据进程号查询内存交换信息:
# cat /proc/4476/smaps | grep Swap
Swap : 0 kB
Swap : 0 kB
Swap : 4 kB
Swap : 0 kB
Swap : 0 kB
......

如果交换量都是0KB或者个别的是4KB,则是正常现象,说明Redis进程内存没有被交换。

预防内存交换的方法有:

  • 保证机器充足的可用内存。
  • 确保所有Redis实例设置最大可用内存(maxmemory),防止极端情况下Redis内存不可控的增长。
  • 降低系统使用swap优先级,如echo 10>/proc/sys/vm/swappiness,

7、外在原因-网络问题

1)连接拒绝
当出现网络闪断或者连接数溢出时,客户端会出现无法连接Redis的情况。我们需要区分这三种情况:网络闪断、Redis连接拒绝、连接溢出。

  • 网络闪断。一般发生在网络割接或者带宽耗尽的情况,对于网络闪断的识别比较困难,常见的做法可以通过sar-n DEV查看本机历史流量是否正常,或者借助外部系统监控工具(如Ganglia)进行识别。
  • Redis连接拒绝。Redis通过maxclients参数控制客户端最大连接数,默认10000。当Redis连接数大于maxclients时会拒绝新的连接进入,info stats的rejected_connections统计指标记录所有被拒绝连接的数量。
    1. Redis使用多路复用IO模型可支撑大量连接,但是不代表可以无限连接。客户端访问Redis时尽量采用NIO长连接或者连接池的方式。
    2. 当Redis用于大量分布式节点访问且生命周期比较短的场景时,如比较典型的在Map/Reduce中使用Redis。因为客户端服务存在频繁启动和销毁的情况且默认Redis不会主动关闭长时间闲置连接或检查关闭无效的TCP连接,因此会导致Redis连接数快速消耗且无法释放的问题。这种场景下建议设置tcp-keepalive和timeout参数让Redis主动检查和关闭无效连接。
  • 连接溢出。这是指操作系统或者Redis客户端在连接时的问题。
    1. 进程限制:
      1. 操作系统一般会对进程使用的资源做限制,其中一项是对进程可打开最大文件数控制,通过ulimit-n查看,通常默认1024。
      2. 由于Linux系统对TCP连接也定义为一个文件句柄,因此对于支撑大量连接的Redis来说需要增大这个值,如设置ulimit-n65535 ,防止Too many open files错误。
    2. backlog队列溢出:
      1. 系统对于特定端口的TCP连接使用backlog队列保存。Redis默认的长度为511,通过tcp-backlog参数设置。
      2. 如果Redis用于高并发场景为了防止缓慢连接占用,可适当增大这个设置,但必须大于操作系统允许值才能生效。
      3. 系统的backlog默认值为128,使用echo 511>/proc/sys/net/core/somaxconn命令进行修改。
      4. 可以通过netstat -s命令获取因backlog队列溢出造成的连接拒绝统计。
      5. 如果怀疑是backlog队列溢出,线上可以使用cron定时执行netstat -s | grep overflowed统计,查看是否有持续增长的连接拒绝情况。
# netstat -s | grep overflowed
663 times the listen queue of a socket overflowed

2)网络延迟

  • 常见的物理拓扑按网络延迟由快到慢可分为:同物理机>同机架>跨机架>同机房>同城机房>异地机房。
  • 但它们容灾性正好相反,同物理机容灾性最低而异地机房容灾性最高。
  • Redis提供了测量机器之间网络延迟的工具,在redis-cli -h{host} -p{port}命令后面加入如下参数进行延迟测试:
    1. –latency:持续进行延迟测试,分别统计:最小值、最大值、平均值、采样次数。
    2. –latency-history:统计结果同–latency,但默认每15秒完成一行统计,可通过-i参数控制采样时间。
    3. –latency-dist:使用统计图的形式展示延迟统计,每1秒采样一次。

3)网卡软中断

  • 网卡软中断是指由于单个网卡队列只能使用一个CPU,高并发下网卡数据交互都集中在同一个CPU,导致无法充分利用多核CPU的情况。
  • 网卡软中断瓶颈一般出现在网络高流量吞吐的场景,如下使用“top+数字1”命令可以很明显看到CPU1的软中断指标(si)过高。
  • Linux在内核2.6.35以后支持Receive Packet Steering(RPS),实现了在软件层面模拟硬件的多队列网卡功能。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值