Redis的「死鍵」問題

大規模的數據庫存儲系統中,數據的生命週期管理是頗有必要的;從業務角度發現過時數據,數據歸檔和數據碎片整理等。以MySQL爲例,1個運行好久的TB級MySQL實例中,極有可能數百GB的數據,對業務來講是」過時數據」可直接歸檔後清理。若是不能發現和及時清理,這部分「過時數據」對生產數據庫備份資源消耗,佔用工做集數據內存(過時數據行可能分散InnoDB的page中),影響數據還原的RTO等。從成本和運維的角度看,代價都是很大的。針對MySQL這類」過時數據」問題,經過MySQL巡檢系統發現問題,使用MySQL歸檔系統備份和刪除數據等。 node

1、Redis死鍵的定義

本文簡單聊下Redis」死鍵」的問題,從業務角度對」死鍵」的2個定義:redis

  • 設置有生存時間Time to live:TTL的鍵,已通過期」死亡」,但因Redis主動清理不及時,致使這類鍵堆積.(這裏可能不清晰,後文會詳解)
  • 未設置有TTL鍵,使用這批鍵的程序功能已下線,致使這類鍵在集羣中堆積,無人管理;有的鍵長達6個月訪問過一次。

2、Redis過時鍵不能及時清理

Redis可對鍵設置生存時間, 當鍵的生存時間爲0(過時鍵)理論就會被刪除,並釋放佔用的數據結構和內存資源。shell

但Redis爲保證請求的性能,過時鍵並非當即刪除的。數據庫

這節主要討論,當產生過時鍵的速度>>Redis刪除過時鍵的速度時,致使過時鍵堆積的問題。緩存

3、Redis刪除過時鍵的策略

Redis刪除過時鍵有兩種策略:passive way和active way.服務器

  • passive way(惰性刪除):當客戶端訪問到過時鍵時,發現它已過時,Redis會主動刪除它
  • active way(按期刪除):Redis會按期調用刪除過時鍵,調用頻率由參數hz控制,默認每秒調用10次

咱們重點討論第二種」按期刪除策略」。Redis每一個database(Cluster模式下只有0號庫)都對應expire的dict,用以保存Redis設置有生存時間的鍵;Redis每秒調用10次(hz參數決定)activeExpireCycle函數;session

  • 每次隨機獲取20個帶有生存時間的鍵。
  • 刪除其中已過時的鍵。
  • 若是其中過時鍵超過25%(即大於5個鍵是過時的),activeExpireCycle函數會從新調用,開始第一步(若是大量KEY同時過時,可能引發Redis性能抖動)。

4、Redis按期刪除的速度

Redis按期刪除過時鍵的速度? 怎麼監控它?數據結構

Redis按期刪除動做每秒執行10次,正常狀況每次刪除幾個過時鍵,這樣每秒刪除過時鍵約數十個。 經過info stats的expired_keys指標記錄累計刪除的過時鍵數量。根據生產監控(hz=10)Redis每秒刪除過時鍵20~25個,天天能刪除約200百萬個過時鍵。有的Redis單個實例包含數千萬個鍵,若是業務設計鍵過時處理不合理,天天產生過時鍵多於200百萬。這容易致使Redis實例中存在過時鍵,最壞狀況佔整個鍵容量的25%;也就說Redis實例最壞有1/4的內存被這類過時的」死鍵」所佔據浪費。併發

Redis 查看過時鍵刪除數量
127.0.0.1:xxx> info stats
# Stats
total_connections_received:33843364
total_commands_processed:211474375292
instantaneous_ops_per_sec:9438
total_net_input_bytes:19661370696457
total_net_output_bytes:34509115216581
expired_keys:7575307675
evicted_keys:0
keyspace_hits:72743876832
keyspace_misses:57604962586
latest_fork_usec:95143

大量過時鍵堆積,最直接影響是浪費內存空間;另外還會有些」靈異現象」運維

  • Master的鍵個數比Slave多20%
  • 讀定分離時,應用程序讀取Slave時能返回快過時的鍵
  • Redis scan或keys出來的鍵個數,遠小於dbsize返回的個數
  • 高併發狀況下,可能出現performance抖動,按期刪除最壞可佔25%的CPU時間片

這些現象都和過時鍵的堆積有關。那麼咱們怎麼避免這類過時鍵堆積呢。

5、如何避免過時鍵堆積,成爲」死鍵」

有效避免Redis過時鍵堆積,從兩個方面解決: 下降過時鍵產生的速度;和加快按期刪除的速度。

  • 業務設計鍵的過時時長時,是否考慮過時鍵生成的速度;可否加大過時鍵的生存時間。 如天氣緩存集羣,大量的鍵要求1分鐘過時,從產品需求角度,可否設置更大。
  • 儘可能避免使用大實例,控制Redis單實例的鍵個數(如1kw),可有效控制單個實例過時鍵產生的速度;拆分爲更多的分片,加大集羣按期刪除的速度
  • 適當調大hz的值,增大每秒按期刪除的次數;建議調整60,官方建議小100; 因調用serverCron除了過時刪除動做外,還有不少其餘操做,可能佔用過多的CPU時間片,影響業務請求。 咱們測試hz從默認10調整到100時,清理過時鍵的速度從20個升高到140個。
  • 主動觸發Redis」惰性刪除策略」,經過scan命令掃描整個實例的鍵,Redis會刪除全部已過時的鍵。 若是經過業務優化,擴容實例和調整hz都不能解決,可考慮按期使用這個大招。
