Mysql 5.7 Gtid內部學習(二) Gtid相關內部資料結構

gaopengtttt發表於2017-12-18

Mysql 5.7 Gtid內部學習(二) Gtid相關內部資料結構

簡書地址:



1、 Gtid基本格式

  • 單個Gtid:
 e859a28b-b66d-11e7-8371-000c291f347d:1 

前一部分是server_uuid,後面一部分是執行事物的唯一標誌,通常是自增的。內部使用Gtid這種資料結構表示,後面會描述。

  • 區間Gtid:
e859a28b-b66d-11e7-8371-000c291f347d:1-5 

前一部分是server_uuid,後面一部分是執行事物的唯一標誌集合,在內部使用Gtid_set中某個Sidno對應的Interval節點表示,後面會描述。

2、server_uuid的生成

既然說到了server_uuid這裡就開始討論server_uuid的生成。server_uuid實際上是一個32位元組+1位元組(/0)的字串。Mysql啟動的時候會呼叫init_server_auto_options() 讀取auto.cnf檔案。如果沒有讀取到則呼叫generate_server_uuid()呼叫生成一個server_id。
實際上在這個函式里面會看到server_uuid至少和下面部分有關:

  • 1、mysql啟動時間
  • 2、執行緒Lwp有關
  • 3、一個隨機的記憶體地址有關

請看程式碼片段:

 const time_t save_server_start_time= server_start_time; //獲取Mysql 啟動時間
  server_start_time+= ((ulonglong)current_pid << 48) + current_pid;//加入Lwp號運算
  thd->status_var.bytes_sent= (ulonglong)thd;//這是一個記憶體指標

  lex_start(thd);
  func_uuid= new (thd->mem_root) Item_func_uuid();
  func_uuid->fixed= 1;
  func_uuid->val_str(&uuid);     //這個函式里面有具體的運算過程 

獲得這些資訊後會進入Item_func_uuid::val_str做運算返回,有興趣的朋友可以深入看一下,最終會生成一個server_uuid並且複製到實際的server_uuid中如下:

strncpy(server_uuid, uuid.c_ptr(), UUID_LENGTH); 

呼叫棧幀:

#0  init_server_auto_options () at /root/mysql5.7.14/percona-server-5.7.14-7/sql/mysqld.cc:3810
#1  0x0000000000ec625e in mysqld_main (argc=97, argv=0x2e9af08) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/mysqld.cc:4962
#2  0x0000000000ebd604 in main (argc=10, argv=0x7fffffffe458) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/main.cc:25 

3、server_uuid的內部表示binary_log::Uuid

binary_log::Uuid是server_uuid的內部表示實際上核心就是一個16位元組的記憶體空間,如下:

 /** The number of bytes in the data of a Uuid. */
  static const size_t BYTE_LENGTH= 16;
  /** The data for this Uuid. */
  unsigned char bytes[BYTE_LENGTH]; 

server_uuid和binary_log::Uuid之間可以互相轉換,在Sid_map中binary_log::Uuid表示的server_uuid實際上就是其sid。

4、類結構Gtid

本結構是單個Gtid的內部表示其核心元素包括:

 /// SIDNO of this Gtid.
  rpl_sidno sidno;
  /// GNO of this Gtid.
  rpl_gno gno; 

其中gno就是我們說的事物唯一標誌,而sidno其實是server_uuid的內部表示binary_log::Uuid (sid)透過hash演算法得出的一個查詢表中的值。參考函式Sid_map::add_sid本函式則根據binary_log::Uuid (sid)返回一個sidno。

5、類結構Sid_map

既然說到了hash演算法那麼需要一個內部結構來儲存這種整個hash查詢表,在Mysql中使用Sid_map來作為這樣一個結構,其中包含一個可變陣列和一個hash查詢表其作用用來已經在註釋裡面給出。全域性只有一個Sid_map。會在Gtid模組初始化的時候分配記憶體。Sid_map核心元素如下:

