深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

騰訊技術工程發表於2019-04-28

摘要:作為noSql中的kv資料庫的王者,redis以其高效能,低時延,豐富的資料結構備受開發者青睞,但是由於redis在水平伸縮性上受限,如何做到能夠水平擴容,同時對業務無侵入性是很多使用redis的開發人員都會面臨的問題,而redis分散式解決方案的一個開源產品【codis】較好的彌補了這一弱勢,本文主要講解codis是如何做到對業務無感知,平滑遷移,遷移效能高,遷移異常處理,高可用以及常見的redis的避坑指南,雖然codis目前隨著公司的nosql產品越來越成熟,生命週期也即將結束,不過鑑於還有很多同學對codis的原理比較感興趣,於是將以前的分享的內容重新整理,當然codis在公司外應用目前依舊還是相對比較廣泛。

目錄

一、背景 

二、Redis相關基礎概覽 

      2.1Redis簡介 

      2.2Redis的特點 

      2.3Redis應用場景 

三、Redis分散式解決方案公司內外比較 

四、Codis的架構設計 

      4.1 Codis整體的架構設計 

      4.2Codisproxy的架構設計實現 

五、資料可靠性 &高可用&容災&故障轉移&腦裂處理 

      5.1 資料可靠性

      5.2 高可用&容災&故障轉移 

六、codis水平擴容細節&遷移異常處理 

      6.1 Codis擴容遷移細節

      6.2 遷移異常處理 

七、Codis相關資料 

八、運維手冊及避坑指南 

九、參考資料 


一、背景

隨著直播元年開啟,越來越多的直播產品如春筍般出現,在拉動營收的過程中,產品竭盡全力思考著各種活動來刺激使用者的消費慾望,而這類活動的基礎形式就是榜單,在2016年我們基於cmem及掃描流水錶的方式來實現榜單排名,2017開始,我們對原有系統進行重構,使用redis作為我們的榜單基礎儲存,在重構的過程中接到調研redis分散式解決方案的任務之後,比對業內各種開源產品,最後定下Codis,並對其中細節做了一些研究,期間在與Codis作者交流的過程中,有幸知道增值產品部的simotang已經在部門引入codis近2年時間,遂加入到codis的運維工作中,目前在部門內部署運維codis叢集15套,2T容量,總日訪問量百億+.支撐了互動視訊產品部基礎儲存,運營活動,榜單類業務2年多,共計100多個活動,榜單上千個。同時在這裡非常感謝codis作者spinlock在接入codis過程中給予的指導與幫助。見spinlock github 與 codis地址

二、Redis相關基礎概覽

2.1 Redis簡介

redis是一個基於記憶體同時具備資料持久化能力的高效能,低時延的KV資料庫,value的資料結構可以是string,hash表,list(列表),set(集合),sortedset(有序集合)。

Redis(RemoteDictionary Server)

Redis is anopen source (BSD licensed), in-memory data structure store, used as adatabase, cache and message broker. It supports data structures suchas strings, hashes, lists, sets, sorted sets with rangequeries,Practice: http://try.redis.io/


2.2 Redis的特點

1. 單執行緒非同步架構(單執行緒,收包,發包,解析,執行,多路io複用接收檔案事件)

2. k-v結構,value支援豐富的資料結構(string,hash,list,set,sortset)

3. 高效能,低時延,基於記憶體操作,Get/Set10w+,高效能,基於RDB、AOF落地保證資料可靠性

4. 豐富的特性,可用於快取,訊息佇列,TTL過期

5. 支援事務,操作是原子性,要麼全部提交,要麼全部不提交。

2.3 Redis應用場景

string

計數器,使用者資訊(id)對映,唯一性(例如使用者資格判斷),bitmap

hash

常見場景:儲存物件的屬性資訊(使用者資料)

list

常見場景:評論儲存,訊息佇列

set

常見場景:資格判斷(例如使用者獎勵領取判斷),資料去重等

sorted set

常見場景:排行榜,延時佇列

其他

分散式鎖設計  推薦2篇文章:

基於Redis的分散式鎖到底安全嗎(上)

http://zhangtielei.com/posts/blog-redlock-reasoning.html

基於Redis的分散式鎖到底安全嗎(下)

http://zhangtielei.com/posts/blog-redlock-reasoning-part2.html


