问题描述
在 TiDB 的产品迭代中,不免会碰到一些兼容性问题出现。通常协议上的兼容性 protobuf 已经能帮我们处理的很好,在进行功能开发,性能优化时,通常会保证版本是向后兼容的,但并不保证向前兼容性,因此,当集群中同时有新旧版本节点存在时,旧版本不能兼容新版本的特性,就有可能造成该节点崩溃,影响集群可用性,甚至丢失数据。目前在有不兼容的版本升级时,会要求进行离线升级,但这会影响到服务,我们需要一个适合的机制来进行不停服务的升级。因此我们需要在进行滚动升级时,让这些不能保证整个集群的向后兼容性的功能不被启用。只有在保证集群中所有节点都已经升级完成后,我们才安全的启用这些功能。
常见的当我们对引入新的 RaftCommand
的时候,旧版本的 TiKV 并不能识别新的添加的 RaftCommand
,对于不能认知的 RaftCommand
TiKV 有不同的处理,可能会报错退出或忽略。比如为了支持 Raft Learner, 在 raftpb 里对添加新的 ConfChange 类型。 当 PD 在进行 Region 调度时,会先发送 AddLearner
到 TiKV 上,接受到这个命令的肯定是这个 Region 的 Leader,在进行一系列检查后,会将该命令 Proposal, 而 Follwer 如果是旧版本的话,在 Apply 这条 Command 就会出错。而在滚动升级时,很有可能存在 Leader 是新版本,Follwer 是老版本的情况。
引入版本检查机制
TiDB 的版本定义是遵循 Semver 的版本规则的。版本格式一般由主版本号(Major),次版本号(Minor),修订号(Patch),版本号递增规则如下:
- 主版本号:当进行了不兼容的 API 修改。
- 次版本号:当做了向下兼容的功能性新增。
- 修订号:当做了向下兼容的问题修正。
先行版本号(PreRelase)及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。比如 TiDB 目前的版本是 2.1.0-beta,先行版号为 beta 版。
在此之前,集群并没有版本的概念,虽然每个组件都有各自的版本信息,但各个节点的各自组件的版本都可以任意的。没有一个管理机制可以管理或查看所有组件的版本信息。为了解决滚动升级过程中存在多个版本的兼容性问题,这里引入集群版本的概念,并由 TiDB 集群的中心节点 PD 来进行管理和检查。
具体实现
1.升级集群
在 PD 中,会设置一个 cluster_version
的键值对,对应当前运行集群中 TiKV 节点中最旧的版本。也就是必须要兼容这个版本, 因此不能打开集群中其他新版本的节点的一些不兼容的特性。
在集群启动的时候,每个 TiKV 都需要向 PD 注册,注册时会带上版本信息。当当前 TiKV 的版本低于集群版本的时候,该 TiKV 会注册失败。因为此时集群的版本已经是更高的版本了,而加入旧版本的节点需要对旧版本进行兼容,为了防止已有的特性降级,直接拒绝不兼容的版本加入,目前默认主版本号和此版本号一样则为兼容的版本。
如果 TiKV 的版本高于或等于当前的 cluster_version
时, TiKV 能够注册成功并成功启动。每次注册都会触发 PD 的一次检查,会检测当前集群中正常运行的 TiKV 的最低版本,并与当前的 cluster_version
进行比对,如果最低版本比 cluster_version
更加新,则将 cluster_version
更新。因此每次滚动升级的时候,能够自动更新集群的版本。
2. 版本特性的开启
TiKV 很多功能是需要 PD 的参与,目前这些新功能的开启也是通过 PD 进行控制的。在 PD 中,会将每个版本新特性记录下来,在 TiKV 2.0 中,对应有 Raft Leaner, Region Merge。 TiKV 2.1 中有 Batch Split,Joint Consensus 等。这些特性都需要 PD 的参与与控制。比如说 Add Leaner,Region Merge,Joint Consensus 需要 PD 下发调度给 TiKV,Batch Split 则是 TiKV 主动发起并请求 PD 分配新的 Region ID。因此这些功能都是能通过 PD 进行控制的。PD 会通过比对当前的集群版本,选择开启当前集群版本所支持的新特性。从而保证版本的兼容性。
3. 集群回滚
当升级完成后,如果遇到问题需要进行集群进行回滚时, 需要手动修改集群版本后。PD 提供了 pdctl 可以通过命令手动修改集群的 cluster_version
,这时旧版本的 TiKV 就能注册并启动,从而进行回滚。
PD 对 cluster_version
是通过 etcd 进行了持久化,在每次 PD 启动的时候,leader 都会从 etcd kv 中加载出 clustrer_version
,然后提供服务。从而保证在 PD leader 切换后 cluster_version
的一致性。另外 PD 本身的版本可能会小于当前 cluster_version
。因此在滚动升级的时候,需要先升级 PD,如果只升级了 TiKV,虽然 cluster_version
已经更新到新的版本的,但 PD 并不能开启新的功能,因为对它来说是不支持的。如果出现这种情况,PD 的日志中会有报警。在升级的时候,最好按 PD,TiKV,TiDB 的顺序逐一对各个组件。
后续计划
上面提到的新功能特性一般都是需要 PD 参与的。而有些特性不需要PD的参与,因此需要保证这种特性在 TiKV 之间是可以兼容的,实现的时候可以采用类是 http2 <-> http
的方式,对请求进行降级装发,保留两套接口等。另为 TiDB 目前是自身保证可以无缝兼容,但与 TiKV 可能存在兼容性问题,往后同样考虑让TiDB 也在 PD上进行注册。
作者:陈书宁