日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

优酷蓝鲸近千节点的Redis集群运维经验总结

發(fā)布時間:2025/4/5 数据库 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 优酷蓝鲸近千节点的Redis集群运维经验总结 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

http://www.infoq.com/cn/news/2016/08/youku-Redis-nosql

Redis是時下比較流行的Nosql技術。在優(yōu)酷我們使用Redis Cluster構建了一套內存存儲系統(tǒng),項目代號藍鯨。到目前為止集群有700+節(jié)點,即將達到作者推薦的最大集群規(guī)模1000節(jié)點。集群從Redis Cluster發(fā)布不久就開始運行,到現(xiàn)在已經(jīng)將近兩年時間。在運維集群過程中遇到了很多問題,記錄下來希望對他人有所幫助。

主從重同步問題

問題描述

服務器宕機并恢復后,需要重啟Redis實例,因為集群采用主從結構并且宕機時間比較長,此時宕機上的節(jié)點對應的節(jié)點都是主節(jié)點,宕掉的節(jié)點重啟后都應該是從節(jié)點。啟動Redis實例,我們通過日志發(fā)現(xiàn)節(jié)點一直從不斷的進行主從同步。我們稱這種現(xiàn)象為主從重同步。

主從同步機制

為了分析以上問題,我們首先應該搞清楚Redis的主從同步機制。以下是從節(jié)點正常的主從同步流程日志:

17:22:49.763 * MASTER <-> SLAVE sync started
17:22:49.764 * Non blocking connect for SYNC fired the event. 17:22:49.764 * Master replied to PING, replication can continue... 17:22:49.764 * Partial resynchronization not possible (no cached master) 17:22:49.765 * Full resync from master: c9fabd3812295cc1436af69c73256011673406b9:1745224753247
17:23:42.223 * MASTER <-> SLAVE sync: receiving 1811656499 bytes from master
17:24:04.484 * MASTER <-> SLAVE sync: Flushing old data
17:24:25.646 * MASTER <-> SLAVE sync: Loading DB in memory
17:27:21.541 * MASTER <-> SLAVE sync: Finished with success
17:28:22.818 # MASTER timeout: no data nor PING received... 17:28:22.818 # Connection with master lost. 17:28:22.818 * Caching the disconnected master state. 17:28:22.818 * Connecting to MASTER xxx.xxx.xxx.xxx:xxxx
17:28:22.818 * MASTER <-> SLAVE sync started
17:28:22.819 * Non blocking connect for SYNC fired the event. 17:28:22.824 * Master replied to PING, replication can continue... 17:28:22.824 * Trying a partial resynchronization (request c9fabd3812295cc1436af69c73256011673406b9:1745240101942). 17:28:22.825 * Successful partial resynchronization with master.