如下是一個shell, 獲取當前服務器,Cluser的Master經過scan方式清理過時鍵
local_ip=`ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'`
redis-cli -p 6379 cluster nodes | grep "master" | grep "$local_ip" | while read node
do
  node_ins=`echo $node | awk '{print $2}' | cut -f 1 -d ":" `
  node_port=`echo $node | awk '{print $2}' | cut -f 2 -d ":" `
  redis-cli -h $node_ins -p $node_port --scan  >> /dev/null
done

6、你的Redis有堆積過時鍵嗎?

業務低峯期,找個Redis Master實例,支持scan命令(QPS會增加1w),查看命令執行先後,dbsize/used_memory是否有明顯降低 redis-cli -h $node_ins -p $node_port –scan >> /dev/null

7、應用程序已不使用的鍵

一個Redis集羣,分析鍵空間發現70%的鍵,3個月未訪問過。這類鍵沒未設置生存時間,實例也不能設置淘汰機制。 不少應用程序功能已下線,但它使用的Redis鍵每每無人清理或經過DBA處理;這樣的鍵從業務角度看,屬於無用的」死鍵」。

8、獲取鍵的idletime

每一個Redis鍵都有一個lru的屬性字段,用於記錄它最後一次被訪問的時間。

而object idletime命令,可經過系統當前時間-lru時間,獲得鍵多久沒有被訪問的秒數。

說明:object idletime命令訪問鍵時,不會改變鍵的lru屬性,即不會影響鍵的訪問時間 

如下示例,鍵"key:000000008149"已有150039秒未被訪問過
127.0.0.1:7000> object idletime "key:000000008149"
(integer) 150039
127.0.0.1:7000> object idletime "key:000000008149"
(integer) 150041

9、獲取鍵空間空閒時間超過指定時間的鍵

使用Python寫個簡單程序,scan指定數據庫的鍵空間,打印idletime超過指定時閥值的鍵。

#-*- coding:utf8 -*
import redis
import time
//Action: scan 0號數據庫的鍵空間,獲取空閒時長大於指定時間的鍵的列表,達到獲取業務死鍵的做用
//日期: 2016-08-11
TIME_THRESHOLD_SECOND = 2592000  # 獲取idletime時長超過TIME_THRESHOLD_SEC秒數鍵打印. 默認:30天
COUNT = 200  #scan每次返回的鍵個數,建議不要太大,避免O(n)的n過大出現慢查詢. 默認:200個
YEILD_SECOND = 0.05 #每次scan後,sleep 0.05秒;本地測試若是不sleep,此工具會增長約2w的QPS. 避免對高負載的Redis實例產生影響。
            #默認:0.05秒,增加約3500個QPS,其中一個時間複雜度是O(COUNT). 若是實例負載高,key很少能夠考慮sleep 0.1秒
def get_key_idletime():
    r = redis.StrictRedis(host='127.0.0.1', port=6380, password="xxxx" ,db=0)
    cursor = '0'
        while cursor != 0:
            cursor, data = r.scan(cursor=cursor, count=COUNT)
            for key in data:
                  key_idletime = r.object("idletime",key)
            if key_idletime > TIME_THRESHOLD_SECOND:
                print key , " ", key_idletime
            time.sleep(YEILD_SECOND)
get_key_idletime()

咱們定位Redis的長期未被訪問的鍵,咱們怎麼確認屬於哪一個業務功能呢? 怎麼預防業務的「死鍵」存在?

10、怎麼減小業務」死鍵」的產生

  • 經過3.1中按期巡檢,自動發現1個月未訪問過的鍵,並自動通知業務確認
  • 設置合理的命名空間,咱們建議三段式,用」:」分隔。每一個集羣固定前綴:每一個業務功能前綴:實際鍵名(前綴儘可能短,建議2個字節,減小內存消耗)。

每一個團隊按大業務功能有多個集羣,每一個集羣有多個小功能模塊;這樣命空間管理後,集羣有任何問題,DBA定位致使問題的」鍵前綴」,經過集羣對接負責的工程師 很快就定位是哪一個功能,什麼狀況引發的問題。

  • 給鍵設置合理的生存時間; 有效避免業務死鍵發生。好比用戶session, 用戶最近x小時已安裝APP列表等業務場景。有存儲性質的集羣,也可要求設置合理過時時間,如幾個月。經過info Keyspace命令,可查看當前實例有多少鍵設置有生存時間屬性。(另外設置過時時間,每一個鍵多消耗約32Bytes)
相關文章
相關標籤/搜索