乐趣区

关于java:Redis-忽然变慢了如何排查并解决

Redis 通常是咱们业务零碎中一个重要的组件,比方:缓存、账号登录信息、排行榜等。

一旦 Redis 申请提早减少,可能就会导致业务零碎“雪崩”。

我在独身红娘婚恋类型互联网公司工作,在双十一推出下单就送女朋友的流动。

谁曾想,凌晨 12 点之后,用户量暴增,呈现了一个技术故障,用户无奈下单,过后老大火冒三丈!

通过查找发现 Redis 报 Could not get a resource from the pool

获取不到连贯资源,并且集群中的单台 Redis 连贯量很高。

大量的流量没了 Redis 的缓存响应,间接打到了 MySQL,最初数据库也宕机了……

于是各种更改最大连接数、连贯期待数,尽管报错信息频率有所缓解,但还是 继续报错

起初通过线下测试,发现寄存 Redis 中的 字符数据很大,均匀 1s 返回数据

能够发现,一旦 Redis 提早过高,会引发各种问题。

明天「码哥」跟大家一起来剖析下如何确定 Redis 有性能问题和解决方案。

[toc]

Redis 性能出问题了么?

最大提早是客户端收回命令到客户端收到命令的响应的工夫,失常状况下 Redis 解决的工夫极短,在微秒级别。

当 Redis 呈现性能稳定的时候,比方达到几秒到十几秒,这个很显著咱们能够认定 Redis 性能变慢了。

有的硬件配置比拟高,当提早 0.6ms,咱们可能就认定变慢了。硬件比拟差的可能 3 ms 咱们才认为呈现问题。

那咱们该如何定义 Redis 真的变慢了呢?
所以,咱们须要对以后环境的 Redis 基线性能 做测量,也就是在一个零碎在低压力、无烦扰状况下的根本性能。

当你发现 Redis 运行时时的提早是基线性能的 2 倍以上,就能够断定 Redis 性能变慢了。

提早基线测量

redis-cli 命令提供了–intrinsic-latency 选项,用来监测和统计测试期间内的最大提早(以毫秒为单位),这个提早能够作为 Redis 的基线性能。

redis-cli --latency -h `host` -p `port`

比方执行如下指令:

redis-cli --intrinsic-latency 100
Max latency so far: 4 microseconds.
Max latency so far: 18 microseconds.
Max latency so far: 41 microseconds.
Max latency so far: 57 microseconds.
Max latency so far: 78 microseconds.
Max latency so far: 170 microseconds.
Max latency so far: 342 microseconds.
Max latency so far: 3079 microseconds.
45026981 total runs (avg latency: 2.2209 microseconds / 2220.89 nanoseconds per run).
Worst run took 1386x longer than the average latency.

留神:参数 100 是测试将执行的秒数。咱们运行测试的工夫越长,咱们就越有可能发现提早峰值。

通常运行 100 秒通常是适合的,足以发现提早问题了,当然咱们能够抉择不同工夫运行几次,防止误差。
「码哥」运行的最大提早是 3079 微秒,所以基线性能是 3079(3 毫秒)微秒。

须要留神的是,咱们要在 Redis 的服务端运行,而不是客户端。这样,能够 防止网络对基线性能的影响

能够通过 -h host -p port 来连贯服务端,如果想监测网络对 Redis 的性能影响,能够应用 Iperf 测量客户端到服务端的网络提早。

如果网络提早几百毫秒,阐明网络可能有其余大流量的程序在运行导致网络拥塞,须要找运维协调网络的流量调配。

慢指令监控

如何判断是否是慢指令呢?
看操作复杂度是否是 O(N)。官网文档对每个命令的复杂度都有介绍,尽可能应用O(1) 和 O(log N) 命令。

波及到汇合操作的复杂度个别为 O(N),比方汇合 全量查问 HGETALL、SMEMBERS,以及汇合的 聚合操作:SORT、LREM、SUNION 等。

有监控数据能够观测呢?代码不是我写的,不晓得有没有人用了慢指令。
有两种形式能够排查到:

  • 应用 Redis 慢日志性能查出慢命令;
  • latency-monitor(提早监控)工具。

此外,能够应用本人(top、htop、prstat 等)疾速查看 Redis 主过程的 CPU 耗费。如果 CPU 使用率很高而流量不高,通常表明应用了慢速命令。

慢日志性能

Redis 中的 slowlog 命令能够让咱们疾速定位到那些超出指定执行工夫的慢命令,默认状况下命令若是执行工夫超过 10ms 就会被记录到日志。

slowlog 只会记录其命令执行的工夫,不蕴含 io 往返操作,也不记录单由网络提早引起的响应慢。

