深入學(xué)習(xí)Redis:主從復(fù)制

????????在Redis的持久化中曾提到,Redis高可用的方案包括持久化蛇券、主從復(fù)制(及讀寫分離)剃毒、哨兵和集群。其中持久化側(cè)重解決的是Redis數(shù)據(jù)的單機(jī)備份問題(從內(nèi)存到硬盤的備份)洁闰;而主從復(fù)制則側(cè)重解決數(shù)據(jù)的多機(jī)熱備歉甚。此外,主從復(fù)制還可以實(shí)現(xiàn)負(fù)載均衡和故障恢復(fù)扑眉。

這篇文章中纸泄,將詳細(xì)介紹Redis主從復(fù)制的方方面面,包括:如何使用主從復(fù)制腰素、主從復(fù)制的原理(重點(diǎn)是全量復(fù)制和部分復(fù)制聘裁、以及心跳機(jī)制)、實(shí)際應(yīng)用中需要注意的問題(如數(shù)據(jù)不一致問題弓千、復(fù)制超時問題衡便、復(fù)制緩沖區(qū)溢出問題)、主從復(fù)制相關(guān)的配置(重點(diǎn)是repl-timeout、client-output-buffer-limit slave)等镣陕。

一征唬、主從復(fù)制概述

主從復(fù)制,是指將一臺Redis服務(wù)器的數(shù)據(jù)茁彭,復(fù)制到其他的Redis服務(wù)器。前者稱為主節(jié)點(diǎn)(master)扶歪,后者稱為從節(jié)點(diǎn)(slave)理肺;數(shù)據(jù)的復(fù)制是單向的,只能由主節(jié)點(diǎn)到從節(jié)點(diǎn)善镰。

默認(rèn)情況下妹萨,每臺Redis服務(wù)器都是主節(jié)點(diǎn);且一個主節(jié)點(diǎn)可以有多個從節(jié)點(diǎn)(或沒有從節(jié)點(diǎn))炫欺,但一個從節(jié)點(diǎn)只能有一個主節(jié)點(diǎn)乎完。

主從復(fù)制的作用

主從復(fù)制的作用主要包括:

數(shù)據(jù)冗余:主從復(fù)制實(shí)現(xiàn)了數(shù)據(jù)的熱備份,是持久化之外的一種數(shù)據(jù)冗余方式品洛。

故障恢復(fù):當(dāng)主節(jié)點(diǎn)出現(xiàn)問題時树姨,可以由從節(jié)點(diǎn)提供服務(wù),實(shí)現(xiàn)快速的故障恢復(fù)桥状;實(shí)際上是一種服務(wù)的冗余帽揪。

負(fù)載均衡:在主從復(fù)制的基礎(chǔ)上,配合讀寫分離辅斟,可以由主節(jié)點(diǎn)提供寫服務(wù)转晰,由從節(jié)點(diǎn)提供讀服務(wù)(即寫Redis數(shù)據(jù)時應(yīng)用連接主節(jié)點(diǎn),讀Redis數(shù)據(jù)時應(yīng)用連接從節(jié)點(diǎn))士飒,分擔(dān)服務(wù)器負(fù)載查邢;尤其是在寫少讀多的場景下,通過多個從節(jié)點(diǎn)分擔(dān)讀負(fù)載酵幕,可以大大提高Redis服務(wù)器的并發(fā)量扰藕。

高可用基石:除了上述作用以外,主從復(fù)制還是哨兵和集群能夠?qū)嵤┑幕A(chǔ)裙盾,因此說主從復(fù)制是Redis高可用的基礎(chǔ)实胸。

二、如何使用主從復(fù)制

為了更直觀的理解主從復(fù)制番官,在介紹其內(nèi)部原理之前庐完,先說明我們需要如何操作才能開啟主從復(fù)制。

1. 建立復(fù)制

需要注意徘熔,主從復(fù)制的開啟门躯,完全是在從節(jié)點(diǎn)發(fā)起的;不需要我們在主節(jié)點(diǎn)做任何事情酷师。

從節(jié)點(diǎn)開啟主從復(fù)制讶凉,有3種方式:

(1)配置文件

在從服務(wù)器的配置文件中加入:slaveof

(2)啟動命令

redis-server啟動命令后加入 --slaveof

(3)客戶端命令

Redis服務(wù)器啟動后染乌,直接通過客戶端執(zhí)行命令:slaveof ,則該Redis實(shí)例成為從節(jié)點(diǎn)懂讯。

上述3種方式是等效的荷憋,下面以客戶端命令的方式為例,看一下當(dāng)執(zhí)行了slaveof后褐望,Redis主節(jié)點(diǎn)和從節(jié)點(diǎn)的變化勒庄。

2. 實(shí)例

準(zhǔn)備工作:啟動兩個節(jié)點(diǎn)

方便起見,實(shí)驗(yàn)所使用的主從節(jié)點(diǎn)是在一臺機(jī)器上的不同Redis實(shí)例瘫里,其中主節(jié)點(diǎn)監(jiān)聽6379端口实蔽,從節(jié)點(diǎn)監(jiān)聽6380端口;從節(jié)點(diǎn)監(jiān)聽的端口號可以在配置文件中修改:

啟動后可以看到:

兩個Redis節(jié)點(diǎn)啟動后(分別稱為6379節(jié)點(diǎn)和6380節(jié)點(diǎn))谨读,默認(rèn)都是主節(jié)點(diǎn)局装。

建立復(fù)制

此時在6380節(jié)點(diǎn)執(zhí)行slaveof命令,使之變?yōu)閺墓?jié)點(diǎn):

觀察效果

下面驗(yàn)證一下劳殖,在主從復(fù)制建立后铐尚,主節(jié)點(diǎn)的數(shù)據(jù)會復(fù)制到從節(jié)點(diǎn)中。

(1)首先在從節(jié)點(diǎn)查詢一個不存在的key:

(2)然后在主節(jié)點(diǎn)中增加這個key:

(3)此時在從節(jié)點(diǎn)中再次查詢這個key哆姻,會發(fā)現(xiàn)主節(jié)點(diǎn)的操作已經(jīng)同步至從節(jié)點(diǎn):

(4)然后在主節(jié)點(diǎn)刪除這個key:

(5)此時在從節(jié)點(diǎn)中再次查詢這個key塑径,會發(fā)現(xiàn)主節(jié)點(diǎn)的操作已經(jīng)同步至從節(jié)點(diǎn):

3. 斷開復(fù)制

通過slaveof 命令建立主從復(fù)制關(guān)系以后,可以通過slaveof no one斷開填具。需要注意的是统舀,從節(jié)點(diǎn)斷開復(fù)制后,不會刪除已有的數(shù)據(jù)劳景,只是不再接受主節(jié)點(diǎn)新的數(shù)據(jù)變化誉简。

從節(jié)點(diǎn)執(zhí)行slaveof no one后,打印日志如下所示盟广;可以看出斷開復(fù)制后闷串,從節(jié)點(diǎn)又變回為主節(jié)點(diǎn)。

主節(jié)點(diǎn)打印日志如下:

三筋量、主從復(fù)制的實(shí)現(xiàn)原理

上面一節(jié)中烹吵,介紹了如何操作可以建立主從關(guān)系;本小節(jié)將介紹主從復(fù)制的實(shí)現(xiàn)原理桨武。

主從復(fù)制過程大體可以分為3個階段:連接建立階段(即準(zhǔn)備階段)肋拔、數(shù)據(jù)同步階段、命令傳播階段呀酸;下面分別進(jìn)行介紹凉蜂。

1. 連接建立階段

該階段的主要作用是在主從節(jié)點(diǎn)之間建立連接,為數(shù)據(jù)同步做好準(zhǔn)備窿吩。

步驟1:保存主節(jié)點(diǎn)信息

從節(jié)點(diǎn)服務(wù)器內(nèi)部維護(hù)了兩個字段茎杂,即masterhost和masterport字段,用于存儲主節(jié)點(diǎn)的ip和port信息纫雁。

需要注意的是煌往,slaveof是異步命令,從節(jié)點(diǎn)完成主節(jié)點(diǎn)ip和port的保存后轧邪,向發(fā)送slaveof命令的客戶端直接返回OK携冤,實(shí)際的復(fù)制操作在這之后才開始進(jìn)行。

這個過程中闲勺,可以看到從節(jié)點(diǎn)打印日志如下:

步驟2:建立socket連接

從節(jié)點(diǎn)每秒1次調(diào)用復(fù)制定時函數(shù)replicationCron(),如果發(fā)現(xiàn)了有主節(jié)點(diǎn)可以連接扣猫,便會根據(jù)主節(jié)點(diǎn)的ip和port菜循,創(chuàng)建socket連接。如果連接成功申尤,則:

從節(jié)點(diǎn):為該socket建立一個專門處理復(fù)制工作的文件事件處理器癌幕,負(fù)責(zé)后續(xù)的復(fù)制工作,如接收RDB文件昧穿、接收命令傳播等勺远。

