在程序设计的发展过程中,插件(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(插件名);
使用何种设计方式和设计架构,是由开发者的设计思想和当时的开发水平限制的,谈不上哪个最好,随着技术水平和设计思想的不断的进步,整个程序的设计也会不断的进行迭代开发。
看一下在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
/*
The allowable types of plugins
*/
#define MYSQL_UDF_PLUGIN 0 /* 用户自定义模式 User-defined function */
#define MYSQL_STORAGE_ENGINE_PLUGIN 1 /* Storage Engine */
#define MYSQL_FTPARSER_PLUGIN 2 /* Full-text parser plugin */
#define MYSQL_DAEMON_PLUGIN 3 /* The daemon/raw plugin type */
#define MYSQL_INFORMATION_SCHEMA_PLUGIN 4 /* The I_S plugin type */
#define MYSQL_AUDIT_PLUGIN 5 /* The Audit plugin type */
#define MYSQL_REPLICATION_PLUGIN 6 /* The replication plugin type */
#define MYSQL_AUTHENTICATION_PLUGIN 7 /* The authentication plugin type */
#define MYSQL_VALIDATE_PASSWORD_PLUGIN 8 /* validate password plugin type */
#define MYSQL_GROUP_REPLICATION_PLUGIN 9 /* The Group Replication plugin */
#define MYSQL_KEYRING_PLUGIN 10 /* The Keyring plugin type */
#define MYSQL_CLONE_PLUGIN 11 /* The Clone plugin type */
#define MYSQL_MAX_PLUGIN_TYPE_NUM 12 /* The number of plugin types */
再看一下动态宏定义相关的定义:
/*
Macros for beginning and ending plugin declarations. Between
mysql_declare_plugin and mysql_declare_plugin_end there should
be a st_mysql_plugin struct for each plugin to be declared.
*/
#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不熟悉的,就不用纠结这个宏定义了,即使是熟悉的,也未必几个能搞清楚。如果一定想看清楚,也很简单,编译的时候儿使用宏参数展开一下就看出来了。
再看一下插件定义的标志:
/*
Constants for plugin flags.
*/
#define PLUGIN_OPT_NO_INSTALL 1UL /* Not dynamically loadable */
#define PLUGIN_OPT_NO_UNINSTALL 2UL /* Not dynamically unloadable */
#define PLUGIN_OPT_ALLOW_EARLY 4UL /* allow --early-plugin-load */
插件描述结构体,在前面讲handlerton时顺手聊过一下:
/*
Plugin description structure.
*/
struct st_mysql_plugin {
int type; /* the plugin type (a MYSQL_XXX_PLUGIN value) */
void *info; /* pointer to type-specific plugin descriptor */
const char *name; /* plugin name */
const char *author; /* plugin author (for I_S.PLUGINS) */
const char *descr; /* general descriptive text (for I_S.PLUGINS) */
int license; /* the plugin license (PLUGIN_LICENSE_XXX) */
/** Function to invoke when plugin is loaded. */
int (*init)(MYSQL_PLUGIN);
/** Function to invoke when plugin is uninstalled. */
int (*check_uninstall)(MYSQL_PLUGIN);
/** Function to invoke when plugin is unloaded. */
int (*deinit)(MYSQL_PLUGIN);
unsigned int version; /* plugin version (for I_S.PLUGINS) */
SHOW_VAR *status_vars;
SYS_VAR **system_vars;
void *__reserved1; /* reserved for dependency checking */
unsigned long flags; /* flags for plugin */
};
相关参数类型的定义,其它类似的也有不少如SHOW_INT(unsigned int *):
/*
declarations for server variables and command line options
*/
#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 /* Variable is per-connection */
#define PLUGIN_VAR_READONLY 0x0200 /* Server variable is read only */
#define PLUGIN_VAR_NOSYSVAR 0x0400 /* Configurable only by cmd-line */
还有下面这个宏定义:
#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)))
它被应用于下列的宏定义中:
/*
for global variables, the value pointer is the first
element after the header, the default value is the second.
for thread variables, the value offset is the first
element after the header, the default value is the second.
*/
#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
/* Copyright (c) 2006, 2021, Oracle and/or its affiliates.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#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 /* HAVE_PSI_INTERFACE */
#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:%02d\n", 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;
}
/*
Initialize the daemon example at server start or plugin installation.
SYNOPSIS
daemon_example_plugin_init()
DESCRIPTION
Starts up heartbeatbeat thread
RETURN VALUE
0 success
1 failure (cannot happen)
*/
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; /* Thread attributes */
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));
/*
No threads exist at this point in time, so this is thread safe.
*/
localtime_r(&result, &tm_tmp);
snprintf(buffer, sizeof(buffer),
"Starting up at %02d%02d%02d %2d:%02d:%02d\n", 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);
/* now create the thread */
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;
}
/*
Terminate the daemon example at server shutdown or plugin deinstallation.
SYNOPSIS
daemon_example_plugin_deinit()
Does nothing.
RETURN VALUE
0 success
1 failure (cannot happen)
*/
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:%02d\n",
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));
/*
Need to wait for the hearbeat thread to terminate before closing
the file it writes to and freeing the memory it uses
*/
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};
/*
Plugin library descriptor
*/
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, /* Plugin Init */
nullptr, /* Plugin Check uninstall */
daemon_example_plugin_deinit, /* Plugin Deinit */
0x0100 /* 1.0 */,
nullptr, /* status variables */
nullptr, /* system variables */
nullptr, /* config options */
0, /* flags */
} mysql_declare_plugin_end;
看一下这个例子是不是很简单的一个符合上面提到的使用的定义模式。具体的插件应用分析将以观察者的Delegate和引擎的handlerton来分别展开。
插件设计有优势,也有劣势,如果插件太多,一个是不容易管理,另外一个是不容易维护升级。所以说设计无所谓优劣,看实际应用的场景。重要是学习设计思想,而不是僵化的照搬技术。
努力吧,归来的少年!