咱们能够 依据基线性能来自定义慢命令的规范(配置成基线性能最大提早的 2 倍),调整触发记录慢命令的阈值。

能够在 redis-cli 中输出以下命令配置记录 6 毫秒以上的指令:

redis-cli CONFIG SET slowlog-log-slower-than 6000

也能够在 Redis.config 配置文件中设置,以微秒为单位。

想要查看所有执行工夫比较慢的命令,能够通过应用 Redis-cli 工具,输出 slowlog get 命令查看,返回后果的第三个字段以微秒位单位显示命令的执行工夫。

如果只须要查看最初 2 个慢命令,输出 slowlog get 2 即可。

示例:获取最近 2 个慢查问命令
127.0.0.1:6381> SLOWLOG get 2
1) 1) (integer) 6
   2) (integer) 1458734263
   3) (integer) 74372
   4) 1) "hgetall"
      2) "max.dsp.blacklist"
2) 1) (integer) 5
   2) (integer) 1458734258
   3) (integer) 5411075
   4) 1) "keys"
      2) "max.dsp.blacklist"

以第一个 HGET 命令为例剖析,每个 slowlog 实体共 4 个字段:

  • 字段 1:1 个整数,示意这个 slowlog 呈现的序号,server 启动后递增,以后为 6。
  • 字段 2:示意查问执行时的 Unix 工夫戳。
  • 字段 3:示意查问执行微秒数, 以后是 74372 微秒, 约 74ms。
  • 字段 4: 示意查问的命令和参数, 如果参数很多或很大, 只会显示局部并给数参数个数。
    以后命令是hgetall max.dsp.blacklist

Latency Monitoring

Redis 在 2.8.13 版本引入了 Latency Monitoring 性能,用于以秒为粒度监控各种事件的产生频率。

启用提早监视器的第一步是 设置提早阈值(单位毫秒)。只有超过该阈值的工夫才会被记录,比方咱们依据基线性能(3ms)的 3 倍设置阈值为 9 ms。

能够用 redis-cli 设置也能够在 Redis.config 中设置;

CONFIG SET latency-monitor-threshold 9

工具记录的相干事件的详情可查看官网文档:https://redis.io/topics/laten…

如获取最近的 latency

127.0.0.1:6379> debug sleep 2
OK
(2.00s)
127.0.0.1:6379> latency latest
1) 1) "command"
   2) (integer) 1645330616
   3) (integer) 2003
   4) (integer) 2003
  1. 事件的名称;
  2. 事件产生的最新提早的 Unix 工夫戳;
  3. 毫秒为单位的时间延迟;
  4. 该事件的最大提早。

如何解决 Redis 变慢?

Redis 的数据读写由单线程执行,如果主线程执行的操作工夫太长,就会导致主线程阻塞。

一起剖析下都有哪些操作会阻塞主线程,咱们又该如何解决?

网络通信导致的提早

客户端应用 TCP/IP 连贯或 Unix 域连贯连贯到 Redis。1 Gbit/s 网络的典型提早约为 200 us。

redis 客户端执行一条命令分 4 个过程:

发送命令-〉 命令排队 -〉 命令执行-〉 返回后果
这个过程称为 Round trip time(简称 RTT, 往返工夫),mget mset 无效节约了 RTT,但大部分命令(如 hgetall,并没有 mhgetall)不反对批量操作,须要耗费 N 次 RTT,这个时候须要 pipeline 来解决这个问题。

Redis pipeline 将多个命令连贯在一起来缩小网络响应往返次数。

慢指令导致的提早

依据上文的慢指令监控查问文档,查问到慢查问指令。能够通过以下两种形式解决:

  • 比方在 Cluster 集群中,将聚合运算等 O(N) 操作运行在 slave 上,或者在客户端实现。
  • 应用高效的命令代替。应用增量迭代的形式,防止一次查问大量数据,具体请查看 SCAN、SSCAN、HSCAN 和 ZSCAN 命令。

除此之外,生产中禁用 KEYS 命令,它只实用于调试。因为它会遍历所有的键值对,所以操作延时高。

Fork 生成 RDB 导致的提早

生成 RDB 快照,Redis 必须 fork 后盾过程。fork 操作(在主线程中运行)自身会导致提早。

Redis 应用操作系统的多过程 写时复制技术 COW(Copy On Write) 来实现快照长久化,缩小内存占用。

但 fork 会波及到复制大量链接对象,一个 24 GB 的大型 Redis 实例须要 24 GB / 4 kB * 8 = 48 MB 的页表。

执行 bgsave 时,这将波及调配和复制 48 MB 内存。

此外,从库加载 RDB 期间无奈提供读写服务,所以主库的数据量大小管制在 2~4G 左右,让从库疾速的加载实现