2.4 寫在前面:codis與redis的關係

codis與redis之間關係就是codis是基於多個redis例項做了一層路由層來進行資料的路由,每個redis例項承擔一定的資料分片

2.5 redis學習資料

由於本文重點在於redis分散式解決方案,對於redis相關的基礎部分,大家可以參考兩本書及相關原始碼分析文章

1. Redis開發與運維(付磊)

2. Redis設計與實踐(黃健巨集)(值得多看兩遍)

三、Redis分散式解決方案公司內外比較

在比較方案之前,我們先根據我們的經驗輸出了我們期望的解決方案應該具備的能力,以此來衡量我們的選擇標準

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

基於此我們對公司內外做了一個如下的比較

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

【公司內元件對比】

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

 【公司外元件對比】


基於以上比較,codis作為開源產品,可以很直觀的展示出codis運維成本低,擴容平滑最核心的優勢.

對於資料安全目前我們基於機器本機48小時滾動備份加上公司劉備備份(每天定時目錄備份的系統)的兜底備份,對於監控,目前接入monitor單機備份和米格監控告警)

四、codis的架構設計

4.1Codis整體的架構設計

codis官網

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

【圖codis架構圖】

如上圖所示,codis整體屬於二層架構,proxy+儲存,相對於ckv+無proxy的設計來說整體設計會相對簡單,同時對於客戶端連線資料逐漸增大的情況下,也不用去做資料層的副本擴容,而只需要做proxy層的擴容,從這一點上看,成本會低一些,但是對於連線數不大的情況下,還需要單獨去部署proxy,從這一點上看,成本會高一些。

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

其中,開源的codisproxy的服務的註冊發現是通過zk來實現,目前部門是基於l5來做.

從整體的架構設計圖來看,codis整體的架構比較清晰,其中codisproxy是分散式解決方案設計中最核心的部分,儲存路由,分片遷移均與codisproxy分不開,這塊我們來看一下codisproxy的設計實現。

4.2Codisproxy的架構設計實現

codisproxy的架構實現分成2個部分,分別為4.2.1的路由對映的細節與4.2.2的proxy請求處理的細節

4.2.1 路由對映細節

如下圖所示:該部分主要涉及到codis的路由細節,主要涉及到如何將一個key對映到具體的物理結點

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

【圖】路由對映細節


如上圖所示:該部分主要涉及到codis的路由細節

| 相關詞彙說明

slot:分片資訊,在redis當中僅僅表示一個數字,代表分片索引。每個分片會歸屬於具體的redis例項

group:主要是虛擬結點,由多臺redis機器組成,形成一主多從的模式,是邏輯意義上的結點

為了幫助大家對proxy路由對映的細節有一個更深入的理解,我整理了幾個常見的路由對映的相關問題來幫忙大家理解

問題一:proxy是如何把請求對映到具體的redis例項中?

Codis基於crc32的演算法%1024得到對應的slot,slot就是所謂的邏輯分片,同時codis會將對應的邏輯分片對映到對應的虛擬結點上,每個虛擬結點是由1主多從的物理redis結點組成。至於為啥會用crc32,這個具體也沒有細究,作者也是借鑑於rediscluster中的實現引入的。通過引入邏輯儲存結點group,這樣即使底層的主機機器例項變更,也不對映上層的對映資料,對上層對映透明,便於分片的管理。

問題二,proxy是如何做到讀寫分離

如上圖所示,key對映到具體的虛擬結點時,能夠感知到虛擬結點對應的主與備機例項,此時redisproxy層面能夠識別到具體的redis命令得到對應的命令是讀與寫,再根據叢集的配置是否支援讀寫分離的特性,如配置的是支援,則隨機路由到主與從機例項,如配置的是不支援,則路由到主機補全

問題三,proxy目前支援哪些命令,是否支援批量命令,如何保證原子性

命令支援連結

不支援命令

半支援命令

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

命令支援部分:Prxoy支援的命令分為三種:不支援命令,半支援命令,支援命令,除了上表所示命令外,其他命令proxy均是支援的,其中不支援命令部分主要是因為這些命令引數中沒有key,因此無法識別路由資訊,不知道具體路由到哪臺例項上,而半支援命令部分通常是會操作多個key,codis基於一種簡單實現,以第一個key的路由為準,因此需要業務方自己來保持多個key路由到同一個slot,當然業務也是可以不保證,具體後果業務來承擔,是一種弱校驗的模式,而公司級產品ckv+對於多key操作是強校驗,如果多key不在同一slot上,則以錯誤的形式返回。