主節(jié)點(diǎn):接收到從節(jié)點(diǎn)的socket連接后(即accept之后),為該socket創(chuàng)建相應(yīng)的客戶端狀態(tài)时鸵,并將從節(jié)點(diǎn)看做是連接到主節(jié)點(diǎn)的一個客戶端胶逢,后面的步驟會以從節(jié)點(diǎn)向主節(jié)點(diǎn)發(fā)送命令請求的形式來進(jìn)行。

這個過程中饰潜,從節(jié)點(diǎn)打印日志如下:

步驟3:發(fā)送ping命令

從節(jié)點(diǎn)成為主節(jié)點(diǎn)的客戶端之后初坠,發(fā)送ping命令進(jìn)行首次請求,目的是:檢查socket連接是否可用彭雾,以及主節(jié)點(diǎn)當(dāng)前是否能夠處理請求碟刺。

從節(jié)點(diǎn)發(fā)送ping命令后,可能出現(xiàn)3種情況:

(1)返回pong:說明socket連接正常薯酝,且主節(jié)點(diǎn)當(dāng)前可以處理請求半沽,復(fù)制過程繼續(xù)。

(2)超時:一定時間后從節(jié)點(diǎn)仍未收到主節(jié)點(diǎn)的回復(fù)吴菠,說明socket連接不可用者填,則從節(jié)點(diǎn)斷開socket連接,并重連做葵。

(3)返回pong以外的結(jié)果:如果主節(jié)點(diǎn)返回其他結(jié)果幔托,如正在處理超時運(yùn)行的腳本,說明主節(jié)點(diǎn)當(dāng)前無法處理命令,則從節(jié)點(diǎn)斷開socket連接重挑,并重連嗓化。

在主節(jié)點(diǎn)返回pong情況下,從節(jié)點(diǎn)打印日志如下:

步驟4:身份驗(yàn)證

如果從節(jié)點(diǎn)中設(shè)置了masterauth選項(xiàng)谬哀,則從節(jié)點(diǎn)需要向主節(jié)點(diǎn)進(jìn)行身份驗(yàn)證刺覆;沒有設(shè)置該選項(xiàng),則不需要驗(yàn)證史煎。從節(jié)點(diǎn)進(jìn)行身份驗(yàn)證是通過向主節(jié)點(diǎn)發(fā)送auth命令進(jìn)行的谦屑,auth命令的參數(shù)即為配置文件中的masterauth的值。

如果主節(jié)點(diǎn)設(shè)置密碼的狀態(tài)篇梭,與從節(jié)點(diǎn)masterauth的狀態(tài)一致(一致是指都存在氢橙,且密碼相同,或者都不存在)恬偷,則身份驗(yàn)證通過悍手,復(fù)制過程繼續(xù);如果不一致袍患,則從節(jié)點(diǎn)斷開socket連接坦康,并重連。

步驟5:發(fā)送從節(jié)點(diǎn)端口信息

身份驗(yàn)證之后诡延,從節(jié)點(diǎn)會向主節(jié)點(diǎn)發(fā)送其監(jiān)聽的端口號(前述例子中為6380)滞欠,主節(jié)點(diǎn)將該信息保存到該從節(jié)點(diǎn)對應(yīng)的客戶端的slave_listening_port字段中;該端口信息除了在主節(jié)點(diǎn)中執(zhí)行info Replication時顯示以外肆良,沒有其他作用筛璧。

2. 數(shù)據(jù)同步階段

主從節(jié)點(diǎn)之間的連接建立以后,便可以開始進(jìn)行數(shù)據(jù)同步惹恃,該階段可以理解為從節(jié)點(diǎn)數(shù)據(jù)的初始化隧哮。具體執(zhí)行的方式是:從節(jié)點(diǎn)向主節(jié)點(diǎn)發(fā)送psync命令(Redis2.8以前是sync命令),開始同步座舍。

數(shù)據(jù)同步階段是主從復(fù)制最核心的階段沮翔,根據(jù)主從節(jié)點(diǎn)當(dāng)前狀態(tài)的不同,可以分為全量復(fù)制和部分復(fù)制曲秉,下面會有一章專門講解這兩種復(fù)制方式以及psync命令的執(zhí)行過程采蚀,這里不再詳述。

需要注意的是承二,在數(shù)據(jù)同步階段之前榆鼠,從節(jié)點(diǎn)是主節(jié)點(diǎn)的客戶端,主節(jié)點(diǎn)不是從節(jié)點(diǎn)的客戶端亥鸠;而到了這一階段及以后妆够,主從節(jié)點(diǎn)互為客戶端识啦。原因在于:在此之前,主節(jié)點(diǎn)只需要響應(yīng)從節(jié)點(diǎn)的請求即可神妹,不需要主動發(fā)請求颓哮,而在數(shù)據(jù)同步階段和后面的命令傳播階段,主節(jié)點(diǎn)需要主動向從節(jié)點(diǎn)發(fā)送請求(如推送緩沖區(qū)中的寫命令)鸵荠,才能完成復(fù)制冕茅。

3. 命令傳播階段

數(shù)據(jù)同步階段完成后,主從節(jié)點(diǎn)進(jìn)入命令傳播階段蛹找;在這個階段主節(jié)點(diǎn)將自己執(zhí)行的寫命令發(fā)送給從節(jié)點(diǎn)姨伤,從節(jié)點(diǎn)接收命令并執(zhí)行,從而保證主從節(jié)點(diǎn)數(shù)據(jù)的一致性庸疾。

在命令傳播階段乍楚,除了發(fā)送寫命令,主從節(jié)點(diǎn)還維持著心跳機(jī)制:PING和REPLCONF ACK届慈。由于心跳機(jī)制的原理涉及部分復(fù)制徒溪,因此將在介紹了部分復(fù)制的相關(guān)內(nèi)容后單獨(dú)介紹該心跳機(jī)制。

延遲與不一致

需要注意的是拧篮,命令傳播是異步的過程,即主節(jié)點(diǎn)發(fā)送寫命令后并不會等待從節(jié)點(diǎn)的回復(fù)牵舱;因此實(shí)際上主從節(jié)點(diǎn)之間很難保持實(shí)時的一致性串绩,延遲在所難免。數(shù)據(jù)不一致的程度芜壁,與主從節(jié)點(diǎn)之間的網(wǎng)絡(luò)狀況礁凡、主節(jié)點(diǎn)寫命令的執(zhí)行頻率、以及主節(jié)點(diǎn)中的repl-disable-tcp-nodelay配置等有關(guān)慧妄。

repl-disable-tcp-nodelay no:該配置作用于命令傳播階段顷牌,控制主節(jié)點(diǎn)是否禁止與從節(jié)點(diǎn)的TCP_NODELAY;默認(rèn)no塞淹,即不禁止TCP_NODELAY窟蓝。當(dāng)設(shè)置為yes時,TCP會對包進(jìn)行合并從而減少帶寬饱普,但是發(fā)送的頻率會降低运挫,從節(jié)點(diǎn)數(shù)據(jù)延遲增加,一致性變差套耕;具體發(fā)送頻率與Linux內(nèi)核的配置有關(guān)谁帕,默認(rèn)配置為40ms。當(dāng)設(shè)置為no時冯袍,TCP會立馬將主節(jié)點(diǎn)的數(shù)據(jù)發(fā)送給從節(jié)點(diǎn)匈挖,帶寬增加但延遲變小碾牌。

一般來說,只有當(dāng)應(yīng)用對Redis數(shù)據(jù)不一致的容忍度較高儡循,且主從節(jié)點(diǎn)之間網(wǎng)絡(luò)狀況不好時舶吗,才會設(shè)置為yes;多數(shù)情況使用默認(rèn)值no贮折。

四裤翩、【數(shù)據(jù)同步階段】全量復(fù)制和部分復(fù)制

在Redis2.8以前,從節(jié)點(diǎn)向主節(jié)點(diǎn)發(fā)送sync命令請求同步數(shù)據(jù)调榄,此時的同步方式是全量復(fù)制踊赠;在Redis2.8及以后,從節(jié)點(diǎn)可以發(fā)送psync命令請求同步數(shù)據(jù)每庆,此時根據(jù)主從節(jié)點(diǎn)當(dāng)前狀態(tài)的不同筐带,同步方式可能是全量復(fù)制或部分復(fù)制。后文介紹以Redis2.8及以后版本為例缤灵。

全量復(fù)制:用于初次復(fù)制或其他無法進(jìn)行部分復(fù)制的情況伦籍,將主節(jié)點(diǎn)中的所有數(shù)據(jù)都發(fā)送給從節(jié)點(diǎn),是一個非常重型的操作腮出。

部分復(fù)制:用于網(wǎng)絡(luò)中斷等情況后的復(fù)制帖鸦,只將中斷期間主節(jié)點(diǎn)執(zhí)行的寫命令發(fā)送給從節(jié)點(diǎn),與全量復(fù)制相比更加高效胚嘲。需要注意的是作儿,如果網(wǎng)絡(luò)中斷時間過長,導(dǎo)致主節(jié)點(diǎn)沒有能夠完整地保存中斷期間執(zhí)行的寫命令馋劈,則無法進(jìn)行部分復(fù)制攻锰,仍使用全量復(fù)制。

