作者简介:高鹏,笔名八怪。《深入理解MySQL主从原理》图书作者,同时运营个人公众号“MySQL学习”,持续分享遇到的有趣case以及代码解析!
仅仅作为自己的学习笔记,不做其他用途。原文中有大图,参考https://www.jianshu.com/p/d27f46975453
一、恢复的主要阶段
实际上节点的加入会经历几个主要的流程
- 发起一个Control_notification调用Gcs_xcom_control::retry_do_join连接到种子节点, 发送加入的请求给xcom线程,xom线程检测到有节点加入后,然后由xcom线程下发Global_view_notification给各个节点(包括加入节点)表示有节点申请加入。
- 各个节点收到Global_view_notification的消息后,获取本节点的一些信息,比如本节点执行的gtid信息、视图更新后的节点信息等。完成后发送一个信息给xcom线程 (Cargo_type::CT_INTERNAL_STATE_EXCHANGE)
- xcom线程下发Data_notification ,交由xcom engine线程处理,主要就是安装视图
- 对于现有节点而言,主要更新视图信息,将加入节点的状态更改为recovery
- 对于加入节点而言,也是先将自己的状态设置为recovery然后进行恢复。
- 加入节点恢复完成后,会发送online消息给各个节点,各个节点将节点状态状态 设置为online。
在加入的过程中视图版本会加1,由于view change event会封装在一个事务里面,因此 GTID也会加1,封装的过程主要处于Certification_handler::handle_event中对于view change event的处理。
二、加入阶段数据恢复的简图
这里我们主要看看加入节点的恢复流程,这里主要涉及到3个线程的协同工作,用图表示:
恢复简图.jpg
本处小结:
- 很显然由于group_replication_clone_threshold参数默认巨大,因此很难选择clone插件,除非GTID为空。
- 节点处于recovery状态是比较早的,如果其中流程出错,看到的视图信息依旧是recovery,比如选择donor节点恢复出现问题,这是常见的。
- 节点上线依赖参数group_replication_recovery_complete_at的设置,默认设置为TRANSACTIONS_APPLIED,如果有大量的事务应用,那么节点上线可能推迟,因此加入节点选择低峰期进行。
- incoming队列的缓存的是否如果过多,可能出现问题。因此加入节点选择低峰期。
三、代码部分笔记
<code>Gcs_xcom_control::process_control_message
视图版本+1
->Gcs_xcom_control::install_view
根据新的节点信息建立新的视图
->Plugin_gcs_events_handler::on_view_changed
->Plugin_gcs_events_handler::handle_joining_members
-> is_joining == ture
先学习这个分支
->状态在恢复当中 MEMBER_IN_RECOVERY
update_member_status(
new_view.get_joined_members(), Group_member_info::MEMBER_IN_RECOVERY,
Group_member_info::MEMBER_OFFLINE, Group_member_info::MEMBER_END);
->设置super read only 如果设置出错
enable_server_read_mode(PSESSION_DEDICATED_THREAD)
->如果是多主需要考虑自增是否符合要求
new_view.get_members().size() > auto_increment_increment
挂起applier通道----->applier通道处于挂起状态
->根据下发的view id进行View_change_packet的构建,并且推送给
applier通道的pipeline 进行处理 Applier_module.add_view_change_packet
View_change_packet *view_change_packet = new View_change_packet(view_id);
applier_module->add_view_change_packet(view_change_packet);
->进行恢复方式的选择
->是否使用clone
recovery_strategy = remote_clone_handler->check_clone_preconditions();
->这里根据参数来判定,到底是使用clone还是recovery的方式
->如果是clone则新建clone线程
remote_clone_handler->clone_server 先不考虑clone
->如果是recovery则新建recovery线程
recovery_module->start_recovery(new_view.get_group_id().get_group_id(),new_view.get_view_id().get_representation())
启动恢复线程,加入的是group id和view id
见下面的流程
进行恢复
Recovery_module::start_recovery
->初始化group_name和view_id
this->group_name = group_name;
recovery_state_transfer.initialize(rec_view_id);
->启动新线程
->launch_handler_thread
->handler->recovery_thread_handle()
实际调用为Recovery_module::recovery_thread_handle
->step0
->step1 applier_module->wait_for_applier_complete_suspension(&recovery_aborted); 等待applier线程的SQL线程应用完现有的relay log的事务,等待是通过收到的GTID 为最终等待的GTID,完成后进入下一步
->step2 如果只有一个节点,直接进入上线流程,跳过步骤3
->step3 进行recovery流程 recovery_state_transfer.state_transfer(stage_handler); ->establish_donor_connection 建立donor的连接 ->选择donor节点?这里看的和前期看的不同,需要确认 这里暴露了一些构建的方法,比如必须是online。 ->尝试连接donor节点,并且进行不同的报错 initialize_donor_connection ->进行恢复 start_recovery_donor_threads 这里带入了view_id,也就是进行恢复结束的位置为view change event的来到 ->donor_connection_interface.start_threads(true, true, &view_id, true);
->step4 ->applier_module->awake_applier_module(); 唤醒apllier通道继续应用缓存的event,这里前面挂起了applier通道,只是event和消息 缓存到了incoming队列。!!!! ->Recovery_module::wait_for_applier_module_recovery 等待上线,开启循环 while (!recovery_aborted && applier_monitoring) ->获取incoming 队列大小 applier_module->get_message_queue_size(); ->获取每秒恢复应用事务的数量 ->如果参数设置为 cerify,则上线条件为 ->恢复期间认证为0则继续进行处理 根据需要认证的事务总数和已经认证的事务数量进行统计,其中已经认证的事务数量在Certifier::certify的末尾进行增加。 ->如果参数设置为applier,则上线条件为 ->1.如果剩余需要过pipeline流程的事务,数量少于每秒恢复的事务数量,说明已经很少了,下一秒即可执行完成 ->2.如果incoming队列为0 、每秒恢复的事务数量为0 、applier的SQL线程处于空闲状态了 如果满足上面其中一个1个条件,进入等待,等待为接受的GTID全部应用完成。 ->如果不满足上面的条件则进行循环等待 否则进行睡眠 incoming队列大小有关,但最大睡眠 为 100*5000 微秒
->step5 ->发送节点恢复完成的消息 notify_group_recovery_end(); 这个消息是发给xcom的,因此是全员消息,应该是通知各个节点更改本节点状态为online
->step7 终止recovery线程
applier_module->add_view_change_packet(view_change_packet); xcom engine推送view change packet给 applier进行pipeline处理 Applier_module::applier_thread_handle -> packet_application_error = apply_view_change_packet((View_change_packet *)packet, fde_evt, cont); -> Applier_module::apply_view_change_packet -> Applier_module::inject_event_into_pipeline ->Event_cataloger::handle_event ->Event_handler::next ->Certification_handler::handle_event -> Certification_handler::extract_certification_info ->Certification_handler::log_view_change_event_in_order ->Certification_handler::inject_transactional_events 生成一个gtid事务,包含事务的所有event,并且会分配gtid ->Event_handler::next
参考:https://zhuanlan.zhihu.com/p/40627399