轉載--日志

問題概述

在分布式系統中,宕機是需要考慮的重要組成部分。日志技術是宕機恢復的重要技術之一。日志技術應用廣泛,早些更是廣泛應用在數據庫設計實現中。本文先介紹基本原理概念,最后通過redis介紹生產環境中的實現方法。

Redo Log

數據庫設計中,需要滿足ACID,尤其是在支持事務的系統中。當系統遇到未知錯誤時,可以恢復到一個穩定可靠的狀態。有一個很簡單的思路,就是記錄所有對數據庫的寫操作日志。那么一旦發生故障,即使丟失掉內存中所有數據,當下一次啟動時,通過復現已經記錄的數據庫寫操作日志,依然可以回到故障之前的狀態(如果在寫操作作日志的時候發生故障,那么這次數據庫操作失敗)。

操作流程簡單如下(假設每次數據變化,都提交):

  1. 更新的操作方式依次記錄到磁盤日志文件。
  2. 更新內存中的數據。
  3. 返回更新成功結果。

恢復流程如下

  1. 讀取日志文件,依次修改內存中的數據。

優點:

  1. 日志文件有序,可以通過append的方式寫入磁盤,性能很高。
  2. 簡單可靠,應用廣泛。可以把內存中的數據,做備份在磁盤中。

缺點:

  1. 使用時間一長,恢復宕機的時間很慢。

解決方法

先具體化下,如果我們內存中保留一個a的值,記錄了寫操作比如 a = 4; a++; a--; 當這些操作上千萬、億之后,恢復非常慢。甚至可能最后一條就是a=0,按照之前的算法,我們卻跑了很長時間。

那么根據這個場景,很容易想到一個解決方案。

操作流程:

  1. 日志文件記錄begin check point
  2. 在某個時刻,把內存中的數值,直接snapshot或dump到磁盤上.(比如直接記錄a=4)
  3. 日志文件記錄end check point

恢復流程:

1.掃描日志文件,找到最后的end check point中配對的begin check point。
2.讀入dump文件。
3.依次回放記錄的日志操作。

優點:

  1. 應用廣泛,包括 mysql,oracle。

一些棘手的問題:

  1. 在做snapshot的時候,往往不能停止數據庫的服務,那么很可能記錄了begin check point之后的日志。那么在重新load begin check point之后的日志時,最后恢復的數據很有可能不對。比如我們記錄的是a++這樣的日志, 那么重復一條日志,就會讓a的值加1。反之如果我們記錄是冪等的,比如一直是 a=5 這種操作,那么就對最后結果沒有影響。很顯然,設計冪等操作系統很麻煩。

  2. 設計一個支持snapshot的內存數據結構,也比較麻煩。

典型的是通過copy-on-write機制。和操作系統中的概念一樣。當這個數據結構被修改,就創建一份真正的copy。老數據增加一份dirty flag。如果沒有修改就繼續使用之前的內存。這樣在做snapshot的時候,保證我們的dump數據是begin check point這個時刻的數據。顯然這個也比較麻煩。

還有一種支持snapshot的思路是begin check point后,不動老的數據。內存中的數據在新的地方,日志也寫在新的地方。最后在end check point做一次merge。這個實現起來簡單,但是內存消耗不小。

Redis是如何解決日志問題的

Redis 是一個基于內存的database,不同于memcached,他支持持久化。另外由于redis處理client request 和 response 都是在一個thread里面,也沒有搶占式的調度系統,核心業務都是按照event loop順序執行,而磁盤寫日志又開銷很大,所以redis實現日志功能做了很多優化。并且提供2種持久化方案。我們需要在不同的場景下,采用不同的方式配置。

snapshotting

某個時刻,redis會把內存中的所有數據snapshot到磁盤文件。更通俗的說法是fork一個child process,把內存中的數據序列化到臨時文件,然后在main event loop 中原子的更換文件名。redis,利用了操作系統VM的copy-on-write機制,在不阻塞主線程的情況下,利用子進程和父進程共享的data segment實現snapshot。具體是代碼實現在rdb.c, function at rdbSaveBackground

優點:

  1. 簡單可靠,如果database 不大,執行的效果非常好。

缺點:

1.如果database size 很大,每一次snapshot時間非常長。不得不配置大的間隔,提高了宕機時數據丟失的風險。
為了解決上面的問題,redis增加了AOF。

Append Only File(AOF)

在database術語中,也被叫做WAL。如果開啟的AOF的配置,redis會記錄所有寫操作到日志文件中。那么redis同樣會遇到之前我們提到過的問題。

  1. 即便是追加寫,磁盤的操作依然比內存慢好幾個數量級,頻繁的操作容易產生瓶頸。
  2. 如果數據量操作頻繁,會產生大量的重復日志數據,導致恢復時間太長。比如記錄一條微博的瀏覽量,會記錄大量重復的+1日志。

那么redis是如何解決的呢?

  1. 文件寫操作消耗的時間很長,redis會先把記錄日志寫在內存buffer中,在每一次event loop 結束之后,根據配置判斷是否做寫操作。每個buffer的大小有限制,這樣每次寫操作時間不會太長。
  2. 即便是調用write操作,OS并沒有立即寫入磁盤,redis 同樣提供了一些方案決定刷新OS IO buffer的時機(1秒、從不、每次)。
  3. redis 提供一種AOF重寫的方式rewriteAppendOnlyFile來處理AOF文件過大情況。

前面我們知道了,這種check point的機制還是比較麻煩的。那么redis是怎么設計的呢?

rewrite

  1. 為了避免加鎖,redis 依然創建了一個child process,利用VM的copy-on-write,共享數據。同時保證主線程依然可以處理client請求。
  2. 根據KV的類型,先從內存讀取數據,然后再寫數據到磁盤,和之前的AOF文件無關。
    那么當子進程rewrite AOF的過程中,main thread依然可以處理新的client request。新增的數據會被放在rewrite buffer中,而且寫到原有的AOF文件中。
  3. child process完成后會通知主線程。主線程有一個定時任務,也就是會不斷輪詢child process是否已經完成(通過信號量)。
  4. 主線程會merge 變化的數據到temp file。
  5. 主線程原子的rename到一個新的AOF文件,老AOF就不起作用了。

優點:

  1. 除了merge 和 rename需要阻塞主線程,rewrite不會阻塞主線程。(前提是使用bgrewrite command)。

最后

這些都是性能和穩定性之間做的權衡,根據不同場景需要調整。

參考

Redis latency problems troubleshooting
分布式系統原理介紹
Thoughts on Redis

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容