第一章:字节跳动Go单仓库万级模块CI/CD体系全景概览
字节跳动采用统一的 monorepo 架构管理超万级 Go 模块,所有业务、中间件、基础设施组件均归属同一 Git 仓库(bytedance/golang)。该体系并非简单叠加构建任务,而是围绕“可复用性”“确定性”“可观测性”三大原则深度定制:模块间依赖通过语义化版本锚定 + 本地符号解析双机制保障;构建粒度精确到 go.mod 级别而非仓库级;CI 触发策略支持路径感知(path-based trigger),仅构建受影响模块及其显式依赖链。
核心架构分层
- 调度层:基于自研调度器
Triton,支持 DAG 编排与跨集群资源弹性伸缩,单次全量构建可并发调度 2000+ 构建单元 - 执行层:容器化构建节点预装 Go 1.19–1.22 多版本运行时,通过
gobuildkit工具链实现模块级缓存复用(哈希键含go.sum+go.mod+ 构建参数) - 可观测层:所有构建日志、性能指标(如 module resolve time、test coverage delta)实时接入
ByteMonitor,支持按模块名、提交 SHA、开发者维度下钻分析
关键实践示例
当开发者提交 PR 后,系统自动执行以下流程:
- 解析变更路径,调用
go list -deps -f '{{.ImportPath}}' ./...生成影响模块拓扑 - 查询
ByteCache中对应模块的最近成功构建产物哈希(SHA256) - 若缓存命中且依赖未变更,则跳过构建,直接复用二进制与测试报告
# 示例:手动触发某模块增量构建(调试用)
$ go run internal/ci/cli.go build \
--module github.com/bytedance/kitex/client \
--cache-key "v1.23.0-go1.21-darwin-amd64" \
--with-test # 同时运行单元测试并生成覆盖率报告
# 注:--cache-key 由 CI 自动计算生成,确保环境一致性
模块构建状态分布(近30天统计)
| 状态类型 | 占比 | 平均耗时 | 典型原因 |
|---|---|---|---|
| 缓存命中 | 68.3% | 12s | 依赖未变更,复用产物 |
| 增量构建 | 27.1% | 84s | 仅修改源码,跳过 vendor |
| 全量重建 | 4.6% | 312s | go.mod 更新或 toolchain 升级 |
第二章:超低延迟构建引擎的底层实现原理
2.1 增量编译与AST级依赖图动态裁剪实践
传统全量编译在大型前端项目中耗时显著,而增量编译需精准识别变更影响边界。核心在于构建并维护 AST 级细粒度依赖图,而非文件粒度。
AST 节点级依赖建模
每个 ImportDeclaration、CallExpression 和 Identifier 节点均携带 scopeId 与 referencedIds 元数据,形成双向引用关系。
// AST 节点依赖注入示例(Babel 插件)
export default function({ types: t }) {
return {
visitor: {
ImportDeclaration(path) {
const moduleId = path.node.source.value; // 如 './utils'
const localName = path.node.specifiers[0]?.local?.name;
// 注入依赖边:当前模块 → moduleId,关联 localName 作用域
path.scope.getProgramParent().addDependence(moduleId, {
from: path.scope.block.id,
symbol: localName
});
}
}
};
}
该插件在遍历阶段为每个导入语句注册模块间符号级依赖,addDependence 方法将 symbol 绑定到目标模块的导出声明,支撑后续按符号裁剪。
动态裁剪策略对比
| 策略 | 精度 | 冷启动开销 | 支持重命名 |
|---|---|---|---|
| 文件级 | 低 | 极小 | ❌ |
| 导出名级 | 中 | 中等 | ⚠️(需重解析) |
| AST节点级 | 高 | 较高(首次全量分析) | ✅ |
裁剪执行流程
graph TD
A[源文件变更] --> B[提取变更AST节点]
B --> C[反向追踪依赖链]
C --> D[标记受影响导出/调用链]
D --> E[仅重编译子图内节点]
关键参数:maxDepth=3 控制依赖回溯深度,避免雪崩;cacheKey 基于 AST hash + 依赖图版本生成,保障缓存一致性。
2.2 Go build cache联邦化架构与跨节点一致性保障
Go 1.18 引入的构建缓存联邦(GOCACHE=remote)允许多个构建节点共享缓存元数据,避免重复编译。其核心在于将本地 ~/.cache/go-build 映射为分布式键值存储的客户端视图。
数据同步机制
采用最终一致性+版本向量(Version Vector) 控制缓存项生命周期:
// cacheentry.go:缓存条目携带逻辑时钟与节点ID
type CacheEntry struct {
Digest string `json:"digest"`
NodeID string `json:"node_id"` // 如 "build-node-03"
Version uint64 `json:"version"` // Lamport clock
Timestamp time.Time `json:"ts"`
Dependencies map[string]uint64 `json:"deps"` // dep → version
}
该结构支持冲突检测:当两节点并发写入同一 digest,通过 NodeID+Version 组合判定因果序,拒绝陈旧更新。
一致性保障策略
- ✅ 读操作:本地缓存未命中时,异步拉取远程最新版本(带
If-None-Match: <etag>) - ✅ 写操作:先提交至中心协调器(如 etcd),再广播 invalidate 消息
- ❌ 不保证强一致性——因构建缓存本质是幂等、可重算的只读资源
| 策略 | 延迟 | 容错性 | 适用场景 |
|---|---|---|---|
| 协调器仲裁 | ~50ms | 高 | CI 流水线关键路径 |
| Gossip 广播 | ~200ms | 中 | 边缘集群离线构建 |
| 本地优先+TTL | 低 | 开发者本机构建 |
架构拓扑
graph TD
A[Build Node A] -->|PUT/GET| C[(Cache Coordinator)]
B[Build Node B] -->|PUT/GET| C
D[Build Node C] -->|Gossip Sync| B
C -->|etcd watch| E[etcd Cluster]
2.3 构建产物指纹生成算法:基于go.mod+源码哈希的双模校验
核心设计思想
单一哈希易受依赖版本漂移或无关文件变更干扰。双模校验将确定性依赖快照(go.mod)与精确源码边界(排除vendor/、testdata/等非构建路径)解耦校验,提升指纹抗干扰能力。
指纹计算流程
# 1. 提取 go.mod 的规范哈希(忽略注释与空行)
go mod edit -json | jq -c 'del(.Replace, .Exclude)' | sha256sum | cut -d' ' -f1
# 2. 计算源码哈希(仅含 *.go 文件,按路径字典序归并)
find . -name "*.go" -not -path "./vendor/*" -not -path "./testdata/*" \
| sort \
| xargs cat \
| sha256sum | cut -d' ' -f1
# 3. 拼接双哈希生成最终指纹
echo "${MOD_HASH}_${SRC_HASH}" | sha256sum | cut -d' ' -f1
逻辑分析:
go mod edit -json输出标准化依赖图,del(.Replace, .Exclude)移除非锁定字段,确保跨环境一致性;源码哈希通过sort | xargs cat强制路径顺序,消除find的不确定性;最终拼接后二次哈希避免长度泄露。
双模校验优势对比
| 维度 | 单哈希(全目录) | go.mod+源码双模 |
|---|---|---|
| 依赖变更敏感度 | 高(go.sum变动即失效) |
低(仅require/module变动触发) |
| 无关文件干扰 | 易受.gitignore外临时文件影响 |
完全隔离vendor/、docs/等非构建路径 |
graph TD
A[输入源码树] --> B{过滤路径}
B --> C[提取 go.mod 锁定依赖]
B --> D[收集有效 *.go 文件]
C --> E[生成 MOD_HASH]
D --> F[生成 SRC_HASH]
E & F --> G[拼接 + 二次哈希 → 最终指纹]
2.4 并行化构建调度器:CPU/IO-bound任务混合拓扑感知调度
现代构建系统需同时调度编译(CPU-bound)与文件下载/链接(IO-bound)任务,而物理资源拓扑(如NUMA节点、PCIe带宽域)显著影响性能。
拓扑感知任务分类
- CPU密集型:Clang编译、LLVM优化
- IO密集型:npm install、S3 artifact fetch
- 混合型:Rust cargo build(前期解析IO-heavy,后期codegen CPU-heavy)
调度策略核心逻辑
def schedule_task(task: Task, topology: Topology) -> Placement:
if task.is_cpu_bound():
return topology.closest_core(task.affinity_hint) # 绑定L3缓存邻近核心
elif task.is_io_bound():
return topology.closest_nvme_node(task.data_loc) # 优先同PCIe根复合体
else:
return topology.balance_across_numa(task.weight) # 动态权重分配
closest_core()利用/sys/devices/system/cpu/cpu*/topology/获取L3共享关系;closest_nvme_node()通过lspci -t识别NVMe设备所属NUMA节点;balance_across_numa()按任务内存带宽需求加权分配。
混合负载调度效果对比(单节点8核16GB)
| 调度策略 | 构建耗时(s) | L3缓存命中率 | PCIe带宽利用率 |
|---|---|---|---|
| FIFO(无拓扑) | 142 | 63% | 92%(瓶颈) |
| 拓扑感知调度 | 97 | 89% | 58%(均衡) |
graph TD
A[Task Queue] --> B{Task Type?}
B -->|CPU-bound| C[Find L3-shared Core]
B -->|IO-bound| D[Match NVMe NUMA Node]
B -->|Hybrid| E[Split & Co-schedule]
C --> F[Pin to Core w/ Cache Locality]
D --> G[Bind to PCIe Root Complex]
E --> H[CPU subtask → C<br>IO subtask → D]
2.5 构建沙箱轻量化设计:无root容器+glibc精简镜像+syscall白名单
核心设计三要素
- 无 root 容器:通过
--user 1001:1001 --cap-drop=ALL启动,消除特权风险; - glibc 精简镜像:基于
alpine-glibc衍生,剔除libnsl、libresolv等非必需共享库; - Syscall 白名单:依托
seccomp-bpf仅放行read/write/brk/mmap/munmap/clone/futex/exit_group等 37 个最小集。
典型 seccomp 配置片段
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{ "names": ["read", "write", "brk"], "action": "SCMP_ACT_ALLOW" },
{ "names": ["mmap", "munmap"], "action": "SCMP_ACT_ALLOW", "args": [
{ "index": 2, "value": 3, "op": "SCMP_CMP_EQ" }
]}
]
}
逻辑说明:
defaultAction拒绝所有系统调用;args字段约束mmap的prot参数必须为PROT_READ | PROT_WRITE(值为3),防止执行内存注入。
轻量化效果对比
| 维度 | 传统 Ubuntu 镜像 | 本方案镜像 |
|---|---|---|
| 镜像大小 | 72 MB | 9.3 MB |
| glibc 符号数 | 2,148 | 386 |
| 启动后 syscall 数 | ~280+ | ≤ 37 |
graph TD
A[应用进程] --> B[seccomp 过滤器]
B --> C{是否在白名单?}
C -->|是| D[内核执行]
C -->|否| E[返回 EPERM]
第三章:精准依赖变更触发的核心机制
3.1 模块级依赖关系图实时增量更新与拓扑排序优化
数据同步机制
采用事件驱动架构捕获模块元数据变更(如 ModuleAdded、DependencyUpdated),通过轻量级变更日志(ChangeLog)触发局部图更新,避免全量重建。
增量图更新算法
def update_dependency_graph(event: DependencyEvent, graph: DiGraph) -> DiGraph:
if event.type == "ADDED":
graph.add_edge(event.from_id, event.to_id) # O(1) 边插入
elif event.type == "REMOVED":
graph.remove_edge(event.from_id, event.to_id) # O(1) 边删除
return graph # 返回更新后图结构,支持后续拓扑计算
逻辑分析:仅操作变更边,跳过节点重扫描;
from_id/to_id为模块唯一标识符(UUID),确保跨版本一致性;图结构复用原实例,降低内存抖动。
拓扑排序优化策略
| 优化项 | 传统Kahn算法 | 本方案 |
|---|---|---|
| 时间复杂度 | O(V+E) | O(ΔV+ΔE) |
| 触发条件 | 全图变更 | 仅变更子图重排 |
执行流程
graph TD
A[接收依赖事件] --> B{事件类型}
B -->|ADD/REMOVE| C[定位受影响子图]
C --> D[执行局部入度更新]
D --> E[对子图运行Kahn变体]
E --> F[输出新拓扑序]
3.2 Git diff语义解析引擎:识别go.mod变更、import路径变动与符号引用变化
Git diff 原生输出是行级文本差异,而 Go 项目需理解其语义层变化。该引擎基于 AST 解析与正则协同策略,精准捕获三类关键变更。
go.mod 变更识别
通过解析 go.mod 的 require/replace/exclude 块差异,提取模块名、版本、替换路径:
// 示例 diff 片段解析逻辑
diff := "diff --git a/go.mod b/go.mod\n+ require github.com/gorilla/mux v1.8.0"
re := regexp.MustCompile(`\+ require (\S+) (\S+)`)
if matches := re.FindStringSubmatch([]byte(diff)); len(matches) > 0 {
module := string(matches[0][1]) // "github.com/gorilla/mux"
version := string(matches[0][2]) // "v1.8.0"
}
该正则匹配新增依赖行,matches[0][1] 提取模块路径,matches[0][2] 提取语义化版本,支持 v1.8.0、v2.0.0+incompatible 等格式。
import 路径与符号引用联动分析
| 变更类型 | 触发影响范围 | 检测方式 |
|---|---|---|
| import 路径修改 | 包导入一致性 | AST ImportSpec 对比 |
| 符号重命名引用 | 函数/变量调用失效 | ast.Ident + ast.SelectorExpr 跨文件追踪 |
graph TD
A[git diff 输出] --> B[按文件分类]
B --> C{go.mod?}
B --> D{*.go?}
C --> E[模块依赖图更新]
D --> F[Parse Go AST]
F --> G[Extract ImportSpec]
F --> H[Track Ident usage]
G & H --> I[生成语义变更事件]
3.3 跨模块调用链静态分析:基于go list -deps与ssa pass的调用边界收敛
核心分析流程
跨模块调用边界识别依赖两层收敛:构建模块依赖图(go list -deps)与函数级控制流精炼(SSA pass)。前者定位潜在调用范围,后者剔除虚调用与未达路径。
依赖图生成
go list -deps -f '{{if .Name}}{{.ImportPath}} {{.Name}}{{end}}' ./cmd/app
-deps:递归展开所有直接/间接依赖模块-f:模板过滤仅输出非空包路径与名称,避免 vendor 和 testdata 干扰
SSA 中间表示提取
prog := ssautil.CreateProgram(fset, ssa.SanityCheckFunctions)
mainPkg := prog.Package(pkg) // pkg 来自 go list 解析结果
mainPkg.Build() // 构建函数级 SSA 形式
逻辑:ssautil.CreateProgram 仅加载指定包及其依赖的 SSA,Build() 触发函数体转换,为后续调用图(CallGraph)构造提供基础。
边界收敛效果对比
| 分析阶段 | 模块粒度 | 函数粒度 | 虚调用误报率 |
|---|---|---|---|
go list -deps |
✅ | ❌ | 高 |
| SSA pass | ❌ | ✅ |
graph TD
A[go list -deps] --> B[模块依赖图]
C[SSA Build] --> D[函数内联/逃逸分析]
B --> E[粗粒度调用候选集]
D --> E
E --> F[收敛后的跨模块调用边界]
第四章:万级模块协同演进的工程治理实践
4.1 模块生命周期管理:从注册、发布到废弃的自动化状态机
模块生命周期不再依赖人工运维,而是由声明式状态机驱动。核心是将 Registered → Validated → Published → Deprecated → Archived 的流转建模为不可绕过的原子状态跃迁。
状态迁移规则
- 每次变更需携带签名凭证与上下文元数据(如
triggeredBy,reason) Deprecated状态自动触发兼容性检查与下游影响分析Archived后模块不可恢复,仅保留只读元信息
状态机定义(YAML Schema)
# module-lifecycle.sm.yaml
states:
- name: Registered
on: [validate] → Validated
- name: Validated
on: [publish] → Published
- name: Published
on: [deprecate] → Deprecated
- name: Deprecated
on: [archive] → Archived
该配置被编译为 Mermaid 可视化流程图:
graph TD
A[Registered] -->|validate| B[Validated]
B -->|publish| C[Published]
C -->|deprecate| D[Deprecated]
D -->|archive| E[Archived]
关键参数说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
lifecycleVersion |
string | 是 | 状态机语义版本,影响迁移兼容性 |
gracePeriodDays |
integer | 否 | Deprecated 后强制归档宽限期,默认30天 |
状态跃迁通过 webhook + 签名验证双因子校验,确保审计可追溯。
4.2 构建可观测性体系:eBPF注入式构建性能剖析与瓶颈定位
传统探针存在侵入性强、重启依赖高、覆盖粒度粗等问题。eBPF凭借内核态零拷贝、安全沙箱与动态加载能力,成为现代可观测性体系的核心底座。
核心优势对比
| 维度 | Agent 模式 | eBPF 注入式 |
|---|---|---|
| 内核上下文获取 | 间接(/proc, perf_event) | 直接(kprobe/uprobe/tracepoint) |
| 性能开销 | 中~高(用户态上下文切换) | 极低(纯内核态轻量执行) |
| 部署灵活性 | 需重启进程或服务 | 运行时热加载,无感知注入 |
示例:HTTP 请求延迟热力图采集
// bpf_program.c:在 tcp_sendmsg 返回点捕获发送延迟
SEC("tracepoint/syscalls/sys_exit_sendto")
int trace_http_send_latency(struct trace_event_raw_sys_exit *ctx) {
u64 ts = bpf_ktime_get_ns(); // 纳秒级时间戳
u32 pid = bpf_get_current_pid_tgid() >> 32;
bpf_map_update_elem(&latency_map, &pid, &ts, BPF_ANY);
return 0;
}
逻辑分析:该程序挂载于 sys_exit_sendto tracepoint,避免了对应用代码修改;bpf_ktime_get_ns() 提供高精度时序,latency_map 为 BPF_MAP_TYPE_HASH 类型,用于后续用户态聚合计算请求端到端延迟分布。
graph TD A[应用进程] –>|uprobe hook| B[eBPF 程序] B –> C{内核事件环} C –> D[用户态 perf buffer] D –> E[Go/Python 分析器] E –> F[火焰图/延迟直方图]
4.3 多环境差异化构建策略:开发/预发/线上三态配置分发与灰度验证
配置驱动的构建时注入机制
通过 --env 参数在 CI 构建阶段动态加载对应环境配置:
# 示例:基于环境变量注入配置
npm run build -- --env=staging # 触发预发环境构建
该命令将 staging 传入 Webpack DefinePlugin,生成含 process.env.API_BASE = 'https://api-staging.example.com' 的静态包,避免运行时泄露敏感地址。
三态配置映射表
| 环境 | 配置来源 | 灰度开关默认值 | 配置热更新支持 |
|---|---|---|---|
| dev | config/dev.js |
false | ✅ |
| staging | config/staging.yaml |
true | ❌(构建时固化) |
| prod | Vault + K8s ConfigMap | false | ❌ |
灰度验证流程
graph TD
A[构建产物打 tag] --> B{灰度比例 5%?}
B -->|是| C[发布至 staging-traffic]
B -->|否| D[全量上线 prod]
C --> E[埋点监控异常率]
E -->|<0.1%| F[自动升版]
环境隔离关键实践
- 所有 API 域名、Feature Flag、日志上报端点均通过构建参数注入,禁止硬编码;
- 预发环境复用线上数据库只读副本,但强制启用
X-Env: staging请求头拦截写操作; - 线上灰度通过 Service Mesh 实现按 Header 路由,无需修改业务代码。
4.4 构建安全加固:SBOM生成、依赖许可证合规扫描与CVE实时拦截
现代软件供应链安全需三位一体协同防御:可追溯的组件清单、合规性前置校验与漏洞动态拦截。
SBOM自动化生成
使用 syft 生成 SPDX 格式 SBOM:
syft ./app --format spdx-json -o sbom.spdx.json
--format spdx-json 确保兼容性;-o 指定输出路径;./app 支持目录、容器镜像或二进制文件输入。
许可证合规扫描
| 工具 | 检查维度 | 实时性 |
|---|---|---|
| FOSSA | 传染性许可识别 | ✅ |
| ClearlyDefined | 多源许可证置信度 | ⚠️(需缓存) |
CVE实时拦截流程
graph TD
A[CI构建触发] --> B[Syft生成SBOM]
B --> C[Trivy扫描CVE]
C --> D{CVSS≥7.0?}
D -->|是| E[阻断流水线]
D -->|否| F[推送至SCA平台]
执行策略联动
- 扫描结果需注入 CI 环境变量(如
TRIVY_EXIT_CODE_ON_VULN=1) - 许可证黑名单配置支持正则匹配(例:
/(AGPL|CC-BY-NC)/)
第五章:面向未来的Go大规模工程化演进方向
模块化依赖治理与语义版本精细化协同
在字节跳动内部,随着微服务数量突破3000+,go.mod 文件中直接依赖平均达47个,间接依赖超2000项。团队通过自研工具 gomod-guard 实现依赖图谱实时扫描,强制要求所有内部模块发布时附带 //go:build 标签约束兼容范围,并将 SemVer 的补丁/小版本升级纳入 CI 流水线门禁——当 github.com/bytedance/kit/v3 发布 v3.2.1 时,自动触发所有引用 v3.2.x 的服务执行兼容性测试套件(含接口契约验证、HTTP 响应结构快照比对),失败则阻断合并。该机制使跨模块升级引发的线上 5xx 错误下降 68%。
构建系统向增量云原生范式迁移
美团外卖核心订单服务采用 Bazel + Remote Build Execution (RBE) 替代传统 go build,构建耗时从平均 8.2 分钟压缩至 1.4 分钟。关键改造包括:将 proto 编译、embed 资源打包、swag 文档生成定义为独立可缓存 action;构建缓存命中率稳定在 92% 以上;所有构建作业运行于 Kubernetes 集群中,节点资源按 CPU 密集型/IO 密集型标签调度。下表对比两种构建模式在千级 Go 文件项目中的表现:
| 指标 | go build(本地) |
Bazel + RBE(云) |
|---|---|---|
| 平均构建时间 | 8.2 min | 1.4 min |
| 内存峰值 | 3.1 GB | 1.8 GB |
| 缓存复用率 | 31% | 92% |
| 构建日志可追溯性 | 文件级 | action 级(含输入哈希、环境变量快照) |
运行时可观测性深度集成
腾讯云 TKE 团队在 golang.org/x/net/http2 中注入轻量级 trace hook,无需修改业务代码即可捕获 HTTP/2 流级指标(如 SETTINGS 帧往返时延、流优先级抢占次数)。配合 OpenTelemetry Collector 的 prometheusremotewriteexporter,将 200+ 个 Go 微服务的连接复用率、头部压缩效率、流控窗口变化等维度统一接入 Grafana,实现跨服务的 HTTP/2 性能基线告警。某次发现某 SDK 的 http2.Transport.MaxConnsPerHost 默认值(0)导致连接池无上限,在 10w QPS 场景下引发内核 TIME_WAIT 爆炸,该问题通过仪表盘中 http2_client_streams_per_conn{quantile="0.99"} 异常毛刺被快速定位。
flowchart LR
A[Go 应用启动] --> B[加载 otel-go-instrumentation]
B --> C[Hook net/http Server/Client]
C --> D[采集 http2.StreamMetrics]
D --> E[OTLP Exporter]
E --> F[OpenTelemetry Collector]
F --> G[(Prometheus TSDB)]
G --> H[Grafana Dashboard]
类型安全的配置即代码实践
阿里云 ACK 控制平面将全部配置项定义为 Go struct,并通过 go:generate 自动生成 JSON Schema 与 Protobuf 定义。例如 ClusterConfig 结构体经 protoc-gen-go 和 jsonschema-gen 双向生成后,既支持 Kubernetes CRD 的 validation.openapiv3schemas 字段校验,又可被 Terraform Provider 直接消费。当新增 nodePool.taints[].effect 字段时,类型系统强制要求枚举值仅限 "NoSchedule"/"PreferNoSchedule"/"NoExecute",避免 YAML 手写错误引发节点调度异常。该方案已在 12 个核心组件中落地,配置相关 P0 故障归零。
多运行时混合部署架构演进
PingCAP TiDB Operator 在 Kubernetes 上同时管理 Go 编写的 Operator 进程与 Rust 编写的 tidb-scheduler 插件,二者通过 gRPC over Unix Domain Socket 通信。Operator 启动时动态加载 scheduler 插件的 .so 文件(由 cgo 封装),并通过 plugin.Open() 加载符号表。该设计使调度策略迭代周期从“Operator 全量发布”缩短为“插件热更新”,某次紧急修复 PD 调度热点逻辑,仅需 37 秒完成插件编译、签名、推送与 reload,期间 TiDB 集群持续提供读写服务。
