1拟杉、 Gtid基本格式
- 單個Gtid:
e859a28b-b66d-11e7-8371-000c291f347d:1
前一部分是server_uuid庄涡,后面一部分是執(zhí)行事務(wù)的唯一標(biāo)志,通常是自增的搬设。內(nèi)部使用Gtid這種數(shù)據(jù)結(jié)構(gòu)表示穴店,后面會描述。
- 區(qū)間Gtid:
e859a28b-b66d-11e7-8371-000c291f347d:1-5
前一部分是server_uuid拿穴,后面一部分是執(zhí)行事務(wù)的唯一標(biāo)志集合泣洞,在內(nèi)部使用Gtid_set中某個Sidno對應(yīng)的Interval節(jié)點表示,后面會描述默色。
2球凰、server_uuid的生成
既然說到了server_uuid這里就開始討論server_uuid的生成。server_uuid實際上是一個32字節(jié)+1字節(jié)(/0)的字符串腿宰。Mysql啟動的時候會調(diào)用init_server_auto_options() 讀取auto.cnf文件呕诉。如果沒有讀取到則調(diào)用generate_server_uuid()調(diào)用生成一個server_id。
實際上在這個函數(shù)里面會看到server_uuid至少和下面部分有關(guān):
- 1吃度、mysql啟動時間
- 2义钉、線程Lwp有關(guān)
- 3、一個隨機(jī)的內(nèi)存地址有關(guān)
請看代碼片段:
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;//這是一個內(nèi)存指針
lex_start(thd);
func_uuid= new (thd->mem_root) Item_func_uuid();
func_uuid->fixed= 1;
func_uuid->val_str(&uuid); //這個函數(shù)里面有具體的運算過程
獲得這些信息后會進(jìn)入Item_func_uuid::val_str做運算返回规肴,有興趣的朋友可以深入看一下,最終會生成一個server_uuid并且拷貝到實際的server_uuid中如下:
strncpy(server_uuid, uuid.c_ptr(), UUID_LENGTH);
調(diào)用棧幀:
#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的內(nèi)部表示binary_log::Uuid
binary_log::Uuid是server_uuid的內(nèi)部表示實際上核心就是一個16字節(jié)的內(nèi)存空間拖刃,如下:
/** 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之間可以互相轉(zhuǎn)換,在Sid_map中binary_log::Uuid表示的server_uuid實際上就是其sid贪绘。
4兑牡、類結(jié)構(gòu)Gtid
本結(jié)構(gòu)是單個Gtid的內(nèi)部表示其核心元素包括:
/// SIDNO of this Gtid.
rpl_sidno sidno;
/// GNO of this Gtid.
rpl_gno gno;
其中g(shù)no就是我們說的事務(wù)唯一標(biāo)志,而sidno其實是server_uuid的內(nèi)部表示binary_log::Uuid (sid)通過hash算法得出的一個查找表中的值税灌。參考函數(shù)Sid_map::add_sid本函數(shù)則根據(jù)binary_log::Uuid (sid)返回一個sidno均函。
5、類結(jié)構(gòu)Sid_map
既然說到了hash算法那么需要一個內(nèi)部結(jié)構(gòu)來存儲這種整個hash查找表菱涤,在Mysql中使用Sid_map來作為這樣一個結(jié)構(gòu)苞也,其中包含一個可變數(shù)組和一個hash查找表其作用用來已經(jīng)在注釋里面給出。全局只有一個Sid_map粘秆。會在Gtid模塊初始化的時候分配內(nèi)存如迟。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<Node*, 8, true>_sidno_to_sid; //因為sidno是一個連續(xù)的數(shù)值那么更具sidno找到sid只需要簡單的做
//數(shù)組查找即可這里將node指針存入
/**
Hash that maps SID to SIDNO. The keys in this array are of type
rpl_sid.
*/
HASH _sid_to_sidno; //因為sid是一個數(shù)據(jù)結(jié)構(gòu)其核心為bytes關(guān)鍵值存儲了16字節(jié)根據(jù)server_uuid
//轉(zhuǎn)換而來的無規(guī)律內(nèi)存空間,需要使用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<rpl_sidno, 8, true> _sorted;//額外的一個關(guān)于sidno的數(shù)組,具體作用未知
這里在看一下可變數(shù)組中的指針元素Node的類型:
struct Node
{
rpl_sidno sidno; //sid hash no
rpl_sid sid; //sid
};
其實他就是一個sidno和sid的對應(yīng)殷勘。
6此再、類結(jié)構(gòu)Gtid_set
本結(jié)構(gòu)是一個關(guān)于某種類型Gtid總的集合,比如我們熟知的有execute_gtid集合玲销,purge_gtid集合输拇。我們知道在一個execute_gtid集合中可能包含多個數(shù)據(jù)庫的Gtid也就是多個sidno同時存在的情況,并且可能存在某個數(shù)據(jù)庫的Gtid出現(xiàn)區(qū)間的情況如下:
| gtid_executed |
3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
da267088-9c22-11e7-ab56-5254008768e3:1-34 |
這里3558703b-de63-11e7-91c3-5254008768e3的Gno出現(xiàn)了多個區(qū)間:
- 1-6
- 20-30
那么這種情況下內(nèi)部表示應(yīng)該是數(shù)組加區(qū)間鏈表的方式贤斜,當(dāng)然Mysql內(nèi)部正是這樣實現(xiàn)的策吠。我們來看核心元素:
/**
Array where the N'th element contains the head pointer to the
intervals of SIDNO N+1.
*/
Prealloced_array<Interval*, 8, true> 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的關(guān)聯(lián)類結(jié)構(gòu)Interval
它正是前面說到的表示Gtid區(qū)間如下:
- da267088-9c22-11e7-ab56-5254008768e3:1-34
他的內(nèi)部類就是Interval類結(jié)構(gòu)我們看一下核心元素就懂了:
/// 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指針奴曙,標(biāo)示了一個區(qū)間。
8草讶、類結(jié)構(gòu)Gtid_state
本結(jié)構(gòu)也是在數(shù)據(jù)庫啟動的時候和Sid_map一起進(jìn)行初始化洽糟,也是一個全局的變量。
我們熟知的參數(shù)幾個參數(shù)如下:
- gtid_executed
- gtid_owned
- gtid_purged
都來自于次堕战,當(dāng)然除了以上的我們常見的還包含了其他一些核心元素我們來具體看看:
/// 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; //對應(yīng)gtid_purged參數(shù)坤溃,這個參數(shù)一般由Mysql自動維護(hù)除非手動設(shè)置了gtid_purged參數(shù)
/*
The set of GTIDs that has been executed and
stored into gtid_executed table.
*/
Gtid_set executed_gtids; //對應(yīng)gtid_executed參數(shù),這個參數(shù)一般由Mysql主動維護(hù)
/*
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才會達(dá)到這個效果薪介,
//否則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已經(jīng)執(zhí)行的所有的在binlog的Gtid
/// The set of GTIDs that are owned by some thread.
Owned_gtids owned_gtids;//當(dāng)前所有線程擁有的全部Gtid集合
/// The SIDNO for this server.
rpl_sidno server_sidno;//就是服務(wù)器server_uuid對應(yīng)sid hash出來的sidno
9、類結(jié)構(gòu) Owned_gtids
這個結(jié)構(gòu)包含當(dāng)前線程所包含的所有正在持有的Gtid集合,為事務(wù)分配Gtid 會先將這個Gtid和線程號加入到給Owned_gtids然后將線程的thd->owned_gtid指向這個Gtid缀旁。
參考函數(shù)Gtid_state::acquire_ownership记劈,在commit的最后階段會將這個Gtid從Owned_gtids中移除參考函數(shù)Owned_gtids::remove_gtid 并且將他加入到Gtid_state::executed_gtids中。
這個過程會在后面進(jìn)行描述并巍。其核心元素包括:
/// Growable array of hashes.
Prealloced_array<HASH*, 8, true> sidno_to_hash;
這樣一個每個sidno都會有hash結(jié)構(gòu)其hash的內(nèi)容則是:
struct Node
{
/// GNO of the group.
rpl_gno gno;
/// Owner of the group.
my_thread_id owner;
};
這樣一個結(jié)構(gòu)體目木,我們看到其中包含了gno和線程ID。
10懊渡、類結(jié)構(gòu)Gtid_table_persistor
本結(jié)構(gòu)主要是包含一些操作mysql.gtid_executed表的函數(shù)接口
主要包含:
- 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、內(nèi)部結(jié)構(gòu)圖示
為了能夠用圖的方式解釋這些類結(jié)構(gòu)之間的關(guān)系剃执,我修改mysql.gtid_executed表和auto.cnf構(gòu)造出了這種有區(qū)間的Gtid案例誓禁,同時在源碼處增加打印代碼將啟動完成后的get_executed_gtids/get_lost_gtids/get_gtids_only_in_table/get_previous_gtids_logged輸出到了日志。但是在線上情況下很難見到這種有區(qū)間的Gtid忠蝗。
假設(shè)某一時刻我們數(shù)據(jù)庫啟動后各種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
啟動后我們馬上執(zhí)行了一個事務(wù)现横,這個事務(wù)正處于ordered_commit的flush階段由Gtid_state::acquire_ownership獲得了一個Gtid那么它正在Owned_gtids中,所以這個時候的圖如下:
11、本節(jié)小結(jié)
學(xué)習(xí)完本節(jié)至少能夠?qū)W習(xí)到:
- 1戒祠、server_uuid是什么骇两,如何生成,按照什么規(guī)則生成
- 2姜盈、Gtid內(nèi)部是如何表示
- 3低千、 server_uuid和Gtid內(nèi)部表示之間的聯(lián)系
- 4、 gtid_executed/gtid_owned/gtid_purged表示了什么具體對應(yīng)哪個內(nèi)存結(jié)構(gòu)馏颂,當(dāng)然這些將在后面的文章中多次提到示血,也會加深對它的了解。
如果有源碼閱讀能力的朋友可以按照這個框架繼續(xù)深入學(xué)習(xí)救拉。