Redis中的緩衝區機制就是為了平衡客戶端發送命令和服務端處理命令的速度差異,如果客戶端寫入過快或者服務端讀取過慢這就會導致緩衝區溢出,緩衝區一旦溢出將引發一系列的效能問題,下面我們詳細聊聊。
Redis為每個客戶端都分配了一個輸入緩衝區和輸出緩衝區,輸入緩衝區會把客戶端的請求命令暫存起來,Redis主線程會從緩衝區取得指令,當Redis處理完指令後會將結果寫入輸出緩衝區中,透過輸出緩衝區傳回給客戶端,如下所示
輸入緩衝區溢位一般就是兩種情況
#寫入資料過快,或是寫入bigkey的資料佔全資料緩衝區。
服務端處理資料過慢,一般是主執行緒被阻塞無法正常回應客戶端請求。
我們可以採用client list
查看輸入緩衝區的具體資訊
127.0.0.1:6379> client list id=13 addr=127.0.0.1:50484 fd=7 name= age=1136 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client user=default id=14 addr=127.0.0.1:50486 fd=8 name= age=1114 idle=6 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client user=default
每連接上一個客戶端就會多一條輸入緩衝區信息,上面命令是我本地連接兩個客戶端的結果,我們查看緩衝區主要關注內存相關的兩個參數
qbuf :緩衝區已經使用的長度(位元組為單位,0表示沒有分配緩衝區)。
qbuf-free:緩衝區剩餘空閒空間(位元組為單位),上面一個客戶端的qbuf=26,空閒緩衝區qbuf-free=32742,那麼分配記憶體總大小為26 32742=32768位元組也就是32KB。
如果輸入緩衝區資訊中的qbuf-free很小且qbuf很大時就需要注意了,這時輸入緩衝區可能已經快溢出了,如果此時還有大量請求寫入輸入緩衝區,Redis的解決方案就是關閉和這個客戶端的連接,那麼業務資料將無法正常存取。
而且還有一個問題就是輸入緩衝區是每一個客戶端都會存在,那麼當所有客戶端的輸入緩衝區內存總和超過了maxmemory配置,那麼將引發內存淘汰,部分淘汰的數據再次訪問需要從後台資料庫獲取,取得的耗時肯定比Redis直接讀取慢的多,所以這也是Redis產生延遲的原因之一。
輸入緩衝區溢位本質就是緩衝區的容量不夠,所以第一個想法就是擴大輸入緩衝區的大小,很不幸Redis沒有提供給我們修改輸入緩衝區大小的配置,Redis要求每個客戶端的輸入緩衝區最大不能超過1G,注意是每個客戶端! ! ! ,如果客戶端的輸入緩衝區超過一個1G將關閉客戶端連接,所以這個是行不通的。
那就只能從客戶端發送資料的大小以及服務端處理命令的速度,客戶端需要避免bigkey的寫入bigkey的劣勢太多一般都需要拆分,第二服務端的命令處理速度這個一般依賴於主線程是否阻塞,需要盡量的避免一些阻塞操作如AOF檔重寫,鍵值刪除,fork線程等等。
對於服務端而言,客戶端的輸入資訊通常都是不可預測的,但是輸出資訊大多可以預測,如Set指令回傳資訊只是一個簡單的OK,又如一些報錯訊息,Redis為這些不變的回傳訊息分配了16KB的固定緩衝空間,也就是說輸出緩衝區區分為兩個部分一部分是輸出緩衝區固定回傳訊息,一部分是可變的回傳訊息。
輸出緩衝區溢位分為三種情況
輸出bigkey等容量大的鍵值。
客戶端執行Monitor指令,監控Redis執行。
緩衝區設定不合理。
bigkey是老生常談的一個問題,當服務端輸出一個bigkey或keys這類指令,對輸出緩衝區的考驗是非常大的,因為查詢的一瞬間會佔據輸入緩衝區大量的記憶體空間。
Monitor指令一般是一個debug指令,用來監控Redis的特定執行情況,能夠傳回伺服器處理的每一個指令。
127.0.0.1:6379> monitor OK 1652184977.609761 [0 127.0.0.1:50484] "get" "name" 1652185391.529292 [0 127.0.0.1:50484] "set" "test" "lisi" ......
一直運行monitor將一直佔據輸出緩衝區,也就是說佔據時間越長,越容易造成輸出緩衝區的溢出,所以Monitor命令僅僅只適用於調試環境,不能在生產上執行這些命令。
輸入緩衝區的大小不能設置,但是輸出緩衝區的是可以設定的我們可以透過設定項client-output-buffer-limit
來設置,設定的內容就是兩部分
緩衝區的記憶體大小,當超過緩衝區配置的大小,服務端會關閉和客戶端的連線。
持续写入的时间限制和持续写入的容量限制,当超过持续写入时间限制和容量限制,服务端也会强制关闭和客户端的连接。
客户端种类
在聊缓冲区配置时,我们需要先了解下客户端的种类,本文中强调的客户端并不是单纯指通过命令./redis-cli -c -h 127.0.0.1 -p 6379
去连接Redis服务器这类客户端称为常规客户端,我们还有通过消息订阅Redis频道的客户端,还有一种最为特殊的主从同步,从节点也是一个特殊的客户端称为从节点客户端。
配置项client-output-buffer-limit
也是针对这三种,给出了不一样的配置,如下所示
## 普通客户端配置 client-output-buffer-limit normal 0 0 0 ## 从节点客户端配置 client-output-buffer-limit replica 256mb 64mb 60 ## 消息订阅频道的客户端 client-output-buffer-limit pubsub 32mb 8mb 60 ######################配置解释###################### ## 第一个参数:代表分配给客户端的缓存大小,为0代表没有限制 ## 第二个参数:表示持续写入的最大内存,为0代表没有限制 ## 第三个参数:表示持续写入的最长时间,为0代表没有限制
普通客户端设置
普通客户端就是传输的一些普通的指令,一个指令发送完需要等待其返回后才会发送下一个指令,也就是说只要不是返回的bigkey数据,占用输出缓冲区的内存就极少,能够立即发送给客户端响应,所以一般正常客户端默认配置都是0,也就是不限制。
消息订阅频道客户端
当订阅频道产生消息后,会将消息通过输出缓冲区发送给客户端,这种属于非阻塞的方式,一瞬间可能有多个指令到达,所以需要指定缓冲区大小。
如何解决输出缓冲区溢出
到这里其实我们已经能够得到输出缓冲区溢出的解决方案了
bigkey应当避免使用。
Monitor命令只在调试的时候使用,不能应用到生产。
合理设置输出缓冲区上限、持续写入时间上限以及持续写入内存容量上限。
除了输入缓冲区和输出缓冲区外在主从集群场景下还存在两种缓冲区,我们称为复制缓冲区和复制积压缓冲区,这两个缓冲区的溢出和输入输出缓冲区稍有不同。
复制缓冲区这个名词看着很陌生,但是我们之前在聊主从同步时讲过,主从全量同步期间从节点会加载主节点的RDB文件,这时主节点同样还能写入数据,但是从节点在加载RDB文件没办法实时同步,所以Redis就为每一个从节点开辟了一片空间,用来存放主从全量同步期间产生的操作命令,这就是replication buffer,也就是复制缓冲区。
复制缓冲区什么时候会溢出呢?
当从节点在加载RDB文件这个过程中如果存在大量的写操作就会造成复制缓冲区内存溢出。
从节点加载RDB文件的时间过长。
发生溢出后,主节点会关闭与从节点的连接,导致全量同步失败。
解决复制缓冲区溢出
控制主节点实例的大小,减小生成的RDB文件,这样就能减少从节点加载RDB文件的时间,减小复制缓冲区的压力。
从节点其本质就是主节点的特殊客户端,所以使用的是输出缓冲区(也就是指replication buffer),可以设置client-output-buffer-limit replica 256mb 64mb 60
扩大缓冲区大小。
注意:主节点上的复制缓冲区会为每一个从节点分配一个,那么从节点的数量过多即使每个从节点没有达到maxmemory,但累加的结果也会给主节点带来内存压力。
复制积压缓冲区溢出
主从集群在写操作时会将操作写入复制缓冲区和复制积压缓冲区中,一旦网络发送故障后恢复连接,在2.8版本之前主从节点会进行全量同步开销非常大,所以2.8版本后还是采用了增量同步,仅仅将网络断开这段时间的操作同步给从节点,所以在网络恢复连接后从节点会将自己的复制偏移量slave_repl_offset发送给主节点,主节点将自身的写入偏移量master_repl_offset和slave_repl_offset在复制积压缓冲区中做对比得到网络断连期间的操作。
复制积压缓冲区又叫repl_backlog_buffer,是一个环形缓冲区,同步示意图如下。
复制积压缓冲区溢出其实也就是因为复制积压缓冲区是一个有限环形结构,一般主节点写入偏移量要大于从节点的读取偏移量,但如果写入偏移量覆盖了从节点的读取偏移量这就引发了复制积压缓冲区溢出。
一般是調整repl_backlog_size這個參數的大小,擴大複製積壓緩衝區的大小,減少主節點寫入偏移量覆蓋從節點讀取偏移量的風險。
以上是Redis緩衝區機制實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!