mysql group relication 原理解析

该内容是作者在全球运维技术大会上分享主题中的部分内容。提供给想了解MGR的读者参考!


        为了让解析mgr过程变得相对有条理,我们从跟踪一个事务在mgr的执行流程开始。首先从对执行sql的用户线程的解析开始:


图片


    在mgr节点中,用户线程的执行流程跟在普通mysql节点的执行流程,几乎是没有区别的。
    唯一的区别就是,在事务的binlog提交阶段,增加了一个函数,这个函数的名称叫
    group_replication_trans_before_commit, 
    这个函数作用就是将事务日志写入本地的通道,
    然后就等待mgr将事务日志通过paxos协议进行全局扩散,
    并利用事物日志进行事务间的冲突检查。当mgr完成冲突检查之后,
    就会将用户线程唤醒。用户线程被唤醒以后呢,就会进行根据冲突检查的结果,
    来决定继续提交这个事务还是回滚这个事务。
    
    下面我们来看一下,在MGR内部,相关线程是怎么对事务来进行处理的。

        前面讲到,用户(执行sql)线程将事务日志写入到本地通道之后, 后续对这个事务日志的处理,由MGR的线程来执行。 首先我们来看这个主任务线程xcom_taskmain2线程, 该线程的作用就是将本地通道的消息通过paxos协议进行全局(整个集群范围)一致性广播, 广播完毕之后, 就调用executed_msg函数执行消息(包含事务日志),但一个消息被执行的前提是: 这个消息的类型不是一个no_op类型的消息, 也就是广播成功的消息。 广播成功的消息,也就是被大多数节点所接受的消息。但是,在此步骤,executed_msg函数对消息的执行,并不是真正的执行,而是将消息丢到队列m_notification_queque里面。

        接下来,如上面的图片所示,对这个消息的处理,由process_notification线程来完成的,也就是处理消息的线程。 而这个线程的作用, 类似一个消息中转站,将m_notification_queque队列里面的消息逐个取出,然后根据消息类型的不同,调用不同的函数进行处理。我们来看on_message_received 函数的具体代码:



将从m_notification_queue队列里面取出的消息,根据消息的类型,调用不同的函数进行处理。如果是一个事务类型的消息,也就是CT_TRANSACTION_MESSAGE类型的消息,则调用函数handle_transaction_message来进行处理。下面这个图就是该函数的处理过程。

该函数最终将消息丢入应用模块(applier_module)的队列里面, 队列的名称为incoming.  将包含事务日志的消息丢入该队列后, process_notification的线程的任务完成。

         接下来的消息处理由applier线程来完成。流程如下图


       applier线程通过调用函数applier_thread_handle函数来处理incoming队列的消息,处理过程见上面的流程图或者见下面的代码段:

图片

该函数的第一行是获取incoming队列的头部第一个消息,然后根据消息/packet不同的类型,调用不同的函数来进行处理。如果是一个DATA_PACKET_TYPE类型的消息,则调用app_data_packet函数进行处理。 因为事务的binlog日志里面包含的是一行一行的记录,所以是一个数据类型的消息,因此会调用该函数处理。 而apply_data_packet函数,后面会调用冲突检测模块的函数,也就是

Certifier::certify函数验证冲突,验证从不同节点发起的事务之间是否有冲突(同一节点发起的事务肯定不冲突),验证冲突的方法接下来介绍。验证完毕之后,将调用releaseTicket函数来唤醒等待冲突验证结果的用户线程。用户线程被唤醒之后,根据冲突验证的结果来决定继续提交事务还是回滚事务。

        这就是事务在MGR节点中的处理流程。 但事务跟事务之间是否冲突的部分,我们还没有详细介绍。下面我们将介绍这部分的内容:       

