redis故障診斷和優化

8. redis的故障診斷與優化

8.1 常見緩存問題

8.1.1 緩存穿透

  • 什麼叫緩存穿透?

一般的緩存系統,都是按照key去緩存查詢,如果不存在對應的value,就應該去後端系統查找(比如DB)。如果key對應的value是一定不存在的,並且對該key併發請求量很大,就會對後端系統造成很大的壓力。

也就是說,對不存在的key進行高併發訪問,導致數據庫壓力瞬間增大,這就叫做【緩存穿透】。

  • 如何解決?

1:對查詢結果為空的情況也進行緩存,緩存時間設置短一點,或者該key對應的數據insert了之後清理緩存。

2:對一定不存在的key進行過濾。可以把所有的可能存在的key放到一個大的Bitmap中,查詢時通過該bitmap過 濾。(布隆表達式)

8.1.2 緩存雪崩

  • 什麼叫緩存雪崩

當緩存服務器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(比如DB)帶來很大壓 力。

  • 如何解決

1:在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和 寫緩存,其他線程等待。

2:不同的key,設置不同的過期時間,讓緩存失效的時間點儘量均勻。

3:做二級緩存,A1為原始緩存,A2為拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置為短期,A2設置為長 期(此點為補充)

8.1.3 緩存擊穿

  • 什麼叫做緩存擊穿

對於一些設置了過期時間的key,如果這些key可能會在某些時間點被超高併發地訪問,是一種非常“熱點”的數據。這 個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的區別在於這裡針對某一key緩存,前者則是很多key。

緩存在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的併發請求過來,這些請求發現緩存過期一般都會 從後端DB加載數據並回設到緩存,這個時候大併發的請求可能會瞬間把後端DB壓垮。

  • 如何解決

使用redis的setnx互斥鎖先進行判斷,這樣其他線程就處於等待狀態,保證不會有大併發操作去操作數據庫。

<code>if(redis.sexnx()==1){
 //先查詢緩存
//查詢數據庫
//加入緩存
}/<code>

8.2 啟動redis日誌功能

redis默認不記錄log文件, 需要在redis.conf文件修改logfile參數.如下:

  • 日誌級別 loglevel notic
  • 日誌路勁 logfile "/usr/local/redis/log/redis.log"

8.3 redis的監控狀態

8.3.1 RDB狀態監控

<code>bin/redis-cli info |grep rdb_/<code>
  • rdb_changes_since_last_save表明上次RDB保存以後改變的key次數
  • rdb_bgsave_in_progress 表示當前是否在進行 bgsave 操作,1 表示正在進行;0 表示沒有進行
  • rdb_last_save_time 上次保存 RDB 文件的時間戳
  • rdb_last_bgsave_time_sec 上次保存的耗時
  • rdb_last_bgsave_status 上次保存的狀態
  • rdb_current_bgsave_time_sec 目前保存 RDB 文件已花費的時間

8.3.2 aof的監控狀態

<code>bin/redis-cli info |grep aof_/<code>
  • aof_enabledAOF文件是否啟用
  • aof_rewrite_in_progress 表示當前是否在進行 AOF 日誌的重寫
  • aof_rewrite_scheduled
  • aof_last_rewrite_time_sec 上次寫入的時間戳
  • aof_current_rewrite_time_sec:-1
  • aof_last_bgrewrite_status:ok 上次寫入狀態
  • aof_last_write_status:ok 上次寫入狀態

8.3.3 內存監控

<code>bin/redis-cli info |grep mem/<code>
  • used_memory:13490096 //數據佔用了多少內存(字節)
  • used_memory_human:12.87M //數據佔用了多少內存(帶單位的,可讀性好)
  • used_memory_rss:13490096 //redis 佔用了多少內存
  • used_memory_peak:15301192 //佔用內存的峰值(字節)
  • used_memory_peak_human:14.59M //佔用內存的峰值(帶單位的,可讀性好)
  • used_memory_lua:31744 //lua 引擎所佔用的內存大小(字節)
  • mem_fragmentation_ratio:1.00 //內存碎片率
  • mem_allocator:libc //redis 內存分配器版本,在編譯時指定的。有 libc、jemalloc、tcmalloc 這 3 種

8.4 redis慢查詢

8.4.1 慢查詢日誌

慢查詢日誌幫助開發和運維人員定位系統存在的慢操作。慢查詢日誌就是系統