多key操作&原子性部分:Redis本身對於多key的一些操作例如mset等命令是原子性的,而在分散式操作下,多key會分佈到多個redis例項當中,涉及到分散式事務,所以在codis當中進行了簡化處理,多key操作拆成多個單key命令操作,所以codis當中的mset多key操作不具備原子性的語義。

  問題四,如何保證多個key在一個slot當中

有些場景下,我們希望使用到lua或者一些半支援命令來保證我們操作的原子性,因此我們需要在業務層面來去保證多key在一個slot當中,codis採用了和rediscluster一樣的模式,基於hashtag,例如我想讓七天的主播榜單都中路由在同一個slot的話,{anchor_rank}day1,{anchor_rank}day2,{anchor_rank}day3,即可支援,對就是採用大括號的模式,codis會識別大括號,只會取大括號中的字串進行hash操作。

4.2.2Proxy請求處理細節

如下圖所示:該部分主要涉及到proxy的處理細節,涉及到如何接受一個請求到響應回包的過程

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

【圖】Proxy請求處理細節

如上圖所示:該部分主要涉及到proxy的處理細節

Codisproxy主要基於go語言這種從語言層面天然支援協程的語言來實現的

1)proxy接收客戶端的連線之後,新建一個session,同時啟動session中reader與writer兩個協程,reader主要用於接收客戶端請求資料並解析,對多key的場景下進行命令的拆分,然後將請求通過router進行分發到具體的redis例項,並將redis處理的資料結果寫到通道到中,writer從通道中接收對應的結果,將寫回給客戶端。

loop reader

loop writer

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

2)Router層主要是通過crc命令得到key對應的路由資訊,從原始碼可以看到hashtag的特性,codis其實也是支援的。

hash原始碼

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕


至此,proxy相關的路由對映與請求處理細節已經結束,整體下來是不是很簡單

五、資料可靠性&高可用&容災&故障轉移&腦裂處理

作為儲存層,資料可靠性與服務高可用是穩定性的核心指標,直接影響到上層核心服務的穩定性,本節將主要針對這兩個指標來做一下闡述。

5.1 資料可靠性

作為codis的實現來講,資料高可靠主要是redis本身的能力,通常儲存層的資料高可靠,主要是單機資料高可靠+遠端資料熱備+定期冷備歸檔實現的

單機資料高可靠主要是藉助於redis本身的持久化能力,rdb模式(定期dum)與aof模式(流水日誌),這塊可以參考前文所示的2本書來了解,其中aof模式的安全性更高,目前我們線上也是將aof開關開啟,在文末也會詳細描述一下。

遠端資料熱備主要是藉助於redis自身具備主從同步的特性,全量同步與增量同步的實現,讓redis具體遠端熱備的能力

定期冷備歸檔由於儲存服務在執行的過程中可能存在人員誤運算元據,機房網路故障,硬體問題導致資料丟失,因此我們需要一些兜底方案,目前主要是單機滾動備份備份最近48小時的資料以及sng的劉備系統來做冷備,以備非預期問題導致資料丟失,能夠快速恢復。

5.2 高可用&容災&故障轉移

codis的架構本身分成proxy叢集+redis叢集,proxy叢集的高可用,可以基於zk或者l5來做故障轉移,而redis叢集的高可用是藉助於redis開源的哨兵叢集來實現,那邊codis作為非redis元件,需要解決的一個問題就是如何整合redis哨兵叢集。本節將該問題分成三部分,介紹redis哨兵叢集如何保證redis高可用,codisproxy如何感知redis哨兵叢集的故障轉移動作,redis叢集如何降低“腦裂”的發生概率。

5.2.1 哨兵叢集如何保證redis高可用

Sentinel(哨崗,哨兵)是Redis的高可用解決方案:由一個或多個Sentinel例項組成的Sentinel系統,可以監視任意多個主伺服器,以及這些主伺服器屬下的所有的從伺服器,並在被監視的主伺服器進入下線狀態時,自動將下線主伺服器屬下的某個從伺服器升級為新的主伺服器,然後由主伺服器代替已下線的主伺服器繼續處理命令請求。

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

