TiDB 插件框架深度解析:从 manifest 定义到 SPI 扩展点
数据库分布式数据库后端OLAP【免费下载链接】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点击查看免费下载TiDB 的插件框架Plugin Framework是官方为定制化需求提供的标准化扩展机制它基于 Go 1.9 的原生 plugin 能力通过统一的 Manifest元数据描述、SPI服务提供接口和打包工具pluginpkg让开发者可以在不改动 TiDB 主仓库的前提下为 TiDB 增加审计、认证等自定义能力。本文将结合设计文档 docs/design/2018-12-10-plugin-framework.md 与 pkg/plugin 下的完整实现带你走通从编写manifest.toml、实现 SPI 回调、用pluginpkg打包.so到通过启动参数加载、用 SQL 管理与热更新的全流程并深入解析加载生命周期与源码级接入点。为什么 TiDB 需要插件框架很多定制化需求如企业级审计、自定义认证、连接限制非常有价值但将这类与特定业务绑定的逻辑合并进 TiDB 主仓库并不合适既增加了主干维护负担也不利于生态共建。插件框架的引入正是为了在不侵入核心代码的前提下解决这类需求同时借助 Go 1.9 新增的原生插件支持吸引更多开发者一起构建 TiDB 生态。从实现角度看框架的价值在于统一Go 的原生 plugin 只解决了能否动态加载一个.so的问题而 TiDB 插件框架在其之上进一步统一了插件清单manifest、打包格式package与灵活的 SPI让插件具备自描述能力TiDB 加载插件后可以明确知道这个插件是什么、能做什么、该怎么配合。底层基础Go Plugin 机制TiDB 插件框架直接建立在 Go 原生 plugin 机制之上理解它是理解整个框架的前提。Go 的 plugin 使用非常简单核心三步用go build -buildmodeplugin把main包编译成.so动态库用plugin.Open相当于dlopen打开.so用plugin.Lookup相当于dlsym按符号名查找导出符号。框架中对应的实际调用位于 pkg/plugin/plugin.go 的loadManifestByGoPluginplugin.Path filepath.Join(dir, string(pluginID)LibrarySuffix) plugin.library, err gplugin.Open(plugin.Path) manifestSym, err : plugin.library.Lookup(ManifestSymbol) manifest, ok manifestSym.(func() *Manifest)其中LibrarySuffix .so、ManifestSymbol PluginManifest定义在 pkg/plugin/spi.goTiDB 约定每个插件.so都必须导出PluginManifest符号TiDB 通过这个统一入口拿到插件的 Manifest。另一个关键概念是pluginpath它是插件打包后的包路径标识可以通过-ldflags -pluginpath[path-value]显式指定也可以由go build自动生成按 1.11.1 的实现构建目录下的包用包名直接构建源码文件时用内容哈希。pluginpath有两个重要作用符号名会带上pluginpath前缀例如方法DoIt在pluginpath为pkg1时表现为pkg1.DoItGo 运行时用它检测重复加载同一个pluginpath的插件被第二次Open会直接报错。因此 TiDB 插件框架把每个插件的pluginpath固定为[插件名]-[版本]从而在同一个进程内允许多个版本的同一插件共存为后续的版本化热更新奠定基础详见下文 Reload 一节。依赖方面Go 运行时要求依赖包的运行期哈希与链接期哈希一致所以插件依赖某个 TiDB 组件时无需额外操心编译级依赖但这也意味着 TiDB 代码一旦变化随新版本发布时往往需要同步重新编译插件。核心设计Manifest SPITiDB 插件框架围绕两个核心概念展开Manifest插件元数据插件的自描述信息声明插件的类别、名称、版本、依赖、配置变量以及生命周期回调。TiDB 加载插件后只和 Manifest 打交道SPI服务提供接口插件的统一入口约定——每个插件必须导出一个返回 Manifest 的函数即PluginManifest这个函数由打包工具pluginpkg自动生成开发者只需在manifest.toml里声明再实现具体回调函数即可。也就是说整个框架里只有plugin.Open/plugin.Lookup是重量级的 CGO 调用加载完成后的所有交互调用生命周期回调、访问扩展点方法都是普通 Go 方法调用性能开销可控。当前仓库在 pkg/plugin/spi.go 中定义的基类Manifest结构如下type Manifest struct { Name string Description string RequireVersion map[string]uint16 License string BuildTime string Validate func(ctx context.Context, manifest *Manifest) error OnInit func(ctx context.Context, manifest *Manifest) error OnShutDown func(ctx context.Context, manifest *Manifest) error OnFlush func(ctx context.Context, manifest *Manifest) error Version uint16 Kind Kind }各字段与生命周期回调的含义Kind插件类别决定插件在 TiDB 中的调用点Name插件名用于标识插件在已加载的 TiDB 实例中不能重复Version插件版本同一插件可以加载多个版本但只激活其中一个用于热修复/热升级RequireVersion插件间的版本依赖关系Validate在所有插件加载完成后、onInit 之前调用可用于跨插件校验例如认证插件检查-with-skip-grant-tables配置返回 error 会中止加载流程与 TiDB 启动OnInit插件在正式工作前准备资源OnShutdown插件在消亡前清理外部资源通常在 TiDB 关闭时触发返回 error 只记录日志、继续关闭流程OnFlush执行FLUSH TIDB PLUGINS后的刷新逻辑在 OnInit 之后生效。注意设计文档中的SysVars字段在当前的 spi.go 实现中已被移除——插件系统变量改由插件在OnInit里通过variable.RegisterSysVar注册见下文配置管理一节这也对应设计文档中read sysVars in OnInit 会得到意外值但可以通过 Manifest 访问默认配置的限制描述。子 Manifest 与 unsafe.Pointer 转换基于Kind框架定义了多个子 Manifest。以审计插件为例pkg/plugin/audit.go 中的AuditManifesttype AuditManifest struct { Manifest OnConnectionEvent func(ctx context.Context, event ConnectionEvent, info *variable.ConnectionInfo) error OnGeneralEvent func(ctx context.Context, sctx *variable.SessionVars, event GeneralEvent, cmd string) OnGlobalVariableEvent func(ctx context.Context, sctx *variable.SessionVars, varName, varValue string) OnParseEvent func(ctx context.Context, sctx *variable.SessionVars, event ParseEvent) error }关键设计每个子 Manifest 都把基类Manifest作为结构体定义中的第一个匿名嵌入字段因此子 Manifest 可以通过unsafe.Pointer直接转换成基类Manifest。转换由 pkg/plugin/helper.go 中的DeclareAuditManifest等帮助函数完成func DeclareAuditManifest(m *Manifest) *AuditManifest { return (*AuditManifest)(unsafe.Pointer(m)) }选嵌入结构体 unsafe.Pointer转换而非 Go interface 的原因从源码结构看是前者在访问数据成员时更灵活高效且可以让 TiDB 主代码在拿到基类 Manifest 后按需声明成任何子类型避免 interface 带来的固定方法集约束。而插件侧只需要plugin.ExportManifest(m)spi.go把子 Manifest 一键转回基类导出。当前仓库定义了四种插件类别pkg/plugin/const.goAudit审计可捕获连接事件、语句执行事件、全局变量变更、SQL 解析事件Authentication认证自定义认证与口令编码方式SchemaSchema 变更可改变 TiDB schemaDaemon守护任务可作为后台任务运行。对应的子 Manifest 定义在 spi.goAuthenticationManifest提供AuthenticateUser、GenerateAuthenticationString、ValidateAuthenticationString、SetSalt四个扩展点SchemaManifest与DaemonManifest目前只嵌入基类扩展点由后续类别演进填充。插件开发七步实战以 conn_ip_example 为例设计文档给出了一条清晰的七步开发路径仓库 pkg/plugin/conn_ip_example 提供了可直接对照学习的完整示例选择已存在的插件类别如Audit或创建新类别创建一个普通 Go 包包内添加manifest.toml仓库示例 manifest.toml实现所有插件都必须提供的validate、init、destroy对应Validate、OnInit、OnShutdown方法实现类别专属的方法来完成插件逻辑审计插件即OnGeneralEvent、OnConnectionEvent等用cmd/pluginpkg打包插件二进制把.so放到插件部署目录用-plugin-dir与-plugin-load启动参数加载执行show plugins检查加载状态。下面按步骤展开。第 2 步编写 manifest.toml示例插件的manifest.toml仓库路径 pkg/plugin/conn_ip_example/manifest.tomlname 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} ]各字段含义name插件名在已加载的 TiDB 实例中必须唯一kind插件类别决定 TiDB 内的调用点打包工具据此生成不同的子 Manifestversion插件版本同一插件的同一版本只能被加载一次description插件用途描述license插件许可证会显示在show plugins中validate/onInit/onShutdown分别对应Validate、OnInit、OnShutdown回调函数名export类别专属扩展点的回调列表例如认证插件用NotifyEvent实现notifyEvent扩展点示例中把OnGeneralEvent、OnConnectionEvent映射到同名的实现函数。第 3、4 步实现 SPI 回调示例实现位于 pkg/plugin/conn_ip_example/conn_ip_example.go。Validate、OnInit、OnShutdown是每个插件都必须实现的生命周期方法func Validate(ctx context.Context, m *plugin.Manifest) error { fmt.Println(## conn_ip_example Validate called ##) return nil } func OnInit(ctx context.Context, manifest *plugin.Manifest) error { // 在 OnInit 中注册插件自己的系统变量详见配置管理 sv : variable.SysVar{ Name: conn_ip_example_key, Scope: vardef.ScopeGlobal | vardef.ScopeSession, Value: v1, } variable.RegisterSysVar(sv) return nil } func OnShutdown(ctx context.Context, manifest *plugin.Manifest) error { atomic.SwapInt32(connection, 0) return nil }审计插件再实现类别专属方法。OnGeneralEvent在每条语句执行期间被调用可从sctx.StmtCtx读取 SQL 原文、digest、涉及的表、执行用户等信息OnConnectionEvent在连接建立/断开/认证前/变更用户/拒绝等连接事件发生时被调用info.Host、info.User、info.DB、info.ConnectionType提供连接上下文返回值 error 会导致当前连接被关闭func OnGeneralEvent(ctx context.Context, sctx *variable.SessionVars, event plugin.GeneralEvent, cmd string) { if sctx ! nil { digest, _ : sctx.StmtCtx.SQLDigest() fmt.Printf(---- statement sql: %s, digest: %s\n, sctx.StmtCtx.OriginalSQL, digest) fmt.Printf(---- executed by user: %#v\n, sctx.User) } switch event { case plugin.Starting: fmt.Println(---- event: Statement Starting) case plugin.Completed: fmt.Println(---- event: Statement Completed) case plugin.Error: fmt.Println(---- event: ERROR!) } fmt.Printf(---- cmd: %s\n, cmd) } func OnConnectionEvent(ctx context.Context, event plugin.ConnectionEvent, info *variable.ConnectionInfo) error { if r : ctx.Value(plugin.RejectReasonCtxValue{}); r ! nil { fmt.Printf(---- reason: [%s]\n, r.(string)) } fmt.Printf(---- connection host: %s\n, info.Host) atomic.AddInt32(connection, 1) return nil }审计事件枚举定义在 pkg/plugin/audit.goGeneralEventStarting即将开始、Completed已完成、Error出错ConnectionEventConnected认证完成后建连、Disconnect断开、ChangeUser变更用户、PreAuth认证前、Reject拒绝连接拒绝原因通过 context 中的RejectReasonCtxValue传递给插件。此外OnGeneralEvent收到的 context 中还可能携带ExecStartTimeCtxKey语句开始执行时间、PrepareStmtIDCtxKey预处理语句 ID、IsRetryingCtxKey当前执行是否为重试等键值audit.go方便插件感知重试与预处理场景。第 5 步用 pluginpkg 打包打包工具入口是 cmd/pluginpkg/pluginpkg.go用法go run cmd/pluginpkg/pluginpkg.go --pkg-dir [插件源码包目录] --out-dir [插件输出目录]支持的参数-pkg-dir插件源码包目录必填目录名必须与manifest.toml中的name一致工具会校验pluginName ! filepath.Base(pkgDir)时报错退出-out-dir打包产物目录必填-pgo-file可选的 Go PGOProfile-Guided Optimization文件路径传入后构建命令会追加-pgo参数-next-gen是否按 next-gen 特性构建插件追加nextgenbuild tag默认构建标签为codes。工具的实际工作流程对应源码逻辑解析manifest.toml得到插件的元数据并注入当前时间作为buildTime用内建 Go 模板codeTemplate生成[插件名].gen.go文件其中自动生成PluginManifest()函数——它会构造对应类别的子 Manifestplugin.{{.kind}}Manifest填入 name、description、version、license、buildTime 以及validate/onInit/onShutdown/onFlush指定的回调并按export列表把extPoint映射到impl实现函数在pkg-dir下执行go build -tagscodes[,nextgen] -buildmodeplugin -o [out-dir]/[name]-[version].so [pkg-dir]构建成功后打印Package %s as plugin %s success.以及以 JSON 形式输出的 Manifest 详情。打包工具对产物格式做了三处统一使框架能可靠地识别与加载插件插件文件名固定为[插件名]-[版本].so从文件名即可得知版本pluginpath固定为[插件名]-[版本]从而允许同一宿主程序加载同名的不同版本插件Manifest 中自动注入构建时间等杂项信息。因此打包工具在 Manifest 之上提供了一层抽象未来即使 Manifest 结构演进插件开发者的开发方式也不会受影响。配置管理把插件配置变成系统变量每个插件都可能有自己的配置项。设计文档的方案是把插件的配置管理完全交给 TiDB 系统变量sysVar在manifest.toml中通过sysVars字段声明变量名、作用域与默认值插件变量会被注册为 TiDB 系统变量用户可以像读写普通系统变量一样SET/SHOW。实现方式是把插件变量在bootstrap之前加入variable.SysVars之后doDMLWorker会把它们当作普通 sysVar 处理loadCommonGlobalVarsSQL也会加载它们。由于插件不可卸载、reload 时不允许修改 sysVar这套实现可以保持简单。两点约束需要注意插件变量名必须以插件名为前缀违反时触发ErrInvalidPluginSysVarNamePlugin %ss sysVar %s must start with its plugin name %s见 pkg/errno/errname.go如果 reload 时改变了插件的 sysVar包括默认值变化、增删变量插件将无法被 reload对应ErrUnsupportedReloadPluginVar。当前仓库中设计文档里的SysVars字段已不在基类Manifest内插件改为在OnInit中显式调用variable.RegisterSysVar(sv)注册示例见 conn_ip_example.go——它在初始化时注册了一个名为conn_ip_example_key、GlobalSession 双作用域、默认值v1的系统变量并可选挂上Validation值规范化、SetSession会话变更钩子、SetGlobal全局变更钩子回调。注册后即可用标准 SQL 读写SET GLOBAL conn_ip_example_key v2; SHOW VARIABLES LIKE conn_ip_example_key;插件间依赖RequireVersionGo 插件机制只保证编译包依赖版本一致运行期哈希与链接期哈希校验但现实世界中还存在逻辑级依赖例如某个授权插件依赖 vault 插件只有 vault 启用时才工作但两者在源码上并不直接引用。manifest.toml中可通过requireVersion声明A 插件要求 B 插件为 X 版本以上对应基类Manifest.RequireVersion map[string]uint16插件运行时在 load/reload 阶段进行校验。校验逻辑见 pkg/plugin/plugin.goif p.RequireVersion ! nil { for component, reqVer : range p.RequireVersion { if ver, ok : tiPlugins.versions[component]; !ok || ver reqVer { return errRequireVersionCheckFail.GenWithStackByArgs(p.Name, component, reqVer, ver) } } }tiPlugins.versions汇集了运行环境中各组件含插件的版本号——框架的Config.EnvVersion字段plugin.go允许把 Go 工具链等环境组件的版本注入版本表测试代码中即用EnvVersion: map[string]uint16{go: 1112}模拟如 conn_ip_example_test.go。校验失败且未设置SkipWhenFail时加载流程直接返回错误设置SkipWhenFail时该插件会被标记为Disable状态并继续。加载与生命周期Load → Init → Shutdown 与状态机框架的核心加载流程实现在 pkg/plugin/plugin.go分为两个阶段Load(ctx, cfg)plugin.go——必须在 domain 初始化之前调用以便在 bootstrap 期间注入全局变量信息。流程遍历cfg.Plugins中的插件 ID用ID.Decode()helper.go按最后一个-切分出name与version检查重复加载同名插件已在版本表中时SkipWhenFail则告警忽略否则报ErrDuplicatePlugin调用loadOne加载单个插件优先从StaticPlugins静态注册表取 Manifest静态插件不校验版本否则走loadManifestByGoPlugin用plugin.Open打开.so并Lookup(PluginManifest)。加载后校验 Manifest 中的Name与插件 ID 一致、Version与插件 ID 版本一致ErrInvalidPluginName/ErrInvalidPluginVersion所有插件 dl 加载完成后进行跨插件校验RequireVersionValidate回调通过 COWcopy-on-write指针把插件集合发布到全局pluginGlobal。Init(ctx, cfg)plugin.go——必须在Load之后、任何其他插件方法调用之前调用此时 TiDB domain 已经就绪。流程对每个插件调用OnInit失败时按SkipWhenFail处理标记 Disable 或返回错误若插件定义了OnFlush且传入了EtcdClient框架会为它启动一个flushWatcher监听 etcd 键/tidb/plugins/[插件名]值1表示禁用、其他情况为启用收到变更即调用OnFlush并同步本机Disabled标志plugin.go从而让ADMIN PLUGINS ENABLE/DISABLE与FLUSH TIDB PLUGINS能够在集群各节点间联动状态置为Ready。Shutdown(ctx)plugin.go——清理所有插件资源把插件状态置为Dying、取消 flushWatcher、依次调用OnShutdown错误只记日志继续最后清空全局插件表。注意它只是清理资源受 Go plugin 限制并不能真正 unload.so。TiDB 主程序退出时在 cmd/tidb-server/main.go 调用plugin.Shutdown(context.Background())。插件状态机定义在 pkg/plugin/const.goUninitialized→Ready→Dying/Disable。Plugin.StateValue()plugin.go返回状态-启用标志的可读字符串如Ready-enable、Disable-disable。框架还提供一组查询/遍历 APIplugin.goGet(kind, name)按类别与名称查找插件ForeachPlugin(kind, fn)遍历指定类别的所有Ready 且未禁用的插件执行器内部的统一回调入口IsEnable(kind)判断某类别是否有启用的插件GetAll()返回全部插件的map[Kind][]Plugin。静态插件注册表除了.so动态加载框架还提供StaticPlugins注册表plugin.go允许 TiDB 内部其他包不经过plugin.Open直接以代码方式注册插件Add/Get/Clear。loadOne中静态插件优先级最高plugin.go且不校验版本号——这一点在 plugin_test.go 的TestLoadStaticRegisteredPlugin中有明确测试静态插件用不存在的目录也能加载成功且用tpluginstatic-2这类未注册版本也能通过。插件接入点Plugin PointTiDB 如何调用插件在 TiDB 主代码中任何需要扩展的位置都可以成为插件接入点。标准调用模式三步设计文档原文用plugin.GetByKind或plugin.Get找到匹配插件当前实现为ForeachPlugin/IsEnable用plugin.Declare[Kind]Manifest把基类 Manifest 转换为对应子类型调用子类型的扩展点方法。以审计插件为例仓库中的实际接入点包括连接事件pkg/server/server.go 与 server.gocheckAuditPlugin在握手前调用PreAuth事件返回 error 会直接关闭连接可用于 IP 白名单等准入控制握手失败时以Reject事件调用OnConnectionEvent拒绝原因通过plugin.RejectReasonCtxValue{}塞入 context连接建立Connected、断开Disconnect事件同样在这里触发Disconnect时还会填充连接时长Duration。语句事件pkg/server/conn.go 的audit()与 pkg/executor/adapter.go每条 SQL 执行过程中调用OnGeneralEvent命令字串通过mysql.Command2Str映射如Querycontext 中携带ExecStartTimeCtxKey、PrepareStmtIDCtxKeyEXECUTE语句、IsRetryingCtxKey重试标记。集成测试佐证pkg/plugin/integration_test.go 的TestAuditLogNormal用真实 mock 服务器跑完上百条 DDL/DML/SHOW 语句逐条断言OnGeneralEvent收到的 SQL 原文、stmtType、涉及的库表、Starting/Completed事件顺序与重试计数是理解接入点行为的最佳测试样例。管理与运维加载、查看、启停与刷新启动加载TiDB 通过两个启动参数加载插件-plugin-dir指定存放插件的目录。命令行默认值为/data/deploy/plugin见 cmd/tidb-server/main.go与配置文件默认值一致pkg/config/config.go-plugin-load指定需要加载的插件 ID插件名-版本多个插件用逗号分隔例如-plugin-loadconn_ip_example-1默认空不加载任何插件。主程序在 cmd/tidb-server/main.go 把这些命令行参数写入cfg.Instance.PluginLoad/cfg.Instance.PluginDir随后 pkg/session/session.go 在 bootstrap 阶段按逗号切分调用plugin.Load在 domain 初始化完成后调用plugin.Initsession.go此时传入EtcdClient以支持 flushWatcher。同时TiDB 支持在配置文件里以 TOML 形式配置插件pkg/config/config.go 的Plugin结构dir与load老配置路径[plugin] dir/load会迁移到[instance] plugin_dir/plugin_load配置兼容映射见 config.go。完整示例[instance] plugin_dir /data/deploy/plugin plugin_load conn_ip_example-1 # 审计日志缓冲字节0 表示不缓冲 plugin_audit_log_buffer_size 0 # 审计日志刷盘间隔秒仅在缓冲大小大于 0 时生效 plugin_audit_log_flush_interval 30PluginAuditLogBufferSize/PluginAuditLogFlushInterval两个配置项也定义在 config.go服务于审计类插件的日志缓冲。查看插件状态用SHOW PLUGINS查看执行器实现在 pkg/executor/show.go每行输出Name、StateValue状态-启用标志、Kind.String()、Path.so全路径、License、Versionmysql 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)示例沿用设计文档中的 conn_limit 插件本仓库示例插件为 conn_ip_example。启用 / 禁用 / 刷新SHOW PLUGINS之外当前仓库支持的运维 SQL 如下ADMIN PLUGINS ENABLE plugin1[, plugin2...]/ADMIN PLUGINS DISABLE plugin1[, plugin2...]切换插件的启用标志并广播到集群。语法定义见 pkg/parser/ast/misc.go解析测试见 pkg/parser/parser_test.go执行链路为planbuilder.go的AdminPluginEnable/Disable→ pkg/planner/core/common_plans.go 的AdminPlugins计划 → pkg/executor/admin_plugins.go 的AdminPluginsExec→plugin.ChangeDisableFlagAndFlushplugin.go写 etcd 键/tidb/plugins/[插件名]。FLUSH TIDB PLUGINS plugin1[, plugin2...]触发已定义OnFlush的插件执行刷新逻辑实现见 pkg/executor/simple.go 与plugin.NotifyFlushplugin.go解析测试见 parser_test.go。关于 Reload热更新设计文档中提出的 reload 语义是Go plugin 不支持卸载但框架通过加载多个版本、最新加载的激活、其余禁用的方式实现受限的热更新——用pluginpkg打出新版本[插件名]-[新版本号].so加载同名新版本后它成为活动插件旧版本只是未卸载但禁用。这正是pluginpath [插件名]-[版本]设计的原因不同版本的pluginpath不同Go 运行时的重复加载检测不会拦截。设计文档给出mysql admin plugins reload conn_limit-2;作为 reload 示例。需要说明的是该语法属于 2018 年设计稿的提案形态当前仓库中ADMIN PLUGINS子句已演化为ENABLE/DISABLE见 pkg/parser/ast/misc.go 与 admin_plugins.go并配套FLUSH TIDB PLUGINS触发OnFlush。同时 pkg/errno/errname.go 中保留了ErrUnsupportedReloadPluginPlugin %s isnt loaded so cannot be reloaded与ErrUnsupportedReloadPluginVarreload 时变更 sysVar 不被支持两条错误表明受限热更新的语义延续至今reload 可以换实现逻辑但元信息sysVar 等不可变。限制与注意事项设计文档与当前实现共同确认了以下边界开发与运维前务必了解插件无法卸载一旦加载只能重启服务器才能移除但可以在受限场景下 reload 新版本来热修复插件 bugOnInit 中读取系统变量可能得到意外值插件变量在 OnInit 时才完成注册此时可访问Manifest获取默认配置值当前实现中SysVars已移出 Manifest默认值应在插件代码内维护或通过variable.GetSysVar在注册后读取如示例 conn_ip_example.go 所示reload 不能改变 sysVar 默认值也不能增删变量构建插件需要 TiDB 源码树与 MySQL 可独立构建插件Information Schema 与存储引擎插件除外不同TiDB 插件必须基于同一份源码树编译以保证依赖哈希一致插件只能用 Go 编写因为它是基于 Go 的-buildmodeplugin机制。测试与验证框架自带两层测试可直接作为开发参照单元级pkg/plugin/plugin_test.go 覆盖静态插件注册、版本不校验、优先级等机制pkg/plugin/helper_test.go 与 pkg/plugin/spi_test.go 覆盖 Manifest 声明转换与 ID 编解码conn_ip_example_test.go 则演示了完整流程用plugin.SetTestHook注入加载钩子 →plugin.Load→plugin.Init→ForeachPlugin触发事件 → 断言连接计数 →plugin.Shutdown后断言清零。集成级pkg/plugin/integration_test.go 的TestAuditLogNormal用 mock server 跑真实 SQL逐条验证审计事件捕获的完整性与正确性是插件接入点行为的权威参考。小结TiDB 插件框架 Go plugin 动态加载 统一 Manifest 自描述 类别化 SPI pluginpkg标准化打包 系统变量配置 版本化受限热更新。它把扩展 TiDB这件事收敛为七个固定步骤既保证了插件与主仓库的解耦又通过Kind与子 Manifest 让 TiDB 主代码可以类型安全地调用插件能力。如果你需要为企业 TiDB 补充审计、自定义认证或后台任务能力直接对照 pkg/plugin/conn_ip_example 示例起步即可。赞分享数据库分布式数据库后端OLAP【免费下载链接】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点击查看免费下载相关推荐Dubbo SPI 机制深度解析从 SPI、Adaptive 到自定义协议扩展实战advanced-javaDubbo SPI 机制深度解析从 SPI、Adaptive 到自定义协议扩展实战advanced java 本篇技术指南以 advanced jav文档教程知识库后端TiDB Plugin Framework 插件框架设计解析从 Go Plugin 到可热升级的 SPI 插件体系TiDB Plugin Framework 插件框架设计解析从 Go Plugin 到可热升级的 SPI 插件体系 TiDB Plugin Framework数据库分布式数据库后端OLAP深入解析 DolphinScheduler Alert SPI从告警插件架构到自定义插件开发实战深入解析 DolphinScheduler Alert SPI从告警插件架构到自定义插件开发实战 本指南以 Apache DolphinScheduler 的任务调度数据编排工作流自动化后端大数据上一篇kubectl-view-allocations终极Kubernetes资源分配可视化工具3分钟掌握集群资源全景下一篇Brave浏览器多语言支持终极指南如何实现全球化用户体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考