这段时间在准备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中究竟在事务真正提交前做了什么工作。见下篇。

共 0 条回复
暂时没有人回复哦,赶紧抢沙发
发表新回复

作者

sryan
today is a good day