通常來說要達到服務的高可用的效果需要做2個事情:故障探測與故障轉移(即選主並做主從切換)

故障

探測

Sentinel叢集故障轉移

1)選出一臺Sentinel-leader,來進行故障轉移操作(raft協議,過半選舉)

if (winner && (max_votes < voters_quorum || max_votes < master->quorum))

2)領頭sentinel在已下線的從伺服器裡面,挑選一個從伺服器,並將其轉換為主伺服器

3)讓已下線主伺服器屬下的所有從伺服器改為複製新的主伺服器

4)將已下線主伺服器設定為新的主伺服器的從伺服器,當這個舊的主伺服器重新上線時,它就會成為新的主伺服器的從伺服器

注:從伺服器中挑選新的主伺服器的步驟

1)選舉列表中剔除所有下線或者斷線狀態的從伺服器

剔除所有最近五秒內沒有回覆過領頭Sentinel的info命令的從伺服器

剔除所有與已下線伺服器超過down-after-millisenconds * 10(ms)的從伺服器

2)根據從伺服器優先順序(取最高),複製偏移量(取最大),執行ID(取最小)1)每1秒,向主伺服器,從伺服器,其他sentinel例項傳送ping命令

有效回覆:+PONG, -Loading,+MASTERDOWN三種回覆一種

無效回覆:除以上三種回覆之外的回覆,或者在指定時限內沒有返回的回覆

Sentinel.conf -> Sentinel down-master-millsenconds master 50000

(當連續50秒,sentinel都接收到無效請求或者無回覆時,就會將master標記為主觀下線)

2)主觀下線之後,向其他sentinel傳送詢問命令,如果達到配置中指定的數量時,則標記master為客觀下線

Sentinel monitor master xx.xx.xx.xx 2

故障

轉移

Sentinel叢集故障轉移

1)選出一臺Sentinel-leader,來進行故障轉移操作(raft協議,過半選舉)

if (winner && (max_votes < voters_quorum || max_votes < master->quorum))

2)領頭sentinel在已下線的從伺服器裡面,挑選一個從伺服器,並將其轉換為主伺服器

3)讓已下線主伺服器屬下的所有從伺服器改為複製新的主伺服器

4)將已下線主伺服器設定為新的主伺服器的從伺服器,當這個舊的主伺服器重新上線時,它就會成為新的主伺服器的從伺服器

注:從伺服器中挑選新的主伺服器的步驟

1)選舉列表中剔除所有下線或者斷線狀態的從伺服器

剔除所有最近五秒內沒有回覆過領頭Sentinel的info命令的從伺服器

剔除所有與已下線伺服器超過down-after-millisenconds * 10(ms)的從伺服器

2)根據從伺服器優先順序(取最高),複製偏移量(取最大),執行ID(取最小)

5.2.2 codis如何感知哨兵叢集的故障轉移動作

codis的架構本身分成proxy叢集+redis叢集,redis叢集的高可用是由哨兵叢集來保證的,那麼proxy是如何感知redis主機故障,然後切換新主保證服務高可用的呢?

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

如上圖所示,proxy本身會監聽sentinle叢集的+switch-master事件,該事件發出,意味著redis叢集主機出現問題,sentinel叢集開始進行選舉並切換主機,proxy監聽了sentinel的主從切換事件,收到主從切換事件之後,proxy會做一個動作,就是把所有sentinel上的叢集所感知的當前認為的主機拉取出來,選取過半sentinel認為的主機當作目前的叢集主機。

講到這裡,大家可能會忽略一個問題,就是配置儲存,配置中心的儲存還是舊的主機,一旦proxy重起,那拉取的依舊是故障的主機,其實dashboard和proxy也做了一樣的事情,收到主從切換事件之後,就會將新主持久化到storage中(目前為zk)

5.2.3 腦裂處理

腦裂(split-brain)叢集的腦裂通常是發生在叢集中部分節點之間不可達而引起的。如下述情況發生時,不同分裂的小叢集會自主的選擇出master節點,造成原本的叢集會同時存在多個master節點。,結果會導致系統混亂,資料損壞。

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