内存大页(transparent huge pages)

惯例的内存页是依照 4 KB 来调配,Linux 内核从 2.6.38 开始反对内存大页机制,该机制反对 2MB 大小的内存页调配。

Redis 应用了 fork 生成 RDB 做长久化提供了数据可靠性保障。

当生成 RDB 快照的过程中,Redis 采纳 写时复制 技术使得主线程仍然能够接管客户端的写申请。

也就是当数据被批改的时候,Redis 会复制一份这个数据,再进行批改。

采纳了内存大页,生成 RDB 期间,即便客户端批改的数据只有 50B 的数据,Redis 须要复制 2MB 的大页。当写的指令比拟多的时候就会导致大量的拷贝,导致性能变慢。

应用以下指令禁用 Linux 内存大页即可:

echo never > /sys/kernel/mm/transparent_hugepage/enabled

swap:操作系统分页

当物理内存(内存条)不够用的时候,将局部内存上的数据交换到 swap 空间上,以便让零碎不会因内存不够用而导致 oom 或者更致命的状况呈现。

当某过程向 OS 申请内存发现有余时,OS 会把内存中临时不必的数据交换进来,放在 SWAP 分区中,这个过程称为 SWAP OUT。

当某过程又须要这些数据且 OS 发现还有闲暇物理内存时,又会把 SWAP 分区中的数据交换回物理内存中,这个过程称为 SWAP IN。

内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,波及到磁盘的读写。

触发 swap 的状况有哪些呢?
对于 Redis 而言,有两种常见的状况:

  • Redis 应用了比可用内存更多的内存;
  • 与 Redis 在同一机器运行的其余过程在执行大量的文件读写 I/O 操作(包含生成大文件的 RDB 文件和 AOF 后盾线程),文件读写占用内存,导致 Redis 取得的内存缩小,触发了 swap。

码哥,我要如何排查是否因为 swap 导致的性能变慢呢?
Linux 提供了很好的工具来排查这个问题,所以当狐疑因为替换导致的提早时,只需依照以下步骤排查。

获取 Redis 实例 pid

$ redis-cli info | grep process_id
process_id:13160

进入此过程的 /proc 文件系统目录:

cd /proc/13160

在这里有一个 smaps 的文件,该文件形容了 Redis 过程的内存布局,运行以下指令,用 grep 查找所有文件中的 Swap 字段。

$ cat smaps | egrep '^(Swap|Size)'
Size:                316 kB
Swap:                  0 kB
Size:                  4 kB
Swap:                  0 kB
Size:                  8 kB
Swap:                  0 kB
Size:                 40 kB
Swap:                  0 kB
Size:                132 kB
Swap:                  0 kB
Size:             720896 kB
Swap:                 12 kB

每行 Size 示意 Redis 实例所用的一块内存大小,和 Size 下方的 Swap 对应这块 Size 大小的内存区域有多少数据曾经被换出到磁盘上了。

如果 Size == Swap 则阐明数据被齐全换出了。

能够看到有一个 720896 kB 的内存大小有 12 kb 被换出到了磁盘上(仅替换了 12 kB),这就没什么问题。

Redis 自身会应用很多大小不一的内存块,所以,你能够看到有很多 Size 行,有的很小,就是 4KB,而有的很大,例如 720896KB。不同内存块被换出到磁盘上的大小也不一样。

敲重点了

如果 Swap 一切都是 0 kb,或者零星的 4k,那么一切正常。

当呈现百 MB,甚至 GB 级别的 swap 大小时,就表明,此时,Redis 实例的内存压力很大,很有可能会变慢。

解决方案

  1. 减少机器内存;
  2. 将 Redis 放在独自的机器上运行,防止在同一机器上运行须要大量内存的过程,从而满足 Redis 的内存需要;
  3. 减少 Cluster 集群的数量分担数据量,缩小每个实例所需的内存。

AOF 和磁盘 I/O 导致的提早

为了保证数据可靠性,Redis 应用 AOF 和 RDB 快照实现当即疾速复原和长久化。

能够应用 appendfsync 配置将 AOF 配置为以三种不同的形式在磁盘上执行 write 或者 fsync(能够在运行时应用 CONFIG SET命令批改此设置,比方:redis-cli CONFIG SET appendfsync no)。

  • no:Redis 不执行 fsync,惟一的提早来自于 write 调用,write 只须要把日志记录写到内核缓冲区就能够返回。
  • everysec:Redis 每秒执行一次 fsync。应用后台子线程异步实现 fsync 操作。最多失落 1s 的数据。
  • always:每次写入操作都会执行 fsync,而后用 OK 代码回复客户端(实际上 Redis 会尝试将同时执行的许多命令汇集到单个 fsync 中),没有数据失落。在这种模式下,性能通常非常低,强烈建议应用疾速磁盘和能够在短时间内执行 fsync 的文件系统实现。

