1. 程式人生 > >redis開發與運維筆記(3)

redis開發與運維筆記(3)

1、客戶端

通訊協議是建立在TCP協議之上的。Redis制定了RESP(REdis Serialization Protocol, Redis序列化協議) 實現客戶端與服務端的正常互動

(1)客戶端API。client .. 命令

  • client list。列出與Redis服務端相連的所有客戶端連線資訊。輸出結果的每一行代表一個客戶端的資訊, 可以看到每行包含了十幾個屬性
    id: 客戶端連線的唯一標識, 這個id是隨著Redis的連線自增的, 重啟Redis後會重置為0
    addr: 客戶端連線的ip和埠。
    fd: socket的檔案描述符, 與lsof命令結果中的fd是同一個, 如果fd=-1代表當前客戶端不是外部客戶端, 而是Redis內部的偽裝客戶端。
    name: 客戶端的名字
    age和idle分別代表當前客戶端已經連線的時間和最近一次的空閒時間。當age等於idle時,說明連線一直處於空閒狀態
    flag是用於標識當前客戶端的型別, 例如flag=S代表當前客戶端是slave客戶端、 flag=N代表當前是普通客戶端, flag=O代表當前客戶端正在執行monitor命令

  • 輸入緩衝區: qbuf(總容量)、 qbuf-free(剩餘容量)。
    redis為每個客戶端分配了輸入緩衝區, 它的作用是將客戶端傳送的命令臨時儲存, 同時Redis從會輸入緩衝區拉取命令並執行, 輸入緩衝區為客戶端傳送命令到Redis執行命令提供了緩衝功能
    輸入緩衝區會根據輸入內容大小的不同動態調整, 只是要求每個客戶端緩衝區的大小不能超過1G, 超過後客戶端將被關閉。輸入緩衝區過大主要是因為Redis的處理速度跟不上輸入緩衝區的輸入速度

  • 輸入緩衝使用不當會產生兩個問題:
    ·一旦某個客戶端的輸入緩衝區超過1G, 客戶端將會被關閉。
    ·輸入緩衝區不受maxmemory控制,一旦超過maxmemory限制, 可能會產生資料丟失、 鍵值淘汰、 OOM等情況

  • 監控輸入緩衝區異常的方法有兩種:
    通過定期執行client list命令,收集qbuf和qbuf-free找到異常的連線記錄並分析,最終找到可能出問題的客戶端。能精準定位客戶端,但是執行速度較慢,頻繁指定可能阻塞redis
    通過info命令的info clients模組,找到最大的輸入緩衝區,client_biggest_input_buf代表最大的輸入緩衝區,可以設定超過10M(閾值)就進行報警。定位不精準,執行速度快。

  • 輸出緩衝區: obl、 oll、 omem。
    Redis為每個客戶端分配了輸出緩衝區, 它的作用是儲存命令執行的結果返回給客戶端, 為Redis和客戶端互動返回結果提供緩衝。
    輸出緩衝區由兩部分組成: 固定緩衝區(16KB) 和動態緩衝區, 其中固定緩衝區返回比較小的執行結果, 而動態緩衝區返回比較大的結果
    client list中的obl代表固定緩衝區的長度, oll代表動態緩衝區列表的長度, omem代表使用的位元組數
    監控輸出緩衝區的方法同輸入緩衝區監控方法

  • 輸出緩衝區的容量可以通過引數client-outputbuffer-limit來進行設定,輸出緩衝區也不會受到maxmemory的限制
    配置規則:client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
    <class>: 客戶端型別, 分為三種。 a) normal: 普通客戶端; b)slave: slave客戶端, 用於複製; c) pubsub: 釋出訂閱客戶端。
    <hard limit>: 如果客戶端使用的輸出緩衝區大於<hard limit>, 客戶端會被立即關閉。
    <soft limit>和<soft seconds>: 如果客戶端使用的輸出緩衝區超過了<softlimit>並且持續了<soft limit>秒, 客戶端會被立即關閉

  • 輸出緩衝區出現異常的概率相對會比較大,如何預防;
    進行上述監控, 設定閥值, 超過閥值及時處理
    限制普通客戶端輸出緩衝區的, 把錯誤扼殺在搖籃中
    適當增大slave的輸出緩衝區的,master節點寫入較大, slave客戶端的輸出緩衝區可能會比較大, 一旦slave客戶端連線因為輸出緩衝區溢位被kill, 會造成複製重連
    限制容易讓輸出緩衝區增大的命令, 例如, 高併發下的monitor命令就是一個危險的命令。
    及時監控記憶體, 一旦發現記憶體抖動頻繁, 可能就是輸出緩衝區過大。

  • client setName和client getName。client setName用於給客戶端設定名字, 這樣比較容易標識出客戶端的來源。
  • client kill ip:port。此命令用於殺掉指定IP地址和埠的客戶端。
  • client pause timeout(毫秒)。client pause命令用於阻塞客戶端timeout毫秒數,在此期間客戶端連線將被阻塞。生產環境中,暫停客戶端成本非常高
    該命令可以在如下場景起到作用:
    client pause只對普通和釋出訂閱客戶端有效,對於主從複製(從節點內部偽裝了一個客戶端)是無效的,也就是此期間主從複製是正常進行的,此命令可以用來讓主從複製保持一致
    client pause可以用一種可控的方式將客戶端連線從一個Redis節點切換到另一個Redis節點

  • monitor命令用於監控Redis正在執行的命令
    每個客戶端都有自己的輸出緩衝區, 既然monitor能監聽到所有的命令, 一旦Redis的併發量過大,monitor客戶端的輸出緩衝會暴漲, 可能瞬間會佔用大量記憶體