在這個問題上,這裡simotang同學已經講解的非常完善了,大規模codis叢集的治理與實踐,這裡簡單說一下,由於redis叢集不能單純的依賴過半選舉的模式,因為redismaster自身沒有做檢測自身健康狀態而降級的動作,所以我們需要一種master健康狀態輔助判斷降級的方式。具體實現為

1)降級雙主出現的概率,讓Quorums判斷更加嚴格,讓主機下線判斷時間更加嚴格,我們部署了5臺sentinel機器覆蓋各大運營商IDC,只有4臺主觀認為主機下線的時候才做下線。

2)被隔離的master降級,基於共享資源判斷的方式,redis伺服器上agent會定時持續檢測zk是否通常,若連線不上,則向redis傳送降級指令,不可讀寫,犧牲可用性,保證一致性。

六、codis水平擴容細節&遷移異常處理

由於codis是針對redis分散式的解決方案,必然會面臨著redis單點容量不足的情況下水平擴容的問題,本節主要針對codis水平擴容與遷移異常的細節做一下說明,大家先帶著兩個問題來看,問題一,遷移過程中,正在遷移的key的讀寫請求怎麼處理,問題二,遷移過程中的異常(例如失敗,超時)怎麼處理。

6.1 Codis擴容遷移細節

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

【圖】遷移流程


影響面:

一階段期間的影響:通知到通知成功結束期間,proxy讀寫請求阻塞,不丟失,延時增高(時間極短,並行通知,僅僅修改狀態,使proxy中slot狀態達到一致)

遷移過程:可讀,正在遷移批次的不可寫,遷移完成的批次涉及到兩次網路io


如上圖所示,其實redis平滑遷移過程,主要是實現了3個點,遷移準備,遷移動作,遷移效能保證。

遷移準備

主要是在遷移動作執行前,所有的請求都能夠感知到路由的變化,所以有了一階段的處理流程,此處實現是通過並行傳送給所有的proxy,proxy會對相應的slot加寫鎖,所以的請求在佇列中排隊,直到所有的proxy都通知dashboard之後,proxy的鎖才放開,此時請求的延時會有輕微增高,但由於是並行響應,影響時間很短,檢視會輕微抖動。

遷移動作

主要由dashboard按批次觸發直到所有的key都遷移ok,遷移的過程,slot上的key可能存在2種情況,一種在新的redis例項上A,一種在舊的redis例項上B,所以對於有遷移狀態的slot,所有向這個slot傳送的命令都通過在redis中定製的命令SLOTSMGRT-EXEC-WRAPPER來處理,該命令是基於3.2的分支新增的,該命令主要做這幾個事情,1)判斷key是否存在,如果存在,但不在遷移批次,則直接對key呼叫真實方法,如果存在,但在遷移批次,則允許讀操作,不允許寫操作,2)如果key不存大,則key可能已經被遷移到新例項,也可能key不存在,則通知proxy前往新的例項進行操作

遷移效能

Codis的遷移其實之前2.x版本的遷移效能並不高,3.x之前效能提升了非常之大,千萬級別的zset結構遷移只需要10多秒,而在原來的模式需要50多秒,具體原因在於
深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

遷移效能資料

6.2 遷移異常處理

另外,看到這裡,不知道大家有沒有什麼問題,不過這裡我準備了一些問題,來看看codis是如何來處理的,特別在網路環境複雜,不穩定的情況下怎麼操作

問題一,把大key拆分成小批次進行遷移,如果批次遷移失敗,超時,怎麼做?

我們知道分佈場景下網路呼叫有三態,成功,失敗,超時,對於失敗還好一點,超時的情況,我們能否盲目進行重試,這裡顯然不行,通常對於資料層面的重試,我們需要保證一個非常重要的原則,冪等性,但是在redis結構中除了zset,set,hash,string結構重試理論不會受影響,對於list怎麼辦?所以codis用了一種比較暴力的方式,批次遷移成功重試時,會先帶上一個del命令,讓目標結點先將key刪掉,再進行重試。

問題二,帶過期時間key遷移過程中,先在目標結點上設定過期時間再傳資料,還是先傳資料在最後再設定過期時間?

先看一下在目標結點上設定過期時間再傳資料的問題:傳輸一半B機器的key過期,後續key就沒有過期時間。不符合我們的期望