以上日志是以從節(jié)點的視角呈現(xiàn)的,因為以從節(jié)點的角度更能反映主從同步流程,所以以下的分析也以從節(jié)點的視角為主。日志很清楚的說明了Redis主從同步的流程,主要步驟為:

  • 從節(jié)點接收RDB文件
  • 從節(jié)點清空舊數(shù)據(jù)
  • 從節(jié)點加載RDB文件
  • 到此一次全量主從同步完成。等等日志中“Connection with master lost”是什么鬼,為什么接下來又進行了一次主從同步。

    “Connection with master lost”的字面意思是從節(jié)點與主節(jié)點的連接超時。在Redis中主從節(jié)點需要互相感知彼此的狀態(tài),這種感知是通過從節(jié)點定時PING主節(jié)點并且主節(jié)點返回PONG消息來實現(xiàn)的。那么當主節(jié)點或者從節(jié)點因為其他原因不能及時收到PING或者PONG消息時,則認為主從連接已經(jīng)斷開。

    問題又來了何為及時,Redis通過參數(shù)repl-timeout來設定,它的默認值是60s。Redis配置文件(redis.conf)中詳細解釋了repl-timeout的含義:

    # The following option sets the replication timeout for:
    #
    # 1) Bulk transfer I/O during SYNC, from the point of view of slave.
    # 2) Master timeout from the point of view of slaves (data, pings).
    # 3) Slave timeout from the point of view of masters (REPLCONF ACK pings).
    #
    # It is important to make sure that this value is greater than the value
    # specified for repl-ping-slave-period otherwise a timeout will be detected
    # every time there is low traffic between the master and the slave.
    #
    # repl-timeout 60

    我們回過頭再來看上邊的同步日志,從節(jié)點加載RDB文件花費將近三分鐘的時間,超過了repl-timeout,所以從節(jié)點認為與主節(jié)點的連接斷開,所以它嘗試重新連接并進行主從同步。

    部分同步

    這里補充一點當進行主從同步的時候Redis都會先嘗試進行部分同步,部分同步失敗才會嘗試進行全量同步。

    Redis中主節(jié)點接收到的每個寫請求,都會寫入到一個被稱為repl_backlog的緩存空間中,這樣當進行主從同步的時候,首先檢查repl_backlog中的緩存是否能滿足同步需求,這個過程就是部分同步。

    考慮到全量同步是一個很重量級別并且耗時很長的操作,部分同步機制能在很多情況下極大的減小同步的時間與開銷。

    重同步問題

    通過上面的介紹大概了解了主從同步原理,我們在將注意力放在加載RDB文件所花費的三分鐘時間上。在這段時間內,主節(jié)點不斷接收前端的請求,這些請求不斷的被加入到repl_backlog中,但是因為Redis的單線程特性,從節(jié)點是不能接收主節(jié)點的同步寫請求的。所以不斷有數(shù)據(jù)寫入到repl_backlog的同時卻沒有消費。

    當repl_backlog滿的時候就不能滿足部分同步的要求了,所以部分同步失敗,需要又一次進行全量同步,如此形成無限循環(huán),導致了主從重同步現(xiàn)象的出現(xiàn)。不僅侵占了帶寬,而且影響主節(jié)點的服務。

    解決方案

    至此解決方案就很明顯了,調大repl_backlog。

    Redis中默認的repl_backlog大小為1M,這是一個比較小的值,我們的集群中曾經(jīng)設置為100M,有時候還是會出現(xiàn)主從重同步現(xiàn)象,后來改為200M,一切太平。可以通過以下命令修改repl_backlog的大小:

    //200Mredis-cli -h xxx -p xxx config set repl-backlog-size 209715200

    內存碎片

    首先對于絕大部分系統(tǒng)內存碎片是一定存在的。試想內存是一整塊連續(xù)的區(qū)域,而數(shù)據(jù)的長度可以是任意的,并且會隨時發(fā)生變化,隨著時間的推移,在各個數(shù)據(jù)塊中間一定會夾雜著小塊的難以利用的內存,所以在Redis中內存碎片是存在的。

    在Redis中通過info memory命令能查看內存及碎片情況:

    # Memory
    used_memory:4221671264????????? /* 內存分配器為數(shù)據(jù)分配出去的內存大小,可以認為是數(shù)據(jù)的大小 */
    used_memory_human:3.93G???????? /* used_memoryd的閱讀友好形式 */
    used_memory_rss:4508459008????? /* 操作系統(tǒng)角度上Redis占用的物理內存空間大小,注意不包含swap */
    used_memory_peak:4251487304???? /* used_memory的峰值大小 */
    used_memory_peak_human:3.96G??? /* used_memory_peak的閱讀友好形式 */
    used_memory_lua:34816mem_fragmentation_ratio:1.07??? /* 碎片率 */
    mem_allocator:jemalloc-3.6.0??? /* 使用的內存分配器 */

    對于每一項的意義請注意查看注釋部分,也可以參考官網(wǎng)上info命令memory部分。Redis中內存碎片計算公式為:

    mem_fragmentation_ratio = used_memory_rss / used_memory

    可以看出上邊的Redis實例的內存碎片率為1.07,是一個較小的值,這也是正常的情況,有正常情況就有不正常的情況。發(fā)生數(shù)據(jù)遷移之后的Redis碎片率會很高,以下是遷移數(shù)據(jù)后的Redis的碎片情況:

    used_memory:4854837632
    used_memory_human:4.52G
    used_memory_rss:7362924544
    used_memory_peak:7061034784
    used_memory_peak_human:6.58G
    used_memory_lua:39936
    mem_fragmentation_ratio:1.52
    mem_allocator:jemalloc-3.6.0

    可以看到碎片率是1.52,也就是說有三分之一的內存被浪費掉了。針對以上兩種情況,對于碎片簡單的分為兩種:

    • 常規(guī)碎片

    • 遷移碎片

    常規(guī)碎片數(shù)量較小,而且一定會存在,可以不用理會。那么如何去掉遷移碎片呢?其實方案很簡單,只需要先BGSAVE再重新啟動節(jié)點,重新加載RDB文件會去除絕大部分碎片。

    但是這種方案有較長的服務不可用窗口期,所以需要另一種較好的方案。這種方案需要Redis采用主從結構為前提,主要思路是先通過重啟的方式處理掉從節(jié)點的碎片,之后進行主從切換,最后處理老的主節(jié)點的碎。這樣通過極小的服務不可用時間窗口為代價消除了絕大大部分碎片。

    Redis Cluster剔除節(jié)點失敗

    Redis Cluster采用無中心的集群模式,集群中所有節(jié)點通過互相交換消息來維持一致性。當有新節(jié)點需要加入集群時,只需要將它與集群中的一個節(jié)點建立聯(lián)系即可,通過集群間節(jié)點互相交換消息所有節(jié)點都會互相認識。所以當需要剔除節(jié)點的時候,需要向所有節(jié)點發(fā)送cluster forget命令。

    而向集群所有節(jié)點發(fā)送命令需要一段時間,在這段時間內已經(jīng)接收到cluster forget命令的節(jié)點與沒有接收的節(jié)點會發(fā)生信息交換,從而導致cluster forget命令失效。

    為了應對這個問題Redis設計了一個黑名單機制。當節(jié)點接收到cluster forget命令后,不僅會將被踢節(jié)點從自身的節(jié)點列表中移除,還會將被剔除的節(jié)點添加入到自身的黑名單中。當與其它節(jié)點進行消息交換的時候,節(jié)點會忽略掉黑名單內的節(jié)點。所以通過向所有節(jié)點發(fā)送cluster forget命令就能順利地剔除節(jié)點。

    但是黑名單內的節(jié)點不應該永遠存在于黑名單中,那樣會導致被踢掉的節(jié)點不能再次加入到集群中,同時也可能導致不可預期的內存膨脹問題。所以黑名單是需要有時效性的,Redis設置的時間為一分鐘。

    所以當剔除節(jié)點的時候,在一分鐘內沒能向所有節(jié)點發(fā)出cluster forget命令,會導致剔除失敗,尤其在集群規(guī)模較大的時候會經(jīng)常發(fā)生。

    解決方案是多個進程發(fā)送cluster forget命令,是不是很簡單。

    遷移數(shù)據(jù)時的JedisAskDataException異常

    問題描述

    Redis Cluster集群擴容,需要將一部分數(shù)據(jù)從老節(jié)點遷移到新節(jié)點。在遷移數(shù)據(jù)過程中會出現(xiàn)較多的JedisAskDataException異常。

    遷移流程

    由于官方提供遷移工具redis-trib在大規(guī)模數(shù)據(jù)遷移上的一些限制,我們自己開發(fā)了遷移工具,Redis Cluster中數(shù)據(jù)遷移是以Slot為單位的,遷移一個Slot主要流程如下:

    目標節(jié)點 cluster setslot <slot> importing <source_id>
    源節(jié)點?? cluster setslot <slot> migrating <target_id>
    源節(jié)點?? cluster getkeysinslot <slot> <count>? ==> keys 源節(jié)點?? migrate <target_ip> <target_port> <key> 0 <timeout>
    重復3&4直到遷移完成 任一節(jié)點 cluster setslot <slot> node <target_id>

    我們使用Redis中的MIGRATE命令來把數(shù)據(jù)從一個節(jié)點遷移到另外一個節(jié)點。MIGRATE命令實現(xiàn)機制是先在源節(jié)點上DUMP數(shù)據(jù),再在目標節(jié)點上RESTORE它。

    但是DUMP命令并不會包含過期信息,又因為集群中所有的數(shù)據(jù)都有過期時間,所以我們需要額外的設置過期時間。所以遷移一個SLOT有點類似如下:

    while (from.clusterCountKeysInSlot(slot) != 0) {??
    ??? keys = from.clusterGetKeysInSlot(slot, 100);??? for (String key : keys) {??????? //獲取key的ttl??????? Long ttl = from.pttl(key);??????? if (ttl > 0) {???????
    ??????????? from.migrate(host, port, key, 0, 2000);??????????? to.asking();??????????? to.pexpire(key, ttl);??????? }??? } }

    但是上邊的遷移工具在運行過程中報了較多的JedisAskDataException異常,通過堆棧發(fā)現(xiàn)是“Long ttl = from.pttl(key)”這一行導致的。為了解釋上述異常,我們需要先了解Redis的一些內部機制。

    Redis數(shù)據(jù)過期機制

    Redis數(shù)據(jù)過期混合使用兩種策略

    • 主動過期策略:定時掃描過期表,并刪除過期數(shù)據(jù),注意這里并不會掃描整個過期表,為了減小任務引起的主線程停頓,每次只掃描一部分數(shù)據(jù),這樣的機制導致數(shù)據(jù)集中可能存在較多已經(jīng)過期但是并沒有刪除的數(shù)據(jù)。

    • 被動過期策略:當客戶端訪問數(shù)據(jù)的時候,首先檢查它是否已經(jīng)過期,如果過期則刪掉它,并返回數(shù)據(jù)不存在標識。

    這樣的過期機制兼顧了每次任務的停頓時間與已經(jīng)過期數(shù)據(jù)不被訪問的功能性,充分體現(xiàn)了作者優(yōu)秀的設計能力,詳細參考官網(wǎng)數(shù)據(jù)過期機制。

    Open狀態(tài)Slot訪問機制

    在遷移Slot的過程中,需要先在目標節(jié)點將Slot設置為importing狀態(tài),然后在源節(jié)點中將Slot設置為migrating 狀態(tài),我們稱這種Slot為Open狀態(tài)的Slot。

    因為處于Open狀態(tài)的Slot中的數(shù)據(jù)分散在源與目標兩個節(jié)點上,所以如果需要訪問Slot中的數(shù)據(jù)或者添加數(shù)據(jù)到Slot中,需要特殊的訪問規(guī)則。Redis推薦規(guī)則是首先訪問源節(jié)點再去訪問目標節(jié)點。如果源節(jié)點不存在,Redis會返回ASK標記給客戶端,詳細參考官網(wǎng)。

    問題分析

    讓我們回到問題本身,經(jīng)過閱讀Redis代碼發(fā)現(xiàn)clusterCountKeysInSlot函數(shù)不會觸發(fā)被動過期策略,所以它返回的數(shù)據(jù)包含已經(jīng)過期但是沒有被刪除的數(shù)據(jù)。當程序執(zhí)行到“Long ttl = from.pttl(key);”這一行時,首先Redis會觸發(fā)觸發(fā)被動過期策略刪掉已經(jīng)過期的數(shù)據(jù),此時該數(shù)據(jù)已經(jīng)不存在,又因為該節(jié)點處于migrating狀態(tài),所以ASK標記會被返回。而ASK標記被Jedis轉化為JedisAskDataException異常。

    這種異常只需要捕獲并跳過即可。

    Redis Cluster flush失敗

    flush是一個極少用到的操作,不過既然碰到過詭異的現(xiàn)象,也記錄在此。

    問題場景是在Reids Cluster中使用主從模式,向主節(jié)點發(fā)送flush命令,預期主從節(jié)點都會清空數(shù)據(jù)庫。但是詭異的現(xiàn)象出現(xiàn)了,我們得到的結果是主從節(jié)點發(fā)生了切換,并且數(shù)據(jù)并沒有被清空。

    分析以上case,Redis采用單線程模型,flush操作執(zhí)行的時候會阻塞所有其它操作,包括集群間心跳包。當Redis中有大量數(shù)據(jù)的時候,flush操作會消耗較長時間。所以該節(jié)點較長時間不能跟集群通信,當達到一定閾值的時候,集群會判定該節(jié)點為fail,并且會切換主從狀態(tài)。

    Redis采用異步的方式進行主從同步,flush操作在主節(jié)點執(zhí)行完成之后,才會將命令同步到從節(jié)點。此時老的從節(jié)點變?yōu)榱酥鞴?jié)點,它不會再接受來自老的主節(jié)點的刪除數(shù)據(jù)的操作。

    當老的主節(jié)點flush完成的時候,它恢復與集群中其它節(jié)點的通訊,得知自己被變成了從節(jié)點,所又會把數(shù)據(jù)同步過來。最終造成了主從節(jié)點發(fā)生了切換,并且數(shù)據(jù)沒有被清空的現(xiàn)象。

    解決方式是臨時調大集群中所有節(jié)點的cluster-node-timeout參數(shù)。

    Redis啟動異常問題

    這也是個極少碰到的問題,同上也記錄在此。

    我們集群中每個物理主機上啟動多個Redis以利用多核主機的計算資源。問題發(fā)生在一次主機宕機。恢復服務的過程中,當啟動某一個Redis實例的時候,Redis實例正常啟動,但是集群將它標記為了fail狀態(tài)。

    眾所周知Redis Cluster中的實例,需要監(jiān)聽兩個端口,一個服務端口(默認6379),另一個是集群間通訊端口(16379),它是服務端口加上10000。

    經(jīng)過一番調查發(fā)現(xiàn)該節(jié)點的服務通訊端口,已經(jīng)被集群中其它節(jié)點占用了,導致它不能與集群中其它節(jié)點通訊,被標記為fail狀態(tài)。

    解決方式是找到占用該端口的Redis進程并重啟。

    寫在最后

    運維是一個理論落地的過程,對于運維集群而言任何微小的異常背后都是有原因的,了解系統(tǒng)內部運行機制,并且著手去探究,才能更好的解釋問題,消除集群的隱患。

    轉載于:https://www.cnblogs.com/davidwang456/articles/9254085.html

    總結

    以上是生活随笔為你收集整理的优酷蓝鲸近千节点的Redis集群运维经验总结的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。