1. 全量復(fù)制

Redis通過psync命令進(jìn)行全量復(fù)制的過程如下:

(1)從節(jié)點(diǎn)判斷無法進(jìn)行部分復(fù)制妓雾,向主節(jié)點(diǎn)發(fā)送全量復(fù)制的請求娶吞;或從節(jié)點(diǎn)發(fā)送部分復(fù)制的請求,但主節(jié)點(diǎn)判斷無法進(jìn)行全量復(fù)制械姻;具體判斷過程需要在講述了部分復(fù)制原理后再介紹妒蛇。

(2)主節(jié)點(diǎn)收到全量復(fù)制的命令后,執(zhí)行bgsave楷拳,在后臺生成RDB文件材部,并使用一個緩沖區(qū)(稱為復(fù)制緩沖區(qū))記錄從現(xiàn)在開始執(zhí)行的所有寫命令

(3)主節(jié)點(diǎn)的bgsave執(zhí)行完成后,將RDB文件發(fā)送給從節(jié)點(diǎn)唯竹;從節(jié)點(diǎn)首先清除自己的舊數(shù)據(jù)乐导,然后載入接收的RDB文件,將數(shù)據(jù)庫狀態(tài)更新至主節(jié)點(diǎn)執(zhí)行bgsave時的數(shù)據(jù)庫狀態(tài)

(4)主節(jié)點(diǎn)將前述復(fù)制緩沖區(qū)中的所有寫命令發(fā)送給從節(jié)點(diǎn)浸颓,從節(jié)點(diǎn)執(zhí)行這些寫命令物臂,將數(shù)據(jù)庫狀態(tài)更新至主節(jié)點(diǎn)的最新狀態(tài)

(5)如果從節(jié)點(diǎn)開啟了AOF旺拉,則會觸發(fā)bgrewriteaof的執(zhí)行,從而保證AOF文件更新至主節(jié)點(diǎn)的最新狀態(tài)

下面是執(zhí)行全量復(fù)制時棵磷,主從節(jié)點(diǎn)打印的日志蛾狗;可以看出日志內(nèi)容與上述步驟是完全對應(yīng)的。

主節(jié)點(diǎn)的打印日志如下:

從節(jié)點(diǎn)打印日志如下圖所示:

其中仪媒,有幾點(diǎn)需要注意:從節(jié)點(diǎn)接收了來自主節(jié)點(diǎn)的89260個字節(jié)的數(shù)據(jù)沉桌;從節(jié)點(diǎn)在載入主節(jié)點(diǎn)的數(shù)據(jù)之前要先將老數(shù)據(jù)清除;從節(jié)點(diǎn)在同步完數(shù)據(jù)后算吩,調(diào)用了bgrewriteaof留凭。


通過全量復(fù)制的過程可以看出,全量復(fù)制是非常重型的操作:

(1)主節(jié)點(diǎn)通過bgsave命令fork子進(jìn)程進(jìn)行RDB持久化偎巢,該過程是非常消耗CPU蔼夜、內(nèi)存(頁表復(fù)制)、硬盤IO的压昼;關(guān)于bgsave的性能問題求冷,可以參考?深入學(xué)習(xí)Redis(2):持久化

(2)主節(jié)點(diǎn)通過網(wǎng)絡(luò)將RDB文件發(fā)送給從節(jié)點(diǎn),對主從節(jié)點(diǎn)的帶寬都會帶來很大的消耗

(3)從節(jié)點(diǎn)清空老數(shù)據(jù)窍霞、載入新RDB文件的過程是阻塞的匠题,無法響應(yīng)客戶端的命令;如果從節(jié)點(diǎn)執(zhí)行bgrewriteaof但金,也會帶來額外的消耗

2. 部分復(fù)制

由于全量復(fù)制在主節(jié)點(diǎn)數(shù)據(jù)量較大時效率太低韭山,因此Redis2.8開始提供部分復(fù)制,用于處理網(wǎng)絡(luò)中斷時的數(shù)據(jù)同步傲绣。

部分復(fù)制的實(shí)現(xiàn)掠哥,依賴于三個重要的概念:

(1)復(fù)制偏移量

主節(jié)點(diǎn)和從節(jié)點(diǎn)分別維護(hù)一個復(fù)制偏移量(offset)巩踏,代表的是主節(jié)點(diǎn)向從節(jié)點(diǎn)傳遞的字節(jié)數(shù)秃诵;主節(jié)點(diǎn)每次向從節(jié)點(diǎn)傳播N個字節(jié)數(shù)據(jù)時,主節(jié)點(diǎn)的offset增加N塞琼;從節(jié)點(diǎn)每次收到主節(jié)點(diǎn)傳來的N個字節(jié)數(shù)據(jù)時菠净,從節(jié)點(diǎn)的offset增加N。

offset用于判斷主從節(jié)點(diǎn)的數(shù)據(jù)庫狀態(tài)是否一致:如果二者offset相同彪杉,則一致;如果offset不同,則不一致模闲,此時可以根據(jù)兩個offset找出從節(jié)點(diǎn)缺少的那部分?jǐn)?shù)據(jù)蟀悦。例如,如果主節(jié)點(diǎn)的offset是1000渴丸,而從節(jié)點(diǎn)的offset是500侯嘀,那么部分復(fù)制就需要將offset為501-1000的數(shù)據(jù)傳遞給從節(jié)點(diǎn)另凌。而offset為501-1000的數(shù)據(jù)存儲的位置,就是下面要介紹的復(fù)制積壓緩沖區(qū)戒幔。

(2)復(fù)制積壓緩沖區(qū)

復(fù)制積壓緩沖區(qū)是由主節(jié)點(diǎn)維護(hù)的吠谢、固定長度的、先進(jìn)先出(FIFO)隊(duì)列诗茎,默認(rèn)大小1MB工坊;當(dāng)主節(jié)點(diǎn)開始有從節(jié)點(diǎn)時創(chuàng)建,其作用是備份主節(jié)點(diǎn)最近發(fā)送給從節(jié)點(diǎn)的數(shù)據(jù)敢订。注意王污,無論主節(jié)點(diǎn)有一個還是多個從節(jié)點(diǎn),都只需要一個復(fù)制積壓緩沖區(qū)枢析。

在命令傳播階段玉掸,主節(jié)點(diǎn)除了將寫命令發(fā)送給從節(jié)點(diǎn),還會發(fā)送一份給復(fù)制積壓緩沖區(qū)醒叁,作為寫命令的備份司浪;除了存儲寫命令,復(fù)制積壓緩沖區(qū)中還存儲了其中的每個字節(jié)對應(yīng)的復(fù)制偏移量(offset)把沼。由于復(fù)制積壓緩沖區(qū)定長且是先進(jìn)先出啊易,所以它保存的是主節(jié)點(diǎn)最近執(zhí)行的寫命令;時間較早的寫命令會被擠出緩沖區(qū)饮睬。

由于該緩沖區(qū)長度固定且有限租谈,因此可以備份的寫命令也有限,當(dāng)主從節(jié)點(diǎn)offset的差距過大超過緩沖區(qū)長度時捆愁,將無法執(zhí)行部分復(fù)制割去,只能執(zhí)行全量復(fù)制。反過來說昼丑,為了提高網(wǎng)絡(luò)中斷時部分復(fù)制執(zhí)行的概率呻逆,可以根據(jù)需要增大復(fù)制積壓緩沖區(qū)的大小(通過配置repl-backlog-size);例如如果網(wǎng)絡(luò)中斷的平均時間是60s菩帝,而主節(jié)點(diǎn)平均每秒產(chǎn)生的寫命令(特定協(xié)議格式)所占的字節(jié)數(shù)為100KB咖城,則復(fù)制積壓緩沖區(qū)的平均需求為6MB,保險起見呼奢,可以設(shè)置為12MB宜雀,來保證絕大多數(shù)斷線情況都可以使用部分復(fù)制。

從節(jié)點(diǎn)將offset發(fā)送給主節(jié)點(diǎn)后握础,主節(jié)點(diǎn)根據(jù)offset和緩沖區(qū)大小決定能否執(zhí)行部分復(fù)制:

如果offset偏移量之后的數(shù)據(jù)辐董,仍然都在復(fù)制積壓緩沖區(qū)里,則執(zhí)行部分復(fù)制禀综;

如果offset偏移量之后的數(shù)據(jù)已不在復(fù)制積壓緩沖區(qū)中(數(shù)據(jù)已被擠出)简烘,則執(zhí)行全量復(fù)制他匪。

(3)服務(wù)器運(yùn)行ID(runid)

每個Redis節(jié)點(diǎn)(無論主從),在啟動時都會自動生成一個隨機(jī)ID(每次啟動都不一樣)夸研,由40個隨機(jī)的十六進(jìn)制字符組成邦蜜;runid用來唯一識別一個Redis節(jié)點(diǎn)。通過info Server命令亥至,可以查看節(jié)點(diǎn)的runid:

