
假如一个开发了网站,window修改,上传到云端服务器太麻烦了
先不考虑有状态服务,如果实现代码自动化一键部署修改,对创业公司及其重要 先不考虑有状态服务,如果实现代码自动化一键部署修改,对创业公司及其重要。
需要很多框架和平台
•
Jenkins自动化部署:从代码提交到自动上线的全流
•
Docker+GitLab实现全流程自动化
•
做到上面很厉害了。
推荐#ContextOS# 在设计之初解决了这个问题
ContextOS 通过集群化、模块化、自动化的方式, 只用一个 20M 大小的程序文件, 就可以在各种设备上,一键启动完整的云计算服务与云研发环境。
然后 在传统的电信业务,数据库 存储 都需要支持 滚动在线升级, 不影响业务 ,极其复杂。 反复不停测试
例如 版本4.0 升级版本5.0
某 Ceph 分布式存储集群(3 副本,100+OSD 节点)升级 OSD 组件,
新版本为优化增量同步性能,废弃了原增量同步字段sync_flag(用于标记同步数据有效性),
开发未做废弃字段的兼容解析,直接忽略该字段
•
某 MongoDB 副本集(3 节点)升级,灰度升级 1 个节点后,新版本节点的心跳包格式与老版本不一致,老版本节点判定新版本节点不可用;
•
副本集触发重新投票,因 2 个老版本节点投票互选,新版本节点独立形成小集群,出现脑裂;
某企业级自研分布式存储集群,管理 600 + 业务卷,运维执行 MetaDB 节点滚动升级。
新版本为优化池主选举稳定性,在PoolMasterElectionReq池升主请求结构体中新增必选字段master_retry_count,开发未做老版本兼容处理,此
时存储池节点仍为老版本(无法携带该字段);且池升主流程未配置原地重试机制,一次请求失败即终止
老版本消息---新版本
新版本--------老版本
•
新版本 消息发送到老版本,新版本新增消息,老版本不支持
•
老版本消息 发送新版本, 老版本支持,新版本废弃
•
业务不变,新版本发生变化。新老不兼容
•
新增字段 怎办?
•
废弃字段怎么办?
•
服务不可用,节点直接有依赖关系
•
HA等需要同步数据
TiDB 内部的节点间通信(如 PD 与 TiKV、TiDB 与 TiKV 的 RPC 交互)几乎都基于 Protobuf(Golang 生态最主流的序列化协议),这个问题转换Protobuf 怎么解决的。
TiDB
1
新版本→老版本:版本协商 + 消息裁剪 + 特性开关,主动降级消息格式,仅发送对方支持的内容;
2
老版本→新版本:保留废弃字段解析逻辑,解析后转换 / 隔离,不影响核心业务;
3
格式不兼容:引入兼容层做格式转换,双解析 / 双写,滚动升级完成后移除兼容逻辑。