/// Read-write lock that protects updates to the number of SIDNOs.
  mutable Checkable_rwlock *sid_lock;

  /**
    Array that maps SIDNO to SID; the element at index N points to a
    Node with SIDNO N-1.
  */
  Prealloced_array_sidno_to_sid; //因為sidno是一個連續的數值那麼更具sidno找到sid只需要簡單的做
                                                 //陣列查詢即可這裡將node指標存入
  /**
    Hash that maps SID to SIDNO.  The keys in this array are of type
    rpl_sid.
  */
  HASH _sid_to_sidno;                           //因為sid是一個資料結構其核心為bytes關鍵值儲存了16位元組根據server_uuid
                                                //轉換而來的無規律記憶體空間,需要使用hash查詢錶快速定位
  /**
    Array that maps numbers in the interval [0, get_max_sidno()-1] to
    SIDNOs, in order of increasing SID.

    @see Sid_map::get_sorted_sidno.
  */
  Prealloced_array _sorted;//額外的一個關於sidno的陣列,具體作用未知 

這裡在看一下可變陣列中的指標元素Node的型別:

 struct Node
  {
    rpl_sidno sidno; //sid hash no
    rpl_sid sid; //sid
  }; 

其實他就是一個sidno和sid的對應。

6、類結構Gtid_set

本結構是一個關於某種型別Gtid總的集合,比如我們熟知的有execute_gtid集合,purge_gtid集合。我們知道在一個execute_gtid集合中可能包含多個資料庫的Gtid也就是多個sidno同時存在的情況,並且可能存在某個資料庫的Gtid出現區間的情況如下:

| gtid_executed                    | 
3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
da267088-9c22-11e7-ab56-5254008768e3:1-34 | 

這裡3558703b-de63-11e7-91c3-5254008768e3的Gno出現了多個區間:

  • 1-6
  • 20-30

那麼這種情況下內部表示應該是陣列加區間連結串列的方式,當然Mysql內部正是這樣實現的。我們來看核心元素:

/**
    Array where the N'th element contains the head pointer to the
    intervals of SIDNO N+1.
  */
  Prealloced_array m_intervals;//每個sidno 包含一個Interval 單項鍊表,由next指標連線 這就完成了比如分割GTID的問題
  /// Linked list of free intervals.
  Interval *free_intervals;  //空閒的interval連線在這個連結串列上
  /// Linked list of chunks.
  Interval_chunk *chunks; //全域性的一個Interval 連結串列 所有的Interval空間都連線在上面 

7、Gtid_set的關聯類結構Interval

它正是前面說到的表示Gtid區間如下:

  • da267088-9c22-11e7-ab56-5254008768e3:1-34

他的內部類就是Interval類結構我們看一下核心元素就懂了:

 /// The first GNO of this interval.
    rpl_gno start;
    /// The first GNO after this interval.
    rpl_gno end;
    /// Pointer to next interval in list.
    Interval *next; 

非常簡單起始Gno加一個next指標,標示了一個區間。

8、類結構Gtid_state

本結構也是在資料庫啟動的時候和Sid_map一起進行初始化,也是一個全域性的變數。
我們熟知的引數幾個引數如下:

  • gtid_executed
  • gtid_owned
  • gtid_purged

都來自於次,當然除了以上的我們常見的還包含了其他一些核心元素我們來具體看看:

/// The Sid_map used by this Gtid_state.
  mutable Sid_map *sid_map; //使用sid_map
  /**
    The set of GTIDs that existed in some previously purged binary log.
    This is always a subset of executed_gtids.
  */
  Gtid_set lost_gtids; //對應gtid_purged引數,這個引數一般由Mysql自動維護除非手動設定了gtid_purged引數
  /*
    The set of GTIDs that has been executed and
    stored into gtid_executed table.
  */
  Gtid_set executed_gtids; //對應gtid_executed引數,這個引數一般由Mysql主動維護
  /*
    The set of GTIDs that exists only in gtid_executed table, not in
    binlog files.
  */
  Gtid_set gtids_only_in_table;
//正常來講對於主庫這個集合始終為空因為主庫不可能存在只在mysql.gtid_executed表而不再binlog中的gtid,但是從庫則必須開啟log_slave_updates和binlog才會達到這個效果,
//否則binlog不包含relay的Gtid的只能包含在mysql.gtid_executed表中,那麼這種情況下Gtid_set gtids_only_in_table是始終存在的。具體後面還會解釋。
  /* The previous GTIDs in the last binlog. */
  Gtid_set previous_gtids_logged;//包含上一個binlog已經執行的所有的在binlog的Gtid
  /// The set of GTIDs that are owned by some thread.
  Owned_gtids owned_gtids;//當前所有執行緒擁有的全部Gtid集合
  /// The SIDNO for this server.
  rpl_sidno server_sidno;//就是伺服器server_uuid對應sid hash出來的sidno 