咱们通常将 Redis 用于缓存,数据失落齐全歹意从数据获取,并不需要很高的数据可靠性,倡议设置成 no 或者 everysec。

除此之外,防止 AOF 文件过大,Redis 会进行 AOF 重写,生成放大的 AOF 文件。

能够把配置项 no-appendfsync-on-rewrite设置为 yes,示意在 AOF 重写时,不进行 fsync 操作。

也就是说,Redis 实例把写命令写到内存后,不调用后盾线程进行 fsync 操作,就间接返回了。

expires 淘汰过期数据

Redis 有两种形式淘汰过期数据:

  • 惰性删除:当接管申请的时候发现 key 曾经过期,才执行删除;
  • 定时删除:每 100 毫秒删除一些过期的 key。

定时删除的算法如下:

  1. 随机采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个数的 key,删除所有过期的 key;
  2. 如果发现还有超过 25% 的 key 已过期,则执行步骤一。

ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP默认设置为 20,每秒执行 10 次,删除 200 个 key 问题不大主。

如果触发了第二条,就会导致 Redis 统一在删除过期数据取开释内存。而删除是阻塞的。

码哥,触发条件是什么呀?
也就是大量的 key 设置了雷同的工夫参数。同一秒内,大量 key 过期,须要反复删除屡次能力升高到 25% 以下。

简而言之:大量同时到期的 key 可能会导致性能稳定。

解决方案

如果一批 key 确实是同时过期,能够在 EXPIREATEXPIRE 的过期工夫参数上,加上一个肯定大小范畴内的随机数,这样,既保证了 key 在一个邻近工夫范畴内被删除,又防止了同时过期造成的压力。

bigkey

通常咱们会将含有较大数据或含有大量成员、列表数的 Key 称之为大 Key,上面咱们将用几个理论的例子对大 Key 的特色进行形容:

  • 一个 STRING 类型的 Key,它的值为 5MB(数据过大)
  • 一个 LIST 类型的 Key,它的列表数量为 10000 个(列表数量过多)
  • 一个 ZSET 类型的 Key,它的成员数量为 10000 个(成员数量过多)
  • 一个 HASH 格局的 Key,它的成员数量尽管只有 1000 个但这些成员的 value 总大小为 10MB(成员体积过大)

bigkey 带来一问题如下:

  1. Redis 内存一直变大引发 OOM,或者达到 maxmemory 设 置值引发写阻塞或重要 Key 被逐出;
  2. Redis Cluster 中的某个 node 内存远超其余 node,但因 Redis Cluster 的数据迁徙最小粒度为 Key 而无奈将 node 上的内存均衡化;
  3. bigkey 的读申请占用过大带宽,本身变慢的同时影响到该服务器上的其它服务;
  4. 删除一个 bigkey 造成主库较长时间的阻塞并引发同步中断或主从切换;

查找 bigkey

应用 redis-rdb-tools 工具以定制化形式找出大 Key。

解决方案

对大 key 拆分

如将一个含有数万成员的 HASH Key 拆分为多个 HASH Key,并确保每个 Key 的成员数量在正当范畴,在 Redis Cluster 构造中,大 Key 的拆分对 node 间的内存均衡可能起到显著作用。

异步清理大 key

Redis 自 4.0 起提供了 UNLINK 命令,该命令可能以非阻塞的形式迟缓逐渐的清理传入的 Key,通过 UNLINK,你能够平安的删除大 Key 甚至特大 Key。

总结

如下查看清单,帮忙你在遇到 Redis 性能变慢的时候能高效解决问题。

  1. 获取以后 Redis 的基线性能;
  2. 开启慢指令监控,定位慢指令导致的问题;
  3. 找到慢指令,应用 scan 的形式;
  4. 将实例的数据大小管制在 2-4GB,防止主从复制加载过大 RDB 文件而阻塞;
  5. 禁用内存大页,采纳了内存大页,生成 RDB 期间,即便客户端批改的数据只有 50B 的数据,Redis 须要复制 2MB 的大页。当写的指令比拟多的时候就会导致大量的拷贝,导致性能变慢。
  6. Redis 应用的内存是否过大导致 swap;
  7. AOF 配置是否正当,能够将配置项 no-appendfsync-on-rewrite 设置为 yes,防止 AOF 重写和 fsync 竞争磁盘 IO 资源,导致 Redis 提早减少。
  8. bigkey 会带来一些列问题,咱们须要进行拆分防止出现 bigkey,并通过 UNLINK 异步删除。
退出移动版