一、 MySQL中的动态插件

最初想到这个问题是在学习mysql半同步复制相关问题的时候,为何在mysql运行时install半同步插件并开启后就能起到作用,他是如何让事务停下来等待的。安装插件的时候加载的是一个.so动态库,这个库里是插件的实现。那么MySQL源码中应该需要对应的框架去以调用这些插件,这个框架是如何运作的呢?

二、从源码中寻找答案

首先,我们需要要知道插件从何处调用的。以半同步插件为例:众所周知,开启半同步插件后通过设置rpl_semi_sync_master_wait_point 的值可以决定mysql主库在何处等待从库的ack,为after_sync则在binlog刷新到磁盘后,为after_commit则是在事务提交以后。那么在mysql事务刷新到binlog之后和事务提交之后应该需要各有一个地方调用半同步插件的接口进行等待。

首先,进入mysql的事务提交函数,当开启binlog的时候mysql由MYSQL_BIN_LOG::ordered_commit进行提交操作。为了保证不丢数据,我们通常使用after_sync同步点,如果要寻找在这个同步点半同步插件的调用,那么就需要在这个函数中刷新binlog之后与事务正式提交之前寻找。事实上在刷新binlog后,可以找到这样一个函数调用:

int MYSQL_BIN_LOG::ordered_commit(THD *thd, bool all, bool skip_commit)
{
···
if (flush_error == 0 && sync_error == 0)
sync_error= call_after_sync_hook(commit_queue);
···
}

似乎看其函数名字就和半同步有关,那么进入这个函数中:

static inline int call_after_sync_hook(THD *queue_head)
{
const char *log_file= NULL;
my_off_t pos= 0; if (NO_HOOK(binlog_storage))
return 0; DBUG_ASSERT(queue_head != NULL);
for (THD *thd= queue_head; thd != NULL; thd= thd->next_to_commit)
if (likely(thd->commit_error == THD::CE_NONE))
thd->get_trans_fixed_pos(&log_file, &pos); if (DBUG_EVALUATE_IF("simulate_after_sync_hook_error", 1, 0) ||
RUN_HOOK(binlog_storage, after_sync, (queue_head, log_file, pos)))
{
sql_print_error("Failed to run 'after_sync' hooks");
return ER_ERROR_ON_WRITE;
}
return 0;
}

这个函数接受一队待提交的线程的队列作为参数,让这些线程等待从库的ack,那么具体是那个调用呢?就是这个RUN_HOOK:

RUN_HOOK(binlog_storage, after_sync, (queue_head, log_file, pos))

查看RUN_HOOK的源码发现这是一个宏:

#define RUN_HOOK(group, hook, args)             \
(group ##_delegate->is_empty() ? \
0 : group ##_delegate->hook args)

将上面的调用按照这个宏的定义进行文本替换后得到实际上的调用为:

binlog_storage_delegate->is_empty() ? 0 : binlog_storage_delegate->after_sync(queue_head, log_file, pos)

也就是说实际上是调用了binlog_storage_delegate指向的对象的函数来实现半同步的功能,如果is_empty()的返回值为真则不做任何操作,否则调用after_sync函数来进行等待。查找binlog_storage_delegate这个指针的定义,可以在rpl_handler.cc中找到它,这是一个全局指针变量,类型为Binlog_storage_delegate *:

//rpl_handler.cc
Binlog_storage_delegate *binlog_storage_delegate;

进一步查找这个变量在何处初始化可以找到delegates_init函数:

int delegates_init()
{
······
transaction_delegate= new (place_trans_mem) Trans_delegate; if (!transaction_delegate->is_inited())
{
sql_print_error("Initialization of transaction delegates failed. "
"Please report a bug.");
return 1;
} binlog_storage_delegate= new (place_storage_mem) Binlog_storage_delegate; if (!binlog_storage_delegate->is_inited())
{
sql_print_error("Initialization binlog storage delegates failed. "
"Please report a bug.");
return 1;
} server_state_delegate= new (place_state_mem) Server_state_delegate;
······
}

这个函数专门初始化各种xxx_delegate类型的指针,为他们分配对象,实际上继续追踪调用栈可以发现如下函数调用

mysqld_main()
|
|
init_server_components()
|
|
delegates_init()

所以可以看出,实际上在mysql启动的时候这个这些指针就已经得到了初始化。接下来,依然以半同步插件的同步等待点为例子来看看到底是如何实现调用的。

三、 插件中的观察者模式

依然以上面等待从库ack的调用为例,已经知道了实际上是以Binlog_storage_delegate类来实现的,那么接下来看Binlog_storage_delegate的代码:

class Binlog_storage_delegate
:public Delegate {
public: Binlog_storage_delegate()
: Delegate(
#ifdef HAVE_PSI_INTERFACE
key_rwlock_Binlog_storage_delegate_lock
#endif
)
{} typedef Binlog_storage_observer Observer;
int after_flush(THD *thd, const char *log_file,
my_off_t log_pos);
int after_sync(THD *thd, const char *log_file,
my_off_t log_pos);
};

这个类中有一个after_sync函数,也就是说我们上面的RUN_HOOK实际上调用的是这个函数。Binlog_storage_delegate继承了一个基类Delegate:

class Delegate {
public:
typedef List<Observer_info> Observer_info_list;
typedef List_iterator<Observer_info> Observer_info_iterator; int add_observer(void *observer, st_plugin_int *plugin)
{
······
} int remove_observer(void *observer, st_plugin_int *plugin)
{
···
} inline Observer_info_iterator observer_info_iter()
{
return Observer_info_iterator(observer_info_list);
} inline bool is_empty()
{
DBUG_PRINT("debug", ("is_empty: %d", observer_info_list.is_empty()));
return observer_info_list.is_empty();
} inline int read_lock()
{
if (!inited)
return TRUE;
return mysql_rwlock_rdlock(&lock);
} inline int write_lock()
{
if (!inited)
return TRUE;
return mysql_rwlock_wrlock(&lock);
} inline int unlock()
{
if (!inited)
return TRUE;
return mysql_rwlock_unlock(&lock);
} inline bool is_inited()
{
return inited;
} Delegate(
#ifdef HAVE_PSI_INTERFACE
PSI_rwlock_key key
#endif
)
{
inited= FALSE;
#ifdef HAVE_PSI_INTERFACE
if (mysql_rwlock_init(key, &lock))
return;
#else
if (mysql_rwlock_init(0, &lock))
return;
#endif
init_sql_alloc(key_memory_delegate, &memroot, 1024, 0);
inited= TRUE;
}
~Delegate()
{
inited= FALSE;
mysql_rwlock_destroy(&lock);
free_root(&memroot, MYF(0));
} private:
Observer_info_list observer_info_list;
mysql_rwlock_t lock;
MEM_ROOT memroot;
bool inited;
};

上面略去了一些函数的细节,先来看里面的主要成员。observer_info_list是一个链表,里面存储了Observer_info类型的成员,同时还有一把读写锁lock进行保护。其他成员函数为对这个链表的操作函数(例如add_observer为添加成员)以及加锁解锁的函数。注意到其中有个is_empty函数,这个函数用来判断链表是否为空。在RUN_HOOK中调用的is_empty就是这个函数。

接下来继续回到子类Binlog_storage_delegate中,我们已经知道after_sync的等待实际上就是靠Binlog_storage_delegate的after_sync函数实现的。接下来进入这个函数的源代码,来看这个函数如何实现的

int Binlog_storage_delegate::after_sync(THD *thd,
const char *log_file,
my_off_t log_pos)
{
DBUG_ENTER("Binlog_storage_delegate::after_sync");
DBUG_PRINT("enter", ("log_file: %s, log_pos: %llu",
log_file, (ulonglong) log_pos));
Binlog_storage_param param;
param.server_id= thd->server_id; DBUG_ASSERT(log_pos != 0);
int ret= 0;
FOREACH_OBSERVER(ret, after_sync, thd, (&param, log_file, log_pos)); DEBUG_SYNC(thd, "after_call_after_sync_observer");
DBUG_RETURN(ret);
}

除去各种调试信息外,这个函数实际上只是调用了FOREACH_OBSERVER这个宏:

FOREACH_OBSERVER(ret, after_sync, thd, (&param, log_file, log_pos));

继续查看这个宏的定义:

#define FOREACH_OBSERVER(r, f, thd, args)                               \
/*
Use a struct to make sure that they are allocated adjacent, check
delete_dynamic().
*/ \
Prealloced_array<plugin_ref, 8> plugins(PSI_NOT_INSTRUMENTED); \
read_lock(); \
Observer_info_iterator iter= observer_info_iter(); \
Observer_info *info= iter++; \
for (; info; info= iter++) \
{ \
plugin_ref plugin= \
my_plugin_lock(0, &info->plugin); \
if (!plugin) \
{ \
/* plugin is not intialized or deleted, this is not an error */ \
r= 0; \
break; \
} \
plugins.push_back(plugin); \
if (((Observer *)info->observer)->f \
&& ((Observer *)info->observer)->f args) \
{ \
r= 1; \
sql_print_error("Run function '" #f "' in plugin '%s' failed", \
info->plugin_int->name.str); \
break; \
} \
} \
unlock(); \
/*
Unlock plugins should be done after we released the Delegate lock
to avoid possible deadlock when this is the last user of the
plugin, and when we unlock the plugin, it will try to
deinitialize the plugin, which will try to lock the Delegate in
order to remove the observers.
*/ \
if (!plugins.empty()) \
plugin_unlock_list(0, &plugins[0], plugins.size());

这个宏比较长,我们先忽略其他对于插件管理的代码,注意力放在遍历这个链表的for循环中。我们将这个循环进行简化:

Observer_info_iterator iter= observer_info_iter();                    \
Observer_info *info= iter++; \
for (; info; info= iter++) \
{
······ \
if (((Observer *)info->observer)->f \
&& ((Observer *)info->observer)->f args) \
{ \
r= 1; \
sql_print_error("Run function '" #f "' in plugin '%s' failed", \
info->plugin_int->name.str); \
break; \
} \
} \
······

从中可以看出,这个for循环遍历整个链表,取出每个元素的指针并转换为Observer 类型的指针,再以传入的args作为参数调用其名字为f成员函数。我们可以依然将宏进行文本替换,以Binlog_storage_delegate::after_sync中调用的FOREACH_OBSERVER为例,外部的调用为:

FOREACH_OBSERVER(ret, after_sync, thd, (&param, log_file, log_pos));

则内部for循环中,使用每个元素的指针封装出的调用可以翻译为:

if (((Observer *)info->observer)->after_sync &&
((Observer *)info->observer)->after_sync ((&param, log_file, log_pos)))

假设info指针指向的对象的after_sync函数成员存在,则以args作为这个函数的参数调用它。那么也就是说Binlog_storage_delegate::after_sync的作用实际上是用来挨个调用它链表中保存的各个Observer对象的after_sync的成员函数。而半同步插件中等待ack的动作实际上再进一步由observer对象实现的,在Binlog_storage_delegate类中可以看到它的定义:

typedef Binlog_storage_observer Observer;

这里这样定义的原因是为了FOREACH_OBSERVER这个宏统一使用Observer 这个名字。那么实际上就是调用的Binlog_storage_observer的成员函数了。继续查看Binlog_storage_observer的定义:

typedef struct Binlog_storage_observer {
uint32 len; /**
This callback is called after binlog has been flushed This callback is called after cached events have been flushed to
binary log file but not yet synced. @param param Observer common parameter
@param log_file Binlog file name been updated
@param log_pos Binlog position after update @retval 0 Sucess
@retval 1 Failure
*/
int (*after_flush)(Binlog_storage_param *param,
const char *log_file, my_off_t log_pos);
int (*after_sync)(Binlog_storage_param *param,
const char *log_file, my_off_t log_pos);
} Binlog_storage_observer; /**
Binlog storage observer parameters
*/
typedef struct Binlog_storage_param {
uint32 server_id;
} Binlog_storage_param;

从上面可以看出,Binlog_storage_observer其实就是一个含有两个函数指针的class,最终实现等待从库ack的是这里面的函数指针。

那么问题接下来就变成了到底Binlog_storage_delegate中保存Binlog_storage_observer实例是从哪里来的?Binlog_storage_observer中的函数指针具体又是指向哪个函数呢?

回到Binlog_storage_delegate类中,这个类有一个向链表中添加对象的add_observer函数,要向其中添加则应该会调用这个函数。查找对它的调用可以找到添加Binlog_storage_observer到链表中的函数register_binlog_storage_observer:

//rpl_handler
int register_binlog_storage_observer(Binlog_storage_observer *observer, void *p)
{
DBUG_ENTER("register_binlog_storage_observer");
int result= binlog_storage_delegate->add_observer(observer, (st_plugin_int *)p);
DBUG_RETURN(result);
}

而向其中添加的observer则又是register_binlog_storage_observer的参数,那么顺着调用链继续向上找,最终可以在半同步插件的源码中找到semi_sync_master_plugin_init函数:

static int semi_sync_master_plugin_init(void *p)
{
#ifdef HAVE_PSI_INTERFACE
init_semisync_psi_keys();
#endif my_create_thread_local_key(&THR_RPL_SEMI_SYNC_DUMP, NULL); if (repl_semisync.initObject())
return 1;
if (ack_receiver.init())
return 1;
if (register_trans_observer(&trans_observer, p))
return 1;
if (register_binlog_storage_observer(&storage_observer, p))
return 1;
if (register_binlog_transmit_observer(&transmit_observer, p))
return 1;
return 0;
}

这个函数注册半同步插件的各个xxxx_obeserver,将其添加到xxxx_delegate的链表中以供调用。依然关注我们之前的Binlog_storage_observe,上文提到的register_binlog_storage_observer函数参数来自于一个叫storage_observer的变量,这个变量就是Binlog_storage_observe的一个实例。我们继续追踪最后可以发现这是一个在半同步复制插件源码中定义的全局变量:

//semisync_master_plugin.cc
Binlog_storage_observer storage_observer = {
sizeof(Binlog_storage_observer), // len repl_semi_report_binlog_update, // report_update
repl_semi_report_binlog_sync, // after_sync
};

after_sync指针指向的repl_semi_report_binlog_sync就是半同步插件关于等待从库ack的具体实现了,这里不在具体深入半同步的实现。

跟着源码追了了一大圈,这里来做一个回顾。其实从插件被调用的过程(以及叫xxxx_observer的名字)就可以看出这是一个典型的观察者模式,RUN_HOOK调用的时候,如果对应的xxxx_delegate中有对应的xxxx_observer观察者,就挨个调用这些观察者的回调函数,如果没有就什么都不做。

最后,我们以半同步插件等待从库ack的功能总结一个插件调用过程中的时序图:

MySQL插件实现浅析——插件的调用的更多相关文章

  1. typecho流程原理和插件机制浅析(第一弹)

    typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...

  2. 浅析MyBatis(三):聊一聊MyBatis的实用插件与自定义插件

    在前面的文章中,笔者详细介绍了 MyBatis 框架的底层框架与运行流程,并且在理解运行流程的基础上手写了一个自己的 MyBatis 框架.看完前两篇文章后,相信读者对 MyBatis 的偏底层原理和 ...

  3. typecho流程原理和插件机制浅析(第二弹)

    typecho流程原理和插件机制浅析(第二弹) 兜兜 393 2014年04月02日 发布 推荐 1 推荐 收藏 14 收藏,3.7k 浏览 上一次说了 Typecho 大致的流程,今天简单说一下插件 ...

  4. Dynamics 365 CE的插件/自定义工作流活动中调用Web API示例代码

    微软动态CRM专家罗勇 ,回复325或者20190428可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me! 现在Web API越来越流行,有时候为了程序更加健壮,需要在插件 ...

  5. Python+selenium 自动化-启用带插件的chrome浏览器,调用浏览器带插件,浏览器加载配置信息。

    Python+selenium 自动化-启用带插件的chrome浏览器,调用浏览器带插件,浏览器加载配置信息.   本文链接:https://blog.csdn.net/qq_38161040/art ...

  6. Idea插件SequenceDiagram快速查看方法调用

    Idea打开setting->plugins安装插件SequenceDiagram 快速查看方法调用 在方法名上右键点击SequenceDiagram即可生成方法调用图 最上面一行为该方法涉及的 ...

  7. Hapi+MySql项目实战配置插件-加载文件渲染母版(三)

    加载插件 一般在其它node框架下,我们安装好插件直接require('插件')就能正常使用了,但是在Hapi下我们必须要Server.register()方法,才能正常使用插件.举个例子: serv ...

  8. 自己写jquery插件之模版插件高级篇(一)

    需求场景 最近项目改版中,发现很多地方有这样一个操作(见下图gif动画演示),很多地方都有用到.这里不讨论它的用户体验怎么样. 仅仅是从复用的角度,如果每个页面都去写text和select元素,两个b ...

  9. 浅谈C#中一种类插件系统编写的简单方法(插件间、插件宿主间本身不需要通信)

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 三年多前还在上研时,用C#+反射机制写过插件系统,后来又用M ...

随机推荐

  1. [知识梳理]课本3&9.1

    函数:关键词:参数.返回值.函数返回类型.函数体. 函数按照返回类型,可以分为有参函数和无参函数. 函数根据是否有返回值,可以分为返回值函数和非返回值函数.     函数的定义:函数的定义可以放在任意 ...

  2. 基于 Java NIO 实现简单的 HTTP 服务器

    1.简介 本文是上一篇文章实践篇,在上一篇文章中,我分析了选择器 Selector 的原理.本篇文章,我们来说说 Selector 的应用,如标题所示,这里我基于 Java NIO 实现了一个简单的 ...

  3. layui中进行form表单一些问题

    最近一段时间一直在用layui来写一些前段页面,发现有几个问题,不知道是我的编译器的问题还是什么,总之目前是自己改成功了,在这里分享下. 第一个是用layui去写单选按钮,网页上不会显示出来.解决方法 ...

  4. Netty事件监听和处理(上)

    陪产假结束了,今天又开始正常上班了,正好赶上米粉节活动,又要忙上一阵了,米粉节活动时间为4.03 - 4.10,有不少优惠,感兴趣的可以关注mi.com或小米商城app. 今天给大家送了福利:小爱音箱 ...

  5. Ubuntu下安装最新sublime

    1. Install the GPG key: wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key ...

  6. DOM节点删除之empty和remove区别

    要移除页面上节点是开发者常见的操作,jQuery提供了几种不同的方法用来处理这个问题,这里我们开仔细了解下empty和remove方法 empty 顾名思义,清空方法,但是与删除又有点不一样,因为它只 ...

  7. python jquery

    jquery 一.寻找元素(选择器和筛选器) a.选择器 1.基本选择器 1 $("*")  $("#id")   $(".class")  ...

  8. python基础——特性(property)、静态方法(staticmethod)和类方法(classmethod)

    python基础--特性(property) 1 什么是特性property property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值 import math class Circl ...

  9. Spring(二):Spring框架&Hello Spring

    Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架. Spring 框架 ...

  10. YII2框架下使用PHPExcel导出柱状图

    导出结果: 首先,到官网下载PHPExcel插件包,下载后文件夹如下: 将Classes文件夹放入到项目公共方法内. 新建控制器(访问导出的方法):EntryandexitController < ...