再看一下先傳資料在最後再設定過期時間的問題:如果傳輸一半Acrash重啟,而此時key過期,則資料落在B機器上成殭屍資料,也不符合我們的期望。那codis如何來做呢?

為了保證遷移過程中的分片在遷移異常時能自動銷燬,所以每次分片傳輸的時候,都重置一下key過期時間為90秒(大於超時時間30秒),在key遷移完成之後再重置為真實的過期時間,這樣即使遷移過程中Acrash,key過期或者其他的異常,分片資料也只會在目標結點上存活90秒就銷燬。

問題三,遷移過程中Acrash, 此時對應分片的資料一半在A,一半在B,怎麼辦了?

常在河邊走,哪有不挨刀,我們就碰到過codis的一個因expire遷移實現不當造成的血案,不過幸好發生在測試環境,此時千萬千萬不要拉起A,因為A上可能有舊資料,此時會導致已經遷移完成的key重新遷移,造成B的資料丟失,正確的姿勢是A的備機頂上去,繼續遷移,因為A的備機雖然是非同步複製,但基本接近於A的全量資料,所以問題不太大。不過所有的遷移過程中,都最好把資料和分片資訊備份,以防資料丟失。此時也千萬千萬不能反向將B的資料遷移回A,因為B上可能殘留有部分遷移的資料,會覆蓋掉A的全量資料。

問題四,為了效能問題,可否A不做備機,不開啟AOF和RDB

這個也是萬萬不可,因為A如果crash之後,被織雲拉起,則相當於一個空例項,會清掉備機的資料,造成資料丟失。

七、Codis相關資料

其中壓測環境:壓測伺服器(v4-8-100)+proxy(v4-8-100) +  redis( B5(4 -32-100) )

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕


從上圖中可以看出,當單次獲取的資料量越來越大時,proxy的效能下降會非常快,例如ZRANGE_500的直連的效能是proxy的2倍

八、運維手冊及避坑指南

操作注意項:

8.1 主從切換: 每次主從切換之後,都確認一下被切的主或者備機上的conf檔案都已經rewriteok。

