回覆列表
  • 1 # 愛可生雲資料庫

    CrashSafe指MySQL伺服器宕機重啟後,能夠保證:- 所有已經提交的事務的資料仍然存在。- 所有沒有提交的事務的資料自動回滾。前面的文章講過,Innodb透過Redo Log和Undo Log可以保證以上兩點。為了保證嚴格的CrashSafe,必須要在每個事務提交的時候,將Redo Log寫入硬體儲存。這樣做會犧牲一些效能,但是可靠性最好。為了平衡兩者,InnoDB提供了一個系統變數,使用者可以根據應用的需求自行調整。

    - innodb_flush_log_at_trx_commit

    0 - 每N秒將Redo Log Buffer的記錄寫入Redo Log檔案,並且將檔案刷入硬體儲存1次。N由

    innodb_flush_log_at_timeout 控制。

    1 - 每個事務提交時,將記錄從Redo Log Buffer寫入Redo Log檔案,並且將檔案刷入硬體儲存。 2 - 每個事務提交時,僅將記錄從Redo Log Buffer寫入Redo Log檔案。Redo Log何時刷入硬體儲存由作業系統和innodb_flush_log_at_timeout 決定。這個選項可以保證在MySQL宕機,而作業系統正常工作時,資料的完整性。

    那麼CrashSafe和Binlog有什麼關係呢?1 - 帶Binlog的CrashSafe當啟動Binlog後,事務會產生Binlog Event,這些Event被看做事務資料的一部分。因此要保證事務的Binlog Event和InnoDB引擎中的資料的一致性。所以帶Binlog的CrashSafe要求MySQL宕機重啟後能夠保證:- 所有已經提交的事務的資料仍然存在。- 所有沒有提交的事務的資料自動回滾。- 所有已經提交了的事務的Binlog Event也仍然存在。- 所有沒有提交事務沒有記錄Binlog Event。 這些要求很好理解,如果重啟後資料還在,但是Binlog Event沒有了,就沒辦法複製到其他節點上了。如果重啟後,資料沒了,但是Binlog Event還在,那麼不存在的資料就會被複制到其他節點上,從而導致主從的不一致。為了保證帶Binlog的CrashSafe,MySQL內部使用的兩階段提交(Two Phase Commit)。

    2 - MySQL的Two Phase Commit(2PC)在開啟Binlog後,MySQL內部會自動將普通事務當做一個XA事務來處理:

    - 自動為每個事務分配一個唯一的ID

    - COMMIT會被自動的分成Prepare和Commit兩個階段。

    - Binlog會被當做事務協調者(Transaction Coordinator),Binlog Event會被當做協調者日誌。

    想了解2PC,可以參考文件:【https://en.wikipedia.org/wiki/Two-phase_commit_protocol。】

    - 分散式事務ID(XID)使用2PC時,MySQL會自動的為每一個事務分配一個ID,叫XID。XID是唯一的,每個事務的XID都不相同。XID會分別被Binlog和InnoDB記入日誌中,供恢復時使用。MySQ內部的XID由三部分組成:- 字首部分 字首部分是字串"MySQLXid"- Server ID部分 當前MySQL的server_id

    - query_id部分 為了保證XID的的唯一性,數字部分使用了query_id。MySQL內部會自動的為每一個語句分配一個query_id,全域性唯一。參考程式碼:sql/xa。hstruct xid_t結構。- 事務的協調者BinlogBinlog在2PC中充當了事務的協調者(Transaction Coordinator)。由Binlog來通知InnoDB引擎來執行prepare,commit或者rollback的步驟。事務提交的整個過程如下:1. 協調者準備階段(Prepare Phase) 告訴引擎做Prepare,InnoDB更改事務狀態,並將Redo Log刷入磁碟。2. 協調者提交階段(Commit Phase) 2.1 記錄協調者日誌,即Binlog日誌。 2.2 告訴引擎做commit。

    注意:記錄Binlog是在InnoDB引擎Prepare(即Redo Log寫入磁碟)之後,這點至關重要。

    在MySQ的程式碼中將協調者叫做tc_log。在MySQL啟動時,tc_log將被初始化為mysql_bin_log物件。參考sql/binlog.cc中的init_server_components()

    if (opt_bin_log) tc_log= &mysql_bin_log;

    而在事務提交時,會依次執行:

    tc_log->prepare();

    tc_log->commit();

    參考程式碼:sql/binlog.cc中的ha_commit_trans()。當mysql_bin_logtc_log時,prepare和commit的程式碼在sql/binlog.cc中:

    MYSQL_BIN_LOG::prepare();

    MYSQL_BIN_LOG::commit();

    -協調者日誌Xid_log_event

    作為協調者,Binlog需要將事務的XID記入日誌,供恢復時使用。Xid_log_event有以下幾個特點:- 僅記錄query_id

    因為字首部分不變,server_id已經記錄在Event Header中,Xid_log_event中只記錄query_id部分。

    - 標誌事務的結束 在Binlog中相當於一個事務的COMMIT語句。 一個事務在Binlog中看起來時這樣的:

    Query_log_event("BEGIN");DML產生的events; Xid_log_event;

    - DDL沒有BEGIN,也沒有Xid_log_event

    - 僅InnoDB的DML會產生Xid_log_event

    因為MyISAM不支援2PC所以不能用Xid_log_event ,但會有COMMIT Event。

    Query_log_event("BEGIN");DML產生的events;Query_log_event("COMMIT");

    問題:Query_log_event("COMMIT")和Xid_log_event 有不同的影響嗎?

    - Xid_log_event 中的Xid可以幫助master實現CrashSafe。- Slave的CrashSafe不依賴Xid_log_event

    事務在Slave上重做時,會重新產生XID。所以Slave伺服器的CrashSafe並不依賴於Xid_log_event Xid_log_event Query_log_event("COMMIT"),只是作為事務的結尾,告訴Slave Applier去提交這個事務。因此二者在Slave上的影響是一樣的。3 - 恢復(Recovery)這個機制是如何保證MySQL的CrashSafe的呢,我們來分析一下。這裡我們假設使用者設定了以下引數來保證可靠性:

    - 恢復前事務的狀態在恢復開始前事務有以下幾種狀態:- InnoDB中已經提交 根據前面2PC的過程,可知Binlog中也一定記錄了該事務的的Events。所以這種事務是一致的不需要處理。- InnoDB中是prepared狀態,Binlog中有該事務的Events。 需要通知InnoDB提交這些事務。- InnoDB中是prepared狀態,Binlog中沒有該事務的Events。 因為Binlog還沒記錄,需要通知InnoDB回滾這些事務。- Before InnoDB Prepare 事務可能還沒執行完,因此InnoDB中的狀態還沒有prepare。根據2PC的過程,Binlog中也沒有該事務的events。 需要通知InnoDB回滾這些事務。

    - 恢復過程從上面的事務狀態可以看出:恢復時事務要提交還是回滾,是由Binlog來決定的。- 事務的Xid_log_event 存在,就要提交。- 事務的Xid_log_event 不存在,就要回滾。

    恢復的過程非常簡單:- 從Binlog中讀出所有的Xid_log_event

    - 告訴InnoDB提交這些XID的事務- InnoDB回滾其它的事務疑問1:如果事務的Binlog Event只記錄了一部分怎麼辦?只有最後一個事務的Event會發生這樣的情況。在恢復時,binlog會自動的將這個不完整的事務Events從Binlog檔案中給清除掉。

    疑問2:隨著長時間的執行,Binlog中會積累了很多Xid_log_event ,讀取所有的Xid_log_event 會不會效率很低?

    當然很低,所以Binlog中有一個機制來保證恢復時只用讀取最後一個Binlog檔案中的Xid_log_event 。這種機制很像一個簡單的Xid_log_event 的checkpoint機制。

    - Xid_log_event Checkpoint

    這個機制和binlog的檔案切換有關,在切換到一個新的Binlog檔案前:- 要等待當前Binlog檔案中的所有事務都已經在InnoDB中提交了。- 告訴InnoDB刷Redo Log到硬體儲存。透過這個機制可以保證在做恢復時,除了最後一個Binlog檔案中的事務,其他檔案中的事務在InnoDB中一定是已經提交的狀態。

    參考程式碼:

    sql/binlog.cc中:

    MYSQL_BIN_LOG::recovery()MYSQL_BIN_LOG::new_file_impl()MYSQL_BIN_LOG::inc_prep_xids()MYSQL_BIN_LOG::dec_prep_xids()

    4 - CrashSafe的寫盤次數前面說道要想保證CrashSafe就要設定下面兩個引數為1:sync_binlog=1

    innodb_flush_log_at_trx_commit=1下面我們來看看這兩個引數的作用。- sync_binlogsync_binlog是控制Binlog寫盤的,1表示每次都寫。由於Binlog使用了組提交(Group Commit)的機制,它代表一組事務提交時必須要將Binlog檔案寫入硬體儲存1次。- innodb_flush_log_at_trx_commit的寫盤次數這個變數是用來控制InnoDB commit時寫盤的方法的。現在commit被分成了兩個階段,到底在哪個階段寫盤,還是兩個階段都要寫盤呢?- Prepare階段時需要寫盤 2PC要求在Prepare時就要將資料持久化,只有這樣,恢復時才能提交已經記錄了Xid_log_event 的事務。- Commit階段時不需要寫盤 如果Commit階段不寫盤,會造成什麼結果呢?已經Cmmit了的事務,在恢復時的狀態可能是Prepared。由於恢復時,Prepared的事務可以透過Xid_log_event 來提交事務,所以在恢復後事務的狀態就是正確的。因此在Commit階段不需要寫盤。

    總的來說保證MySQL服務的CrashSafe需要寫兩次盤。在2PC的過程中,InnoDB只在prepare階段時,寫一次盤。Binlog在commit階段,會設定一個引數告訴InnoDB不要寫盤。這個引數是thd->durability_property= HA_IGNORE_DURABILITY;程式碼在sql/binlog.ccMYSQL_BIN_LOG:ordered_commit()中。

    - Prepare階段寫盤最佳化

    我們知道Binlog使用了Group Commit機制來減少IO,提高效能。Prepare有沒有可能做Group Commit呢?只要我們能保證任何事務的Redo Log是在它的Binlog Event寫入Binlog檔案前,被刷入了持久儲存就可以。最佳化後的做法是:1. 協調者準備階段(Prepare Phase) 設定thd->durability_property告訴InnoDB不寫盤。 告訴引擎做Prepare,InnoDB更改事務狀態。2. 協調者提交階段(Commit Phase) 2.1.1 獲取一組事務。 2.1.2 通知InnoDB將Redo Log寫入硬體儲存。 2.1.3 將這組事務的Binlog Event寫入Binlog檔案。 2.2 告訴引擎做commit。

    這個結合了Binlog Group Commit機制的改進對效能的提升還是很顯著的。而且這個改進是中國的社群使用者阿里雲的翟衛祥同學提出並提供的程式碼補丁。詳情可參考MySQL的bug頁面:【http://bugs.mysql.com/bug.php?id=73202】。

    參考程式碼:sql/binlog.cc中的MYSQL_BIN_LOG:process_flush_stage_queue()

    5 - 總結

    MySQL透過兩階段提交的方式來保證CrashSafe。CrashSafe需要Server層、Binlog和InnoDB的協同工作才能完成。由於DDL和MyISAM不支援事務性,因此沒辦法保證CrashSafe。

  • 2 # 程式設計師小葛

    我們有很多的手段保證資料的安全,但是要保證100%安全這是不可能的。畢竟在系統執行的過程中,伺服器可以出的問題千奇百怪,只能說盡可能的讓資料儘可能的出出現丟失。

    單純的保證資料庫本身的資料不丟失的話,最直接的方式就是透過建立主從庫,實現資料的熱備

    一般情況下,小的系統我們並不會考慮資料的熱備,一般只是在每天定時進行冷備而已,也就是設定一個定時器,然後到時間就同步資料。不過這樣做的話,一單系統的資料庫出現異常,那麼我們的資料就會回滾到上一個備份的時間點,影響範圍就會比較大。

    因此,對於資料量大一點的系統,我們就會進行主從庫的設定,不過通常情況下,我們做了主從庫都會做讀寫分離。

    現在不管是哪種資料庫,都提供了資料庫之間訂閱同步的機制。以Mysql為例,我們先設定一個Master主庫,然後在基於這個主庫設定1個到多個Salve從主,從庫透過在主庫的SQLLog日誌進行監聽,一旦有SQL執行,就會記錄一個二進位制的Log,從庫發現了這個Log,也會同時執行同樣的操作,這樣就實現了資料的熱備。

    但是,這種熱備的機制並不能100%保證資料不丟失。因為,我們在寫入主庫的時候如果出現異常,導致SQLLog還沒有記錄,那麼從庫是不可能有資料記錄的。當然,此後的資料不會有影響,因為這是從庫會變為主庫來記錄後續資料。同樣,如果主從庫一起宕機,那也只有涼涼。

    那麼,為了讓資料庫的資料更加安全,就需要把資料保證的機制提前,不能單純的依靠資料庫來實現,那麼我們可以加入佇列來試試。

    佇列並不是針對於資料的,佇列其實是用來保證訊息的安全穩定的。自然,當請求沒有被寫入到資料庫是,都是以訊息的形態存在,我們就可以考慮佇列來保證資料安全。

    在資料庫訪問層,或者再靠前,到服務層,我們都可以加入MQ,讓每一個請求都透過MQ來順序的處理,一但資料庫宕機了,MQ的執行就會失敗,這時,失敗的記錄會被儲存在MQ裡面,並不會丟失,一但資料庫重啟,我們可以再次執行MQ中的訊息,保證資料被成功的寫入到資料庫中。

    具體怎麼做呢?

    首先,我們在插入資料庫前,把插入的操作變為向佇列對新增一個訊息,然後,我們不同佇列建立不同的消費者,消費者對佇列的訊息進行執行,再往資料庫裡面插入資料。

    對於我們的服務層,我們只要把訊息插入到了佇列中,即視為成功,返回成功的訊息。這樣,雖然我們的資料處理會有一點點的延時,並且在事務的控制上難度會變大,可能需要建立補償機制,但是我們的資料安全就更加高了。

    這樣是不是就安全了呢?

    並不是的。訊息伺服器也可能會宕機,訊息也有可能出現丟失的情況,所以並不能保證100%的安全。

    如果我們還需要做的更好,我們還可以加上MongoDB來做日誌

    MongoDB是一個非關係型資料庫,在我們現在的系統中應用非常廣。最多的應用場景就是用來記錄日誌。那麼,日誌就是一個幫助我們避免訊息丟失的有效方式了。

    我們對服務層的每個請求報文,都用MongoDB記錄請求的報文,再在請求處理完成返回結果的時候,記錄一個訊息的處理結果(成功或失敗),這樣,我們就能夠很直觀的看到每天發生的請求,處理的請求情況了。

    當有服務處理失敗了,不管是資料庫的問題還是其他的問題,我們都可以對異常進行排查,然後再根據報文進行訊息的重推。這樣,我們的資料就會更加的安全了。

    當然,即使如此,也不可能100%安全的,我們只能說盡可能的讓系統更安全,只不過,安全的同時,付出的成功也是高昂的,我們需要來衡量是否有這個必要,當我們的系統確實足夠大,使用者量很大時,這麼處理是有價值的,否則,那就是一種資源的浪費。

  • 中秋節和大豐收的關聯?
  • 特朗普政府會不會在最後的期限內有更瘋狂的舉動,比如暗殺拜登?