author:sufei
版本:mysql 8.0.30
说明:本文主要介绍mysql service服务以及component组件的实现逻辑,以及解决的问题。
一、插件的局限
我们都知道mysql使用插件引擎,从而支持不同的应用场景。同时,添加一个特性也会快速通过插件实现,方便扩展mysql服务的相关能力。但是插件机制存在一定的局限性。
- 插件只能与mysql服务程序通信,但是无法进行插件之间相互交互;
- 插件可以调用使用server服务暴露的任意符号,并且没有封装,存在安全隐患;
- 插件需要合适的调用使用server层函数,需要对相关使用条件和限制有一定的了解;
从上面可以看到插件是强依赖server层代码的,需要对server层相关功能的实现细节有一定的了解。那如何解决这个问题呢?
目标:server层将提供给插件调用的相关功能进行封装,插件无需关注server层实现细节,只需要了解其相关借口即可正确使用(如合理初始化,合理调用);
针对上面的问题,mysql 5.7在mysql插件的基础上提出了service服务的概念,其本质上就是解偶plugin与server端,使得插件更加方便调用server端的相关功能。
下面我们来看一下service服务的实现。
二、service服务——解偶plugin与server端
首先我们来看一下官方文档中,对于插件中使用服务的流程说明:
服务其实就是mysql服务器暴露的一组函数功能供插件方便使用,同时也避免了插件任意调用内部函数造成故障。
2.1 service实现原理
下面是整个mysql services服务框架图
上面则是整个mysql 5.7引入的插件服务框架结构,具体说明如下:
- 维护了一个全局唯一的服务列表结构体list_of_services,其中包含所有mysql提供的服务函数指针;
- 每一个服务的头文件,必须是self-contained的,同时为了满足插件包含以及服务器包含两种场景;并且插件包含对应头文件时,并没有函数指针赋值真实的函数指针,仅仅是一个非有效的地址;
从上面我们对于服务框架有了简单的了解,那插件如何调用服务框架中的服务呢?
插件如何关联以及使用service呢?
插件使用服务的关键则是如何获得服务暴露出来的函数指针,以lock服务为例进行说
1、通过mysql/plugin.h->mysql/services.h->mysql/service_locking.h(所有服务的实现头文件)包含关系,将所有服务的头文件包含进来,其中声明了一个锁服务结构体的指针,并且也声明了相关暴露服务函数指针,如下
extern "C" struct mysql_locking_service_st {
mysql_acquire_locks_t mysql_acquire_locks;
mysql_release_locks_t mysql_release_locks;
} * mysql_locking_service; // 1、定义了一个锁服务结构体脂针,后续通过该结构体内指针函数进行服务调用
#ifdef MYSQL_DYNAMIC_PLUGIN
// 2、对于插件类型,声明相关函数接口,由于编译时期无法得到正确的锁服务结构体脂针,采用宏替代方式
#define mysql_acquire_locking_service_locks(_THD, _NAMESPACE, _NAMES, _NUM, \
_TYPE, _TIMEOUT) \
mysql_locking_service->mysql_acquire_locks(_THD, _NAMESPACE, _NAMES, _NUM, \
_TYPE, _TIMEOUT)
#define mysql_release_locking_service_locks(_THD, _NAMESPACE) \
mysql_locking_service->mysql_release_locks(_THD, _NAMESPACE)
#else。 // 3、如果是服务器,则声明相关服务实现接口
int mysql_acquire_locking_service_locks(
MYSQL_THD opaque_thd, const char *lock_namespace, const char **lock_names,
size_t lock_num, enum enum_locking_service_lock_type lock_type,
uint64_t lock_timeout);
int mysql_release_locking_service_locks(MYSQL_THD opaque_thd,
const char *lock_namespace);
2、在MYSQL_ADD_PLUGIN编译宏中添加libservices库
3、在进行插件加载plugin_dl_add时,修正编译时锁服务结构体的指针指向真正的结构地址
for (i = 0; i < array_elements(list_of_services); i++) { // 遍历所有的服务
if ((sym = dlsym(plugin_dl.handle, list_of_services[i].name))) {
uint ver = (uint)(intptr) * (void **)sym;
if ((*(void **)sym) !=
list_of_services[i].service && /* already replaced */
(ver > list_of_services[i].version ||
(ver >> 8) < (list_of_services[i].version >> 8))) {
char buf[MYSQL_ERRMSG_SIZE];
snprintf(buf, sizeof(buf), "service '%s' interface version mismatch",
list_of_services[i].name);
mysql_mutex_unlock(&LOCK_plugin);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, 0, buf);
return nullptr;
}
*(void **)sym = list_of_services[i].service; // 为对应的服务结构体指针赋值真正的服务有效地址
}
}
4、最后,可以直接在plugin中调用在对应服务头文件(mysql/service_locking.h)中声明的接口函数即可(此时相关函数已经能够找到真确的地址),如在version token插件中就可以直接使用锁服务进行token检查
case CHECK_VTOKEN: {
char error_str[MYSQL_ERRMSG_SIZE];
const char *token_name_cstr = token_name.str;
if (!mysql_acquire_locking_service_locks(
thd, VTOKEN_LOCKS_NAMESPACE, &(token_name_cstr), 1,
LOCKING_SERVICE_READ, LONG_TIMEOUT) &&
!vtokens_unchanged) {
2.2 实现一个service
下面来看一下如何在mysql服务器中国呢实现一个自己的服务,实现服务相对比较简单,首先你需要将实现文件放入sql文件夹,即让mysql服务器具备相关功能。代码也有相关指导
1、在include/mysql文件夹下实现自己服务的头文件(包含函数指针结构体,以及对外暴露的函数原型),且要求self-contained,主要作用是声明服务接口
作为一个c++规范——Self-contained头文件
头文件应该能够自给自足(self-contained,也就是可以作为第一个头文件被引入),以
.h
结尾。至于用来插入文本的文件,说到底它们并不是头文件,所以应以.inc
结尾。不允许分离出-inl.h
头文件的做法.换言之,用户和重构工具不需要为特别场合而包含额外的头文件。
2、把服务头文件添加到include/mysql/services.h,从而使得插件能够获得相关的函数符号;
3、在include/service_versions.h定义一个自己服务的版本信息;
4、在libservices文件夹下,为插件定义一个对应的函数指针结构体,默认初始化为自己服务版本,后续在加载时会指向真正的实现函数;
5、在sql/sql_plugin_services.h中服务列表list_of_services中,注册自己的服务即可。
三、component组件——实现service动态注册
上面已经说明了服务的实现,下面看看服务还存在哪些问题呢?
- 服务都是固定的,是否可以动态的添加服务呢?
- 插件之间相互提供服务,并且调用是否可行?
mysql 8.0提供了component模块,将service进行了扩展,提供了一个反向service的注册能力。下面我们来看看其实现逻辑。
3.1 component架构
在mysql 8.0的服务列表中最后==有一个特殊的服务即plugin_registry_service(插件服务注册)==。该服务就是为了
- 让插件提供一个服务注册和注销接口;
- 能够获取到其他插件注册的服务;
下面来看一下服务注册的框架的实现,即component框架。不知道对于5.7服务框架是否还有映像,其中最后的plugin_registry_service提供了插件可以直接获取到全局组件服务。
component的其核心的代码框架在component/libminchassis文件夹中,下面是整体架构图
针对上图,几点说明:
- 有关component提供的所有服务全部在静态变量service_registry中;
- 在component组件框架初始化时,不仅将内部服务进行了注册加载,同时也为初始化了全局服务仓库指针srv_registry,为后续服务获取提供了全局地址,整体过程如下:
// 1、通过5.7传统的服务plugin_registry_service获得服务仓库地址
reg_srv = mysql_plugin_registry_acquire();
// 2、通过组建仓库地址,获取注册在仓库中的服务
reg_srv->acquire("keyring_reader_with_status",&h_keyring_reader_service)
- mysql5.7版本提供服务存在在list_of_services结构体中,且固定不变;mysql 8.0在这个基础上构建了component组件,其核心结构体为mysql_registry_imp,其访问需要通过5.7版本的plugin_registry_service服务完成。