對於Redis服務器的輸出(也就是命令的返回值)來說,其大小通常是不可控制的。有可能一個簡單的命令,能夠產生體積龐大的返回數據。另外也有可能因為執行了太多命令,導致產生返回數據的速率超過了往客戶端發送的速率,這是也會導致服務器堆積大量消息,從而導致輸出緩沖區越來越大,占用過多內存,甚至導致系統崩潰。
所幸,Redis設置了一些保護機制來避免這種情況的出現,不同類型的客戶端有不同的限制參數。限制方式有如下兩種:
(1)、大小限制,當某一個客戶端的緩沖區超過某一個大小值時,直接關閉這個客戶端的連接;
(2)、持續性限制,當某一個客戶端的緩沖區持續一段時間占用過大空間時,會直接關閉客戶端連接。
我們來看看配置文件關於客戶端輸出緩沖區的配置:
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 8mb 2mb 60
不同客戶端有不同策略,策略如下:
Ø 對於普通客戶端來說,限制為0,也就是不限制。因為普通客戶端通常采用阻塞式的消息應答模式,何謂阻塞式呢?如:發送請求,等待返回,再發送請求,再等待返回。這種模式下,通常不會導致Redis服務器輸出緩沖區的堆積膨脹;
Ø 對於Pub/Sub客戶端(也就是發布/訂閱模式),大小限制是8M,當輸出緩沖區超過8M時,會關閉連接。持續性限制是,當客戶端緩沖區大小持續60秒超過2M,則關閉客戶端連接;
Ø 對於slave客戶端來說,大小限制是256M,持續性限制是當客戶端緩沖區大小持續60秒超過64M,則關閉客戶端連接。
上述三種規則都是可以修改的。可以通過CONFIG SET 命令設置或者直接修改redis.conf文件。
jedis 的 Unexpected end of stream 解決方案
- redis 服務端版本號:2.8.X
- Jedis 客戶端版本號:2.8.1
- 單線程、無並發操作
Jedis單鏈接、JedisPool、ShardedJedisPool,無論使用哪一種方式對 redis 服務進行操作,均出現了 Unexpected end of stream
的問題。
通過查看源碼發現,報錯具體位置是:RedisInputStream 類的 ensureFill() 方法
private void ensureFill() throws JedisConnectionException { if (count >= limit) { try { limit = in.read(buf); count = 0; if (limit == -1) { throw new JedisConnectionException("Unexpected end of stream."); } } catch (IOException e) { throw new JedisConnectionException(e); } } }
解決方案:
第一步,檢查 redis config 中的 client-output-buffer-limit
配置
client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 0 0 0 client-output-buffer-limit pubsub 0 0 0
- 1
- 2
- 3
請根據實際情況合理設置Redis輸出緩沖區限制,確定不是因為緩沖區太小,導致鏈接關閉,進而引起 Unexpected end of stream
,可以臨時都設置成 “0(關閉緩沖區限制)” 來驗證此種場景
第二步,檢查 redis config 中的 timeout
配置
當此時間設置過短時,同一個 jedis 鏈接,兩次訪問 redis 服務的時間間隔 > ${timeout} , 服務端會單方關閉這個jedis鏈接,第二次使用這個jedis對象 操作 redis 時,會發生 Unexpected end of stream
, 可以將其設置成“0(無過期)”來驗證此種場景
第三步,優化客戶端代碼,增加重試機制
經過前兩步的調試,根據業務實際情況,調整配置,但這樣並不能完全杜絕 Unexpected end of stream
的發生,比如 “網絡抖動”之類的場景下,依然會發生此問題(實際生產環境中,此種情況發生概率極低),
但是為了防止此異常引起 jvm宕機,建議在代碼層面上增加加重試機制。
話外篇:
使用 redis 做存儲層時,有可能出現數數據不一致的情況( redis 無事務)