在将事务冲突验证之前,先来看一下在mgr中,事务日志的内容。它包含两个部分,一部分是普通的mysql主从复制也会使用到的binlog,另外一部分就是专门为mgr所添加的,用来验证事务冲突的内容。它包含两个部分,第一个部分就是事务所涉及的主键,另外一个部分呢,就是事务执行时,当时该节点的数据版本,这个数据版本是用当时该节点已经执行过的所有事务的gtid集合来表示,也就是当时的gtid_executed的值,用来表示该节点当前已经执行那些事务,用这个值来表示事务执行时所基于的数据版本。利用这些内容呢,就能知道事务跟事务之间,它们是否对同一条记录进行操作,以及它们之间执行的先后顺序是否正确,由此此可以用这部分内容来检测事务跟事务之间是否有冲突。


       左上角就是前面讲到的applier模块源代码中的incoming队列,里面包含待验证的事务消息。 将队列首部,包含事务日志的消息进行验证:将用于冲突检测的内容提出,得到事务所操作的记录的主键,以及事务执行时当时的数据快照版本。然后这些主键去write set 里面去查找,看是否命中。 如果没有命中,则该事务跟前面的事务不冲突,因为它们没有操作相同的记录,所以肯定不冲突。 如果命中,则比较数据版本。 如果是包含关系,则不冲突。 为什么说包含关系不冲突呢? 如果是包含关系,则说明该事务是在前面的事务执行后的数据版本上执行的,它们之间有完全正常的先后顺序,所以是不冲突的。否则就是冲突的。 

        事务冲突验证完成之后,如果通过,则就会将用于冲突检测的内容往write set 里面丢。大家可能有个疑问,就是当事务的冲突验证完成之后,就会往write set 里面增加记录,因此可能会担心write set会越来越大。但是mgr后台有任务会及时去清理这个write set 里面不需要的记录。 哪里记录是可以被清理掉的呢? 就是该记录所代表的事务已经在所有的节点都执行完,而且不跟已经进入队列里面的事务产生冲突,那么这些记录都是可以被删除的。 因为后续发起事务,它的数据版本都已经比执行完成的事务的数据版本大,它们是包含关系, 所以是绝对不冲突的,因此该记录也是失去了用于冲突检测的作用,可以被删除。


    我们来看一下,具体的冲突检测的例子:


图片

事务A,B,C,分别从A,B,C三个节点同时发起,且事务执行时的数据版本(节点之间没有数据延迟) 也一样, 但它们各自操作不同的记录,所以不冲突。

图片

         这个例子跟第一个例子的执行环境一样,唯一的区别就是事务A与事务B操作了同一条记录。 所以事务A与B产生了冲突。前面讲过,MGR中的paxos协议实现的就是消息的全局一致性广播,对并发的包含事务日志的消息进行全局统一的排序,事务A与事务B,虽然并发执行,但在全局还是有一个统一的排序。 假设事务A有幸排在前面,那么事务A在冲突检测时,前面还没有事务跟它冲突,冲突检测完成之后,将事务A用于冲突检测的内容丢入write set. 然后再进行事务B冲突检测的时候,在write set 集合里面去查找跟它相冲突的事务,就会发现事务A跟它冲突,因此事务B冲突检测失败,事务B接下来在innodb存储引擎中回滚。


       

图片

        第三个示列:三个事务貌似同时执行(但是否真正同时执行未知),但它们执行时基于的数据版本不一样。虽然事务A跟事务B操作了相同的记录,但是否冲突,取决于实际情况。如果事务真正执行的顺序是事务A 先执行,也就是事务A排序在前面,事务B在后面,那么它们之间是不冲突的,因为事务B的数据版本包含事务A的数据版本,它是在事务A提交完毕后的数据版本上执行的,它们的先后顺序完全正常。 假如是事务B先执行,事务A后执行,那么就会冲突。 大家可能有个疑问?为什么事务A后执行,它的数据版本反而小,这是因为A节点有延迟,有事务在其他节点已执行完成,但还没有在A节点relay完成,所以它即使后执行,但它的事务版本要低。


        以上是事务在mgr中的执行流程,以及如何对它做事务冲突检测的。 分享出来,对大家在理解mgr的原理过程中提供参考。


猜你喜欢

转载自blog.51cto.com/15057824/2648664
今日推荐