<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          宕機了,緩存數(shù)據(jù)沒了。。。

          共 6267字,需瀏覽 13分鐘

           ·

          2021-05-30 02:02

          AOF 日志

          試想一下,如果 Redis 每執(zhí)行一條寫操作命令,就把該命令以追加的方式寫入到一個文件里,然后重啟 Redis 的時候,先去讀取這個文件里的命令,并且執(zhí)行它,這不就相當于恢復(fù)了緩存數(shù)據(jù)了嗎?

          這種保存寫操作命令到日志的持久化方式,就是 Redis 里的 AOF(Append Only File) 持久化功能,注意只會記錄寫操作命令,讀操作命令是不會被記錄的,因為沒意義。

          在 Redis 中 AOF 持久化功能默認是不開啟的,需要我們修改 redis.conf 配置文件中的以下參數(shù):

          AOF 日志文件其實就是普通的文本,我們可以通過 cat 命令查看里面的內(nèi)容,不過里面的內(nèi)容如果不知道一定的規(guī)則的話,可能會看不懂。

          我這里以「set name xiaolin」命令作為例子,Redis 執(zhí)行了這條命令后,記錄在 AOF 日志里的內(nèi)容如下圖:

          我這里給大家解釋下。

          *3」表示當前命令有三個部分,每部分都是以「$+數(shù)字」開頭,后面緊跟著具體的命令、鍵或值。然后,這里的「數(shù)字」表示這部分中的命令、鍵或值一共有多少字節(jié)。例如,「$3 set」表示這部分有 3 個字節(jié),也就是「set」命令這個字符串的長度。

          不知道大家注意到?jīng)]有,Redis 是先執(zhí)行寫操作命令后,才將該命令記錄到 AOF 日志里的,這么做其實有兩個好處。

          第一個好處,避免額外的檢查開銷。

          因為如果先將寫操作命令記錄到 AOF 日志里,再執(zhí)行該命令的話,如果當前的命令語法有問題,那么如果不進行命令語法檢查,該錯誤的命令記錄到 AOF 日志里后,Redis 在使用日志恢復(fù)數(shù)據(jù)時,就可能會出錯。

          而如果先執(zhí)行寫操作命令再記錄日志的話,只有在該命令執(zhí)行成功后,才將命令記錄到 AOF 日志里,這樣就不用額外的檢查開銷,保證記錄在 AOF 日志里的命令都是可執(zhí)行并且正確的。

          第二個好處,不會阻塞當前寫操作命令的執(zhí)行,因為當寫操作命令執(zhí)行成功后,才會將命令記錄到 AOF 日志。

          當然,AOF 持久化功能也不是沒有潛在風險。

          第一個風險,執(zhí)行寫操作命令和記錄日志是兩個過程,那當 Redis 在還沒來得及將命令寫入到硬盤時,服務(wù)器發(fā)生宕機了,這個數(shù)據(jù)就會有丟失的風險。

          第二個風險,前面說道,由于寫操作命令執(zhí)行成功后才記錄到 AOF 日志,所以不會阻塞當前寫操作命令的執(zhí)行,但是可能會給「下一個」命令帶來阻塞風險。

          因為將命令寫入到日志的這個操作也是在主進程完成的(執(zhí)行命令也是在主進程),也就是說這兩個操作是同步的。

          如果在將日志內(nèi)容寫入到硬盤時,服務(wù)器的硬盤的 I/O 壓力太大,就會導(dǎo)致寫硬盤的速度很慢,進而阻塞住了,也就會導(dǎo)致后續(xù)的命令無法執(zhí)行。

          認真分析一下,其實這兩個風險都有一個共性,都跟「 AOF 日志寫回硬盤的時機」有關(guān)。

          三種寫回策略

          Redis 寫入 AOF 日志的過程,如下圖:

          我先來具體說說:

          1. Redis 執(zhí)行完寫操作命令后,會將命令追加到 server.aof_buf 緩沖區(qū);

          2. 然后通過 write() 系統(tǒng)調(diào)用,將 aof_buf 緩沖區(qū)的數(shù)據(jù)寫入到 AOF 文件,此時數(shù)據(jù)并沒有寫入到硬盤,而是拷貝到了內(nèi)核緩沖區(qū) page cache,等待內(nèi)核將數(shù)據(jù)寫入硬盤;

          3. 具體內(nèi)核緩沖區(qū)的數(shù)據(jù)什么時候?qū)懭氲接脖P,由內(nèi)核決定。

          Redis 提供了 3 種寫回硬盤的策略,控制的就是上面說的第三步的過程。

          在 redis.conf 配置文件中的 appendfsync 配置項可以有以下 3 種參數(shù)可填:

          • Always,這個單詞的意思是「總是」,所以它的意思是每次寫操作命令執(zhí)行完后,同步將 AOF 日志數(shù)據(jù)寫回硬盤;

          • Everysec,這個單詞的意思是「每秒」,所以它的意思是每次寫操作命令執(zhí)行完后,先將命令寫入到 AOF 文件的內(nèi)核緩沖區(qū),然后每隔一秒將緩沖區(qū)里的內(nèi)容寫回到硬盤;

          • No,意味著不由 Redis 控制寫回硬盤的時機,轉(zhuǎn)交給操作系統(tǒng)控制寫回的時機,也就是每次寫操作命令執(zhí)行完后,先將命令寫入到 AOF 文件的內(nèi)核緩沖區(qū),再由操作系統(tǒng)決定何時將緩沖區(qū)內(nèi)容寫回硬盤。

          這 3 種寫回策略都無法能完美解決「主進程阻塞」和「減少數(shù)據(jù)丟失」的問題,因為兩個問題是對立的,偏向于一邊的話,就會要犧牲另外一邊,原因如下:

          • Always 策略的話,可以最大程度保證數(shù)據(jù)不丟失,但是由于它每執(zhí)行一條寫操作命令就同步將 AOF 內(nèi)容寫回硬盤,所以是不可避免會影響主進程的性能;

          • No 策略的話,是交由操作系統(tǒng)來決定何時將 AOF 日志內(nèi)容寫回硬盤,相比于 Always 策略性能較好,但是操作系統(tǒng)寫回硬盤的時機是不可預(yù)知的,如果 AOF 日志內(nèi)容沒有寫回硬盤,一旦服務(wù)器宕機,就會丟失不定數(shù)量的數(shù)據(jù)。

          • Everysec 策略的話,是折中的一種方式,避免了 Always 策略的性能開銷,也比 No 策略更能避免數(shù)據(jù)丟失,當然如果上一秒的寫操作命令日志沒有寫回到硬盤,發(fā)生了宕機,這一秒內(nèi)的數(shù)據(jù)自然也會丟失。

          大家根據(jù)自己的業(yè)務(wù)場景進行選擇:

          • 如果要高性能,就選擇 No 策略;

          • 如果要高可靠,就選擇 Always 策略;

          • 如果允許數(shù)據(jù)丟失一點,但又想性能高,就選擇 Everysec 策略。

          我也把這 3 個寫回策略的優(yōu)缺點總結(jié)成了一張表格:

          大家知道這三種策略是怎么實現(xiàn)的嗎?

          深入到源碼后,你就會發(fā)現(xiàn)這三種策略只是在控制 fsync() 函數(shù)的調(diào)用時機。

          當應(yīng)用程序向文件寫入數(shù)據(jù)時,內(nèi)核通常先將數(shù)據(jù)復(fù)制到內(nèi)核緩沖區(qū)中,然后排入隊列,然后由內(nèi)核決定何時寫入硬盤。

          如果想要應(yīng)用程序向文件寫入數(shù)據(jù)后,能立馬將數(shù)據(jù)同步到硬盤,就可以調(diào)用 fsync() 函數(shù),這樣內(nèi)核就會將內(nèi)核緩沖區(qū)的數(shù)據(jù)直接寫入到硬盤,等到硬盤寫操作完成后,該函數(shù)才會返回。

          • Always 策略就是每次寫入 AOF 文件數(shù)據(jù)后,就執(zhí)行 fsync() 函數(shù);

          • Everysec 策略就會創(chuàng)建一個異步任務(wù)來執(zhí)行 fsync() 函數(shù);

          • No 策略就是永不執(zhí)行 fsync() 函數(shù);

          AOF 重寫機制

          AOF 日志是一個文件,隨著執(zhí)行的寫操作命令越來越多,文件的大小會越來越大。

          如果當 AOF 日志文件過大就會帶來性能問題,比如重啟 Redis 后,需要讀 AOF 文件的內(nèi)容以恢復(fù)數(shù)據(jù),如果文件過大,整個恢復(fù)的過程就會很慢。

          所以,Redis 為了避免 AOF 文件越寫越大,提供了 AOF 重寫機制,當 AOF 文件的大小超過所設(shè)定的閾值后,Redis 就會啟用 AOF 重寫機制,來壓縮 AOF 文件。

          AOF 重寫機制是在重寫時,讀取當前數(shù)據(jù)庫中的所有鍵值對,然后將每一個鍵值對用一條命令記錄到「新的 AOF 文件」,等到全部記錄完后,就將新的 AOF 文件替換掉現(xiàn)有的 AOF 文件。

          舉個例子,在沒有使用重寫機制前,假設(shè)前后執(zhí)行了「set name xiaolin」和「set name xiaolincoding」這兩個命令的話,就會將這兩個命令記錄到 AOF 文件。

          但是在使用重寫機制后,就會讀取 name 最新的 value(鍵值對) ,然后用一條 「set name xiaolincoding」命令記錄到新的 AOF 文件,之前的第一個命令就沒有必要記錄了,因為它屬于「歷史」命令,沒有作用了。這樣一來,一個鍵值對在重寫日志中只用一條命令就行了。

          重寫工作完成后,就會將新的 AOF 文件覆蓋現(xiàn)有的 AOF 文件,這就相當于壓縮了 AOF 文件,使得 AOF 文件體積變小了。

          然后,在通過 AOF 日志恢復(fù)數(shù)據(jù)時,只用執(zhí)行這條命令,就可以直接完成這個鍵值對的寫入了。

          所以,重寫機制的妙處在于,盡管某個鍵值對被多條寫命令反復(fù)修改,最終也只需要根據(jù)這個「鍵值對」當前的最新狀態(tài),然后用一條命令去記錄鍵值對,代替之前記錄這個鍵值對的多條命令,這樣就減少了 AOF 文件中的命令數(shù)量。最后在重寫工作完成后,將新的 AOF 文件覆蓋現(xiàn)有的 AOF 文件。

          這里說一下為什么重寫 AOF 的時候,不直接復(fù)用現(xiàn)有的 AOF 文件,而是先寫到新的 AOF 文件再覆蓋過去。

          因為如果 AOF 重寫過程中失敗了,現(xiàn)有的 AOF 文件就會造成污染,可能無法用于恢復(fù)使用。

          所以 AOF 重寫過程,先重寫到新的 AOF 文件,重寫失敗的話,就直接刪除這個文件就好,不會對現(xiàn)有的 AOF 文件造成影響。

          AOF 后臺重寫

          寫入 AOF 日志的操作雖然是在主進程完成的,因為它寫入的內(nèi)容不多,所以一般不太影響命令的操作。

          但是在觸發(fā) AOF 重寫時,比如當 AOF 文件大于 64M 時,就會對 AOF 文件進行重寫,這時是需要讀取所有緩存的鍵值對數(shù)據(jù),并為每個鍵值對生成一條命令,然后將其寫入到新的 AOF 文件,重寫完后,就把現(xiàn)在的 AOF 文件替換掉。

          這個過程其實是很耗時的,所以重寫的操作不能放在主進程里。

          所以,Redis 的重寫 AOF 過程是由后臺子進程 bgrewriteaof 來完成的,這么做可以達到兩個好處:

          • 子進程進行 AOF 重寫期間,主進程可以繼續(xù)處理命令請求,從而避免阻塞主進程;

          • 子進程帶有主進程的數(shù)據(jù)副本(數(shù)據(jù)副本怎么產(chǎn)生的后面會說),這里使用子進程而不是線程,因為如果是使用線程,多線程之間會共享內(nèi)存,那么在修改共享內(nèi)存數(shù)據(jù)的時候,需要通過加鎖來保證數(shù)據(jù)的安全,而這樣就會降低性能。而使用子進程,創(chuàng)建子進程時,父子進程是共享內(nèi)存數(shù)據(jù)的,不過這個共享的內(nèi)存只能以只讀的方式,而當父子進程任意一方修改了該共享內(nèi)存,就會發(fā)生「寫時復(fù)制」,于是父子進程就有了獨立的數(shù)據(jù)副本,就不用加鎖來保證數(shù)據(jù)安全。

          子進程是怎么擁有主進程一樣的數(shù)據(jù)副本的呢?

          主進程在通過 fork 系統(tǒng)調(diào)用生成 bgrewriteaof 子進程時,操作系統(tǒng)會把主進程的「頁表」復(fù)制一份給子進程,這個頁表記錄著虛擬地址和物理地址映射關(guān)系,而不會復(fù)制物理內(nèi)存,也就是說,兩者的虛擬空間不同,但其對應(yīng)的物理空間是同一個。

          這樣一來,子進程就共享了父進程的物理內(nèi)存數(shù)據(jù)了,這樣能夠節(jié)約物理內(nèi)存資源,頁表對應(yīng)的頁表項的屬性會標記該物理內(nèi)存的權(quán)限為只讀。

          不過,當父進程或者子進程在向這個內(nèi)存發(fā)起寫操作時,CPU 就會觸發(fā)缺頁中斷,這個缺頁中斷是由于違反權(quán)限導(dǎo)致的,然后操作系統(tǒng)會在「缺頁異常處理函數(shù)」里進行物理內(nèi)存的復(fù)制,并重新設(shè)置其內(nèi)存映射關(guān)系,將父子進程的內(nèi)存讀寫權(quán)限設(shè)置為可讀寫,最后才會對內(nèi)存進行寫操作,這個過程被稱為「寫時復(fù)制(Copy On Write)」。

          寫時復(fù)制顧名思義,在發(fā)生寫操作的時候,操作系統(tǒng)才會去復(fù)制物理內(nèi)存,這樣是為了防止 fork 創(chuàng)建子進程時,由于物理內(nèi)存數(shù)據(jù)的復(fù)制時間過長而導(dǎo)致父進程長時間阻塞的問題。

          當然,操作系統(tǒng)復(fù)制父進程頁表的時候,父進程也是阻塞中的,不過頁表的大小相比實際的物理內(nèi)存小很多,所以通常復(fù)制頁表的過程是比較快的。

          不過,如果父進程的內(nèi)存數(shù)據(jù)非常大,那自然頁表也會很大,這時父進程在通過 fork 創(chuàng)建子進程的時候,阻塞的時間也越久。

          所以,有兩個階段會導(dǎo)致阻塞父進程:

          • 創(chuàng)建子進程的途中,由于要復(fù)制父進程的頁表等數(shù)據(jù)結(jié)構(gòu),阻塞的時間跟頁表的大小有關(guān),頁表越大,阻塞的時間也越長;

          • 創(chuàng)建完子進程后,如果子進程或者父進程修改了共享數(shù)據(jù),就會發(fā)生寫時復(fù)制,這期間會拷貝物理內(nèi)存,如果內(nèi)存越大,自然阻塞的時間也越長;

          觸發(fā)重寫機制后,主進程就會創(chuàng)建重寫 AOF 的子進程,此時父子進程共享物理內(nèi)存,重寫子進程只會對這個內(nèi)存進行只讀,重寫 AOF 子進程會讀取數(shù)據(jù)庫里的所有數(shù)據(jù),并逐一把內(nèi)存數(shù)據(jù)的鍵值對轉(zhuǎn)換成一條命令,再將命令記錄到重寫日志(新的 AOF 文件)。

          但是子進程重寫過程中,主進程依然可以正常處理命令。

          如果此時主進程修改了已經(jīng)存在 key-value,就會發(fā)生寫時復(fù)制,注意這里只會復(fù)制主進程修改的物理內(nèi)存數(shù)據(jù),沒修改物理內(nèi)存還是與子進程共享的

          所以如果這個階段修改的是一個 bigkey,也就是數(shù)據(jù)量比較大的 key-value 的時候,這時復(fù)制的物理內(nèi)存數(shù)據(jù)的過程就會比較耗時,有阻塞主進程的風險。

          還有個問題,重寫 AOF 日志過程中,如果主進程修改了已經(jīng)存在 key-value,此時這個 key-value 數(shù)據(jù)在子進程的內(nèi)存數(shù)據(jù)就跟主進程的內(nèi)存數(shù)據(jù)不一致了,這時要怎么辦呢?

          為了解決這種數(shù)據(jù)不一致問題,Redis 設(shè)置了一個 AOF 重寫緩沖區(qū),這個緩沖區(qū)在創(chuàng)建 bgrewriteaof 子進程之后開始使用。

          在重寫 AOF 期間,當 Redis 執(zhí)行完一個寫命令之后,它會同時將這個寫命令寫入到 「AOF 緩沖區(qū)」和 「AOF 重寫緩沖區(qū)」

          也就是說,在 bgrewriteaof 子進程執(zhí)行 AOF 重寫期間,主進程需要執(zhí)行以下三個工作:

          • 執(zhí)行客戶端發(fā)來的命令;

          • 將執(zhí)行后的寫命令追加到 「AOF 緩沖區(qū)」;

          • 將執(zhí)行后的寫命令追加到 「AOF 重寫緩沖區(qū)」;

          當子進程完成 AOF 重寫工作(掃描數(shù)據(jù)庫中所有數(shù)據(jù),逐一把內(nèi)存數(shù)據(jù)的鍵值對轉(zhuǎn)換成一條命令,再將命令記錄到重寫日志)后,會向主進程發(fā)送一條信號,信號是進程間通訊的一種方式,且是異步的。

          主進程收到該信號后,會調(diào)用一個信號處理函數(shù),該函數(shù)主要做以下工作:

          • 將 AOF 重寫緩沖區(qū)中的所有內(nèi)容追加到新的 AOF 的文件中,使得新舊兩個 AOF 文件所保存的數(shù)據(jù)庫狀態(tài)一致;

          • 新的 AOF 的文件進行改名,覆蓋現(xiàn)有的 AOF 文件。

          信號函數(shù)執(zhí)行完后,主進程就可以繼續(xù)像往常一樣處理命令了。

          在整個 AOF 后臺重寫過程中,除了發(fā)生寫時復(fù)制會對主進程造成阻塞,還有信號處理函數(shù)執(zhí)行時也會對主進程造成阻塞,在其他時候,AOF 后臺重寫都不會阻塞主進程。

          總結(jié)

          這次小林給大家介紹了 Redis 持久化技術(shù)中的 AOF 方法,這個方法是每執(zhí)行一條寫操作命令,就將該命令以追加的方式寫入到 AOF 文件,然后在恢復(fù)時,以逐一執(zhí)行命令的方式來進行數(shù)據(jù)恢復(fù)。

          Redis 提供了三種將 AOF 日志寫回硬盤的策略,分別是 Always、Everysec 和 No,這三種策略在可靠性上是從高到低,而在性能上則是從低到高。

          隨著執(zhí)行的命令越多,AOF 文件的體積自然也會越來越大,為了避免日志文件過大, Redis 提供了 AOF 重寫機制,它會直接掃描數(shù)據(jù)中所有的鍵值對數(shù)據(jù),然后為每一個鍵值對生成一條寫操作命令,接著將該命令寫入到新的 AOF 文件,重寫完成后,就替換掉現(xiàn)有的 AOF 日志。重寫的過程是由后臺子進程完成的,這樣可以使得主進程可以繼續(xù)正常處理命令。

          用 AOF 日志的方式來恢復(fù)數(shù)據(jù)其實是很慢的,因為 Redis 執(zhí)行命令由單線程負責的,而 AOF 日志恢復(fù)數(shù)據(jù)的方式是順序執(zhí)行日志里的每一條命令,如果 AOF 日志很大,這個「重放」的過程就會很慢了。


          參考資料

          《Redis設(shè)計與實現(xiàn)》

          《Redis核心技術(shù)與實戰(zhàn)-極客時間》

          《Redis源碼分析》


          瀏覽 26
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成年人免费黄色视频网站 | 国产一卡二卡在线观看 | 欧美色图15| 免费观看黄色录像 | 簧片天堂 |