主從節(jié)點(diǎn)初次復(fù)制時悼沈,主節(jié)點(diǎn)將自己的runid發(fā)送給從節(jié)點(diǎn),從節(jié)點(diǎn)將這個runid保存起來姐扮;當(dāng)斷線重連時絮供,從節(jié)點(diǎn)會將這個runid發(fā)送給主節(jié)點(diǎn);主節(jié)點(diǎn)根據(jù)runid判斷能否進(jìn)行部分復(fù)制:

如果從節(jié)點(diǎn)保存的runid與主節(jié)點(diǎn)現(xiàn)在的runid相同茶敏,說明主從節(jié)點(diǎn)之前同步過壤靶,主節(jié)點(diǎn)會繼續(xù)嘗試使用部分復(fù)制(到底能不能部分復(fù)制還要看offset和復(fù)制積壓緩沖區(qū)的情況);

如果從節(jié)點(diǎn)保存的runid與主節(jié)點(diǎn)現(xiàn)在的runid不同惊搏,說明從節(jié)點(diǎn)在斷線前同步的Redis節(jié)點(diǎn)并不是當(dāng)前的主節(jié)點(diǎn)贮乳,只能進(jìn)行全量復(fù)制。

3. psync命令的執(zhí)行

在了解了復(fù)制偏移量恬惯、復(fù)制積壓緩沖區(qū)向拆、節(jié)點(diǎn)運(yùn)行id之后,本節(jié)將介紹psync命令的參數(shù)和返回值酪耳,從而說明psync命令執(zhí)行過程中浓恳,主從節(jié)點(diǎn)是如何確定使用全量復(fù)制還是部分復(fù)制的。

psync命令的執(zhí)行過程可以參見下圖(圖片來源:《Redis設(shè)計(jì)與實(shí)現(xiàn)》):

(1)首先碗暗,從節(jié)點(diǎn)根據(jù)當(dāng)前狀態(tài)颈将,決定如何調(diào)用psync命令:

如果從節(jié)點(diǎn)之前未執(zhí)行過slaveof或最近執(zhí)行了slaveof no one,則從節(jié)點(diǎn)發(fā)送命令為psync ? -1言疗,向主節(jié)點(diǎn)請求全量復(fù)制晴圾;

如果從節(jié)點(diǎn)之前執(zhí)行了slaveof,則發(fā)送命令為psync 洲守,其中runid為上次復(fù)制的主節(jié)點(diǎn)的runid疑务,offset為上次復(fù)制截止時從節(jié)點(diǎn)保存的復(fù)制偏移量沾凄。

(2)主節(jié)點(diǎn)根據(jù)收到的psync命令梗醇,及當(dāng)前服務(wù)器狀態(tài),決定執(zhí)行全量復(fù)制還是部分復(fù)制:

如果主節(jié)點(diǎn)版本低于Redis2.8撒蟀,則返回-ERR回復(fù)叙谨,此時從節(jié)點(diǎn)重新發(fā)送sync命令執(zhí)行全量復(fù)制;

如果主節(jié)點(diǎn)版本夠新保屯,且runid與從節(jié)點(diǎn)發(fā)送的runid相同手负,且從節(jié)點(diǎn)發(fā)送的offset之后的數(shù)據(jù)在復(fù)制積壓緩沖區(qū)中都存在涤垫,則回復(fù)+CONTINUE,表示將進(jìn)行部分復(fù)制竟终,從節(jié)點(diǎn)等待主節(jié)點(diǎn)發(fā)送其缺少的數(shù)據(jù)即可蝠猬;

如果主節(jié)點(diǎn)版本夠新,但是runid與從節(jié)點(diǎn)發(fā)送的runid不同统捶,或從節(jié)點(diǎn)發(fā)送的offset之后的數(shù)據(jù)已不在復(fù)制積壓緩沖區(qū)中(在隊(duì)列中被擠出了)榆芦,則回復(fù)+FULLRESYNC ,表示要進(jìn)行全量復(fù)制喘鸟,其中runid表示主節(jié)點(diǎn)當(dāng)前的runid匆绣,offset表示主節(jié)點(diǎn)當(dāng)前的offset,從節(jié)點(diǎn)保存這兩個值什黑,以備使用崎淳。

4. 部分復(fù)制演示

在下面的演示中,網(wǎng)絡(luò)中斷幾分鐘后恢復(fù)愕把,斷開連接的主從節(jié)點(diǎn)進(jìn)行了部分復(fù)制拣凹;為了便于模擬網(wǎng)絡(luò)中斷,本例中的主從節(jié)點(diǎn)在局域網(wǎng)中的兩臺機(jī)器上恨豁。

網(wǎng)絡(luò)中斷

網(wǎng)絡(luò)中斷一段時間后咐鹤,主節(jié)點(diǎn)和從節(jié)點(diǎn)都會發(fā)現(xiàn)失去了與對方的連接(關(guān)于主從節(jié)點(diǎn)對超時的判斷機(jī)制,后面會有說明)圣絮;此后祈惶,從節(jié)點(diǎn)便開始執(zhí)行對主節(jié)點(diǎn)的重連,由于此時網(wǎng)絡(luò)還沒有恢復(fù)扮匠,重連失敗捧请,從節(jié)點(diǎn)會一直嘗試重連。

主節(jié)點(diǎn)日志如下:

從節(jié)點(diǎn)日志如下:

網(wǎng)絡(luò)恢復(fù)

網(wǎng)絡(luò)恢復(fù)后棒搜,從節(jié)點(diǎn)連接主節(jié)點(diǎn)成功疹蛉,并請求進(jìn)行部分復(fù)制,主節(jié)點(diǎn)接收請求后力麸,二者進(jìn)行部分復(fù)制以同步數(shù)據(jù)可款。

主節(jié)點(diǎn)日志如下:

從節(jié)點(diǎn)日志如下:

五、【命令傳播階段】心跳機(jī)制

在命令傳播階段克蚂,除了發(fā)送寫命令闺鲸,主從節(jié)點(diǎn)還維持著心跳機(jī)制:PING和REPLCONF ACK。心跳機(jī)制對于主從復(fù)制的超時判斷埃叭、數(shù)據(jù)安全等有作用摸恍。

1.主->從:PING

每隔指定的時間,主節(jié)點(diǎn)會向從節(jié)點(diǎn)發(fā)送PING命令,這個PING命令的作用立镶,主要是為了讓從節(jié)點(diǎn)進(jìn)行超時判斷壁袄。

PING發(fā)送的頻率由repl-ping-slave-period參數(shù)控制,單位是秒媚媒,默認(rèn)值是10s嗜逻。

關(guān)于該P(yáng)ING命令究竟是由主節(jié)點(diǎn)發(fā)給從節(jié)點(diǎn),還是相反缭召,有一些爭議变泄;因?yàn)樵赗edis的官方文檔中,對該參數(shù)的注釋中說明是從節(jié)點(diǎn)向主節(jié)點(diǎn)發(fā)送PING命令恼琼,如下圖所示:

但是根據(jù)該參數(shù)的名稱(含有ping-slave)妨蛹,以及代碼實(shí)現(xiàn),我認(rèn)為該P(yáng)ING命令是主節(jié)點(diǎn)發(fā)給從節(jié)點(diǎn)的晴竞。相關(guān)代碼如下:

2. 從->主:REPLCONF ACK

在命令傳播階段蛙卤,從節(jié)點(diǎn)會向主節(jié)點(diǎn)發(fā)送REPLCONF ACK命令噩死,頻率是每秒1次垛耳;命令格式為:REPLCONF ACK {offset}哥纫,其中offset指從節(jié)點(diǎn)保存的復(fù)制偏移量擅憔。REPLCONF ACK命令的作用包括:

(1)實(shí)時監(jiān)測主從節(jié)點(diǎn)網(wǎng)絡(luò)狀態(tài):該命令會被主節(jié)點(diǎn)用于復(fù)制超時的判斷。此外乃坤,在主節(jié)點(diǎn)中使用info Replication,可以看到其從節(jié)點(diǎn)的狀態(tài)中的lag值眶拉,代表的是主節(jié)點(diǎn)上次收到該REPLCONF ACK命令的時間間隔朝刊,在正常情況下织堂,該值應(yīng)該是0或1事示,如下圖所示:

(2)檢測命令丟失:從節(jié)點(diǎn)發(fā)送了自身的offset早像,主節(jié)點(diǎn)會與自己的offset對比,如果從節(jié)點(diǎn)數(shù)據(jù)缺失(如網(wǎng)絡(luò)丟包)肖爵,主節(jié)點(diǎn)會推送缺失的數(shù)據(jù)(這里也會利用復(fù)制積壓緩沖區(qū))卢鹦。注意,offset和復(fù)制積壓緩沖區(qū)劝堪,不僅可以用于部分復(fù)制冀自,也可以用于處理命令丟失等情形;區(qū)別在于前者是在斷線重連后進(jìn)行的秒啦,而后者是在主從節(jié)點(diǎn)沒有斷線的情況下進(jìn)行的熬粗。

