5.1 版本开始MySQL开始支持plugin API,允许在mysqld运行时载入或者卸载组件,而不需要重启mysqld。
plugin API涵盖了UDF、full-text、advanced schema等功能,其中的daemon plugin个人认为是非常的有用。其功能是在plugin载入后可以创建额外的后台线程于mysqld主线程一同协同工作。
plugin API的具体实现在sql/sql_plugin.h 和sql/sql_plugin.cc两个文件中。载入plugin使用dl_open系的动态加载共享库的方法打开so文件,获得需要执行的加载函数和卸载函数的指针。daemon plugin 的启动和storage plugin 启动一样,由init_server_components(sql/mysqld.cc) 里的plugin_init函数来启动。
开发的plugin的时候,只需要关心API接口文件include/plugin.h(我把mysql安装在/usr/local/mysql,所以这个文件在/usr/local/mysql/include/mysql/plugin.h),里面可以看到一些API函数和宏。
plugin的几个相关命令
show plugins 可查询系统内所有的激活的plugin,也包括storage plugin。
mysql>show plugins; +------------+--------+----------------+--------------+---------+ | Name | Status | Type | Library | License | +------------+--------+----------------+--------------+---------+ | binlog | ACTIVE | STORAGE ENGINE | NULL | GPL | | CSV | ACTIVE | STORAGE ENGINE | NULL | GPL | | MEMORY | ACTIVE | STORAGE ENGINE | NULL | GPL | | InnoDB | ACTIVE | STORAGE ENGINE | NULL | GPL | | MyISAM | ACTIVE | STORAGE ENGINE | NULL | GPL | | MRG_MYISAM | ACTIVE | STORAGE ENGINE | NULL | GPL | +------------+--------+----------------+--------------+---------+
mysql.plugin 表也可以获得目前的plugin,但不包括storage plugin,加载错误的plugin也会包含在内,例如so不存在。
plugin_dir参数用于告知系统plugin的目录,这个参数必须在mysqld启动前指定,如果不设置,默认目录为/usr/local/mysql/lib/mysql/plugin/(/usr/local/mysql是我的MySQL安装目录)。
1 2 3 4 5 6 | mysql> show variables like 'plugin_dir'; +---------------+-------------------------+ | Variable_name | Value | +---------------+-------------------------+ | plugin_dir | /usr/local/mysql/plugin | +---------------+------------------------ |
加载plugin
1 | mysql>INSTALL PLUGIN plugin_name SONAME 'plugin_library' |
这里的plugin_name后面会讲到,plugin_library即为要加载的共享库so文件的名字,目录必须是上面的plugin_dir。
加载插件后,通过前面的show plugins、mysql.plugin 可以看到你的plugin。
卸载plugin
1 | mysql>UNINSTALL PLUGIN plugin_name |
编写plugin
plugin模式的软件领域分明是oop继承、重载的用武之地,MySQL却使用了预编译宏和函数指针不是那么优雅的完成了这个任务(个人很喜欢这种方法,虽然代码丑陋了点)。另外说几句MySQL虽然看起来是c++写的,但实际上较少的使用c++的一些特性,只在内部几个模块用到模板类。跑题了,开始写plugin。
下面编写一个plugin,作用是加载后创建一个线程在后台持续的把MySQL内的一些信息打印到err.log日志。
首先介绍一下include/plugin.h里的st_mysql_plugin这个结构体,所有的plugin 都必须是基于这个结构体,填充必须的内容。
struct st_mysql_plugin { int type; /* 插件类型,这里填MYSQL_DAEMON_PLUGIN 即可 */ void *info; /* 插件类型描述符,对于daemon类来说没用,指向一个常量即可 */ const char *name; /* 插件名,这个就是前面的install 命令里的plugin_name */ const char *author; /* 插件作者,随意 */ const char *descr; /* 插件描述,随意 */ int license; /* PLUGIN_LICENSE_GPL */ int (*init)(void); /* install 命令之后调用的函数 */ int (*deinit)(void); /* uninstall 命令之后调用的函数 */ unsigned int version; /* 插件版本,随意*/ struct st_mysql_show_var /* 指向的show_var,可不填*/ struct st_mysql_sys_var /* 指向的sys_var,可不填*/ void * __reserved1; /* 不填*/ };
声明plugin需要使用2个预编译宏,你可以在include/plugin.h 查看mysql_declare_plugin,mysql_declare_plugin_end两个宏的具体内容。
struct st_mysql_daemon monitor_plugin = { MYSQL_DAEMON_INTERFACE_VERSION }; mysql_declare_plugin(monitor_plugin) { MYSQL_DAEMON_PLUGIN, &monitor_plugin, "monitor", "hoterran", "test", PLUGIN_LICENSE_GPL, monitor_plugin_init, monitor_plugin_deinit, 0x0100, NULL, NULL, NULL, } mysql_declare_plugin_end;
结构体中的monitor_plugin_init和monitor_plugin_deinit就是接下来要编写的加载和卸载对应的函数。
下面是全部代码monitor.c
#include <string.h> #include <unistd.h> #include <stdio.h> #include <plugin.h> #include <mysql_version.h> #include <my_global.h> #include <my_sys.h> #include <pthread.h> extern ulong thread_id; extern uint thread_count; extern ulong max_connections; static pthread_t G_thread; pthread_handler_t func(void *p) { while(1) { sleep(5); fprintf(stderr, "Thread id [%ld] Thread_count: %u Max_connections:%lu\n", thread_id, thread_count, max_connections); } } static int monitor_plugin_init(void *p) { if (pthread_create(&G_thread, NULL, func, NULL) != 0) { fprintf(stderr, "Monitor plugin init failure"); return 1; } fprintf(stderr, "%s", "Monitor plugin init\n"); return 0; } static int monitor_plugin_deinit(void *p) { pthread_cancel(G_thread); pthread_join(G_thread, NULL); fprintf(stderr, "%s", "Monitor_plugin deinit\n"); return 0; } struct st_mysql_daemon monitor_plugin = { MYSQL_DAEMON_INTERFACE_VERSION }; mysql_declare_plugin(monitor_plugin) { MYSQL_DAEMON_PLUGIN, &monitor_plugin, "monitor", "hoterran", "test", PLUGIN_LICENSE_GPL, monitor_plugin_init, monitor_plugin_deinit, 0x0100, NULL, NULL, NULL, } mysql_declare_plugin_end;
编译
1 2 3 | gcc -g -Wall -I/usr/local/mysql/include/mysql -DMYSQL_DYNAMIC_PLUGIN -c -o monitor.o monitor.c gcc -shared -o libmonitor.so monitor.o sudo cp libmonitor.so /usr/local/mysql/plugin/ |
加载
1 2 | mysql>install plugin monitor soname 'libmonitor.so'; Query OK, 0 rows affected (0.01 sec) |
观察日志可以看到具体的输出。
1 2 3 4 5 6 7 8 | .... [New Thread 0xb4296b70 (LWP 3895)] [New Thread 0xb1e34b70 (LWP 4042)] Monitor plugin init Thread id [2] Thread_count: 1 Max_connections:151 Thread id [2] Thread_count: 1 Max_connections:151 Thread id [2] Thread_count: 1 Max_connections:151 .... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | mysql>show plugins; +------------+--------+----------------+---------------+---------+ | Name | Status | Type | Library | License | +------------+--------+----------------+---------------+---------+ | monitor | ACTIVE | DAEMON | libmonitor.so | GPL | +------------+--------+----------------+---------------+---------+ mysql> select * from mysql.plugin; +------------+---------------+ | name | dl | +------------+---------------+ .... | monitor | libmonitor.so | +------------+---------------+ |
好了,插件的编写工作就完成了,够简单吧。
在MySQL的附带demo里有个heartbeat的例子,MySQL的ref里面也有另外一个full-text的例子。
如何使用daemon plugin ,时还没想好,淘宝的同事 @ningoo有把zookeeper client 作为deamon plugin 想法。