这段时间在准备17号技术大会的技术分享,第一次在那么大的场合进行分享,还是有点紧张的,所以做了不少准备。但是说实话,双主解决方案只要没有涉及到冲突解决的,实现就那么回事,没有什么太大的技术含量。在方案实现的调研中,其实也大体的看了一下MGR,但是没有太深入的了解。这次把实现了MGR的代码下了下来,准备好好研究下它的实现。本文中的代码,取自5.7.24版本的MySQL。
MGR可以说是未来理想的多节点高可用方案,虽然目前它还有各种不足。
传统的双主方案主要是通过主从复制来实现的,所以多点写入理论上不能提高总集群的效率,因为一个节点的写入,最终会以binlog
的方式在另一个节点中回放,写性能并没有被扩展。MGR通过另一种方案来实现多节点复制,但是它依旧不能提高集群的写入性能,原因是一个节点的写入,依旧会被其它节点应用并写入。所以MGR这个方案,主要是提高集群的可用性,不是用来提升整个集群的写性能的,假设写热点很几种,在不同的Primary节点上写,性能将很差。下面大体的聊聊它的实现理论原理。
首先,MGR的冲突解决主要是基于事务执行时的版本号、操作的库表信息、操作的行主键这三个主要部分构成,而不是分布式一致性协议Paxos
来解决的。MGR使用的一致性协议是改进的Paxos
,称之为XCom
,主要特性为支持多点写入,并且所有的请求在全局顺序唯一,也就是无论多个Primary节点如何并发的写,在全局总有一个有序的执行顺序,而这个顺序在所有的集群节点中都是一致的。这也就保证了所有的事务都以相同的顺序在不同的集群节点中回放。
借助上述提到的冲突解决方案,可以实现假设一个事务在一个集群节点中提交成功,那么其它节点也必然提交成功;假设一个事务在一个节点中出现冲突需要回滚,那么其它节点也必然会回滚。
所以在MGR的实现中,要分清楚XCom
与冲突检测,XCom
并不负责冲突检测,只是为整个集群提供可靠的原子性广播。
MGR主要以插件的形式存在于MySQL中。插件的意思是,它的存在,并不影响MySQL原先的事务提交逻辑,它只是在几个关键流程点上,让MySQL去回调它的代码,可以说是很常见的观察者模式,将MGR与MySQL的耦合性降为最低。
我们首先看看MGR以插件形式注册到MySQL的部分。
MGR的主要几个监听MySQL事件的函数分别为 (observer_trans.h):
/*
Transaction lifecycle events observers.
*/
int group_replication_trans_before_dml(Trans_param *param, int& out);
int group_replication_trans_before_commit(Trans_param *param);
int group_replication_trans_before_rollback(Trans_param *param);
int group_replication_trans_after_commit(Trans_param *param);
int group_replication_trans_after_rollback(Trans_param *param);
最终MySQL只会回调MGR中的这几个函数。
首先,在plugin.cc中,我们可以看到对于MGR插件的声明:
mysql_declare_plugin(group_replication_plugin)
{
MYSQL_GROUP_REPLICATION_PLUGIN,
&group_replication_descriptor,
group_replication_plugin_name,
"ORACLE",
"Group Replication (1.0.0)", /* Plugin name with full version*/
PLUGIN_LICENSE_GPL,
plugin_group_replication_init, /* Plugin Init */
plugin_group_replication_deinit, /* Plugin Deinit */
0x0100, /* Plugin Version: major.minor */
group_replication_status_vars, /* status variables */
group_replication_system_vars, /* system variables */
NULL, /* config options */
0, /* flags */
}
mysql_declare_plugin_end;
这个插件信息里,主要有插件名称、版本等信息,最主要的是插件的初始化与卸载函数,分别是
plugin_group_replication_init
plugin_group_replication_deinit
那么上述提到的MGR的几个回调函数,是在哪里被注册进MySQL的呢?这几个函数主要是在插件初始化的时候被注册的,我们看一下plugin_group_replication_init
的实现:
int plugin_group_replication_init(MYSQL_PLUGIN plugin_info)
{
...
if(register_server_state_observer(&server_state_observer,
(void *)plugin_info_ptr))
{
/* purecov: begin inspected */
log_message(MY_ERROR_LEVEL,
"Failure when registering the server state observers");
return 1;
/* purecov: end */
}
if (register_trans_observer(&trans_observer, (void *)plugin_info_ptr))
{
/* purecov: begin inspected */
log_message(MY_ERROR_LEVEL,
"Failure when registering the transactions state observers");
return 1;
/* purecov: end */
}
if (register_binlog_transmit_observer(&binlog_transmit_observer,
(void *)plugin_info_ptr))
{
/* purecov: begin inspected */
log_message(MY_ERROR_LEVEL,
"Failure when registering the binlog state observers");
return 1;
/* purecov: end */
}
}
上面主要是注册各个观察者的,我们主要是看一下事务相关注册的函数register_trans_observer
,这个函数在rpl_handler.cc中,我们看一下这个函数的实现。
int register_trans_observer(Trans_observer *observer, void *p)
{
return transaction_delegate->add_observer(observer, (st_plugin_int *)p);
}
非常简单,在一个事务委托这儿,将observer注册上,那么这个observer是什么呢 (observer_trans.cc):
Trans_observer trans_observer = {
sizeof(Trans_observer),
group_replication_trans_before_dml,
group_replication_trans_before_commit,
group_replication_trans_before_rollback,
group_replication_trans_after_commit,
group_replication_trans_after_rollback,
};
Trans_observer
是一个函数指针集合的结构体,该observer就是所有需要注册入MySQL的回调函数的集合,于是register_trans_observer
则一次性的将所有的函数打包注册到了事务的委托链当中了。transaction_delegate
中的所有回调会在相应的流程点被调用。
Delegate是所有委托的基类,主要管理一串观察者,用于串行依次地调用观察者们相应的函数。
插件的注册流程大概走完了,现在我们来看看,transaction_delegate
中的观察者们何时被调用。我们这儿只关心在事务真正提交前,也就是group_replication_trans_before_commit
何时被调用,MGR大多的处理都在这里。这个提交主要是在MYSQL_BIN_LOG::commit
中 (binlog.cc),代码块为:
if (RUN_HOOK(transaction,
before_commit,
(thd, all,
thd_get_cache_mngr(thd)->get_binlog_cache_log(true),
thd_get_cache_mngr(thd)->get_binlog_cache_log(false),
max<my_off_t>(max_binlog_cache_size,
max_binlog_stmt_cache_size))) ||
DBUG_EVALUATE_IF("simulate_failure_in_before_commit_hook", true, false))
{
ha_rollback_low(thd, all);
gtid_state->update_on_rollback(thd);
thd_get_cache_mngr(thd)->reset();
//Reset the thread OK status before changing the outcome.
if (thd->get_stmt_da()->is_ok())
thd->get_stmt_da()->reset_diagnostics_area();
my_error(ER_RUN_HOOK_ERROR, MYF(0), "before_commit");
DBUG_RETURN(RESULT_ABORTED);
}
RUN_HOOK
是一个宏,用来调用某个委托中的函数的,上述代码块的含义为调用transaction_delegate
中的before_commit
函数,相应的就是group_replication_trans_before_commit
,函数参数则是后面括号内的内容。我们可以看到,假设before_commit失败了,则会引起回滚操作。
插件的注册、调用流程走通了,那我们就可以着重的来看一下,MGR中究竟在事务真正提交前做了什么工作。见下篇。