(3)輔助保證從節(jié)點(diǎn)的數(shù)量和延遲:Redis主節(jié)點(diǎn)中使用min-slaves-to-write和min-slaves-max-lag參數(shù),來保證主節(jié)點(diǎn)在不安全的情況下不會執(zhí)行寫命令余境;所謂不安全荐糜,是指從節(jié)點(diǎn)數(shù)量太少,或延遲過高葛超。例如min-slaves-to-write和min-slaves-max-lag分別是3和10暴氏,含義是如果從節(jié)點(diǎn)數(shù)量小于3個,或所有從節(jié)點(diǎn)的延遲值都大于10s绣张,則主節(jié)點(diǎn)拒絕執(zhí)行寫命令答渔。而這里從節(jié)點(diǎn)延遲值的獲取,就是通過主節(jié)點(diǎn)接收到REPLCONF ACK命令的時間來判斷的侥涵,即前面所說的info Replication中的lag值沼撕。

六、應(yīng)用中的問題

1. 讀寫分離及其中的問題

在主從復(fù)制基礎(chǔ)上實(shí)現(xiàn)的讀寫分離芜飘,可以實(shí)現(xiàn)Redis的讀負(fù)載均衡:由主節(jié)點(diǎn)提供寫服務(wù)务豺,由一個或多個從節(jié)點(diǎn)提供讀服務(wù)(多個從節(jié)點(diǎn)既可以提高數(shù)據(jù)冗余程度,也可以最大化讀負(fù)載能力)嗦明;在讀負(fù)載較大的應(yīng)用場景下笼沥,可以大大提高Redis服務(wù)器的并發(fā)量。下面介紹在使用Redis讀寫分離時娶牌,需要注意的問題奔浅。

(1)延遲與不一致問題

前面已經(jīng)講到,由于主從復(fù)制的命令傳播是異步的诗良,延遲與數(shù)據(jù)的不一致不可避免汹桦。如果應(yīng)用對數(shù)據(jù)不一致的接受程度程度較低,可能的優(yōu)化措施包括:優(yōu)化主從節(jié)點(diǎn)之間的網(wǎng)絡(luò)環(huán)境(如在同機(jī)房部署)鉴裹;監(jiān)控主從節(jié)點(diǎn)延遲(通過offset)判斷舞骆,如果從節(jié)點(diǎn)延遲過大钥弯,通知應(yīng)用不再通過該從節(jié)點(diǎn)讀取數(shù)據(jù);使用集群同時擴(kuò)展寫負(fù)載和讀負(fù)載等督禽。

在命令傳播階段以外的其他情況下脆霎,從節(jié)點(diǎn)的數(shù)據(jù)不一致可能更加嚴(yán)重,例如連接在數(shù)據(jù)同步階段赂蠢,或從節(jié)點(diǎn)失去與主節(jié)點(diǎn)的連接時等绪穆。從節(jié)點(diǎn)的slave-serve-stale-data參數(shù)便與此有關(guān):它控制這種情況下從節(jié)點(diǎn)的表現(xiàn)辨泳;如果為yes(默認(rèn)值)虱岂,則從節(jié)點(diǎn)仍能夠響應(yīng)客戶端的命令,如果為no菠红,則從節(jié)點(diǎn)只能響應(yīng)info第岖、slaveof等少數(shù)命令。該參數(shù)的設(shè)置與應(yīng)用對數(shù)據(jù)一致性的要求有關(guān)试溯;如果對數(shù)據(jù)一致性要求很高蔑滓,則應(yīng)設(shè)置為no。

(2)數(shù)據(jù)過期問題

在單機(jī)版Redis中遇绞,存在兩種刪除策略:

惰性刪除:服務(wù)器不會主動刪除數(shù)據(jù)键袱,只有當(dāng)客戶端查詢某個數(shù)據(jù)時,服務(wù)器判斷該數(shù)據(jù)是否過期摹闽,如果過期則刪除蹄咖。

定期刪除:服務(wù)器執(zhí)行定時任務(wù)刪除過期數(shù)據(jù),但是考慮到內(nèi)存和CPU的折中(刪除會釋放內(nèi)存付鹿,但是頻繁的刪除操作對CPU不友好)澜汤,該刪除的頻率和執(zhí)行時間都受到了限制。

在主從復(fù)制場景下舵匾,為了主從節(jié)點(diǎn)的數(shù)據(jù)一致性俊抵,從節(jié)點(diǎn)不會主動刪除數(shù)據(jù),而是由主節(jié)點(diǎn)控制從節(jié)點(diǎn)中過期數(shù)據(jù)的刪除坐梯。由于主節(jié)點(diǎn)的惰性刪除和定期刪除策略徽诲,都不能保證主節(jié)點(diǎn)及時對過期數(shù)據(jù)執(zhí)行刪除操作,因此吵血,當(dāng)客戶端通過Redis從節(jié)點(diǎn)讀取數(shù)據(jù)時馏段,很容易讀取到已經(jīng)過期的數(shù)據(jù)。

Redis 3.2中践瓷,從節(jié)點(diǎn)在讀取數(shù)據(jù)時院喜,增加了對數(shù)據(jù)是否過期的判斷:如果該數(shù)據(jù)已過期,則不返回給客戶端晕翠;將Redis升級到3.2可以解決數(shù)據(jù)過期問題喷舀。

(3)故障切換問題

在沒有使用哨兵的讀寫分離場景下砍濒,應(yīng)用針對讀和寫分別連接不同的Redis節(jié)點(diǎn);當(dāng)主節(jié)點(diǎn)或從節(jié)點(diǎn)出現(xiàn)問題而發(fā)生更改時硫麻,需要及時修改應(yīng)用程序讀寫Redis數(shù)據(jù)的連接爸邢;連接的切換可以手動進(jìn)行,或者自己寫監(jiān)控程序進(jìn)行切換拿愧,但前者響應(yīng)慢杠河、容易出錯,后者實(shí)現(xiàn)復(fù)雜浇辜,成本都不算低券敌。

(4)總結(jié)

在使用讀寫分離之前,可以考慮其他方法增加Redis的讀負(fù)載能力:如盡量優(yōu)化主節(jié)點(diǎn)(減少慢查詢柳洋、減少持久化等其他情況帶來的阻塞等)提高負(fù)載能力待诅;使用Redis集群同時提高讀負(fù)載能力和寫負(fù)載能力等。如果使用讀寫分離熊镣,可以使用哨兵卑雁,使主從節(jié)點(diǎn)的故障切換盡可能自動化,并減少對應(yīng)用程序的侵入绪囱。

2. 復(fù)制超時問題

主從節(jié)點(diǎn)復(fù)制超時是導(dǎo)致復(fù)制中斷的最重要的原因之一测蹲,本小節(jié)單獨(dú)說明超時問題,下一小節(jié)說明其他會導(dǎo)致復(fù)制中斷的問題鬼吵。

超時判斷意義

在復(fù)制連接建立過程中及之后扣甲,主從節(jié)點(diǎn)都有機(jī)制判斷連接是否超時,其意義在于:

(1)如果主節(jié)點(diǎn)判斷連接超時而柑,其會釋放相應(yīng)從節(jié)點(diǎn)的連接文捶,從而釋放各種資源,否則無效的從節(jié)點(diǎn)仍會占用主節(jié)點(diǎn)的各種資源(輸出緩沖區(qū)媒咳、帶寬粹排、連接等);此外連接超時的判斷可以讓主節(jié)點(diǎn)更準(zhǔn)確的知道當(dāng)前有效從節(jié)點(diǎn)的個數(shù)涩澡,有助于保證數(shù)據(jù)安全(配合前面講到的min-slaves-to-write等參數(shù))顽耳。

(2)如果從節(jié)點(diǎn)判斷連接超時,則可以及時重新建立連接妙同,避免與主節(jié)點(diǎn)數(shù)據(jù)長期的不一致射富。

判斷機(jī)制

主從復(fù)制超時判斷的核心,在于repl-timeout參數(shù)粥帚,該參數(shù)規(guī)定了超時時間的閾值(默認(rèn)60s)胰耗,對于主節(jié)點(diǎn)和從節(jié)點(diǎn)同時有效;主從節(jié)點(diǎn)觸發(fā)超時的條件分別如下:

(1)主節(jié)點(diǎn):每秒1次調(diào)用復(fù)制定時函數(shù)replicationCron()芒涡,在其中判斷當(dāng)前時間距離上次收到各個從節(jié)點(diǎn)REPLCONF ACK的時間柴灯,是否超過了repl-timeout值卖漫,如果超過了則釋放相應(yīng)從節(jié)點(diǎn)的連接。

(2)從節(jié)點(diǎn):從節(jié)點(diǎn)對超時的判斷同樣是在復(fù)制定時函數(shù)中判斷赠群,基本邏輯是:

如果當(dāng)前處于連接建立階段羊始,且距離上次收到主節(jié)點(diǎn)的信息的時間已超過repl-timeout,則釋放與主節(jié)點(diǎn)的連接查描;

