美文网首页
service与component实现分析

service与component实现分析

作者: 真之棒2016 | 来源:发表于2023-06-18 18:04 被阅读0次

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端

首先我们来看一下官方文档中,对于插件中使用服务的流程说明:

服务与插件.png

服务其实就是mysql服务器暴露的一组函数功能供插件方便使用,同时也避免了插件任意调用内部函数造成故障。

2.1 service实现原理

下面是整个mysql services服务框架图


service框架.png

上面则是整个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库


MYSQL_ADD_PLUGIN.png

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(插件服务注册)==。该服务就是为了

  1. 让插件提供一个服务注册和注销接口;
  2. 能够获取到其他插件注册的服务;

下面来看一下服务注册的框架的实现,即component框架。不知道对于5.7服务框架是否还有映像,其中最后的plugin_registry_service提供了插件可以直接获取到全局组件服务。

component的其核心的代码框架在component/libminchassis文件夹中,下面是整体架构图


component框架.png

针对上图,几点说明:

  • 有关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服务完成。

相关文章

网友评论

      本文标题:service与component实现分析

      本文链接:https://www.haomeiwen.com/subject/ajrsydtx.html