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 中、有些热点用户会关注大量好友,这时可以按时间或其他维度拆分到多个集合中。
- 1)
- 如何发现大对象
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 缓慢的操作系统等。
- fork 操作发生在 RDB 和 AOF 重写时,Redis 主线程调用 fork 操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如果
- AOF 刷盘阻塞
- 当我们开启 AOF 持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对 AOF 文件做 fsync 操作。当硬盘压力过大时,fsync 操作需要等待,直到写入完成。
如果主线程发现距离上一次的 fsync 成功超过2秒,为了数据安全性它会阻塞直到后台线程执行 fsync 操作完成
。这种阻塞行为主要是硬盘压力引起,可以查看 Redis 日志识别出这种情况 - 也可以
查看 info persistence 统计中的 aof_delayed_fsync 指标
,每次发生 fdatasync 阻塞主线程时会累加。
- 当我们开启 AOF 持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对 AOF 文件做 fsync 操作。当硬盘压力过大时,fsync 操作需要等待,直到写入完成。
- HugePage 写操作阻塞
- 子进程在执行重写期间利用 Linux 写时复制技术降低内存开销,因此只有写操作时 Redis 才复制要修改的内存页。
三、外在原因
CPU 竞争
- 进程竞争
- Redis 是典型的 CPU 密集型应用,不建议和其他多核 CPU 密集型服务部署在一起。
- 当
其它进程过度消耗 CPU 时,将严重影响 Redis 吞吐量
。可以通过 top、sar 等命令定位到 CPU 消耗的时间点和具体进程。
- 绑定 CPU
- 部署 Redis 时为了充分利用多核 CPU,通常一台机器部署多个实例。常见的一种优化是
把 Redis 进程绑定到 CPU 上
,用于降低 CPU 频繁上下文切换的开销。
- 部署 Redis 时为了充分利用多核 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(开发与运维):03---Windows下安装Redis数据库
Redis(开发与运维):06---键的基本命令(KEYSSCANEXISTSRENAMEDELRANDOMKEYTYPEDBSIZE)