如果當(dāng)前處于數(shù)據(jù)同步階段突委,且收到主節(jié)點(diǎn)的RDB文件的時間超時,則停止數(shù)據(jù)同步冬三,釋放連接匀油;

如果當(dāng)前處于命令傳播階段,且距離上次收到主節(jié)點(diǎn)的PING命令或數(shù)據(jù)的時間已超過repl-timeout值长豁,則釋放與主節(jié)點(diǎn)的連接钧唐。

主從節(jié)點(diǎn)判斷連接超時的相關(guān)源代碼如下:

/* Replication cron function, called 1 time per second. */

void replicationCron(void) {

? ? static long long replication_cron_loops = 0;

? ? /* Non blocking connection timeout? */

? ? if (server.masterhost &&

? ? ? ? (server.repl_state == REDIS_REPL_CONNECTING ||

? ? ? ? slaveIsInHandshakeState()) &&

? ? ? ? (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)

? ? {

? ? ? ? redisLog(REDIS_WARNING,"Timeout connecting to the MASTER...");

? ? ? ? undoConnectWithMaster();

? ? }

? ? /* Bulk transfer I/O timeout? */

? ? if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER &&

? ? ? ? (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)

? ? {

? ? ? ? redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value.");

? ? ? ? replicationAbortSyncTransfer();

? ? }

? ? /* Timed out master when we are an already connected slave? */

? ? if (server.masterhost && server.repl_state == REDIS_REPL_CONNECTED &&

? ? ? ? (time(NULL)-server.master->lastinteraction) > server.repl_timeout)

? ? {

? ? ? ? redisLog(REDIS_WARNING,"MASTER timeout: no data nor PING received...");

? ? ? ? freeClient(server.master);

? ? }

? ? //此處省略無關(guān)代碼……

? ? /* Disconnect timedout slaves. */

? ? if (listLength(server.slaves)) {

? ? ? ? listIter li;

? ? ? ? listNode *ln;

? ? ? ? listRewind(server.slaves,&li);

? ? ? ? while((ln = listNext(&li))) {

? ? ? ? ? ? redisClient *slave = ln->value;

? ? ? ? ? ? if (slave->replstate != REDIS_REPL_ONLINE) continue;

? ? ? ? ? ? if (slave->flags & REDIS_PRE_PSYNC) continue;

? ? ? ? ? ? if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? redisLog(REDIS_WARNING, "Disconnecting timedout slave: %s",

? ? ? ? ? ? ? ? ? ? replicationGetSlaveName(slave));

? ? ? ? ? ? ? ? freeClient(slave);

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? //此處省略無關(guān)代碼……

}

需要注意的坑

下面介紹與復(fù)制階段連接超時有關(guān)的一些實(shí)際問題:

(1)數(shù)據(jù)同步階段:在主從節(jié)點(diǎn)進(jìn)行全量復(fù)制bgsave時忙灼,主節(jié)點(diǎn)需要首先fork子進(jìn)程將當(dāng)前數(shù)據(jù)保存到RDB文件中匠襟,然后再將RDB文件通過網(wǎng)絡(luò)傳輸?shù)綇墓?jié)點(diǎn)。如果RDB文件過大该园,主節(jié)點(diǎn)在fork子進(jìn)程+保存RDB文件時耗時過多酸舍,可能會導(dǎo)致從節(jié)點(diǎn)長時間收不到數(shù)據(jù)而觸發(fā)超時;此時從節(jié)點(diǎn)會重連主節(jié)點(diǎn)里初,然后再次全量復(fù)制啃勉,再次超時,再次重連……這是個悲傷的循環(huán)双妨。為了避免這種情況的發(fā)生淮阐,除了注意Redis單機(jī)數(shù)據(jù)量不要過大,另一方面就是適當(dāng)增大repl-timeout值刁品,具體的大小可以根據(jù)bgsave耗時來調(diào)整泣特。

(2)命令傳播階段:如前所述,在該階段主節(jié)點(diǎn)會向從節(jié)點(diǎn)發(fā)送PING命令挑随,頻率由repl-ping-slave-period控制状您;該參數(shù)應(yīng)明顯小于repl-timeout值(后者至少是前者的幾倍)。否則兜挨,如果兩個參數(shù)相等或接近膏孟,網(wǎng)絡(luò)抖動導(dǎo)致個別PING命令丟失,此時恰巧主節(jié)點(diǎn)也沒有向從節(jié)點(diǎn)發(fā)送數(shù)據(jù)拌汇,則從節(jié)點(diǎn)很容易判斷超時柒桑。

(3)慢查詢導(dǎo)致的阻塞:如果主節(jié)點(diǎn)或從節(jié)點(diǎn)執(zhí)行了一些慢查詢(如keys *或者對大數(shù)據(jù)的hgetall等),導(dǎo)致服務(wù)器阻塞噪舀;阻塞期間無法響應(yīng)復(fù)制連接中對方節(jié)點(diǎn)的請求魁淳,可能導(dǎo)致復(fù)制超時丢氢。

3. 復(fù)制中斷問題

主從節(jié)點(diǎn)超時是復(fù)制中斷的原因之一,除此之外先改,還有其他情況可能導(dǎo)致復(fù)制中斷疚察,其中最主要的是復(fù)制緩沖區(qū)溢出問題。

復(fù)制緩沖區(qū)溢出

前面曾提到過仇奶,在全量復(fù)制階段貌嫡,主節(jié)點(diǎn)會將執(zhí)行的寫命令放到復(fù)制緩沖區(qū)中,該緩沖區(qū)存放的數(shù)據(jù)包括了以下幾個時間段內(nèi)主節(jié)點(diǎn)執(zhí)行的寫命令:bgsave生成RDB文件该溯、RDB文件由主節(jié)點(diǎn)發(fā)往從節(jié)點(diǎn)岛抄、從節(jié)點(diǎn)清空老數(shù)據(jù)并載入RDB文件中的數(shù)據(jù)。當(dāng)主節(jié)點(diǎn)數(shù)據(jù)量較大狈茉,或者主從節(jié)點(diǎn)之間網(wǎng)絡(luò)延遲較大時夫椭,可能導(dǎo)致該緩沖區(qū)的大小超過了限制,此時主節(jié)點(diǎn)會斷開與從節(jié)點(diǎn)之間的連接氯庆;這種情況可能引起全量復(fù)制->復(fù)制緩沖區(qū)溢出導(dǎo)致連接中斷->重連->全量復(fù)制->復(fù)制緩沖區(qū)溢出導(dǎo)致連接中斷……的循環(huán)蹭秋。

復(fù)制緩沖區(qū)的大小由client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默認(rèn)值為client-output-buffer-limit slave 256MB 64MB 60堤撵,其含義是:如果buffer大于256MB仁讨,或者連續(xù)60s大于64MB,則主節(jié)點(diǎn)會斷開與該從節(jié)點(diǎn)的連接实昨。該參數(shù)是可以通過config set命令動態(tài)配置的(即不重啟Redis也可以生效)洞豁。

當(dāng)復(fù)制緩沖區(qū)溢出時,主節(jié)點(diǎn)打印日志如下所示:

需要注意的是荒给,復(fù)制緩沖區(qū)是客戶端輸出緩沖區(qū)的一種丈挟,主節(jié)點(diǎn)會為每一個從節(jié)點(diǎn)分別分配復(fù)制緩沖區(qū);而復(fù)制積壓緩沖區(qū)則是一個主節(jié)點(diǎn)只有一個志电,無論它有多少個從節(jié)點(diǎn)曙咽。

4. 各場景下復(fù)制的選擇及優(yōu)化技巧

在介紹了Redis復(fù)制的種種細(xì)節(jié)之后,現(xiàn)在我們可以來總結(jié)一下溪北,在下面常見的場景中桐绒,何時使用部分復(fù)制,以及需要注意哪些問題之拨。

(1)第一次建立復(fù)制

此時全量復(fù)制不可避免茉继,但仍有幾點(diǎn)需要注意:如果主節(jié)點(diǎn)的數(shù)據(jù)量較大,應(yīng)該盡量避開流量的高峰期蚀乔,避免造成阻塞烁竭;如果有多個從節(jié)點(diǎn)需要建立對主節(jié)點(diǎn)的復(fù)制,可以考慮將幾個從節(jié)點(diǎn)錯開吉挣,避免主節(jié)點(diǎn)帶寬占用過大派撕。此外婉弹,如果從節(jié)點(diǎn)過多,也可以調(diào)整主從復(fù)制的拓?fù)浣Y(jié)構(gòu)终吼,由一主多從結(jié)構(gòu)變?yōu)闃錉罱Y(jié)構(gòu)(中間的節(jié)點(diǎn)既是其主節(jié)點(diǎn)的從節(jié)點(diǎn)镀赌,也是其從節(jié)點(diǎn)的主節(jié)點(diǎn));但使用樹狀結(jié)構(gòu)應(yīng)該謹(jǐn)慎:雖然主節(jié)點(diǎn)的直接從節(jié)點(diǎn)減少际跪,降低了主節(jié)點(diǎn)的負(fù)擔(dān)商佛,但是多層從節(jié)點(diǎn)的延遲增大,數(shù)據(jù)一致性變差姆打;且結(jié)構(gòu)復(fù)雜良姆,維護(hù)相當(dāng)困難。

