首頁  >  文章  >  資料庫  >  怎麼確定Redis有效能問題及如何解決

怎麼確定Redis有效能問題及如何解決

PHPz
PHPz轉載
2023-06-03 17:16:21689瀏覽

怎麼確定Redis有效能問題及如何解決

Redis 通常是我們業務系統中重要的元件,例如:快取、帳號登入資訊、排行榜等。

一旦 Redis 請求延遲增加,可能就會導致業務系統「雪崩」。

我在單身紅娘婚戀類型網路公司工作,在雙十一推出下單就送女友的活動。

誰曾想,凌晨 12 點以後,用戶量暴增,出現了一個技術故障,用戶無法下單,當時老大火冒三丈!

經過查找發現 Redis 報 Could not get a resource from the pool

取得不到連線資源,且叢集中的單一 Redis 連線量很高。

大量的流量沒了Redis 的快取回應,直接打到了MySQL,最後資料庫也宕機了…

於是各種更改最大連線數、連線等待數,雖然報錯訊息頻率有所緩解,但還是持續報錯。

後來經過線下測試,發現存放 Redis 中的字元資料很大,平均 1s 回傳資料。

可以發現,一旦 Redis 延遲過高,會引發各種問題。

今天跟大家一起來分析下如何確定 Redis 有效能問題和解決方案。

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/latency-monitor

如获取最近的 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

事件的名称;

事件发生的最新延迟的 Unix 时间戳;

毫秒为单位的时间延迟;

该事件的最大延迟。

如何解决 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 将多个命令连接在一起来减少网络响应往返次数。

怎麼確定Redis有效能問題及如何解決

redis-pipeline

慢指令导致的延迟

根据上文的慢指令监控查询文档,查询到慢查询指令。可以通过以下两种方式解决:

在 Cluster 集群中,聚合运算等 O(N) 操作可以在 slave 节点上运行,也可以在客户端端完成。

使用高效的命令代替。采用增量迭代的方法查询数据,避免一次性查询大量数据,在此可参考SCAN、SSCAN、HSCAN和ZSCAN命令。

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

Fork 生成 RDB 导致的延迟

生成 RDB 快照,Redis 必须 fork 后台进程。fork 操作(在主线程中运行)本身会导致延迟。

Redis 使用操作系统的多进程写时复制技术 COW(Copy On Write) 来实现快照持久化,减少内存占用。

怎麼確定Redis有效能問題及如何解決

写时复制技术保证快照期间数据可修改

但 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 实例的内存压力很大,很有可能会变慢。

解决方案

增加机器内存;

将 Redis 放在单独的机器上运行,避免在同一机器上运行需要大量内存的进程,从而满足 Redis 的内存需求;

增加 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。

定时删除的算法如下:

随机采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个数的 key,删除所有过期的 key;

如果发现还有超过 25% 的 key 已过期,则执行步骤一。

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

如果觸發了第二條,就會導致 Redis 一致在刪除過期資料去釋放記憶體。而刪除是阻塞的。

觸發條件是什麼呀?

也就是大量的 key 設定了相同的時間參數。需要多次重複刪除才能將過期鍵的數量降至低於 25%。這些鍵在同一秒內大量過期。

簡而言之:大量同時到期的 key 可能會導致效能波動。

如果一批key 的確是同時過期,可以在EXPIREAT 和EXPIRE 的過期時間參數上,加上一個一定大小範圍內的隨機數,這樣,既保證了key 在一個鄰近時間範圍內被刪除,又避免了同時過期造成的壓力。

bigkey

通常我們會將含有較大資料或含有大量成員、清單數的Key 稱之為大Key,下面我們將用幾個實際的範例對大Key 的特徵進行描述:

  • 一個STRING 類型的Key,它的值為5MB(資料過大)

  • 一個LIST 類型的Key,它的清單數量為10000 個(清單數量過多)

  • 一個ZSET 類型的Key,它的成員數量為10000 個(成員數量過多)

  • 一個HASH 格式的Key,它的成員數量雖然只有1000 個但這些成員的value 總大小為10MB(成員體積過大)

#bigkey 帶來問題如下:

  • Redis 記憶體不斷變大引發OOM,或是達到maxmemory 設定值引發寫阻塞或重要Key 被逐出;

  • #Redis Cluster 中的某個node 記憶體遠超其餘node,但因Redis Cluster 的資料遷移最小粒度為Key 而無法將node 上的記憶體均衡化;

  • #bigkey 的讀取請求佔用過大頻寬,自身變慢的同時影響到該伺服器上的其它服務;

  • 刪除一個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有效能問題及如何解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除