(2)客戶端相關配置

  • Redis提供了maxclients引數來限制最大客戶端連線數, 一旦連線數超過maxclients, 新的連線將被拒絕
    可以通過info clients來查詢當前Redis的連線數
    axclients預設值是10000,可以通過config set maxclients對最大客戶端連線數進行動態設定

  • Redis提供了timeout(單位為秒)引數來限制連線的最大空閒時間,一旦客戶端連線的idle時間超過了timeout,連線將會被關閉。預設的timeout是0,動態設定config set timeout 30
  • tcp-keepalive:檢測TCP連線活性的週期,預設值為0,也就是不進行檢測,如果需要設定,建議為60,那麼Redis會每隔60秒對它建立的TCP連線進行活性檢測,防止大量死連線佔用系統資源
  • tcp-backlog:TCP三次握手後,會將接受的連線放入佇列中,tcpbacklog就是佇列的大小,它在Redis中的預設值是511。通常來講這個引數不需要調整,但是這個引數會受到作業系統的影響

(3)客戶端統計片段

info clients命令

  • connected_clients:代表當前Redis節點的客戶端連線數,需要重點監控,一旦超過maxclients,新的客戶端連線將被拒絕。
  • client_longest_output_list: 當前所有輸出緩衝區中佇列物件個數的最大值。
  • client_biggest_input_buf: 當前所有輸入緩衝區中佔用的最大容量。
  • blocked_clients:正在執行阻塞命令(例如blpop、 brpop、brpoplpush)的客戶端個數。

info stats  命令

  • total_connections_received: Redis自啟動以來處理的客戶端連線數總數
  • rejected_connections: Redis自啟動以來拒絕的客戶端連線數, 需要重點監控

2、客戶端常見異常

(1)無法從連線池獲取到連線

  • JedisPool中的Jedis物件個數是有限的, 預設是8個。如果連線池中沒有空閒Jedis物件,新的請求就需要進行等待(例如設定了maxWaitMillis>0)
    在maxWaitMillis時間內仍然無法獲取到Jedis物件就會丟擲異常:JedisConnectionException: Could not get a resource from the pool
    如果設定了blockWhenExhausted=false, 那麼呼叫者發現池子中沒有資源時, 會立即丟擲異常不進行等待

  • 造成沒有資源的原因非常多:
    客戶端: 高併發下連線池設定過小, 出現供不應求
    客戶端: 沒有正確使用連線池, 比如沒有進行釋放
    客戶端: 存在慢查詢操作, 這些慢查詢持有的Jedis物件歸還速度會比較慢,造成池子滿了
    服務端: 客戶端是正常的, 但是Redis服務端由於一些原因造成了客戶端命令執行過程的阻塞