(2)主節(jié)點(diǎn)重啟

主節(jié)點(diǎn)重啟可以分為兩種情況來討論幔戏,一種是故障導(dǎo)致宕機(jī)玛追,另一種則是有計(jì)劃的重啟。

主節(jié)點(diǎn)宕機(jī)

主節(jié)點(diǎn)宕機(jī)重啟后闲延,runid會發(fā)生變化痊剖,因此不能進(jìn)行部分復(fù)制,只能全量復(fù)制慨代。

實(shí)際上在主節(jié)點(diǎn)宕機(jī)的情況下邢笙,應(yīng)進(jìn)行故障轉(zhuǎn)移處理啸如,將其中的一個從節(jié)點(diǎn)升級為主節(jié)點(diǎn)侍匙,其他從節(jié)點(diǎn)從新的主節(jié)點(diǎn)進(jìn)行復(fù)制;且故障轉(zhuǎn)移應(yīng)盡量的自動化叮雳,后面文章將要介紹的哨兵便可以進(jìn)行自動的故障轉(zhuǎn)移想暗。

安全重啟:debug reload

在一些場景下,可能希望對主節(jié)點(diǎn)進(jìn)行重啟帘不,例如主節(jié)點(diǎn)內(nèi)存碎片率過高说莫,或者希望調(diào)整一些只能在啟動時調(diào)整的參數(shù)。如果使用普通的手段重啟主節(jié)點(diǎn)寞焙,會使得runid發(fā)生變化储狭,可能導(dǎo)致不必要的全量復(fù)制。

為了解決這個問題捣郊,Redis提供了debug reload的重啟方式:重啟后辽狈,主節(jié)點(diǎn)的runid和offset都不受影響,避免了全量復(fù)制呛牲。

如下圖所示刮萌,debug reload重啟后runid和offset都未受影響:

但debug reload是一柄雙刃劍:它會清空當(dāng)前內(nèi)存中的數(shù)據(jù),重新從RDB文件中加載娘扩,這個過程會導(dǎo)致主節(jié)點(diǎn)的阻塞着茸,因此也需要謹(jǐn)慎壮锻。

(3)從節(jié)點(diǎn)重啟

從節(jié)點(diǎn)宕機(jī)重啟后,其保存的主節(jié)點(diǎn)的runid會丟失涮阔,因此即使再次執(zhí)行slaveof猜绣,也無法進(jìn)行部分復(fù)制。

(4)網(wǎng)絡(luò)中斷

如果主從節(jié)點(diǎn)之間出現(xiàn)網(wǎng)絡(luò)問題敬特,造成短時間內(nèi)網(wǎng)絡(luò)中斷途事,可以分為多種情況討論。

第一種情況:網(wǎng)絡(luò)問題時間極為短暫擅羞,只造成了短暫的丟包尸变,主從節(jié)點(diǎn)都沒有判定超時(未觸發(fā)repl-timeout);此時只需要通過REPLCONF ACK來補(bǔ)充丟失的數(shù)據(jù)即可减俏。

第二種情況:網(wǎng)絡(luò)問題時間很長召烂,主從節(jié)點(diǎn)判斷超時(觸發(fā)了repl-timeout),且丟失的數(shù)據(jù)過多娃承,超過了復(fù)制積壓緩沖區(qū)所能存儲的范圍奏夫;此時主從節(jié)點(diǎn)無法進(jìn)行部分復(fù)制,只能進(jìn)行全量復(fù)制历筝。為了盡可能避免這種情況的發(fā)生酗昼,應(yīng)該根據(jù)實(shí)際情況適當(dāng)調(diào)整復(fù)制積壓緩沖區(qū)的大小梳猪;此外及時發(fā)現(xiàn)并修復(fù)網(wǎng)絡(luò)中斷麻削,也可以減少全量復(fù)制。

第三種情況:介于前述兩種情況之間春弥,主從節(jié)點(diǎn)判斷超時呛哟,且丟失的數(shù)據(jù)仍然都在復(fù)制積壓緩沖區(qū)中;此時主從節(jié)點(diǎn)可以進(jìn)行部分復(fù)制匿沛。

5. 復(fù)制相關(guān)的配置

這一節(jié)總結(jié)一下與復(fù)制有關(guān)的配置扫责,說明這些配置的作用、起作用的階段逃呼,以及配置方法等鳖孤;通過了解這些配置,一方面加深對Redis復(fù)制的了解抡笼,另一方面掌握這些配置的方法苏揣,可以優(yōu)化Redis的使用,少走坑蔫缸。

配置大致可以分為主節(jié)點(diǎn)相關(guān)配置腿准、從節(jié)點(diǎn)相關(guān)配置以及與主從節(jié)點(diǎn)都有關(guān)的配置,下面分別說明。

(1)與主從節(jié)點(diǎn)都有關(guān)的配置

首先介紹最特殊的配置吐葱,它決定了該節(jié)點(diǎn)是主節(jié)點(diǎn)還是從節(jié)點(diǎn):

1) ? slaveof :Redis啟動時起作用街望;作用是建立復(fù)制關(guān)系,開啟了該配置的Redis服務(wù)器在啟動后成為從節(jié)點(diǎn)弟跑。該注釋默認(rèn)注釋掉灾前,即Redis服務(wù)器默認(rèn)都是主節(jié)點(diǎn)。

2) ? repl-timeout 60:與各個階段主從節(jié)點(diǎn)連接超時判斷有關(guān)孟辑,見前面的介紹哎甲。

(2)主節(jié)點(diǎn)相關(guān)配置

1) ? repl-diskless-sync no:作用于全量復(fù)制階段,控制主節(jié)點(diǎn)是否使用diskless復(fù)制(無盤復(fù)制)饲嗽。所謂diskless復(fù)制炭玫,是指在全量復(fù)制時,主節(jié)點(diǎn)不再先把數(shù)據(jù)寫入RDB文件貌虾,而是直接寫入slave的socket中吞加,整個過程中不涉及硬盤;diskless復(fù)制在磁盤IO很慢而網(wǎng)速很快時更有優(yōu)勢尽狠。需要注意的是衔憨,截至Redis3.0,diskless復(fù)制處于實(shí)驗(yàn)階段袄膏,默認(rèn)是關(guān)閉的庐杨。

2) ? repl-diskless-sync-delay 5:該配置作用于全量復(fù)制階段藤韵,當(dāng)主節(jié)點(diǎn)使用diskless復(fù)制時嗡午,該配置決定主節(jié)點(diǎn)向從節(jié)點(diǎn)發(fā)送之前停頓的時間抚吠,單位是秒;只有當(dāng)diskless復(fù)制打開時有效悍及,默認(rèn)5s闽瓢。之所以設(shè)置停頓時間,是基于以下兩個考慮:(1)向slave的socket的傳輸一旦開始心赶,新連接的slave只能等待當(dāng)前數(shù)據(jù)傳輸結(jié)束,才能開始新的數(shù)據(jù)傳輸 (2)多個從節(jié)點(diǎn)有較大的概率在短時間內(nèi)建立主從復(fù)制缺猛。

3) ? client-output-buffer-limit slave 256MB 64MB 60:與全量復(fù)制階段主節(jié)點(diǎn)的緩沖區(qū)大小有關(guān)缨叫,見前面的介紹。

4) ? repl-disable-tcp-nodelay no:與命令傳播階段的延遲有關(guān)荔燎,見前面的介紹耻姥。

5) ? masterauth :與連接建立階段的身份驗(yàn)證有關(guān),見前面的介紹有咨。

6) ? repl-ping-slave-period 10:與命令傳播階段主從節(jié)點(diǎn)的超時判斷有關(guān)琐簇,見前面的介紹。

7) ? repl-backlog-size 1mb:復(fù)制積壓緩沖區(qū)的大小,見前面的介紹婉商。

8) ? repl-backlog-ttl 3600:當(dāng)主節(jié)點(diǎn)沒有從節(jié)點(diǎn)時似忧,復(fù)制積壓緩沖區(qū)保留的時間,這樣當(dāng)斷開的從節(jié)點(diǎn)重新連進(jìn)來時丈秩,可以進(jìn)行全量復(fù)制盯捌;默認(rèn)3600s。如果設(shè)置為0蘑秽,則永遠(yuǎn)不會釋放復(fù)制積壓緩沖區(qū)饺著。

9) ? min-slaves-to-write 3與min-slaves-max-lag 10:規(guī)定了主節(jié)點(diǎn)的最小從節(jié)點(diǎn)數(shù)目,及對應(yīng)的最大延遲肠牲,見前面的介紹幼衰。

(3)從節(jié)點(diǎn)相關(guān)配置

1) ? slave-serve-stale-data yes:與從節(jié)點(diǎn)數(shù)據(jù)陳舊時是否響應(yīng)客戶端命令有關(guān),見前面的介紹缀雳。