grep "Generatedby CONFIG REWRITE" -C 10 {redis_conf路徑}/*.conf

8.2 遷移資料:關鍵操作前,備份資料,若涉及切片資訊,備份切片資訊

A遷移B時間過長的命令檢視:連上Acodisserver,命令列中執行slotsmgrt-async-status檢視正在遷移的分片資訊(尤其是大key),做到心中有數。千萬級別的key約20秒左右可以遷移完成

8.3 異常處理:redis當機後重啟,重啟之後載入key快載入完時,頁面上報error

原因

可能是當機後,redis命令寫入aof,只寫了命令的部分或者事務提交之後只寫入了事務的部分命令導致啟動失敗,此時日誌會aof的異常

修復

第一步 備份aof檔案

第二步 執行VIP_CodisAdmin/bin中的redis-check-aof --fix appendonly.aof

第三步 重啟

8.4 客戶端出現大量超時

1)網路原因,聯絡“連線NOC智慧助手”,確認鏈路網路是否出現擁塞

2)觀察檢視,檢視監聽佇列是否溢位

全連線佇列的大小取決於:min(backlog, somaxconn) ,backlog是在socket建立的時候傳入的,somaxconn是一個os級別的系統引數,基於命令ss -lnt,觀察監聽佇列目前的長度是否與預期一致,

調整引數:vim /etc/sysctl.conf net.core.somaxconn=1024   sysctl -p

3)慢查詢,slowlogget,確認是否有耗時操作執行,現網預設是10ms

slowlog-log-slower-than和slowlog-max-len

其中注意:慢查詢不包含請求排隊時間,只包含請求執行時間,所以有可能是redis本身排隊導致的問題,但通過慢查詢可能查不出來

8.5 fork耗時高

原因

1)當Redis做RDB或AOF重寫時,一個必不可少的操作就是執行fork操作建立子程式,雖然fork建立的子程式不需要拷貝父程式的實體記憶體空間,但是會複製父程式的空間記憶體頁表,可以在info stats統計中查latest_fork_usec指標獲取最近一次fork操作耗時,單位(微秒)。

改善

1)優先使用物理機或者高效支援fork操作的虛擬化技術。

2)控制redis單例項的記憶體大小。fork耗時跟記憶體量成正比,線上建議每個Redis例項記憶體控制在10GB以內。

3)適度放寬AOF rewrite觸發時機,目前線上配置:auto-aof-rewrite-percentage增長100 %

子程式開銷

監控與優化


cpu

    不要和其他CPU密集型服務部署在一起,造成CPU過度競爭

    如果部署多個Redis例項,儘量保證同一時刻只有一個子程式執行重寫工作

    1G記憶體fork時間約20ms

記憶體

    背景:子程式通過fork操作產生,佔用記憶體大小等同於父程式,理論上需要兩倍的記憶體來完成持久化操作,但Linux有寫時複製機制(copy-on-write)。父子程式會共享相同的實體記憶體頁,當父程式處理寫請求時會把要修改的頁建立副本,而子程式在fork操作過程中共享整個父程式記憶體快照。

Fork耗費的記憶體相關日誌:AOF rewrite: 53 MB of memory used by copy-on-write,RDB: 5 MB of memory used by copy-on-write

    關閉巨頁,開啟之後,複製頁單位從原來4KB變為2MB,增加fork的負擔,會拖慢寫操作的執行時間,導致大量寫操作慢查詢

“sudo echo never>/sys/kernel/mm/transparent_hugepage/enabled

硬碟

    不要和其他高硬碟負載的服務部署在一起。如:儲存服務、訊息佇列

8.6  AOF持久化細節

常用的同步硬碟的策略是everysec,用於平衡效能和資料安全性。對於這種方式,Redis使用另一條執行緒每秒執行fsync同步硬碟。當系統硬碟資源繁忙時,會造成Redis主執行緒阻塞。

深入淺出百億請求高可用Redis(codis)分散式叢集揭祕

1)主執行緒負責寫入AOF緩衝區(原始碼:flushAppendOnlyFile)

2)AOF執行緒負責每秒執行一次同步磁碟操作,並記錄最近一次同步時間。

3)主執行緒負責對比上次AOF同步時間:

如果距上次同步成功時間在2秒內,主執行緒直接返回。

如果距上次同步成功時間超過2秒,主執行緒將呼叫write(2)阻塞,直到同步操作完成


備註:開啟AOF持久化功能後,Redis處理完每個事件後會呼叫write(2)將變化寫入kernel的buffer,如果此時write(2)被阻塞,Redis就不能處理下一個事件。Linux規定執行write(2)時,如果對同一個檔案正在執行fdatasync(2)將kernel buffer寫入物理磁碟, write(2)會被Block住,整個Redis被Block住。

通過對AOF阻塞流程可以發現兩個問題:

1)everysec配置最多可能丟失2秒資料,不是1秒。

2)如果系統fsync緩慢,將會導致Redis主執行緒阻塞影響效率。

Redis提供了一個自救的方式,當發現檔案有在執行fdatasync(2)時,就先不呼叫write(2),只存在cache裡,免得被Block。但如果已經超過兩秒都還是這個樣子,則會硬著頭皮執行write(2),即使redis會被Block住。

Asynchronous AOF fsync is taking too long (disk is busy). Writing the AOF buffer,without waiting for fsync to complete, this may slow down Redis

8.7 不小心手抖執行了flushdb

如果配置appendonlyno,迅速調大rdb觸發引數,然後備份rdb檔案,若備份失敗,趕緊跑路。配置了appedonlyyes, 辦法調大AOF重寫引數auto-aof-rewrite-percentage和auto-aof-rewrite-minsize,或者直接kill程式,讓Redis不能產生AOF自動重寫。·拒絕手動bgrewriteaof。備份aof檔案,同時將備份的aof檔案中寫入的flushdb命令幹掉,然後還原。若還原不了,則依賴於冷備。

8.8 線上redis想將rdb模式換成aof模式

切不可,直接修改conf,重啟

正確方式:備份rdb檔案,configset的方式開啟aof,同時configrewrite寫回配置,執行bgrewriteof,記憶體資料備份至檔案

九、參考資料

Redis開發與運維(付磊)

Redis設計與實踐(黃健巨集)

大規模codis叢集的治理與實踐

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559354/viewspace-2642830/,如需轉載,請註明出處,否則將追究法律責任。

相關文章