尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

TiDB Plugin Framework 插件框架设计解析:从 Go Plugin 到可热升级的 SPI 插件体系

TiDB Plugin Framework 插件框架设计解析:从 Go Plugin 到可热升级的 SPI 插件体系 TiDB Plugin Framework 插件框架设计解析从 Go Plugin 到可热升级的 SPI 插件体系【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidbTiDB Plugin Framework 是 TiDB 面向定制化需求开放的官方插件体系它基于 Go 1.9 原生的-buildmodeplugin动态库机制用统一的 Manifest 元数据、pluginpkg打包工具和分层 SPI 接口把给 TiDB 加自定义能力收敛成一套 7 步可复现的开发流程。读完本文你将掌握 TiDB 插件从设计理念、Manifest/SPI 编程模型到manifest.toml配置、打包构建、-plugin-dir/-plugin-load启动加载、show plugins/管理命令使用的完整链路并能直接基于仓库中的conn_ip_example示例插件写出自己的 TiDB 插件。本文主体依据 TiDB 仓库中的设计文档 docs/design/2018-12-10-plugin-framework.md 展开并结合当前仓库源码pkg/plugin、cmd/pluginpkg 等对设计落地情况进行佐证与对照。一、背景为什么 TiDB 需要插件框架很多有价值的定制化需求例如连接数限制、审计日志、自定义鉴权并不适合直接合入 TiDB 主仓库——它们往往属于特定部署环境、特定业务形态的诉求直接进主仓库会带来维护与兼容性负担。与此同时Go 1.9 引入了官方插件支持plugin包。在此背景下TiDB 提出插件框架的设计目标让那些不方便合入主仓库的需求以独立插件的形式得到满足通过标准化的插件体系吸引更多开发者共建 TiDB 生态复用 Go 语言原生能力把复杂的动态库加载细节屏蔽在框架内部。设计文档为此给出的结论Proposal非常直接为 TiDB 添加一个插件框架。框架构建在 Go plugin 之上但在此基础上提供统一的插件 Manifest插件描述、统一的打包格式与灵活的 SPI服务提供接口让插件开发、分发、加载与运维都规范化。二、方案概览开发一个 TiDB 插件的 7 个步骤设计文档给出了插件开发者视角的完整闭环总共 7 步选择一个已存在的插件类型kind若不存在则新建插件类型创建一个普通 Go package并加入一份类似示例的manifest.toml实现所有插件都必需的validate、init、destroy即OnShutdown三个回调实现所选 kind 特有的方法如审计插件的事件回调完成插件核心逻辑使用cmd/pluginpkg打包插件二进制并把生成的.so放入插件部署目录以-plugin-dir与-plugin-load两个启动参数启动 TiDB执行show plugin完整 SQL 为show plugins检查插件的加载状态。下文将逐层拆解这 7 步背后的设计原理与源码实现。三、基石Go Plugin 机制与其关键约束TiDB 插件框架建立在 Go 原生 plugin 机制之上。Go plugin 的能力非常简单直接总共三步在main包中通过go build -buildmodeplugin把插件源码编译成.so共享库宿主程序用plugin.Open等价于dlopen打开插件.so用plugin.Lookup等价于dlsym在插件中按符号名查找函数或变量。在此基础上设计文档强调了一个未写入正式文档却非常重要的概念pluginpath。当插件代码放在main包并用-buildmodeplugin编译后pluginpath就是这个插件被打包后的包路径。例如插件中有一个方法DoIt若pluginpath为pkg1用nm查看符号时方法名会呈现为pluginpath.DoIt。pluginpath有两种来源通过链接参数-ldflags -pluginpath[path-value]显式指定由go build自动生成在 Go 1.11.1 的实现里若按 package 目录编译则取包名若按源文件编译则取内容哈希。pluginpath直接影响插件加载的语义其中最重要的一条是如果两次加载相同pluginpath的 Go 插件第二次Load会报错——Go plugin 用pluginpath来检测重复加载。这正是后面 TiDB 插件多版本共存 reload设计见第七章的出发点必须让不同版本的插件拥有不同的pluginpath。最后还需关注 Go 插件的依赖问题。几乎所有插件都要依赖 TiDB 代码来完成业务逻辑而 Go 运行时要求依赖包在运行时的 hash 必须与链接时的 hash 相等。因此插件与 TiDB 之间的二进制依赖关系由 Go 运行时自动保证无需框架操心但反过来一旦 TiDB 发布新版本开发者就需要为插件重新编译发布新版本保证 hash 匹配。四、TiDB 插件模型Manifest 与 SPIGo plugin 只是给了打开共享库的能力TiDB 还需要插件的自我介绍来知道自己该如何与库交互。这套自描述元数据即Manifest清单。4.1 Manifest 需要回答的问题设计文档梳理了框架希望从插件元数据中获得的信息每一项都有明确用途元数据用途说明插件名NameTiDB 需要支持 reload即用不同版本加载同一插件因此标识层级要高于pluginpath插件版本Version便于维护与多版本管理简单依赖检查RequireVersion现实中常出现插件 A 依赖插件 B 的某个新逻辑维护插件之间的最小版本关系配置SysVars插件是独立模块应像普通 MySQL 变量一样引入专属系统变量用户可像调普通变量一样调整插件行为统计Stats插件会引入新的统计信息TiDB 使用 Prometheus插件可方便地向 Prometheus 推送指标插件类别与灵活 SPITiDB 的插件类别是有限的新插件需要选定一个 kind 并实现该 kind 定义的 SPI以上信息共同构造成插件的元数据即Manifest。框架的交互模式由此变得非常干净只用 Go plugin 机制完成加载加载成功后插件向框架交出一个 Manifest之后框架只与 Manifest 交互。其中只有 load/lookup 阶段是重量级的 CGO 调用后续对 Manifest 的调用都是普通的 Go 方法调用。4.2 SPI 的定义Manifest 与子 Manifest插件 SPI 由两部分组成一个返回 Manifest 的方法如PluginManifest()Manifest 本身携带的信息。返回 Manifest 的方法可以由pluginpkg自动生成因此插件开发者在实现 SPI 时实际要做的是选择并构造对应的 Manifest。Manifest是所有子 Manifest 的基类结构调用方通过Kind字段结合DeclareXXManifest系列函数将其转换为对应的子 Manifest见 4.4。设计文档给出的通用 Manifest 代码模型如下type Manifest struct { Kind Kind Name string Description string Version uint16 RequireVersion map[string]uint16 License string BuildTime string SysVars map[string]*variable.SysVar Validate func(ctx context.Context, manifest *Manifest) error OnInit func(ctx context.Context) error OnShutdown func(ctx context.Context) error }Manifest 提供三类公共元数据Kind插件类别。当时已有 Audit审计、Authentication鉴权等类别且易于继续扩展Name插件名用于唯一标识一个插件不能与其他插件重复Version / RequireVersion允许向 TiDB 加载同一插件的多个版本但只激活其中一个以支持热修复或热升级RequireVersion用于表达插件间的最小依赖版本关系。同时 Manifest 暴露三个生命周期扩展点Validate在所有插件都加载完成之后、OnInit之前被调用可在此做跨插件的校验例如鉴权插件校验--with-skip-grant-tables配置OnInit插件在正式工作前用其准备资源OnShutdown插件退出通常是 TiDB 关闭前用它清理外部资源。基于Kind可以为鉴权插件、审计插件等定义各自的子 Manifest。所有子 Manifest 都把Manifest匿名内嵌作为结构体的第一个字段因此任何子 Manifest 都可以通过unsafe.Pointer强转回Manifest。例如审计插件Audit的 Manifest 形态如下type AuditManifest struct { Manifest NotifyEvent func(ctx context.Context) error }选择内嵌结构体 unsafe.Pointer强转而不是 Go interface 的原因设计文档给出的解释是前者在数据成员访问上比固定接口更灵活、更高效。同时框架通过打包工具与 helper 方法把这类底层细节对插件开发者完全隐藏。4.3 当前仓库中的 Manifest 实现对照上述结构是 2018 年的设计模型。当前仓库中基础 Manifest 落在 pkg/plugin/spi.go字段演进如下可作为研读源码的索引// Manifest describes plugin info and how it can do by plugin itself. type Manifest struct { Name string Description string RequireVersion map[string]uint16 License string BuildTime string // Validate defines the validate logic for plugin. // returns error will stop load plugin process and TiDB startup. Validate func(ctx context.Context, manifest *Manifest) error // OnInit defines the plugin init logic. // it will be called after domain init. // return error will stop load plugin process and TiDB startup. OnInit func(ctx context.Context, manifest *Manifest) error // OnShutDown defines the plugin cleanup logic. // return error will write log and continue shutdown. OnShutdown func(ctx context.Context, manifest *Manifest) error // OnFlush defines flush logic after executed flush tidb plugins. // it will be called after OnInit. // return error will write log and continue watch following flush. OnFlush func(ctx context.Context, manifest *Manifest) error flushWatcher *flushWatcher Version uint16 Kind Kind }可以看到落地版相比设计稿有两处显著演进新增了OnFlush扩展点与私有的flushWatcher字段用于支持flush tidb plugins时的配置热刷新见第七章各回调签名统一带上了manifest *Manifest参数方便回调读取自身元数据例如读默认配置值。类型转换入口为ExportManifestpkg/plugin/spi.go其实现正是设计文档所说的unsafe.Pointer强转它把任意子 Manifestreflect.ValueOf(m).Pointer()直接转为*Manifest首地址。框架侧则用DeclareAuditManifest、DeclareAuthenticationManifest、DeclareSchemaManifest、DeclareDaemonManifest四个 helperpkg/plugin/helper.go把基类 Manifest 反向强转为子 Manifest。当前仓库定义的可选 Kind 位于 pkg/plugin/const.goKind取值含义Audit1审计类插件可观察连接事件与语句执行事件Authentication2鉴权类插件Schema3可改变 TiDB schema 的插件Daemon4可作为后台任务运行的插件对应的子 Manifest 定义在 pkg/plugin/spi.go 与 pkg/plugin/audit.goAuthenticationManifest预留了AuthenticateUser、GenerateAuthenticationString、ValidateAuthenticationString、SetSalt四个鉴权扩展点审计类AuditManifest则提供以下 SPI 扩展点扩展点语义依据 pkg/plugin/audit.go 注释OnConnectionEvent(ctx, event ConnectionEvent, info *variable.ConnectionInfo) errorTiDB 收到/断开客户端连接时被调用返回 error 会被忽略并关闭当前连接OnGeneralEvent(ctx, sctx *variable.SessionVars, event GeneralEvent, cmd string)TiDB 执行语句期间被调用OnGlobalVariableEvent(ctx, sctx, varName, varValue)全局变量被修改时被调用OnParseEvent(ctx, sctx, event ParseEvent) error语句解析前后被调用事件枚举也在 pkg/plugin/audit.go 中连接事件ConnectionEvent含Connected / Disconnect / ChangeUser / PreAuth / Rejectaudit.go#L64-L78通用语句事件GeneralEvent含Starting / Completed / Erroraudit.go#L28-L38。五、打包工具 pluginpkg从 manifest.toml 到 .so为了让插件格式统一、并隐藏 4.2 中的 Manifest 构造细节框架提供了打包工具pluginpkg源码见 cmd/pluginpkg/pluginpkg.go。插件开发者不再需要手工书写 Manifest 与unsafe.Pointer转换代码只需在插件包里提供一份manifest.toml。5.1 manifest.toml 字段全解设计文档给出的manifest.toml示例为name conn_ip_example kind Audit description just a test version 2 license sysVars [ {nameconn_ip_example_test_variable, scopeGlobal, value2}, {nameconn_ip_example_test_variable2, scopeSession, value2}, ] validate Validate onInit OnInit onShutdown OnShutdown export [ {extPointOnGeneralEvent, implOnGeneralEvent} ]各字段含义依据设计文档name插件名在已加载插件的 TiDB 实例中必须唯一kind插件类别决定该插件在 TiDB 中的挂载点call-pointpluginpkg也据此生成不同的 Manifest 类型version插件版本号同一插件的同一版本只会被加载一次description插件用途说明license插件许可证会显示在show plugins结果中sysVars以 name/scope/default 声明该插件需要的系统变量validate指定加载前的校验回调函数例如鉴权插件在此检查--with-skip-grant-tables配置onInit指定初始化回调插件在正式工作前调用它完成初始化onShutdown指定关闭回调插件关闭通常是 TiDB 关闭时调用以释放其持有的外部资源export针对特定 kind 定义回调列表例如鉴权插件用NotifyEvent方法实现notifyEvent扩展点在当前实现里审计插件则在此把OnGeneralEvent/OnConnectionEvent等导出到 Manifest。pluginpkg依据这些配置生成PluginManifest()代码、编译成 Go 插件从而把插件二进制的格式也一并规范化具体约定如下插件文件名固定为[pluginName]-[version].so从文件名即可获知插件版本pluginpath被固定为[pluginName]-[version]从而允许在同一宿主进程中加载同一插件的不同版本规避第三章所述的 Go 同pluginpath重复加载限制打包工具还会把编译时间等杂项信息写入 ManifestBuildTime字段。打包工具在 Manifest 之上增加了一层抽象未来即使 Manifest 结构需要演进也可在打包工具内平滑消化。5.2 仓库中的真实示例conn_ip_example当前仓库自带一个可运行的审计类示例插件 pkg/plugin/conn_ip_example其manifest.toml为name conn_ip_example kind Audit description just a test version 1 license # Suggested: APLv2 or GPLv3. See https://choosealicense.com/ for details validate Validate onInit OnInit onShutdown OnShutdown export [ {extPointOnGeneralEvent, implOnGeneralEvent}, {extPointOnConnectionEvent, implOnConnectionEvent} ]其核心实现 pkg/plugin/conn_ip_example/conn_ip_example.go 演示了一个审计插件该有的全部要素Validate(ctx, m)在OnInit之前被调用示例只打印日志OnInit(ctx, manifest)在 domain 初始化之后被调用示例在此通过variable.RegisterSysVar向 TiDB 注册一个自己的系统变量conn_ip_example_key带Validation/SetSession/SetGlobal钩子并重置连接计数器OnShutdown(ctx, manifest)清理插件资源OnGeneralEvent(ctx, sctx, event, cmd)读取会话状态、StmtCtx中的 SQL 原文与 digest、涉及的表、执行用户并按Starting/Completed/Error打印事件OnConnectionEvent(ctx, event, info)打印连接来源 host、用户、库名、连接类型并累加全局连接计数器。5.3 pluginpkg 的实际工作流程与命令阅读 cmd/pluginpkg/pluginpkg.go 的源码可以看到打包流程实际分四步解析参数--pkg-dir插件源码包目录、--out-dir产物输出目录为必填--pgo-file可选传入 profile-guided optimization 文件与--next-gen是否按 next-gen 特性构建为可选读取并校验 manifest用 TOML 解析器读取pkg-dir/manifest.toml写入buildTime校验pluginName与pkg-dir目录同名plugin package must be same with plugin name in manifest file即包目录名必须等于 manifest 里的插件名生成代码向插件包写入临时文件pluginName.gen.go内容基于codeTemplate模板——自动生成一个main包里的PluginManifest() *plugin.Manifest函数内部通过plugin.ExportManifest构造plugin.{{kind}}Manifest并把manifest.toml的validate/onInit/onShutdown/export全部填入对应回调package main import ( github.com/pingcap/tidb/pkg/plugin ) func PluginManifest() *plugin.Manifest { return plugin.ExportManifest(plugin.{{.kind}}Manifest{ Manifest: plugin.Manifest{ Kind: plugin.{{.kind}}, Name: {{.name}}, Description: {{.description}}, Version: {{.version}}, RequireVersion: map[string]uint16{}, License: {{.license}}, BuildTime: {{.buildTime}}, Validate: {{.validate}}, OnInit: {{.onInit}}, OnShutdown: {{.onShutdown}}, }, {{range .export}}{{.extPoint}}: {{.impl}},{{end}} }) }编译插件以插件包目录为工作目录执行go build -tagscodes -buildmodeplugin -o out-dir/name-version.so pkg-dirpluginpkg.go中设置了GO111MODULEon环境随后删除临时.gen.go文件并打印打包结果与 Manifest 详情。因此打包命令形如# 1. 先构建 pluginpkg 工具 go build -o pluginpkg ./cmd/pluginpkg # 2. 将插件源码包含 manifest.toml 与 SPI 实现打成 .so ./pluginpkg --pkg-dir ./pkg/plugin/conn_ip_example --out-dir /data/deploy/tidb/plugin产物为/data/deploy/tidb/plugin/conn_ip_example-1.so随后即可放入 TiDB 的插件部署目录。需要注意的是-buildmodeplugin要求整个插件含间接依赖 TiDB与宿主 TiDB 使用同一份依赖树编译见第四章依赖说明因此打包必须在对应 TiDB 版本源码树中进行。六、插件点Plugin PointTiDB 侧如何调用插件TiDB 代码中几乎任何位置都可以新增插件点标准调用范式是三步用plugin.GetByKind或plugin.Get当前源码中为plugin.Get/plugin.ForeachPlugin找到匹配的插件用plugin.Declare[Kind]Manifest把基类 Manifest 强转为特定 kind 的子 Manifest调用子 Manifest 暴露的扩展点方法。设计文档特别以clientConn#Run与conn_ip_example插件为例说明了这种用法。在当前仓库中这个调用点已经落地在服务端连接处理代码里例如 pkg/server/conn.go#L1840-L1846 会在语句执行的关键阶段遍历所有 Audit 插件并派发OnGeneralEventerr : plugin.ForeachPlugin(plugin.Audit, func(p *plugin.Plugin) error { audit : plugin.DeclareAuditManifest(p.Manifest) if audit.OnGeneralEvent ! nil { ... audit.OnGeneralEvent(ctx, cc.ctx.GetSessionVars(), eventType, cmd) } })连接事件则在 pkg/server/server.go#L642 与 pkg/server/conn.go#L2857 附近派发先用plugin.ForeachPlugin(plugin.Audit, ...)找到所有已就绪的审计插件再DeclareAuditManifest后调用OnConnectionEvent。插件的统一状态机见第九章保证了只有Ready且未被禁用的插件才会收到事件。新增一个插件点需要修改 TiDB 代码把所需 context 与参数透传进来——这也是插件机制留给主仓库侧的唯一成本。七、配置管理插件系统变量每个插件都有自己的配置。TiDB 插件选择用**系统变量system variables**统一承载配置管理在设计稿中插件通过manifest.toml的sysVar字段声明变量名、作用域与默认值这些变量会被注册为 TiDB 系统变量用户可以像读写普通系统变量一样读写它们实现方式原设计文档描述是把插件变量在bootstrap之前注入variable.SysVars后续doDMLWorker会像处理普通系统变量一样处理它们同时修改loadCommonGlobalVarsSQL以便启动时加载这些全局变量插件不可卸载、reload 时不可修改 sysVar这两条限制让该实现变得简单存在两条硬性约定插件变量名必须以插件名为前缀如果改动插件的 sysVar默认值、增删变量该插件将无法 reload。对比当前仓库源码可以看到机制的演进spi.go中的基础Manifest已不再携带SysVars字段插件自定义系统变量改为在OnInit中通过variable.RegisterSysVar程序化注册——conn_ip_example的OnInit正是这样注册conn_ip_example_key的pkg/plugin/conn_ip_example/conn_ip_example.go#L49-L76并在OnInit/OnShutdown中通过variable.GetSysVar(conn_ip_example_key)读取默认值。另外TiDB 自身也把两个启动参数暴露为只读实例级系统变量ScopeInstanceReadOnly见 pkg/sessionctx/variable/sysvar.go#L670-L674底层直接读取config.GetGlobalConfig().Instance.PluginLoad / PluginDir。八、插件间依赖Go 运行时校验 RequireVersion依赖问题在 TiDB 插件框架里分两层解决第一层编译期/加载期依赖一致性交给 Go 运行时。Go 插件机制会检查所有依赖包的 hash确保链接期与运行期使用的是同一版本设计文档引用了runtime/plugin.go中基于pluginpath的校验逻辑因此插件依赖某个 TiDB 内部组件这类编译级依赖无需框架额外处理。第二层插件之间的逻辑依赖用RequireVersion声明。现实世界中可能存在这样的场景某人写了一个授权插件它逻辑上依赖 vault 插件——只有 vault 启用时它才工作但它的源码并不直接 import vault。设计文档给出的解法是在manifest.toml中用requireVersion声明A 插件要求 B 插件为 X 版本插件运行时会在加载或 reload 阶段完成检查。该检查在当前源码 pkg/plugin/plugin.go#L123-L137 的(*Plugin).validate中实现遍历插件的RequireVersion映射若依赖组件未加载或已加载版本小于所需版本则抛出errRequireVersionCheckFail。这印证了设计文档简单 Required-Version 关系的定位——它只做最小版本检查不承担完整语义依赖解析。九、加载、Reload 与运行时状态机9.1 Go 无法卸载但可以多版本共存Go plugin 不支持卸载但这并不妨碍框架加载同一插件的多个版本框架会保证最后 reload 进来的版本处于激活状态其余版本不被卸载而只是被禁用disabled。因此可以使用pluginpkg打包的另一个版本来 reload 插件从而修改插件实现逻辑。虽然目前无法在 reload 时修改插件元数据如 sysVars但用于 Bug 热修复仍然很有价值。这一设计在当前实现中体现为插件版本化的注册表copyOnWriteContextCOW 语义包裹的plugins结构以map[Kind][]Plugin收集已加载插件、以map[string]uint16记录各插件已加载的最新版本pkg/plugin/plugin.go#L41-L80。9.2 启动加载与生命周期框架核心运行时在 pkg/plugin/plugin.goLoad(ctx, cfg)plugin.go#L141-L199对cfg.Plugins中每个插件ID依次执行用ID.Decode()解析出 name 与 version按最后一个-分隔见 helper.go#L52-L61→ 检查是否重复加载 →gplugin.Open(dir/插件ID.so)→Lookup(ManifestSymbol)符号常量PluginManifest见 spi.go#L23-L29→ 类型断言为func() *Manifest并执行得到 Manifest → 校验 Manifest 中的 name/version 与插件 ID 一致。所有插件加载完成后进入跨插件校验阶段逐一调用validate先做RequireVersion依赖检查再调用插件自定义Validate回调。设计文档要求的Validate 在所有插件加载完成后、onInit 之前调用由此实现Init(ctx, cfg)plugin.go#L203-L248必须紧跟在Load之后、任何其他插件方法调用之前执行。它遍历每个插件调用OnInit初始化成功后将插件置为Ready若插件声明了OnFlush且提供了 etcd client还会启动一个flushWatcher监听/tidb/plugins/插件名的 etcd 变更用于多节点间同步启用/禁用与配置刷新Shutdown(ctx)plugin.go#L428-L453TiDB 关闭时把所有插件置为Dying取消各自的 flushWatcher并逐个调用OnShutdown清理资源调用失败仅记日志不阻断关闭。配置Configplugin.go#L88-L94除插件列表、插件目录外还支持SkipWhenFail加载/校验/初始化失败时跳过并禁用仅告警不阻断启动与EnvVersion把运行环境组件版本注入版本表供RequireVersion比对。插件状态定义在 pkg/plugin/const.go#L46-L57共四种Uninitialized未初始化、Ready就绪可工作、Dying即将关闭、Disable已禁用。StateValue()plugin.go#L106-L112进一步输出状态-启用标记如Ready-enable、Disable-disable。9.3 加载时机启动参数与 bootstrap 时序在真实部署中两个启动参数驱动加载cmd/tidb-server/main.go#L259-L260-plugin-dir指定存放插件的目录默认/data/deploy/plugin例如-plugin-dir/data/deploy/tidb/plugin-plugin-load指定需要加载的插件 idname-version多个用逗号分隔例如-plugin-loadconn_limit-1。这两个参数会被映射到配置结构 pkg/config/config.go#L716-L717 的Instance.PluginDir / Instance.PluginLoad对应 TOML 键plugin_dir/plugin_load默认值见 config.go#L1221-L1222。加载真正发生在会话引导阶段TiDB 启动时若config.GetGlobalConfig().Instance.PluginLoad非空则在 pkg/session/session.go#L4513-L4521bootstrap 早期运行 DDL 建表/系统库引导之前执行plugin.Load随后在 domain 初始化完成、系统变量缓存等就绪后于 session.go#L4653-L4658 执行plugin.Init并把dom.GetEtcdClient()作为 etcd client 传入以支持 flushWatcher。这与设计文档必须在 bootstrap 前注入全局变量信息的意图一脉相承。9.4 查询与日常管理 SQL加载完成后可用 SQL 查看全部插件状态。设计文档给出的输出为mysql show plugins; ------------------------------------------------------------------------------------------------------ | Name | Status | Type | Library | License | Version | ------------------------------------------------------------------------------------------------------ | conn_limit-1 | Ready | Audit | /data/deploy/tidb/plugin/conn_limit-1.so | | 1 | ------------------------------------------------------------------------------------------------------ 1 row in set (0.00 sec)当前实现的列含义与上述输出完全对应——fetchShowPluginspkg/executor/show.go#L2088-L2096对每个插件输出{Name, StateValue(), Kind.String(), Path, License, Version}即 Name插件名-版本、Status如Ready-enable、TypeKind如Audit、Library.so绝对路径、License、Version。reload 一个已加载插件的 SQL 在设计文档中给出mysql admin plugins reload conn_limit-2;其语义是加载一个由pluginpkg打包的、版本号不同的新.so此处为 version 2框架通过[pluginName]-[version]的pluginpath与文件名约定允许多版本共存最后加载的版本被置为激活。对照当前仓库代码ADMIN PLUGINS语句的落地执行器 pkg/executor/admin_plugins.go 定义了Enable/Disable两种动作对应计划层 pkg/planner/core/common_plans.go#L200-L205 的Enable/Disable通过ChangeDisableFlagAndFlush修改禁用标记并经 etcd 通知所有 TiDB 节点同步plugin.go#L542-L557flush tidb plugins语句则在 pkg/executor/simple.go#L3496-L3504 调用plugin.NotifyFlush触发插件的OnFlush。若想确认你所用 TiDB 版本支持的ADMIN PLUGINS动作集合请以该版本源码pkg/planner/core中的AdminPluginsAction定义为准。十、插件框架的边界与局限设计文档明确列出 TiDB 插件的如下限制这些约束至今仍然成立且大多由 Go plugin 机制本身决定插件无法被卸载。一旦加载进 TiDB只能等服务重启但可在有限场景下通过 reload 不同版本来热修复插件 Bug在OnInit中读取系统变量会得到预期之外的值此时应访问Manifest获取默认配置值源码中回调签名携带manifest *Manifest即为此设计reload 不能修改 sysVar 的默认值也不能增删变量构建插件必须在 TiDB 源码树中进行——这与 MySQL 多数插件可脱离服务端单独构建Information Schema 与存储引擎插件除外不同插件只能用 Go 编写受-buildmodeplugin限制。十一、从设计文档到运行框架代码阅读地图如需继续深入建议按以下顺序阅读当前仓库中的相关实现均与本设计文档一一对应设计文档docs/design/2018-12-10-plugin-framework.md框架运行时与生命周期Load/Init/Shutdown/ForeachPlugin位于 pkg/plugin/plugin.goManifest/SPI 定义与转换 helperpkg/plugin/spi.go、pkg/plugin/helper.go审计 SPI 的事件模型pkg/plugin/audit.goKind / State 枚举pkg/plugin/const.go打包工具manifest 解析、代码生成、-buildmodeplugin编译cmd/pluginpkg/pluginpkg.go端到端示例插件与测试pkg/plugin/conn_ip_example/manifest.toml、pkg/plugin/conn_ip_example/conn_ip_example.go、pkg/plugin/conn_ip_example/conn_ip_example_test.go集成测试加载/初始化测试钩子LoadPluginForTestpkg/plugin/helper.go、pkg/plugin/integration_test.go启动参数注册与默认值cmd/tidb-server/main.go、配置项 pkg/config/config.go#L716-L717引导期加载与初始化时机pkg/session/session.go#L4513-L4521、pkg/session/session.go#L4653-L4658插件点调用示例Audit 事件派发pkg/server/conn.go#L1840-L1846、pkg/server/server.go#L642show plugins与管理语句实现pkg/executor/show.go#L2088-L2096、pkg/executor/admin_plugins.go、pkg/executor/simple.go#L3496-L3504一句话总结TiDB 插件框架用 Go plugin 解决怎么把.so打开用 Manifest 解决打开之后 TiDB 怎么理解它用pluginpkg解决开发者怎么最省力地生产它再用[pluginName]-[version]的命名与pluginpath约定解决怎么在 Go 无法卸载插件的前提下完成版本热替换——四层设计层层递进构成了一个对开发者友好、对 TiDB 可控的插件体系。【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表