2) ? slave-read-only yes:從節(jié)點(diǎn)是否只讀塑顺;默認(rèn)是只讀的。由于從節(jié)點(diǎn)開啟寫操作容易導(dǎo)致主從節(jié)點(diǎn)的數(shù)據(jù)不一致俏险,因此該配置盡量不要修改严拒。

6. 單機(jī)內(nèi)存大小限制

在?深入學(xué)習(xí)Redis(2):持久化?一文中,講到了fork操作對Redis單機(jī)內(nèi)存大小的限制竖独。實(shí)際上在Redis的使用中裤唠,限制單機(jī)內(nèi)存大小的因素非常之多,下面總結(jié)一下在主從復(fù)制中莹痢,單機(jī)內(nèi)存過大可能造成的影響:

(1)切主:當(dāng)主節(jié)點(diǎn)宕機(jī)時种蘸,一種常見的容災(zāi)策略是將其中一個從節(jié)點(diǎn)提升為主節(jié)點(diǎn),并將其他從節(jié)點(diǎn)掛載到新的主節(jié)點(diǎn)上竞膳,此時這些從節(jié)點(diǎn)只能進(jìn)行全量復(fù)制航瞭;如果Redis單機(jī)內(nèi)存達(dá)到10GB,一個從節(jié)點(diǎn)的同步時間在幾分鐘的級別坦辟;如果從節(jié)點(diǎn)較多刊侯,恢復(fù)的速度會更慢。如果系統(tǒng)的讀負(fù)載很高锉走,而這段時間從節(jié)點(diǎn)無法提供服務(wù)滨彻,會對系統(tǒng)造成很大的壓力。

(2)從庫擴(kuò)容:如果訪問量突然增大挪蹭,此時希望增加從節(jié)點(diǎn)分擔(dān)讀負(fù)載亭饵,如果數(shù)據(jù)量過大,從節(jié)點(diǎn)同步太慢梁厉,難以及時應(yīng)對訪問量的暴增辜羊。

(3)緩沖區(qū)溢出:(1)和(2)都是從節(jié)點(diǎn)可以正常同步的情形(雖然慢),但是如果數(shù)據(jù)量過大,導(dǎo)致全量復(fù)制階段主節(jié)點(diǎn)的復(fù)制緩沖區(qū)溢出八秃,從而導(dǎo)致復(fù)制中斷碱妆,則主從節(jié)點(diǎn)的數(shù)據(jù)同步會全量復(fù)制->復(fù)制緩沖區(qū)溢出導(dǎo)致復(fù)制中斷->重連->全量復(fù)制->復(fù)制緩沖區(qū)溢出導(dǎo)致復(fù)制中斷……的循環(huán)。

(4)超時:如果數(shù)據(jù)量過大喜德,全量復(fù)制階段主節(jié)點(diǎn)fork+保存RDB文件耗時過大山橄,從節(jié)點(diǎn)長時間接收不到數(shù)據(jù)觸發(fā)超時,主從節(jié)點(diǎn)的數(shù)據(jù)同步同樣可能陷入全量復(fù)制->超時導(dǎo)致復(fù)制中斷->重連->全量復(fù)制->超時導(dǎo)致復(fù)制中斷……的循環(huán)舍悯。

此外航棱,主節(jié)點(diǎn)單機(jī)內(nèi)存除了絕對量不能太大,其占用主機(jī)內(nèi)存的比例也不應(yīng)過大:最好只使用50%-65%的內(nèi)存萌衬,留下30%-45%的內(nèi)存用于執(zhí)行bgsave命令和創(chuàng)建復(fù)制緩沖區(qū)等饮醇。

7. info Replication

在Redis客戶端通過info Replication可以查看與復(fù)制相關(guān)的狀態(tài),對于了解主從節(jié)點(diǎn)的當(dāng)前狀態(tài)秕豫,以及解決出現(xiàn)的問題都會有幫助朴艰。

主節(jié)點(diǎn):

從節(jié)點(diǎn):

對于從節(jié)點(diǎn),上半部分展示的是其作為從節(jié)點(diǎn)的狀態(tài)混移,從connectd_slaves開始祠墅,展示的是其作為潛在的主節(jié)點(diǎn)的狀態(tài)。

info Replication中展示的大部分內(nèi)容在文章中都已經(jīng)講述歌径,這里不再詳述毁嗦。

七、總結(jié)

下面回顧一下本文的主要內(nèi)容:

1回铛、主從復(fù)制的作用:宏觀的了解主從復(fù)制是為了解決什么樣的問題狗准,即數(shù)據(jù)冗余、故障恢復(fù)茵肃、讀負(fù)載均衡等腔长。

2、主從復(fù)制的操作:即slaveof命令验残。

3捞附、主從復(fù)制的原理:主從復(fù)制包括了連接建立階段、數(shù)據(jù)同步階段胚膊、命令傳播階段故俐;其中數(shù)據(jù)同步階段,有全量復(fù)制和部分復(fù)制兩種數(shù)據(jù)同步方式紊婉;命令傳播階段,主從節(jié)點(diǎn)之間有PING和REPLCONF ACK命令互相進(jìn)行心跳檢測辑舷。

4喻犁、應(yīng)用中的問題:包括讀寫分離的問題(數(shù)據(jù)不一致問題、數(shù)據(jù)過期問題、故障切換問題等)肢础、復(fù)制超時問題还栓、復(fù)制中斷問題等,然后總結(jié)了主從復(fù)制相關(guān)的配置传轰,其中repl-timeout剩盒、client-output-buffer-limit slave等對解決Redis主從復(fù)制中出現(xiàn)的問題可能會有幫助。

主從復(fù)制雖然解決或緩解了數(shù)據(jù)冗余慨蛙、故障恢復(fù)辽聊、讀負(fù)載均衡等問題,但其缺陷仍很明顯:故障恢復(fù)無法自動化期贫;寫操作無法負(fù)載均衡跟匆;存儲能力受到單機(jī)的限制;這些問題的解決通砍,需要哨兵和集群的幫助玛臂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市封孙,隨后出現(xiàn)的幾起案子迹冤,更是在濱河造成了極大的恐慌,老刑警劉巖虎忌,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泡徙,死亡現(xiàn)場離奇詭異,居然都是意外死亡呐籽,警方通過查閱死者的電腦和手機(jī)锋勺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狡蝶,“玉大人庶橱,你說我怎么就攤上這事√叭牵” “怎么了苏章?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奏瞬。 經(jīng)常有香客問我枫绅,道長,這世上最難降的妖魔是什么硼端? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任并淋,我火速辦了婚禮,結(jié)果婚禮上珍昨,老公的妹妹穿的比我還像新娘县耽。我一直安慰自己句喷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布兔毙。 她就那樣靜靜地躺著唾琼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澎剥。 梳的紋絲不亂的頭發(fā)上锡溯,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音哑姚,去河邊找鬼祭饭。 笑死,一個胖子當(dāng)著我的面吹牛蜻懦,可吹牛的內(nèi)容都是我干的甜癞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼宛乃,長吁一口氣:“原來是場噩夢啊……” “哼悠咱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起征炼,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤析既,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谆奥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眼坏,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年酸些,在試婚紗的時候發(fā)現(xiàn)自己被綠了宰译。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡魄懂,死狀恐怖沿侈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情市栗,我是刑警寧澤缀拭,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站填帽,受9級特大地震影響蛛淋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜篡腌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一褐荷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘹悼,春花似錦诚卸、人聲如沸葵第。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缀台,卻和暖如春棠赛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膛腐。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工睛约, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哲身。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓辩涝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勘天。 傳聞我的和親對象是個殘疾皇子怔揩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 本篇就一下方面展開分析 如何使用主從復(fù)制? 主從復(fù)制的原理(重點(diǎn)是全量復(fù)制和部分復(fù)制脯丝、以及心跳機(jī)制) 實(shí)際應(yīng)用中需...
    lucode閱讀 988評論 0 5
  • 通過這篇文章你會知道如下: 如何配置主從關(guān)系商膊?如何斷開主從關(guān)系?如何將從節(jié)點(diǎn)變成主節(jié)點(diǎn)宠进? 主從復(fù)制的拓?fù)浣Y(jié)構(gòu)以及相...
    打傘的Fish閱讀 428評論 0 1
  • 超強(qiáng)晕拆、超詳細(xì)Redis入門教程 轉(zhuǎn)載2017年03月04日 16:20:02 16916 轉(zhuǎn)載自: http://...
    邵云濤閱讀 17,431評論 3 313
  • 文章已經(jīng)放到github上 ,如果對您有幫助 請給個star[https://github.com/qqxuanl...
    尼爾君閱讀 2,282評論 0 22
  • 當(dāng)情人分手昆庇、工作被炒、股票跌停吼旧、健康告急凰锡,那個時刻,你會想什么圈暗? 思考停止掂为,只有真切的痛苦,并且想讓那個痛苦趕緊滾...
    地球激勵師拉娜閱讀 1,432評論 10 7