在命令執行前後計算每條命令的執行時間,當超過預設閥值,就將這條命令的相關 信息(慢查詢 ID,發生時間戳,耗時,命令的詳細信息)記錄下來。

Redis 客戶端一條命令分為如下四部分執行:

redis故障診斷和優化

需要注意的是,慢查詢日誌只是統計步驟 3)執行命令的時間,所以慢查詢並不代 表客戶端沒有超時問題。需要注意的是,慢查詢日誌只是統計步驟 3)執行命令的時間, 所以慢查詢並不代表客戶端沒有超時問題。


8.4.2 慢查詢參數

  • 慢查詢的預設閥值 slowlog-log-slower-than
    • slowlog-log-slower-than參數就是預設閥值,1000,如果一條命令的執行時間超過 10000 微妙,那麼它將被記錄 在慢查詢日誌中。
    • 如果slowlog-log-slower-than的值是0,則會記錄所有命令。
    • 如果slowlog-log-slower-than的值小於0,則任何命令都不會記錄日誌
    • 對於高流量的場景,如果執行命令的時間在 1 毫秒以上,那 麼 redis 最多可支撐 OPS(每秒操作次數)不到 1000,因此高 OPS 場景的 REDIS 建議設置為 1 毫秒.
  • 慢查詢日誌的長度slowlog-max-len
    • slowlog-max-len只是說明了慢查詢日誌最多存儲多少條。Redis使用一個列表來存儲慢查詢日誌,showlog-max-len 就是列表的最大長度。 當慢查詢日誌已經到達列表的最大長度時,又有慢查詢日誌要進入列 表,則最早插入列表的日誌將會被移出列表,新日誌被插入列表的末 尾。
    • slowlog-max-len的設置建議, 線上環境建議調大慢查詢日誌的列表,記錄慢查詢日誌時 Redis 會對長命令做截斷操作,並不會佔用大量內存。增大慢查詢列表可 以減緩慢查詢被剔除出列表的可能性。例如線上可以設置為 1000 以 上

8.4.3 慢查詢日誌的組成

慢查詢日誌由以下四個屬性組成:標識 ID,發生時間戳,命令耗時,執行命令和參數

8.5 redis的pipeline(管道)

簡單來說,PipeLine(管道)就是“批處理”操作。 由於網絡開銷延遲,就算 redis server 端有很強的處理能力,也會由於收到的client 消息少,而造成吞吐量小。當 client 使用 pipelining 發送命令時,redis server 必須將部分請求放到隊列中(使用內存),執行完畢後一次性發送結果;如果發送 的命令很多的話,建議對返回的結果加標籤,當然這也會增加使用的內存。

Pipeline 在某些場景下非常有用,比如有多個 command 需要被“及時的”提 交,而且他們對相應結果沒有互相依賴,對結果響應也無需立即獲得,那麼 pipeline 就可以充當這種“批處理”的工具;而且在一定程度上,可以較大的提升性能,性 能提升的原因主要是 TCP 連接中減少了“交互往返”的時間。

應用場景: 特別是有for循環取值時, 建議使用pipeline

代碼示例:

<code># 自定義一個自己需要的類
List<response>> resAll = new ArrayList();

// 獲取一個管道對象
Pipeline pipeline = jedisUtil.getReis().pipelined();
List<string> ids = new ArrayList();
ids.add("str1");
ids.add("str2");
ids.add("str3");

ids.forEach(id->{
 Response<string> res = pipeline.get(id);
 // 管道的對象結果都存在resAll中
 resAll.add(res);
});
//pipeline.sync(); close內部有sync()方法調用
pipeline.close();
resAll.forEach(item -> {
 System.out.println(Integer.valueof(res) + 100);
});/<string>/<string>/<response>/<code>


8.6 redis的噩耗: 阻塞

8.6.1 耗時長命令造成阻塞

  • keys, sort等命令

當redis的數據量達到一定級別後(比如20G),阻塞操作對性能的影響尤為嚴重;keys命令用於查找所有符合給定模式 pattern 的 key,時間複雜度為O(N), N 為數據庫中 key 的數量。當數據庫中的個數達到千萬時,這個命令會造成讀寫線程阻塞數秒;類似的命令有sunion sort等操作;如果業務需求中一定要使用keys、sort等操作怎麼辦?

解決方案

