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<Node*, 8, true>_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<rpl_sidno, 8, true> _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<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的关联类结构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<HASH*, 8, true> 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

11、本节小结

学习完本节至少能够学习到:

  • 1、server_uuid是什么,如何生成,按照什么规则生成
  • 2、Gtid内部是如何表示
  • 3、 server_uuid和Gtid内部表示之间的联系
  • 4、 gtid_executed/gtid_owned/gtid_purged表示了什么具体对应哪个内存结构,当然这些将在后面的文章中多次提到,也会加深对它的了解。

如果有源码阅读能力的朋友可以按照这个框架继续深入学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容