Plugin - Seaven/incubator-doris GitHub Wiki
[TOC]
MySQL-Plugin
参考mysql 8.0 代码&文档(https://dev.mysql.com/doc/refman/8.0/en/plugin-api.html)
MySQL提供了一套通用插件API来扩展MySQL功能,支持服务端(MySQL-service)插件和客户端(MySQL-Client)插件,通过插件框架,MySQL可以动态(运行时)加载插件使用。
Plugin类型
目前MySQL 8.0中涉及的插件种类有以下几类:
- 全文索引插件(Full-Text Parser Plugins)
可以定义不同的分词算法,例如”我的名字叫李明“,在创建全文索引时,默认的分词结果为”我/的/名字/叫、李明“,在使用全文索引的插件后,实现新的分词算法,分词结果为”我的名字/叫/李明“
- 后台任务插件(Daemon Plugins)
相当于启动一个MySQL的子进程,处理Plugin实现的任务,如:定期一个数据备份,定期做一次表删除
- INFORMATION_SCHEMA插件(INFORMATION_SCHEMA Plugins)
主要用于扩展MySQL的INFORMATION_SCHEMA库,增加元数据等功能,例如InnoDB利用该插件提供事务表、锁信息
- 半同步插件(Semisynchronous Replication Plugins)
MySQL数据同步功能默认是异步的,可以通过半同步插件实现事务的半同步
- 权限插件(Authentication Plugins)
MySQL的权限插件通常有Service端和Client端协同实现,例如:Client端要求用户提供特定的校验信息(证书,秘钥...),完成后发送给Service端做权限校验
- 审计插件(Audit Plugins)
审计相关功能,例如:记录慢查询SQL到日志,DDL SQL发送消息通知
- 查询重写插件(Query Rewrite Plugins)
查询重写插件可以在MySQL执行SQL之前,根据规则将传入的SQL重写成目标SQL后执行,例如:将delete user where userid = 1 重写为 update user set disable = 1 where 1 执行
-
存储引擎插件(Storage Engine Plugins) 》 提供单机innodb,myisam,memory等等引擎都是以plugin方式插入到mysql中的
-
密码验证插件(Password-Validation Plugins)
主要用于密码相关功能,例如:设置密码时拒绝弱密码或者检查密码强度
- Protocol-Trace插件(Protocol Trace Plugins)
用于追踪MySQl Client和Service的通信流程
- 密钥插件(Keyring Plugins)
##Data Struct
plugin.h是MySQL Plugin的基础接口,不同种类的插件实现时除了plugin.h以外,还有部分其他对应的头文件,MySQL中一个plugin的基本定义:
//include/mysql/plugin.h
/*
Plugin description structure.
*/
struct st_mysql_plugin {
// MySQL插件类型,上述几类
int type; /* the plugin type (a MYSQL_XXX_PLUGIN value) */
// plugin核心执行,每种plugin的description不同,可能会包括实际plugin work的方法
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) */
// 框架通用方法,install/加载时调用
/** Function to invoke when plugin is loaded. */
int (*init)(MYSQL_PLUGIN);
// 框架通用方法,uninstall时调用
/** Function to invoke when plugin is uninstalled. */
int (*check_uninstall)(MYSQL_PLUGIN);
// 框架通用方法,uninstall时,mysql shutdown时调用
/** Function to invoke when plugin is unloaded. */
int (*deinit)(MYSQL_PLUGIN);
unsigned int version; /* plugin version (for I_S.PLUGINS) */
// show status语句时,暴露插件状态/数据用
SHOW_VAR *status_vars;
// SHOW VARIABLES语句时,暴露插件状态/数据用,同时也可以通过vars变量控制plugin参数
SYS_VAR **system_vars;
void *__reserved1; /* reserved for dependency checking */
unsigned long flags; /* flags for plugin */
};
除此外,在sql_plugin_ref.h中有个重要的数据结构,内部使用plugin时,plugin相关的上下文数据(handler....)会记录在这里
/* A handle of a plugin */
struct st_plugin_int {
LEX_STRING name{nullptr, 0};
st_mysql_plugin *plugin{nullptr};
st_plugin_dl *plugin_dl{nullptr};
uint state{0};
uint ref_count{0}; /* number of threads using the plugin */
void *data{nullptr}; /* plugin type specific, e.g. handlerton */
MEM_ROOT mem_root; /* memory for dynamic plugin structures */
sys_var *system_vars{nullptr}; /* server variables for this plugin */
enum_plugin_load_option load_option{
PLUGIN_OFF}; /* OFF, ON, FORCE, F+PERMANENT */
};
Demo
实现一个Plugin Demo流程:
- plugin代码实现(可以使用MySQL内置的数据结构,访问MySQL表等)
- 和对应版本的MySQL的版本编译产出plugin.so
- 将plugin.so copy到mysql指定的plugin dir下
- 通过install plugin或者启动mysql时通过--plugin-load加载plugin
这里贴一个注册information_schema表的demo
#include "table.h"
#include "sql_class.h"
#include "sql_show.h"
#include "mysql/plugin.h"
#define MYSQL_DYNAMIC_PLUGIN
static ST_FIELD_INFO simple_table_fields[] =
{
{"NAME", 10, MYSQL_TYPE_STRING, 0, 0, 0, 0},
{"VALUE", 6, MYSQL_TYPE_LONG, 0, MY_I_S_UNSIGNED, 0, 0},
{0, 0, MYSQL_TYPE_NULL, 0, 0, 0, 0}
};
static int simple_fill_table(THD* thd, TABLE_LIST* tables, Item* cond)
{
TABLE* table = tables->table;
table->field[0]->store("seaven", 6, system_charset_info);
table->field[1]->store(1);
if (schema_table_store_record(thd, table)) {
return 1;
}
table->field[0]->store("knight", 6, system_charset_info);
table->field[1]->store(2);
if (schema_table_store_record(thd, table)) {
return 1;
}
return 0;
}
static struct st_mysql_information_schema simple_table_info = {
MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION
};
static int simple_table_init(void *ptr) {
ST_SCHEMA_TABLE *schema_table = (ST_SCHEMA_TABLE* )ptr;
schema_table->fields_info = simple_table_fields;
schema_table->fill_table = simple_fill_table;
return 0;
}
mysql_declare_plugin(is_example)
{
MYSQL_INFORMATION_SCHEMA_PLUGIN,
&simple_table_info,
"SIMPLE_I_S_TABLE",
"Seaven",
"Simple INFORMATION SCHEMA table",
PLUGIN_LICENSE_GPL,
simple_table_init,
NULL,
0x1610,
NULL,
NULL,
NULL,
0
}
mysql_declare_plugin_end;
Components
Plugin涉及的功能点:
-
SQL Statements:
- INSTALL PLUGIN:INSTALL PLUGIN plugin_name SONAME 'shared_library_name'
- UNINSTALL PLUGIN:UNINSTALL PLUGIN plugin_name
- SHOW PLUGIN
-
Command Line Options & System variables:
- --plugin-load
- --plugin-dir
-
Related tables(metadata):
- INFORMATION_SCHEMA.PLUGINS
- mysql.plugin
Progress
plugin相关核心操作主要在sql_plugin.h、sql_plugin.cc中实现。 MySQL在全局会维护一个Plugin Map,存储Plugin的实例。
install
INSTALL PLUGIN plugin_name SONAME 'shared_library_name'
Plugin加载的方式有三种:
- mysqld启动时自动加载(内置plugin,meta中保存的plugin)
- mysqld启动时通过--plugin_load指定加载
- 通过install plugin语句加载
前两种加载的方式涉及到的接口如下,具体的实现基本上也是和install plugin的方式一样,都是一样
// sql_plugin.h
extern bool plugin_register_early_plugins(int *argc, char **argv, int flags);
extern bool plugin_register_builtin_and_init_core_se(int *argc, char **argv);
extern bool plugin_register_dynamic_and_init_all(int *argc, char **argv,
int init_flags);
涉及sql_plugin.cc中plugin加载方法
static bool mysql_install_plugin(THD *thd, const LEX_STRING *name,
const LEX_STRING *dl);
static bool plugin_load_list(MEM_ROOT *tmp_root, int *argc, char **argv,
const char *list, bool load_early);;
static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv);
static bool plugin_add(MEM_ROOT *tmp_root, const LEX_STRING *name,
const LEX_STRING *dl, int *argc, char **argv, int report,
bool load_early);
plugin加载流程:
- 检查plugin是否已经加载过
- dlopen打开so库
- 检查plugin ce version
- 检查service版本(?没理解)
- 查找plugin声明(一个plugin.cc中可以定义多个plugin,参考innodb:ha_innodb.c)
- 检查不同类型plugin的版本要求
- plugin参数初始化
- 调用plugin的init方法初始化
- 注册到全局plugin列表
- load & - load plugin方法中,plugin list从mysql内置的plugin列表和mysql.plugin表读取
- install plugin方法中,完成plugin注册逻辑后,会将plugin信息回写到mysql.plugin
- install方法中,会阻止将install plugin写入到binlog中
- 对于某些特定种类plugin,初始化方法为MySQL内置(例如,storage engine的plugin初始化方法会将handlerton存储在plugin的上下文中)
uninstall
UNINSTALL PLUGIN plugin_name
- 检查plugin状态
- 检查plugin引用计数
- 从元数据(mysql.plugs、information_schema.plugin)中删除plugin
- 调用plugin的deinit方法
- 从全局plugin列表中删除
对于某些特定种类plugin,卸载方法为MySQL内置
Usage
这里用innodb作为例子,叙述store engine plugind流程。不同种类的plugin在mysql中使用比较随意,没有固定的接口和调用流程。
- SQL解析找到table
- 通过table查找frm(元数据),通过frm确定storage engine
- 通过storage engine在全局plugin中获取对应的plugin
- 将的plugin的data转换为handlerton,后续操作交由具体引擎的handlerton处理
- .......
其他
- UDF
- MySQL同样支持UDF功能,也是install一个so,但是UDF和Plugin还是有区别,UDF的上下文在SQL执行流程中,主要为了实现自定义逻辑(数据类型转换、聚合)的算子,而Plugin的上下文在MySQL中,主要为了扩展MySQL功能,不会侵入到具体的SQL执行流程中(SQL重写Plugin工作在SQL执行之前)
- UDF的覆盖范围可能只是某一个DB,Table的,但是Plugin则是覆盖整个MySQL的工作流程
- 调用
MySQL每次调用plugin时,只会根据plugin name确定&调用一个plugin,不会出现一次调用多个plugin(或者调用一组plugin,或者制作一个plugin链调用)的情况。
ES-Plugin
相比较于MySQL,ES作为分布式系统的方式可能会更适合Doris
ES的Plugin安装后,必须重启每个节点,插件才能生效
Management
// install
sudo bin/elasticsearch-plugin install [plugin_name]
sudo bin/elasticsearch-plugin install file:///path/to/plugin.zip
sudo bin/elasticsearch-plugin install http://some.domain/path/to/plugin.zip
// uninstall
sudo bin/elasticsearch-plugin remove [pluginname]
##Demo
plugin-descriptor.properties
ES的plugin必须包括一个plugin-descriptor.properties,内部描述了plugin的classname,version,jar等信息
# Elasticsearch plugin descriptor file
# This file must exist as 'plugin-descriptor.properties' inside a plugin.
#
### example plugin for "foo"
#
# foo.zip <-- zip file for the plugin, with this structure:
# |____ <arbitrary name1>.jar <-- classes, resources, dependencies
# |____ <arbitrary nameN>.jar <-- any number of jars
# |____ plugin-descriptor.properties <-- example contents below:
#
# classname=foo.bar.BazPlugin
# description=My cool plugin
# version=6.0
# elasticsearch.version=6.0
# java.version=1.8
#
### mandatory elements for all plugins:
#
# 'description': simple summary of the plugin
description=${description}
#
# 'version': plugin's version
version=${version}
#
# 'name': the plugin name
name=${name}
#
# 'classname': the name of the class to load, fully-qualified.
classname=${classname}
#
# 'java.version': version of java the code is built against
# use the system property java.specification.version
# version string must be a sequence of nonnegative decimal integers
# separated by "."'s and may have leading zeros
java.version=${javaVersion}
#
# 'elasticsearch.version': version of elasticsearch compiled against
elasticsearch.version=${elasticsearchVersion}
### optional elements for plugins:
#
# 'extended.plugins': other plugins this plugin extends through SPI
extended.plugins=${extendedPlugins}
#
# 'has.native.controller': whether or not the plugin has a native controller
has.native.controller=${hasNativeController}
interface
一个ES plugin的实现类,必须要继承plugin,针对不同的功能实现不同的接口类:
- ActionPlugin
- AnalysisPlugin
- EnginePlugin
- .......(具体可以参考ES代码, 种类很多很多)
这里贴一个简单的Plugin定义,在通过GET/POST访问/_cat/example时,plugin将会返回一条“Hello from Cat Example action”,代码可以参考这里
public class ExampleRestHandlerPlugin extends Plugin implements ActionPlugin {
@Override
public List<RestHandler> getRestHandlers(final Settings settings,
final RestController restController,
final ClusterSettings clusterSettings,
final IndexScopedSettings indexScopedSettings,
final SettingsFilter settingsFilter,
final IndexNameExpressionResolver indexNameExpressionResolver,
final Supplier<DiscoveryNodes> nodesInCluster) {
return singletonList(new ExampleCatAction(restController));
}
}
Components
install
ES并未提供热插拔的功能,所以在ES的install只会校验&安装plugin
- 下载plugin zip(如果是http)
- 解压zip
- 读取zip中的plugin-descriptor.properties,获取plugin信息
- plugin信息完整性校验(name,version等)
- plugin jar文件校验
- 复制plugin的bin、config文件夹到es的plugin文件夹
真正加载plugin的流程,在ES启动时: elasticsearch -> Bootstrap.setup() -> new Node() -> new PluginService() -> classLoad加载plugin
usage
ES在初始化完成PluginService后,会将plugin注册进es不同的模块中,由各个模块自行调用。
例如上述的demo plugin,在PluginService初始化完成后,将Action类别的plugin注册进ActionModule中。最终ActionModule产生Http Dispatcher,在NetworkModule中被调用。
相比较与Mysql,ES的plugin调用逻辑更清晰一点。
汇总
| x | MySQL | ES | |--|--|--|--| |分布式系统|否|是| |语法支持|本地so|本地,文件系统,http| |安装|本地支持|每个node必须自行安装| |插件状态管理|元数据| 没有元数据注册,安装XPackPlugin插件支持| |热插拔|支持|否| |版本管理|版本更新最好重新编译|更新es需要重新安装|
#举个栗子
MySQL: ddl-rewrite Plugin
举个栗子,安装一个MySQL的DDL重写Plugin,Plugin的实现可以从MySQL源码中plugin/ddl_rewriter/下找到。 rewrite Plugin的作用是在MySQL接受create table语句时,会删除ENCRYPTION(InnoDB通过该子句指定页级别的数据加密),DATA DIRECTORY和INDEX DIRECTORY子句(MyISAM可以通过这两个子句指定数据文件和索引文件的存储地址)。
install
- 将ddl_rewrite Plugin编译后产出的so文件copy到Mysql的plugin目录下。
- 执行SQL安装Plugin:
INSTALL PLUGIN ddl_rewriter SONAME 'ddl_rewriter.so';
3 . 安装完成后可以通过SQL查询Plugin状态:
SELECT PLUGIN_NAME, PLUGIN_STATUS, PLUGIN_TYPE FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE '%rewrite%';
可以看到执行结果:
+--------------+---------------+-------------+
| PLUGIN_NAME | PLUGIN_STATUS | PLUGIN_TYPE |
+--------------+---------------+-------------+
| ddl_rewriter | ACTIVE | AUDIT |
+--------------+---------------+-------------+
ACTIVE的状态表示Plugin已经完成安装了,至于PluginType这里是AUDIT,是由于DDL_rewrite是通过MySQL的Audit类Plugin实现的。
usage
rewrite Plugin的作用是在执行create table语句时,会删除ENCRYPTION,DATA DIRECTORY和INDEX DIRECTORY子句。
当执行一条create table SQL:
CREATE TABLE t (i INT) DATA DIRECTORY '/var/mysql/data';
通过MYSQL的SHOW WARNING可以看到ddl_rewrite会将create table的DATA DIRECTORY删除。实际执行的SQL为:
CREATE TABLE t (i INT) ;
mysql> CREATE TABLE t (i INT) DATA DIRECTORY '/var/mysql/data';
Query OK, 0 rows affected, 1 warning (0.03 sec)
mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
Level: Note
Code: 1105
Message: Query 'CREATE TABLE t (i INT) DATA DIRECTORY '/var/mysql/data''
rewritten to 'CREATE TABLE t (i INT) ' by a query rewrite plugin
1 row in set (0.00 sec)