ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TiDB 受限只读模式(Restricted Read Only)端到端测试全解析:readonlytest 环境搭建、用例剖析与实现原理

TiDB 受限只读模式(Restricted Read Only)端到端测试全解析:readonlytest 环境搭建、用例剖析与实现原理 TiDB 受限只读模式Restricted Read Only端到端测试全解析readonlytest 环境搭建、用例剖析与实现原理【免费下载链接】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 的tidb_restricted_read_only全局变量可以让整个集群最终进入只读状态连 SUPER 权限用户也无法写入是云上运维、灾备切换、数据迁移等场景下的关键安全开关。本指南以仓库中 tests/readonlytest/README.md 描述的端到端E2E测试为主线完整讲解如何手动搭建双 TiDB 实例测试环境、运行go test验证只读行为并深入剖析测试背后的源码实现变量定义、规划期拦截、提交期二次校验与权限模型帮助你既会跑测试也能看懂 TiDB 只读模式的底层原理。一、测试背景TiDB 的受限只读模式是什么在理解测试之前先明确被测对象。TiDB 提供了两个与只读相关的全局系统变量定义于 pkg/sessionctx/vardef/tidb_vars.gotidb_restricted_read_onlyTiDB 特有的受限只读开关一旦开启集群对所有用户包括拥有 SUPER 或 CONNECTION_ADMIN 权限的用户最终进入只读状态只有被显式授予RESTRICTED_REPLICA_WRITER_ADMIN动态权限的账号可以继续写入tidb_super_read_onlyTiDB 对 MySQLsuper_read_only的变体实现与 MySQL 版本存在差异只读检查同样作用于普通 DML但权限门槛相对更低。根据仓库中的设计文档 docs/design/2021-06-23-restricted-read-only.mdtidb_restricted_read_only的设计动机是TiDB 原本的read_only/super_read_only只是名义存在而未真正生效而 MySQL 在开启只读时可能因存在表锁或正在提交的事务而失败或阻塞TiDB 的受限只读不构建在这两个变量之上而是独立实现最终只读语义开启操作立即返回成功随后通过 PD 将变更广播到集群内所有 TiDB 节点。两个变量在 pkg/sessionctx/variable/sysvar.go 中注册默认值均为OFF见 pkg/sessionctx/vardef/tidb_vars.go并各自维护一个进程内原子布尔值tidb_vars.go供读写路径即时读取。而 tests/readonlytest 正是这套机制的唯一 E2E 验证阵地——它不依赖 mock而是连接真实 TiDB 集群用真实 SQL 逐条验证只读模式下的行为矩阵。二、测试总体设计三个账号、两台 TiDB目录结构tests/readonlytest/ ├── BUILD.bazel # Bazel 构建目标 ├── README.md # 环境搭建与运行说明 ├── main_test.go # TestMain测试框架初始化与 goroutine 泄漏检查 └── readonly_test.go # 核心测试4 个测试函数三个连接角色测试通过三种身份建立连接见 readonly_test.go 的createReadOnlySuite模拟真实运维场景中的三类人角色连接对象权限在测试中的职责roots.db127.0.0.1:4001超级用户控制端执行SET GLOBAL开关只读、创建测试用户、执行授权u1s.udb127.0.0.1:4002仅test.*库上全部权限普通业务账号验证只读模式下被拦截r1s.rdb127.0.0.1:4002test.*全部权限 全局RESTRICTED_REPLICA_WRITER_ADMIN模拟复制写入者验证可以绕过只读限制两个普通账号均由 root 在测试启动时动态创建-- u1普通用户 create user u1% identified by password; grant all privileges on test.* to u1%; -- r1复制写入者关键差异在于多了一行全局动态权限 create user r1% identified by password; grant all privileges on test.* to r1%; grant RESTRICTED_REPLICA_WRITER_ADMIN on *.* to r1%;为什么需要两台 TiDB 实例这是本测试最容易被忽略的设计点tidb_restricted_read_only是全局变量变更通过 PD 广播到所有 TiDB 节点具有最终一致特性极端情况下如节点与 PD 失联延迟可达约 30 秒见设计文档。用 root 在 4001 上执行SET GLOBAL后需要在另一台4002 上的会话中验证该变更是否被广播生效才能真正证明整集群只读而非单节点只读。这正是 readonly_test.go 中tidb_a_port默认 4001与tidb_b_port默认 4002两个参数存在的原因。三、环境搭建手动部署双 TiDB 实例集群测试目前未接入自动化流水线README 明确说明 The test is not yet automated需要手动准备环境。准备步骤在本机localhost部署一个完整的 TiDB 集群TiKV PD 2 个 TiDB Server2 个 TiDB Server 端口分别为4001和4002。读者可以参照仓库 README.md 或使用本地已有的 TiUP 等方式部署多实例当前仓库未提供针对该测试的启动脚本故手动配置。确保 root 账号可登录默认无密码测试会自动完成建库用户、授权、建表等准备工作无需手工初始化test库内容。由于测试内部已通过 root 自动创建u1、r1账号并完成授权手动阶段只需保证两台 TiDB 可达、root 可登录、端口与默认值一致或通过 flag 覆盖。四、运行测试在 tests/readonlytest 目录下直接执行go test预期输出README 记录的示例$ go test OK: 2 passed PASS ok github.com/pingcap/tidb/tests/readonlytest 2.150s需要说明的是README 编写时期记录的期望是 2 个测试通过而当前仓库的 readonly_test.go 已扩展为4 个测试函数TestRestriction、TestRestrictionWithConnectionPool、TestReplicationWriter、TestInternalSQL其中TestInternalSQL使用testkit.CreateMockStore的内存 mock 存储其余 3 个依赖真实集群。实际运行时应以本仓库代码为准。常用命令行参数测试通过 Go 标准库flag暴露了三个可调参数readonly_test.goFlag默认值含义-passwd空TiDB root 密码-tidb_a_port4001第一台控制端TiDB 监听端口-tidb_b_port4002第二台被测端TiDB 监听端口示例若 root 有密码或端口不同go test -args -passwdyour-root-passwd -tidb_a_port4001 -tidb_b_port4002Bazel 方式仓库为测试声明了 Bazel 目标 tests/readonlytest/BUILD.bazelgo_test名为readonlytest_testtimeout short且flaky True标记为偶发不稳定依赖pkg/kv、pkg/testkit、pkg/testkit/testsetup等内部包以及 testify 等外部依赖。在 Bazel 工作区内可用对应 target 运行但 README 记录的官方执行方式仍是go test。五、测试用例深度解析5.1 TestRestriction核心只读行为矩阵这是最核心的用例readonly_test.go完整覆盖开启 → 拦截 → 关闭的闭环开启前基线普通用户u1可以正常create table、insert、update。开启后root 执行set global tidb_restricted_read_only1依次验证变量广播生效在 4002 的u1、r1会话中查询tidb_restricted_read_only与tidb_super_read_only均为ON——印证开启 restricted 会联动开启 super的源码行为sysvar.goSetGlobal中同时写入TiDBSuperReadOnlyDDL 被拒create table t(a int)报Error 1836: Running in read-only mode点更新被拒update t set b2 where a1同样报 1836插入被拒insert into t values (2, 3)同样报 1836禁止降级 super在 restricted 开启时尝试set global tidb_super_read_only0报Error 1105: cant turn off tidb_super_read_only when tidb_restricted_read_only is on——这是 sysvar.go 中Validation逻辑直接产生的冲突错误普通账号无权改全局变量u1、r1尝试关闭 super 时均报Error 1227: Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation——修改该全局变量要求SUPER或SYSTEM_VARIABLES_ADMIN权限FLASHBACK CLUSTER 被拒flashback cluster to timestamp 报 1836——回滚类高危操作同样纳入只读管控管理类语句放行admin show ddl jobs、admin show slow recent 1执行成功——只读模式仍然允许管理诊断查询关闭 restricted 不会自动关闭 superroot 执行set global tidb_restricted_read_only0后两个节点上 restricted 变为OFF但tidb_super_read_only仍为ON显式关闭 super此时 root 再执行set global tidb_super_read_only0才成功集群完全恢复可写。这个用例是对只读语义最完整的验收清单后续 3 个用例则从不同维度补充边界。5.2 TestRestrictionWithConnectionPool连接池场景下的延迟拦截真实业务通常通过连接池访问 TiDB连接池会复用长连接。该用例readonly_test.go用s.udb.Conn()取出一条复用连接后台协程每 50ms 执行一次insert主协程等待 1 秒后开启 restricted 只读随后断言插入操作在 10 秒内必然命中Error 1836。该用例验证了两个重要事实只读检查发生在语句执行层面而非连接建立层面因此已复用的长连接同样会被拦截开启只读后新提交的写操作会稳定、快速地失败不会出现漏网写入。5.3 TestReplicationWriter复制写入者豁免只读模式必须给数据同步留一条活路这就是RESTRICTED_REPLICA_WRITER_ADMIN的意义。该用例readonly_test.go让拥有该权限的r1账号通过连接池后台持续insert然后 root 开启 restricted 只读并验证SUPER 用户 root 自己执行insert被拒报 1836——证明 restricted 只读连 SUPER 都不放过r1的持续写入全程无报错——拥有RESTRICTED_REPLICA_WRITER_ADMIN的复制写入者不受只读影响。设计文档特别强调该权限检查即使在 SEM安全增强模式未开启时也强制生效即 SUPER 用户也必须被显式授予该动态权限才能绕过只读不会因 SEM 关闭而自动继承对应 pkg/planner/optimize.go 中使用HasExplicitlyGrantedDynamicPrivilege的原因——它明确排除隐式权限继承。5.4 TestInternalSQL内部 SQL 豁免TiDB 自身的后台任务如统计信息更新不能因为集群只读而瘫痪。该用例readonly_test.go用kv.WithInternalSourceType构造内部执行上下文在tidb_restricted_read_only与tidb_super_read_only均为ON时通过ExecuteInternal直接向mysql.stats_top_n系统表写入数据断言不报错。源码侧对应 pkg/planner/optimize.go 与 pkg/session/session.go 中共同的守卫条件!sessVars.InRestrictedSQL (RestrictedReadOnly || VarTiDBSuperReadOnly)——内部 SQLInRestrictedSQLtrue直接豁免只读检查这正是测试用例成立的机制。六、错误消息与错误码速查测试文件顶部集中定义了三条关键错误消息常量readonly_test.go可直接作为运维排障时的对照表常量错误文本触发场景对应错误码ReadOnlyErrMsgError 1836: Running in read-only mode只读模式下执行 DDL/DML/Flashback 等写操作1836pkg/errno/errcode.go消息定义于 pkg/parser/mysql/errname.goConflictErrMsgError 1105: cant turn off tidb_super_read_only when tidb_restricted_read_only is onrestricted 开启时尝试单独关闭 super1105通用错误PriviledgedErrMsgError 1227: Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation无 SUPER/SYSTEM_VARIABLES_ADMIN 权限时修改全局变量1227访问拒绝七、底层实现原理只读检查的两道关卡结合测试行为反推源码TiDB 的只读限制在规划期和提交期各设一道关卡。第一道关卡规划期白名单Optimize 阶段每个 SQL 在生成执行计划时若集群处于只读状态会调用 allowInReadOnlyMode入口见 optimize.go判断是否放行。判断逻辑依次为特权旁路会话拥有显式授予的RESTRICTED_REPLICA_WRITER_ADMIN动态权限 → 直接放行复制写入者语句白名单以下语句类型直接放行确保只读模式可进可出SET语句否则无法关闭只读开关ANALYZE TABLE统计信息维护USE、SHOW查询与诊断CREATE/DROP BINDINGSQL 绑定管理PREPARE、BEGIN、ROLLBACKCOMMIT仅当当前事务为只读事务时放行否则直接回滚兜底判定其余语句交由core.IsReadOnlyInternal判断是否为只读查询覆盖explain、prepare/execute等场景。第二道关卡提交期二次校验doCommit规划期检查无法覆盖一种竞态一条长事务在只读开启前已经规划并执行写操作只读开启后才尝试提交。因此 pkg/session/session.go 的doCommit在真正提交前会再次检查若集群处于只读状态且会话不是内部 SQL、也未显式持有RESTRICTED_REPLICA_WRITER_ADMIN则回滚事务并返回ErrSQLInReadOnlyMode即错误码 1836。测试中的TestReplicationWriter之所以断言 SUPER 用户写入失败正是由这道提交期检查兜底。最终一致与生效边界最后需要强调两个边界来自设计文档与源码均为仓库事实SET GLOBAL开启后立即返回成功但整集群生效是最终的变更通过 PD 广播正常情况下各节点很快收到节点与 PD 失联等异常情况下生效延迟最坏可达约 30 秒期间个别节点可能仍短暂可写只读检查对内部 SQLInRestrictedSQL会话一律豁免这是统计信息、GC 等后台任务能在只读集群中正常运转的前提。八、小结tests/readonlytest是理解 TiDB 受限只读模式的最佳入口它用真实双节点集群验证了最终只读语义、tidb_super_read_only联动与互斥规则、RESTRICTED_REPLICA_WRITER_ADMIN特权旁路、连接池/内部 SQL 等边界行为。若你需要在生产环境实施集群级只读例如迁移前的写保护可直接复用本文的验证矩阵先set global tidb_restricted_read_only1并确认所有节点tidb_super_read_only联动为ON再以业务账号验证写入报 1836、以复制账号验证写入正常最后按相反顺序先关 super 再关 restricted恢复。想要进一步深入可以依次阅读readonly_test.go、设计文档、变量注册与联动逻辑、规划期白名单 与 提交期校验。【免费下载链接】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),仅供参考
返回列表