(2)客戶端讀寫超時,SocketTimeoutException: Read timed out

造成該異常的原因也有以下幾種:
·讀寫超時間設定得過短。
·命令本身就比較慢。
·客戶端與服務端網路不正常。
·Redis自身發生阻塞。

(3)客戶端連線超時,SocketTimeoutException: connect timed out

造成該異常的原因也有以下幾種:
連線超時設定得過短, 可以通過下面程式碼進行設定:jedis.getClient().setConnectionTimeout(time);
Redis發生阻塞, 造成tcp-backlog已滿, 造成新的連線失敗。
客戶端與服務端網路不正常。

(4)客戶端緩衝區異常

造成這個異常的原因可能有如下幾種:
輸出緩衝區滿。 
長時間閒置連線被服務端主動斷開
不正常併發讀寫: Jedis物件同時被多個執行緒併發操作, 可能會出現上述異常

(5)Lua指令碼正在執行

如果Redis當前正在執行Lua指令碼, 並且超過了lua-time-limit, 此時Jedis呼叫Redis時, 會收到下面的異常。

JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. 

(6)Redis正在載入持久化檔案

Jedis呼叫Redis時, 如果Redis正在載入持久化檔案, 那麼會收到下面的異常:
JedisDataException: LOADING Redis is loading the dataset in memory

(7)Redis使用的記憶體超過maxmemory配置

Jedis執行寫操作時, 如果Redis的使用記憶體大於maxmemory的設定, 會收到下面的異常, 此時應該調整maxmemory並找到造成記憶體增長的原因
JedisDataException: OOM command not allowed when used memory > 'maxmemory'.

(8)客戶端連線數過大

  • 如果客戶端連線數超過了maxclients, 新申請的連線就會出現如下異常:
    JedisDataException: ERR max number of clients reached
  • 一般來說可以從兩個方面進行著手解決:
    如果應用方是分散式結構的話,可以通過下線部分應用節點(例如佔用連線較多的節點),使得Redis的連線數先降下來。從而讓絕大部分節點可以正常執行,此時再通過查詢程式bug或者調整maxclients進行問題的修復。
    果此時客戶端無法處理, 而當前Redis為高可用模式(例如Redis Sentinel和Redis Cluster) , 可以考慮將當前Redis做故障轉移。
    但是無論從哪個方面進行處理, 故障的快速恢復極為重要, 當然更為重要的是找到問題的所在, 否則一段時間後客戶端連線數依然會超過maxclients。

 3、客戶端案例分析

(1)Redis主節點記憶體陡增

  • 服務端現象: Redis主節點記憶體陡增, 幾乎用滿maxmemory, 而從節點記憶體並沒有變化
  • 客戶端現象: 客戶端產生了OOM異常, 也就是Redis主節點使用的記憶體已經超過了maxmemory的設定, 無法寫入新的資料
  • 分析原因:
    確實有大量寫入, 但是主從複製出現問題: 查詢了Redis複製的相關資訊, 複製是正常的, 主從資料基本一致。主從的鍵個數基本一致,使用dbsize命令
    其他原因造成主節點記憶體使用過大: 排查是否由客戶端緩衝區造成主節點記憶體陡增, 使用info clients命令發現客戶端輸出緩衝區不正常,client_longest_output_list:225698
    通過client list命令找到omem不正常的連線, 一般來說大部分客戶端的omem為0,redis-cli client list | grep -v "omem=0"
    最後發現是因為有客戶端在執行monitor命令造成的

(2)客戶端週期性超時

  • 客戶端現象: 客戶端出現大量超時, 經過分析發現超時是週期性出現的
  • 服務端現象: 服務端並沒有明顯的異常, 只是有一些慢查詢操作
  • 原因分析:
    網路原因: 服務端和客戶端之間的網路出現週期性問題, 經過觀察網路是正常的
    客戶端: 由於是週期性出現問題, 就和慢查詢日誌的歷史記錄對應了一下時間, 發現只要慢查詢出現, 客戶端就會產生大量連線超時, 兩個時間點基本一致
    最終找到問題是慢查詢操作造成的, 通過執行hlen發現有200萬個元素, 這種操作必然會造成Redis阻塞, 有定時任務程式碼每5分鐘執行一次hgetall操作