•
官方严格规定 Mon -> Mgr -> OSD -> MDS -> … 的滚动升级顺序 就是为了让核心仲裁和管理组件(Mon、Mgr)先升级, 从而能管理和协调后升级的数据面组件(如OSD)
•
ceph的序列化(ENCODE/DECODE)和升级协议兼容性考虑
•
https://github.com/ceph/ceph/blob/266b666e/doc/dev/encoding.rst#L55-L58
When a structure is sent over the network or written to disk, it is encoded into a string of bytes. Usually (but not always -- multiple serialization facilities coexist in Ceph) serializable structures have encode and decode methods that write and read from bufferlist objects representing byte strings.
Ceph使用ENCODE_START和DECODE_START宏来管理版本:
•
版本管理通过struct_v变量在解码时自动处理
•
ENCODE_FINISH会自动跳过未解码的字节,确保向前兼容性
•
消息头包含版本信息,用于路由到正确的解码器
我们再次用AcmeClass的例子来说明:
class AcmeClass {
int member1; // v1
std::string member2; // v1
std::vector<std::string> member3; // v2新增
void encode(bufferlist &bl) const {
ENCODE_START(2, 1, bl); // (当前版本, 兼容版本)
::encode(member1, bl); // 始终编码
::encode(member2, bl); // 始终编码
::encode(member3, bl); // 作为v2的新字段,编码在最后
ENCODE_FINISH(bl);
}
};
1. 实现向后兼容(新读旧) 高版本(v2)
void decode(bufferlist::iterator &bl) {
DECODE_START(2, bl);
::decode(member1, bl); // v1数据里有,正常读
::decode(member2, bl); // v1数据里有,正常读
if (struct_v >= 2) { // 关键判断!
// 如果数据是v2格式写的,就读取member3
::decode(member3, bl);
} else {
// 如果数据是v1格式写的,member3保持默认值(空向量)
// 业务逻辑需能处理这种情况
}
DECODE_FINISH(bl);
}
2. 实现向前兼容(旧读新)
低版本(v1)的解码器知识有限,其代码根本没有member3的概念:
// 假设的 v1 解码器
void decode(bufferlist::iterator &bl) {
DECODE_START(1, bl); // 它只声明自己懂v1
::decode(member1, bl); // 读取
::decode(member2, bl); // 读取
// 注意:没有对member3的解码代码!
DECODE_FINISH(bl); // 在此停止,member3的字节被安全“遗留”在缓冲区中
}
•
v2版本请求 发送到v1
•
v1 不会解析,不然core
Ceph通过一套组合拳保证升级平滑:
•
编码规则:只在末尾追加新字段,这是实现向前兼容的铁律。
•
版本控制:ENCODE_START中的双版本号,为解码提供了决策依据。
•
条件解码:if (struct_v >= N) 实现了向后兼容。
•
特性协商:在通信前主动降级,避免不必要的数据传输和兼容性风险。
•
Ceph风格:在decode函数里到处是if (struct_v >= 2) { decode(field); },耦合在业务类中。
•
OceanBase风格:序列化/反序列化入口处一个版本判断,决定走哪一条完整的编码/解码路径。新旧格式的实现被隔离到不同的“数据盒”或成员列表中,结构更清晰,更易于维护和扩展(例如未来增加V3格式)。 向后兼容(新版本读取旧数据)
1
版本识别:新版本通过cluster_version_字段识别数据版本
2
兼容字节解析:ObTxSerCompatByte::deserialize动态解析兼容字节 ob_tx_serialization.cpp:211-235
3
默认值填充:对于旧版本不存在的字段,使用默认值
向前兼容(旧版本读取新数据)
1
字段跳过:旧版本通过兼容字节跳过不认识的新字段
2
长度计算:get_serialize_size正确计算序列化长度 ob_tx_serialization.cpp:198-209
3
安全忽略:未知字段被安全忽略而不影响解析
int CLZ::serialize(SERIAL_PARAMS) const // 1. 函数定义
{
// 2. 序列化父类部分
int ret = P_CLZ::serialize(buf, buf_len, pos);
// 3. 判断父类序列化是否成功,且目标版本是否为旧版本
if (OB_SUCC(ret) && cluster_version_ <= CLUSTER_VERSION_4_1_0_1) {
// 4. 路径A:序列化旧版本成员列表
LST_DO_CODE(OB_UNIS_ENCODE, CLZ ## _V1_MEMBERS);
} else if (OB_SUCC(ret)) { // 5. 父类成功,但目标版本是新版本
// 6. 路径B:创建“数据盒”并序列化全部成员
CLZ##_box x(const_cast<CLZ&>(*this));
ret = x.serialize(buf, buf_len, pos);
}
// 7. 返回序列化结果
return ret;
}
高版本发送方在 serialize 时, 如果检测到 cluster_version_ 较低 会检查对方节点版本 这个是不是性能太慢了
1
握手时交换:在节点A和B建立网络连接时,作为握手协议的一部分,双方会交换各自支持的最高协议版本和特性位图。这个过程可能发生在TCP连接建立后的第一个应用层握手包里。
2
上下文缓存:一旦握手完成,双方都会将对端的能力信息(最重要的就是那个 cluster_version_)缓存在本次连接的上下文(connection context)中。这是一个存放在内存里的数据结构。
3
高效读取:此后,在该连接的生命周期内,节点A每次要为节点B序列化消息时,它只需要从本地内存中读取这个缓存的 cluster_version_ 值,然后进行简单的整数比较(if (cached_version <= CLUSTER_VERSION_4_1_0_1))。这个操作的成本只是一次内存访问和一次CPU比较指令,与访问一个成员变量无异,开销极低。
•
Protobuf
•
序列号 反序列化
•
预留字段 保证一个结构总大小不变。
TiDB 严格遵循 Protobuf 的向前兼容规范,Golang 解析 Protobuf 时天然支持 忽略未知字段
TiDB 严格遵循 Protobuf 的向前兼容规范,
Golang 解析 Protobuf 时天然支持 “忽略未知字段”,这是兼容的底层保障:
// 以 TiDB 内部的 KV 操作请求消息为例(简化版)
syntax = "proto3";
package tikv;
// 原始版本(老版本)
message KvRequest {
string key = 1; // 必选字段
string value = 2; // 必选字段
int64 ttl = 3; // 可选字段(老版本已有)
}
// 新版本新增字段(向前兼容设计)
message KvRequest {
string key = 1; // 不修改已有字段的编号/类型(核心!)
string value = 2;
int64 ttl = 3;
bool async = 4 [deprecated = false, default = false]; // 新增可选字段,带默认值
string trace_id = 5 [deprecated = false]; // 新增可选字段
}
•
Golang 解析行为:
•
老版本节点发送的 KvRequest 没有 async/trace_id 字段,新版本 Golang 代码解析时会自动忽略这两个未知字段,仅解析已有字段,不会报错;
•
新版本节点向老版本节点发送消息时,会主动不填充 async/trace_id(通过版本协商控制),确保老版本解析无异常。
主动版本控制机制
•
TiDB 不直接使用生成的 Protobuf 结构体,而是通过 Builder 模式 或 工厂函数 来控制字段填充
•
DDL 系统的版本检测
TiDB 的 DDL 系统会主动检测集群中所有 TiDB 实例的版本,并根据版本能力选择合适的作业版本:
// detect versions of all TiDB instances and choose a job version to use
func (d *ddl) detectAndUpdateJobVersion() {
// 检测所有 TiDB 实例版本
infos, err := infosync.GetAllServerInfo(d.ctx)
// 根据版本能力决定使用 V1 还是 V2
if allSupportV2 {
targetVer = model.JobVersion2
} else {
targetVer = model.JobVersion1
}
}
1
BR 系统的版本兼容性检查
BR (Backup & Restore) 系统在执行前会进行严格的版本兼容性检查:
// CheckVersionForBR checks whether version of the cluster and BR itself is compatible
func CheckVersionForBR(s *metapb.Store, tikvVersion *semver.Version) error {
// 检查主版本兼容性
if BRVersion.Major < tikvVersion.Major || BRVersion.Major-tikvVersion.Major > 2 {
return errors.Annotatef(berrors.ErrVersionMismatch, "major version mismatch")
}
// 检查特定不兼容版本
if tikvVersion.Major == 3 {
if tikvVersion.Compare(*incompatibleTiKVMajor3) < 0 && BRVersion.Compare(*incompatibleTiKVMajor3) >= 0 {
return errors.Annotatef(berrors.ErrVersionMismatch, "version mismatch")
}
}
}
•
Kafka 的消息格式中,attributes 字段(1 字节)仅使用低 3 位表示压缩类型,高 5 位全部预留,用于后续新增消息属性(如时间戳类型、事务标记等)
•
•
https://deepwiki.com/tikv/tikv
•
https://deepwiki.com/pingcap/tidb
•
TiDB升级全流程与后问题深度排查:从平滑迁移到故障修复
•
从6.1.1升级到8.5.0建议
•
https://deepwiki.com/ceph/ceph
•
https://deepwiki.com/oceanbase/oceanbase
从零开发分布式文件系统(5.4):如何优化线程模型以提升NVMe SSD性能
从零开发分布式文件系统(三) :JuiceFS|沧海|3FS 百万 OPS 答疑(2)(ceph 默认 5 千)
从零实现分布式文件系统(二) 如何在不升级硬件的前提下,小文件并发读写性能提升十倍