在程序设计的发展过程中,插件(Plugin)形式的设计存在的时间很长了,这种源于硬件的插件接口设计,优势在于可以很从容的进行不同场景应用的切换,甚至在运行时也可以通过动态的参数配置来实现整个功能应用场景的快速适配。从Eclipse到Idea等IDE开发工具,到实际的项目开发中,只要开发经验较多的程序员一定会遇到过类似的工程实践。
插件一般是基于一定的插件协议,通过开放不同的配置文件或者配置窗口来实现软件功能的快速扩展。比如比较常用的多语言插件,可以通过配置不同的参数,使应用程序界面显示中文、英文或者其它具体的语言,而不是通过具体的每个UI元素的逐个修改来实现不同的UI语言状态。
插件设计的优势在于功能的隔离和动态的扩展。插件的接口协议有标准的也有自定义的,这里不扩展,有兴趣的可以自行查找相关的资料。
同样,在Mysql中,使用插件式的设计也是基于上述的原因的。比如前面刚刚提到的不同的数据引擎的加载,不同的通信机制的加载等等,都可以预先在配置文件中进行配置好参数,启动程序时会自动根据配置的参数来加载不同的数据库引擎和相关的查询重写、日志同步、守护进程等插件。在Mysql中有内部插件和外部自定义插件两类,而加载方式也分为两大类,一类是可以在my.cfg配置文件中配置的,另外一类是可以通过install方法在服务器上直接在运行期间加载的。它们主要的形式如下:
//配置文件 plugin-load=plugin(名称)=xxx.so(插件库的SO) //例如: [mysqld] plugin-load=rpl_semi_sync_slave=semisync_slave.so //关闭方式 pluginname(插件名) = OFF //install方式 mysql> install plugin rpl_semi_sync_slave soname 'semisync_slave.so'; //二者均可通过uninstall卸载 mysql> uninstall plugin rpl_semi_sync_slave(插件名);
使用何种设计方式和设计架构,是由开发者的设计思想和当时的开发水平限制的,谈不上哪个最好,随着技术水平和设计思想的不断的进步,整个程序的设计也会不断的进行迭代开发。
二、Plugin的架构看一下在Mysql中插件的文件目录:
从目录中可以看到,插件在整个Mysql的源码目录中是占有很大一部分,其中一些具体的插件都可以在目录里找到,还不提其它各个模块为了配合插件模块所做的接口源码部分。其中有一个Exmaple目录,应该看名字就知道是个例程,这是个好东西,如果想搞自己的插件到MYSQL,这可是一个很好的入门教程。
在Mysql中,可以在include/mysql/plugin.h中查看公开的插件API,它会包含一些特定的插件信息如sql_plugin.h,client_plugin.h等;当然下一步就是可以查看这些包含的文件如sql_plugin.h,比如需要访问内部插件的身份验证就需要sql_acl.cc(sql/auth/sql_acl.h sql_acl.cc)等;在sql-common中有相关client_plugin.cc(头文件在mysql/client_plugin.h)等实现相关客户端的插件的文件。
再看一下整体概况:
1、分为两类情况即观察者模式动态调用和handlerton加载方式。
2、一般存储引擎使用后者而服务层插件一般使用动态加载的观察者模式。
3、一般是通过函数指针来实现。在实际运行时通过对指针的判断为空否来动态使用不同的插件。
再看一下基本架构:
1、在Plugin.h的相关定义来定义插件。
2、在插件的具体的文件中使用宏来声明并实现相关功能。
3、通过plugin_init(启动默认配置)和 plugin_add来加载插件(INSTALL动态加载)。
基本上所有插件的定义类型都在include/mysql/plugin.h中,看一下相关的定义:
#define MYSQL_PLUGIN_INTERFACE_VERSION 0x010A #define MYSQL_UDF_PLUGIN 0 #define MYSQL_STORAGE_ENGINE_PLUGIN 1 #define MYSQL_FTPARSER_PLUGIN 2 #define MYSQL_DAEMON_PLUGIN 3 #define MYSQL_INFORMATION_SCHEMA_PLUGIN 4 #define MYSQL_AUDIT_PLUGIN 5 #define MYSQL_REPLICATION_PLUGIN 6 #define MYSQL_AUTHENTICATION_PLUGIN 7 #define MYSQL_VALIDATE_PASSWORD_PLUGIN 8 #define MYSQL_GROUP_REPLICATION_PLUGIN 9 #define MYSQL_KEYRING_PLUGIN 10 #define MYSQL_CLONE_PLUGIN 11 #define MYSQL_MAX_PLUGIN_TYPE_NUM 12
再看一下动态宏定义相关的定义:
#ifndef MYSQL_DYNAMIC_PLUGIN
#define __MYSQL_DECLARE_PLUGIN(NAME, VERSION, PSIZE, DECLS)
MYSQL_PLUGIN_EXPORT int VERSION = MYSQL_PLUGIN_INTERFACE_VERSION;
MYSQL_PLUGIN_EXPORT int PSIZE = sizeof(struct st_mysql_plugin);
MYSQL_PLUGIN_EXPORT struct st_mysql_plugin DECLS[] = {
#else
#define __MYSQL_DECLARE_PLUGIN(NAME, VERSION, PSIZE, DECLS)
MYSQL_PLUGIN_EXPORT int _mysql_plugin_interface_version_ =
MYSQL_PLUGIN_INTERFACE_VERSION;
MYSQL_PLUGIN_EXPORT int _mysql_sizeof_struct_st_plugin_ =
sizeof(struct st_mysql_plugin);
MYSQL_PLUGIN_EXPORT struct st_mysql_plugin _mysql_plugin_declarations_[] = {
#endif
#define mysql_declare_plugin(NAME)
__MYSQL_DECLARE_PLUGIN(NAME, builtin_##NAME##_plugin_interface_version,
builtin_##NAME##_sizeof_struct_st_plugin,
builtin_##NAME##_plugin)
#define mysql_declare_plugin_end
, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
}
对c++/c不熟悉的,就不用纠结这个宏定义了,即使是熟悉的,也未必几个能搞清楚。如果一定想看清楚,也很简单,编译的时候儿使用宏参数展开一下就看出来了。
再看一下插件定义的标志:
#define PLUGIN_OPT_NO_INSTALL 1UL #define PLUGIN_OPT_NO_UNINSTALL 2UL #define PLUGIN_OPT_ALLOW_EARLY 4UL
插件描述结构体,在前面讲handlerton时顺手聊过一下:
struct st_mysql_plugin {
int type;
void *info;
const char *name;
const char *author;
const char *descr;
int license;
int (*init)(MYSQL_PLUGIN);
int (*check_uninstall)(MYSQL_PLUGIN);
int (*deinit)(MYSQL_PLUGIN);
unsigned int version;
SHOW_VAR *status_vars;
SYS_VAR **system_vars;
void *__reserved1;
unsigned long flags;
};
相关参数类型的定义,其它类似的也有不少如SHOW_INT(unsigned int *):
#define PLUGIN_VAR_BOOL 0x0001 #define PLUGIN_VAR_INT 0x0002 #define PLUGIN_VAR_LONG 0x0003 #define PLUGIN_VAR_LonGLONG 0x0004 #define PLUGIN_VAR_STR 0x0005 #define PLUGIN_VAR_ENUM 0x0006 #define PLUGIN_VAR_SET 0x0007 #define PLUGIN_VAR_DOUBLE 0x0008 #define PLUGIN_VAR_UNSIGNED 0x0080 #define PLUGIN_VAR_THDLOCAL 0x0100 #define PLUGIN_VAR_READonLY 0x0200 #define PLUGIN_VAR_NOSYSVAR 0x0400
还有下面这个宏定义:
#define MYSQL_PLUGIN_VAR_HEADER int flags; const char *name; const char *comment; mysql_var_check_func check; mysql_var_update_func update #define MYSQL_SYSVAR_NAME(name) mysql_sysvar_##name #define MYSQL_SYSVAR(name) ((SYS_VAR *)&(MYSQL_SYSVAR_NAME(name)))
它被应用于下列的宏定义中:
#define DECLARE_MYSQL_SYSVAR_BASIC(name, type)
struct {
MYSQL_PLUGIN_VAR_HEADER;
type *value;
const type def_val;
} MYSQL_SYSVAR_NAME(name)
#define DECLARE_MYSQL_SYSVAR_SIMPLE(name, type)
struct {
MYSQL_PLUGIN_VAR_HEADER;
type *value;
type def_val;
type min_val;
type max_val;
type blk_sz;
} MYSQL_SYSVAR_NAME(name)
#define DECLARE_MYSQL_SYSVAR_TYPELIB(name, type)
struct {
MYSQL_PLUGIN_VAR_HEADER;
type *value;
type def_val;
TYPELIB *typelib;
} MYSQL_SYSVAR_NAME(name)
#define DECLARE_THDVAR_FUNC(type) type *(*resolve)(MYSQL_THD thd, int offset)
#define DECLARE_MYSQL_THDVAR_BASIC(name, type)
struct {
MYSQL_PLUGIN_VAR_HEADER;
int offset;
const type def_val;
DECLARE_THDVAR_FUNC(type);
} MYSQL_SYSVAR_NAME(name)
#define DECLARE_MYSQL_THDVAR_SIMPLE(name, type)
struct {
MYSQL_PLUGIN_VAR_HEADER;
int offset;
type def_val;
type min_val;
type max_val;
type blk_sz;
DECLARE_THDVAR_FUNC(type);
} MYSQL_SYSVAR_NAME(name)
一定要注意,上面的代码有两大类,即DECLARE_MYSQL_SYSVAR_xxx 和 DECLARE_MYSQL_THDVAR_xxx。
再看一下自带的守护进程的例子插件:
//plugin/daemon_example/daemon_example.cc #include#include #include #include #include #include #include #include "m_string.h" // strlen #include "my_dbug.h" #include "my_dir.h" #include "my_inttypes.h" #include "my_io.h" #include "my_psi_config.h" #include "my_sys.h" // my_write, my_malloc #include "my_thread.h" #include "mysql/psi/mysql_memory.h" #include "sql/sql_plugin.h" // st_plugin_int PSI_memory_key key_memory_mysql_heartbeat_context; #ifdef HAVE_PSI_INTERFACE static PSI_memory_info all_deamon_example_memory[] = { {&key_memory_mysql_heartbeat_context, "mysql_heartbeat_context", 0, 0, PSI_document_ME}}; static void init_deamon_example_psi_keys() { const char *category = "deamon_example"; int count; count = static_cast (array_elements(all_deamon_example_memory)); mysql_memory_register(category, all_deamon_example_memory, count); } #endif #define HEART_STRING_BUFFER 100 struct mysql_heartbeat_context { my_thread_handle heartbeat_thread; File heartbeat_file; }; static void *mysql_heartbeat(void *p) { DBUG_TRACE; struct mysql_heartbeat_context *con = (struct mysql_heartbeat_context *)p; char buffer[HEART_STRING_BUFFER]; time_t result; struct tm tm_tmp; while (true) { sleep(5); result = time(nullptr); localtime_r(&result, &tm_tmp); snprintf(buffer, sizeof(buffer), "Heartbeat at %02d%02d%02d %2d:%02d:%02dn", tm_tmp.tm_year % 100, tm_tmp.tm_mon + 1, tm_tmp.tm_mday, tm_tmp.tm_hour, tm_tmp.tm_min, tm_tmp.tm_sec); my_write(con->heartbeat_file, (uchar *)buffer, strlen(buffer), MYF(0)); } return nullptr; } static int daemon_example_plugin_init(void *p) { DBUG_TRACE; #ifdef HAVE_PSI_INTERFACE init_deamon_example_psi_keys(); #endif struct mysql_heartbeat_context *con; my_thread_attr_t attr; char heartbeat_filename[FN_REFLEN]; char buffer[HEART_STRING_BUFFER]; time_t result = time(nullptr); struct tm tm_tmp; struct st_plugin_int *plugin = (struct st_plugin_int *)p; con = (struct mysql_heartbeat_context *)my_malloc( key_memory_mysql_heartbeat_context, sizeof(struct mysql_heartbeat_context), MYF(0)); fn_format(heartbeat_filename, "mysql-heartbeat", "", ".log", MY_REPLACE_EXT | MY_UNPACK_FILENAME); unlink(heartbeat_filename); con->heartbeat_file = my_open(heartbeat_filename, O_CREAT | O_RDWR, MYF(0)); localtime_r(&result, &tm_tmp); snprintf(buffer, sizeof(buffer), "Starting up at %02d%02d%02d %2d:%02d:%02dn", tm_tmp.tm_year % 100, tm_tmp.tm_mon + 1, tm_tmp.tm_mday, tm_tmp.tm_hour, tm_tmp.tm_min, tm_tmp.tm_sec); my_write(con->heartbeat_file, (uchar *)buffer, strlen(buffer), MYF(0)); my_thread_attr_init(&attr); my_thread_attr_setdetachstate(&attr, MY_THREAD_CREATE_JOINABLE); if (my_thread_create(&con->heartbeat_thread, &attr, mysql_heartbeat, (void *)con) != 0) { fprintf(stderr, "Could not create heartbeat thread!n"); exit(0); } plugin->data = (void *)con; return 0; } static int daemon_example_plugin_deinit(void *p) { DBUG_TRACE; char buffer[HEART_STRING_BUFFER]; struct st_plugin_int *plugin = (struct st_plugin_int *)p; struct mysql_heartbeat_context *con = (struct mysql_heartbeat_context *)plugin->data; time_t result = time(nullptr); struct tm tm_tmp; void *dummy_retval; my_thread_cancel(&con->heartbeat_thread); localtime_r(&result, &tm_tmp); snprintf(buffer, sizeof(buffer), "Shutting down at %02d%02d%02d %2d:%02d:%02dn", tm_tmp.tm_year % 100, tm_tmp.tm_mon + 1, tm_tmp.tm_mday, tm_tmp.tm_hour, tm_tmp.tm_min, tm_tmp.tm_sec); my_write(con->heartbeat_file, (uchar *)buffer, strlen(buffer), MYF(0)); my_thread_join(&con->heartbeat_thread, &dummy_retval); my_close(con->heartbeat_file, MYF(0)); my_free(con); return 0; } struct st_mysql_daemon daemon_example_plugin = {MYSQL_DAEMON_INTERFACE_VERSION}; mysql_declare_plugin(daemon_example){ MYSQL_DAEMON_PLUGIN, &daemon_example_plugin, "daemon_example", PLUGIN_AUTHOR_ORACLE, "Daemon example, creates a heartbeat beat file in mysql-heartbeat.log", PLUGIN_LICENSE_GPL, daemon_example_plugin_init, nullptr, daemon_example_plugin_deinit, 0x0100 , nullptr, nullptr, nullptr, 0, } mysql_declare_plugin_end;
看一下这个例子是不是很简单的一个符合上面提到的使用的定义模式。具体的插件应用分析将以观察者的Delegate和引擎的handlerton来分别展开。
四、总结插件设计有优势,也有劣势,如果插件太多,一个是不容易管理,另外一个是不容易维护升级。所以说设计无所谓优劣,看实际应用的场景。重要是学习设计思想,而不是僵化的照搬技术。
努力吧,归来的少年!