9、類結構 Owned_gtids

這個結構包含當前執行緒所包含的所有正在持有的Gtid集合,為事物分配Gtid 會先將這個Gtid和執行緒號加入到給Owned_gtids然後將執行緒的thd->owned_gtid指向這個Gtid。
參考函式Gtid_state::acquire_ownership,在commit的最後階段會將這個Gtid從Owned_gtids中移除參考函式Owned_gtids::remove_gtid 並且將他加入到Gtid_state::executed_gtids中。

這個過程會在後面進行描述。其核心元素包括:

 /// Growable array of hashes.
  Prealloced_array sidno_to_hash; 

這樣一個每個sidno都會有hash結構其hash的內容則是:

 struct Node
  {
    /// GNO of the group.
    rpl_gno gno;
    /// Owner of the group.
    my_thread_id owner;
  }; 

這樣一個結構體,我們看到其中包含了gno和執行緒ID。

10、類結構Gtid_table_persistor

本結構主要是包含一些操作mysql.gtid_executed表的函式介面
主要包含:

  • Insert the gtid into table.
    int save(THD *thd, const Gtid *gtid);
  • Insert the gtid set into table.
    int save(const Gtid_set *gtid_set);
  • Delete all rows from the table.
    int reset(THD *thd);
  • Fetch gtids from gtid_executed table and store them into gtid_executed set.
    int fetch_gtids(Gtid_set *gtid_set);
  • Compress the gtid_executed table completely by employing one or more transactions.
    int compress(THD *thd);
  • Write a gtid interval into the gtid_executed table.
    int write_row(TABLE *table, const char *sid,rpl_gno gno_start, rpl_gno gno_end);
  • Update a gtid interval in the gtid_executed table.
    int update_row(TABLE *table, const char *sid,rpl_gno gno_start, rpl_gno new_gno_end);
  • Delete all rows in the gtid_executed table.
    int delete_all(TABLE *table);
    這些方法也是確定什麼時候讀/寫mysql.gtid_executed的斷點。

10、內部結構圖示

為了能夠用圖的方式解釋這些類結構之間的關係,我修改mysql.gtid_executed表和auto.cnf構造出了這種有區間的Gtid案例,同時在原始碼處增加列印程式碼將啟動完成後的get_executed_gtids/get_lost_gtids/get_gtids_only_in_table/get_previous_gtids_logged輸出到了日誌。但是線上上情況下很難見到這種有區間的Gtid。
假設某一時刻我們資料庫啟動後各種Gtid如下():

  • 2017-12-12T04:10:42.768153Z 0 [Note] gtid_state->get_executed_gtids:
    Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-35,
    da267088-9c22-11e7-ab56-5254008768e3:1-34
  • 2017-12-12T04:10:42.768191Z 0 [Note] gtid_state->get_lost_gtids:
    Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
    da267088-9c22-11e7-ab56-5254008768e3:1-34
  • 2017-12-12T04:10:42.768226Z 0 [Note] gtid_state->get_gtids_only_in_table:
    Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
    da267088-9c22-11e7-ab56-5254008768e3:1-34
  • 2017-12-12T04:10:42.768260Z 0 [Note] gtid_state->get_previous_gtids_logged:
    Gtid have:3558703b-de63-11e7-91c3-5254008768e3:7-19:31-35

啟動後我們馬上執行了一個事物,這個事物正處於ordered_commit的flush階段由Gtid_state::acquire_ownership獲得了一個Gtid那麼它正在Owned_gtids中,所以這個時候的圖如下:

未命名檔案.png
未命名檔案.png

11、本節小結

學習完本節至少能夠學習到:

  • 1、server_uuid是什麼,如何生成,按照什麼規則生成
  • 2、Gtid內部是如何表示
  • 3、 server_uuid和Gtid內部表示之間的聯絡
  • 4、 gtid_executed/gtid_owned/gtid_purged表示了什麼具體對應哪個記憶體結構,當然這些將在後面的文章中多次提到,也會加深對它的瞭解。

如果有原始碼閱讀能力的朋友可以按照這個框架繼續深入學習。

作者微信:


微信.jpg

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2148835/,如需轉載,請註明出處,否則將追究法律責任。

相關文章