前言

  Redis 是典型的单线程架构,所有的读写操作都是在一条主线程完成的。当Redis 用于高并发场景时候,这条线程就成了它的生命线,如果出现阻塞,哪怕是很短时间,对于我们的应用来说都是噩梦。导致阻塞问题的场景大致分为内在原因和外在原因。

  内在原因包括:

    不合理使用API 或者 数据结构,CPU 饱和、持久化阻塞等

    外在原因包括:CPU竞争,内存交换,网络问题。

发现阻塞  

常见的做法是通过在应用方加入异常统计并通过邮件/短信/微信报警  ,以便及时他发现问题。开发人员需要处理如何统计异常预计触发报警的时机。任何处罚处罚报警一般根据应用的并发量决定,如 1 分钟超过10 个异常触发报警。在实现异常统计时需要注意,由于 Redis 调用 API 会分散在项目的多个地方,每个地方都监听异常并加入监控代码必然难以维护。这时候可以借助日志系统,当异常发生时候,异常信息最终会被日志系统收集到 Appender (输出目的地),默认的 Appender 一般是具体的日志文件,开发人员可以自定义一个 Appender ,用于专门统计异常和处罚报警逻辑。

  2、除了在应用方加入统计报警逻辑之外,还可以借助 Redis 监控系统发现阻塞问题,当监控系统检测到 Redis 运行期的一些关键指标不正常触发报警。

     监控系统所监控的关键啊指标有很多,比如命令耗时,慢查询,持久化阻塞,链接拒绝,CPU/内存/网络/磁盘使用情况  

内在原因

  定位到具体的Redis 节点异常后,首先要排查是否是 Redis 自身的原因导致的,围绕下面几个方面排查

    1、API 或 数据结构使用不合理

    2、CPU 饱和的问题

    3、持久化相关的阻塞

  

  1、API 或 数据结构使用不合理  

    通常 Redis 执行命令速度非常快,但是也存在例外,如对一个包含上万个元素的 hash 执行 hgetall 操作,由于数据量比较大并且命令算法复杂度是O(n),这条命令执行速度必然很慢。这个问题就是典型的不合理使用 API 和数据结构,对于高并发的场景应该进来避免在大对象上执行算法复杂度超过O(n)的命令。

    1、如何发现慢查询

慢查询本身只记录命令执行时间,不包括网络传输和命令排队时间。

   发现慢查询后,开发人员需要做出调整。可以按照以下两个方向去调整:

    1、修改为低算法度的命令,如 hgetall 改为 hmget 等,禁用 keys、sort 命令。

    2、调整大对象,减缩大对象数据或把大对象拆分长多个小对象,防止一次命令操作过多的数据,

    

    服务器配置慢查询参数:

      1、slowlog-log-slower-than  指定命令的执行时间超过多少微秒(1秒等于 1000000 微秒)的命令请求会被记录到日志上。

        eg : 如果这个选项值为100,那么执行时间超过 100 微秒的命令就会被记录到慢查询日志

      2、slowlog-max-len 选项指定服务器最多保存多少条慢查询日志

        服务器使用先进先出的方式保存多条慢查询日志,当服务器存储的慢查询日志数量等于slowlog-max-len 选择值的时候,服务器在添加一个新的慢查询日志之前,会先将旧的慢查询日删除。

    慢查询命令

      slowlog get : 查询所有慢查询命令

      slowlog get 10 : 查询最近的 10 条慢查询

    

redisson阻塞队列 redis导致线程阻塞_Redis

 

 

    2、如何发现大对象(value值过大)

      方法一:通过上面,我们可以找到慢查询的key , 然后用 memory usage + key 可以查询所占的空间

10.10.13.50:61501> memory usage cus_parent_1314
1489

1489/1024 = 1.45 k

      方法二:

      

redis-cli -h {ip} -p {port} --bigkeys 
      redis-cli --bigkeys
      /home/bmc/redis-db/redis-5.x/src/redis-cli --raw -h 10.10.13.50 -p 61501 --bigkeys -a wix.s#3@b

      该命令使用scan方式对key进行统计,所以使用时无需担心对redis造成阻塞。

      

redisson阻塞队列 redis导致线程阻塞_慢查询_02

 

  2、CPU 饱和

    单线程的 Redis 处理命令只能使用一个 CPU。而 CPU 饱和是指把单核的 CPU 使用率跑到接近 100%,使用 top 命令很容易识别对应 redis 进程的CPU使用率。CPU 饱和是非常危险的,将导致 Redis 无法处理更多的命令,验证影响吞吐量和应用方的稳定性。对于这种情况,首先判断 当前Redis 的并发量是否达到极限,建议使用统计命令 redis-cli -h {ip} -p {port} --stat 获取当前 redis 使用情况,改命令每秒输出一行统计信息。

      命令:

/home/bmc/redis-db/redis-5.x/src/redis-cli --raw -h 10.10.13.50 -p 61501 --stat -a wix.s#3@b

        

      

redisson阻塞队列 redis导致线程阻塞_redisson阻塞队列_03

        每秒平均处理 1个请求。

      

    

redisson阻塞队列 redis导致线程阻塞_redisson阻塞队列_04

 

   以上输出是一个接近饱和的 Redis 实例统计的信息,它每秒处理 6 万+ 个请求。对于这种情况,垂直层面的命令优化很难达到效果,这个时候就需要集群水平扩展分摊 OPS 压力,如果只有几百或几千 OPS 的 Redis 实例就接近 CPU 饱和是很不正常的,有可能使用了高复杂度算法命令。

 

   还有一种情况是内存过度优化,这种情况有些隐蔽,需要我们根据 info commandstats 统计信息分析不合理开销时间。

  

   3、持久化阻塞

    对于开启了持久化功能的 Redis 节点,需要排查是否持久化导致阻塞。持久化引起主线程阻塞的操作主要有:

      fork 阻塞,AOF 刷盘阻塞,HugePage 写操作阻塞。

    1、fork 阻塞

   fork 阻塞发生在 RDB 和 AOF 重写时候,Redis 主线程调用 fork 操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如果 fork 操作本身耗时过长,必然会导致主线程阻塞。

   可以执行 info status [第二章的命令] 获取到 latest_fork_usec 指标,表示 redis 最近一次 fork 操作耗时,如果很大,比如超过 1s ,则需要做出优化调整,如避免使用过大的的内存实例和规避 fork 缓慢的操作系统等。

    2、AOF刷盘阻塞  

   开启 AOF 持久化功能后,文件刷盘的方式一般采用一秒一次,后台线程每秒对 AOF 文件对 fsync 操作。当硬盘压力过大的时候, fsync 操作需要等待,直到写入完成。如果主线程发现距离上一次的 fsync 成功超过 2 秒。为了数据安全性他会阻塞直到后台现场执行 fsync 操作。这种阻塞行为主要是 硬盘的压力引起,可以查看 redis 日志识别这种情况,当发生这种阻塞行为时候,会打印如下日志。

    Asynchronous AOF fsync is taking too long (disk is busy). Writing the AOFbuffer without waiting for fsync to complete, this may slow down Redis.

   也可以查看 info persistence 统计中的 aof_delayed_fsync 指标,每次发生 fdatasync 阻塞线程主线时会累加。定位阻塞问题后见第5.3 节的AOF 追加阻塞部分     

    3、HugePage 写操作阻塞

    子进程在执行重写期间利用 Linux 写时复制技术降低内存开销,因此只有写操作时候 Redis 才复制要修改的内存页。对于开启 transparent HugePages 的

操作系统,每次写命令引起的复制内存页单位由 4K 变为 2MB。放大了 512 倍。会拖慢写操作的执行时间,导致大量的写操作慢查询。例如简单的incr 命令也会出现在慢查询中。

 

 

 外在原因

  Redis 阻塞外在除了内在原因外,还需要排查是否可能是外在原因。主要的外在原因:1、CPU 竞争  2、内存交换  3、网络问题

  

  1、CPU 竞争

      1、进程竞争:Redis 是典型的 CPU 密集型应用,不建议和其他多核 CPU 密集服务部署在一起,当其他进程过度消耗 CPU 时候,将严重影响 Redis 的吞吐量,可以通过 top 、sar 等命令定位到 CPU 消耗时间点和具体进程,这个问题比较容易发现,需要调整服务之间部署结构。

      2、绑定 CPU : 部署 Redis 时为了充分利用多核 CPU 通常一个机器部署多个实例。常见的一种优化是把 Redis 进程绑定到 CPU 上,用于降低 CPU 频繁上下文切换的开销。

  2、内存交换

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

        1、查询 redis 的进程号

        

redisson阻塞队列 redis导致线程阻塞_redis_05

 

 

        2、根据进程号查询内存交换信息

        

# cat /proc/583/smaps | grep Swap

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

        预防内存交换的方法有:

          1、保证机器充足的可用内存

          2、确保所有 Redis 实例设置最大可以用内存(maxmemory),防止极端情况下 Redis 内存不可控的增长。

   3、网络问题    

      1、连接拒绝

        1、网络闪退

           一般发生在网络

        2、Redis 链接拒绝

 

        3、连接溢出

      2、网络延迟

 

      3、网卡软中断