在架構設計中,有“分流”一招,說的是將處理快的請求和處理慢的請求分離開來,否則,慢的影響到了快的,讓快的也快不起來;這在redis的設計中體現的非常明顯,redis的純內存操作,epoll非阻塞IO事件處理,這些快的放在一個線程中搞定,而持久化,AOF重寫、Master-slave同步數據這些耗時的操作就單開一個進程來處理,不要慢的影響到快的;同樣,既然需要使用keys這些耗時的操作,那麼我們就將它們剝離出去,比如單開一個redis slave結點,專門用於keys、sort等耗時的操作,這些查詢一般不會是線上的實時業務,查詢慢點就慢點,主要是能完成任務,而對於線上的耗時快的任務沒有影響

  • smembers命令

smembers命令用於獲取集合全集,時間複雜度為O(N),N為集合中的數量;如果一個集合中保存了千萬量級的數據,一次取回也會造成事件處理線程的長時間阻塞;

解決方案

和sort,keys等命令不一樣,smembers可能是線上實時應用場景中使用頻率非常高的一個命令,這裡分流一招並不適合,我們更多的需要從設計層面來考慮;在設計時,我們可以控制集合的數量,將集合數一般保持在500個以內;比如原來使用一個鍵來存儲一年的記錄,數據量大,我們可以使用12個鍵來分別保存12個月的記錄,或者365個鍵來保存每一天的記錄,將集合的規模控制在可接受的範圍;

如果不容易將集合劃分為多個子集合,而堅持用一個大集合來存儲,那麼在取集合的時候可以考慮使用SRANDMEMBER key [count];隨機返回集合中的指定數量,當然,如果要遍歷集合中的所有元素,這個命令就不適合了;

  • save命令

save命令使用事件處理線程進行數據的持久化;當數據量大的時候,會造成線程長時間阻塞(我們的生產上,reids內存中1個G保存需要12s左右),整個redis被block;save阻塞了事件處理的線程,我們甚至無法使用redis-cli查看當前的系統狀態,造成“何時保存結束,目前保存了多少”這樣的信息都無從得知;

解決方案

我沒有想到需要用到save命令的場景,任何時候需要持久化的時候使用bgsave都是合理的選擇(當然,這個命令也會帶來問題,後面聊到);

推介兩篇redis阻塞的文章:

https://blog.csdn.net/linbiaorui/article/details/79822318

http://colin115.iteye.com/blog/2263351

8.7 redis持久化故障診斷

  1. 使用 Java 客戶端,循環插入 20 個 200M 大小的數據,程序如下:
  2. 檢查 RDB 的狀態信息
<code>bin/redis-cli info |grep rdb_/<code>
  1. 檢查日誌文件: redis.log
redis故障診斷和優化


  1. 解決問題:修改 vm.overcommit_memory 參數。關於 vm.overcommit_memory 不同的值說明:由於 RDB 文件寫的時候 fork 一個子進程。相當於複製了一個內存鏡像。 當時系統的內存是 4G,而 redis 佔用了近 3G 的內存,因此肯定會報內存無法 分配。如果 「vm.overcommit_memory」設置為 0,在可用內存不足的情況下, 就無法分配新的內存。如果 「vm.overcommit_memory」設置為 1。 那麼 redis將使用交換內存。
  2. 方法一: 修改內核參數 vi /etc/sysctl。設置 vm.overcommit_memory = 1 然後執行 sysctl -p
  3. 方法二: 使用交換內存並不是一個完美的方案。最好的辦法是擴大物 理內存。
  4. 0 表示檢查是否有足夠的內存可用,如果是,允許分配;如果內存不夠,拒絕該請求,並返回一個錯誤給應用程序。
  5. 1 允許分配超出物理內存加上交換內存的請求
  6. 2 內核總是返回 true

8.8 redis內存淘汰策略

8.8.1 最大緩存

在 redis 中,允許用戶設置最大使用內存大小maxmemory,默認為0,沒有指定最大緩存,如果有新的數據添 加,超過最大內存,則會使redis崩潰,所以一定要設置.

redis 內存數據集大小上升到一定大小的時候,就會實行數據淘汰策略

8.8.2 淘汰策略

redis淘汰策略配置: maxmemory-policy voltile-lru,支持熱配置

Redis提供6種數據淘汰策略:

  1. voltile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
  2. volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
  3. volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
  4. allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
  5. allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
  6. no-enviction(驅逐):禁止驅逐數據

8.8.3 LRU原理

LRU( ,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如 果數據最近被訪問過,那麼將來被訪問的幾率也更高”。


分享到:


相關文章: