Redis 开发与运维Redis 的噩梦:阻塞

Posted 木兮同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 开发与运维Redis 的噩梦:阻塞相关的知识,希望对你有一定的参考价值。

文章目录


一、发现阻塞

异常监控

  • 当 Redis 阻塞时,线上应用服务应该最先感知到,这时应用方会收到大量 Redis 超时异常,比如 Jedis 客户端会抛出 JedisConnectionException 异常。常见的做法是在应用方加入异常统计并通过邮件/短信/微信报警,以便及时发现通知问题。
  • 开发人员需要处理如何统计异常以及触发报警的时机。何时触发报警一般根据应用的并发量决定,如 1 分钟内超过 10 个异常触发报警。
  • 由于 Redis 调用 API 会分散在项目的多个地方,每个地方都监听异常并加入监控代码必然难以维护。这时可以借助于日志系统 Java 语言可以使用 logback 或 log4j 。当异常发生时,异常信息最终会被日志系统收集到 Appender(输出目的地),默认的 Appender 一般是具体的日志文件,开发人员可以自定义一个Appender,用于专门统计异常和触发报警逻辑

异常定位

  • 应用方加入异常监控后,收到异常报警,通常会去服务器查看错误日志。这时如果应用操作的是多个 Redis 节点,如何确定是哪一个节点超时还是所有的节点都有超时呢?
  • 所以异常信息中需要打印 ip 和 port 数据,Jedis 中只需要修改 Connection 类下的 connect、sendCommand、readProtocolWithCheckingBroken 方法 专门捕获连接,发送命令,协议读取事件的异常。

CacheCloud

  • 除了在应用方加入统计报警逻辑之外,还可以借助 Redis 监控系统发现阻塞问题,当监控系统检测到 Redis 运行期的一些关键指标出现不正常时会触发报警。
  • 比如可以使用 CacheCloud 监控系统,监控系统所监控的关键指标有很多,如命令耗时、慢查询、持久化阻塞、连接拒绝、CPU/内存/网络/磁盘使用过载等等。

二、内在原因

API 或数据结构使用不合理

  • 如何发现慢查询
    • Redis 原生提供慢查询统计功能,执行 slowlog get n 命令可以获取最近的 n 条慢查询命令,默认对于执行超过 10 毫秒的命令都会记录到一个定长队列中,线上实例建议设置为 1 毫秒便于及时发现毫秒级以上的命令。
    • 如果命令执行时间在毫秒级,则实例实际QPS只有1000左右。慢查询队列长度默认128,可适当调大。
    • 慢查询本身只记录了命令执行时间,不包括数据网络传输时间和命令排队时间,因此客户端发生阻塞异常后,可能不是当前命令缓慢,而是在等待其他命令执行。需要重点比对异常和慢查询发生的时间点,确认是否有慢查询造成的命令阻塞排队。
    • 发现慢查询后,开发人员可以按照以下两个方向去调整:
      • 1)修改为低算法度的命令,如 hgetall 改为 hmget 等,禁用 keys、sort 等命令。
      • 2)调整大对象,缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。大对象拆分过程需要视具体的业务决定,如用户好友集合存储在 Redis 中、有些热点用户会关注大量好友,这时可以按时间或其他维度拆分到多个集合中。
  • 如何发现大对象
    • Redis 本身提供发现大对象的工具,对应命令:redis-cli -h ip -p port --bigkeys。内部原理采用分段进行 scan 操作,把历史扫描过的最大对象统计出来变与分析优化。

CPU 饱和

  • CPU 饱和是指 Redis 把单核 CPU 使用率跑到接近 100%,使用 top 命令很容易识别出对应 Redis 进程的 CPU 使用率。
  • 可以使用命令 redis-cli -h ip -p port --stat 获取当前 Redis 使用情况,该命令每秒输出一行统计信息。
  • 对于这种情况,垂直层面的命令优化很难达到效果,这时就需要做集群化水平扩展来分摊 QPS 压力

持久化阻塞

  • fork 阻塞
    • fork 操作发生在 RDB 和 AOF 重写时,Redis 主线程调用 fork 操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如果 fork 操作本身耗时过长,必然会导致主线程的阻塞
    • 可以执行 info stats 命令获取到 latest_fork_usec 指标,表示 Redis 最近一次 fork 操作耗时,如果耗时很大,比如超过 1 秒,则需要做出优化调整,如避免使用过大内存实例和规避 fork 缓慢的操作系统等。
  • AOF 刷盘阻塞
    • 当我们开启 AOF 持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对 AOF 文件做 fsync 操作。当硬盘压力过大时,fsync 操作需要等待,直到写入完成。如果主线程发现距离上一次的 fsync 成功超过2秒,为了数据安全性它会阻塞直到后台线程执行 fsync 操作完成。这种阻塞行为主要是硬盘压力引起,可以查看 Redis 日志识别出这种情况
    • 也可以查看 info persistence 统计中的 aof_delayed_fsync 指标,每次发生 fdatasync 阻塞主线程时会累加。
  • HugePage 写操作阻塞
    • 子进程在执行重写期间利用 Linux 写时复制技术降低内存开销,因此只有写操作时 Redis 才复制要修改的内存页。

三、外在原因

CPU 竞争

  • 进程竞争
    • Redis 是典型的 CPU 密集型应用,不建议和其他多核 CPU 密集型服务部署在一起。
    • 其它进程过度消耗 CPU 时,将严重影响 Redis 吞吐量。可以通过 top、sar 等命令定位到 CPU 消耗的时间点和具体进程。
  • 绑定 CPU
    • 部署 Redis 时为了充分利用多核 CPU,通常一台机器部署多个实例。常见的一种优化是把 Redis 进程绑定到 CPU 上,用于降低 CPU 频繁上下文切换的开销。

内存交换

  • 内存交换对于 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 优先级

网络问题

  • 连接拒绝,需要分三种情况讨论:
    • 网络闪断。一般发生在网络割接或者贷款耗尽的情况
    • Redis 连接拒绝。Redis 通过 maxclients 参数控制客户端最大连接数,默认 10000。大于时会拒绝新的连接进入,info stats 的 rejected_connections 统计指标记录所有被拒绝连接的数量。
    • 连接溢出。是指操作系统或者 Redis 客户端在连接时的问题,这个问题原因比较多,有可能是进程限制、backlog 队列溢出等原因。
  • 网络延迟
    • 取决于客户端到 Redis 服务器之间的网络环境,主要包括它们之间的物理拓扑和带宽占用情况。
  • 网卡软中断
    • 网卡软中断是指由于单个网卡队列只能使用一个 CPU,高并发下网卡数据交互都集中在同一个 CPU,导致无法充分利用多核 CPU 的情况。

来源:《Redis 开发与运维》第 7 章 Redis 的噩梦:阻塞

以上是关于Redis 开发与运维Redis 的噩梦:阻塞的主要内容,如果未能解决你的问题,请参考以下文章

Redis 开发与运维Redis 的噩梦:阻塞

Redis(开发与运维):03---Windows下安装Redis数据库

Redis(开发与运维):06---键的基本命令(KEYSSCANEXISTSRENAMEDELRANDOMKEYTYPEDBSIZE)

Redis开发与运维

Redis 开发与运维Redis Sentinel 哨兵

Redis 开发与运维Redis Sentinel 哨兵