服务治理中间件之版本策略

× 文章目录
  1. 1. 背景介绍
    1. 1.1. 常见的版本策略
    2. 1.2. 我们的思路
    3. 1.3. Dolphin的策略
  2. 2. 实现细节
    1. 2.1. 版本定义
    2. 2.2. 版本兼容规则
    3. 2.3. 版本兼容实例
  3. 3. Dophin中设置版本

在部署服务并向不同的客户端提供服务时,我们会涉及到一个版本的策略问题。

其核心内容是在多版本的情况下如何协调和制约不同版本的服务器和客户端: 如何规范版本之间的选择问题,当实现和接口发生变化时版本应该如何演进。

在本文中,我们将介绍业界常见的几种版本策略,我们面对的需求,以及我们最终在dolphin项目中选择的策略。然后会详尽的介绍该版本策略的实现细节和使用方式。

背景介绍

常见的版本策略

一般来说,对于RPC(REST也类似)系统,在版本控制上通常有以下几种策略:

  • 无版本

    无版本,或者说永远只有一个最新版本,这要求一旦服务器端升级,所有的客户端都必然被逼着使用最新的API。这也意味着任何API的修改都会影响到所有的客户端用户,甚至扩散到整个生态系统。

    这种方式是影响最恶劣的一种版本策略,逼迫所有客户端必须和服务器端同步更新。

    缺点是升级时阻力最大代价最高,客户端众多时如何协调所有客户端和服务器端一起同时升级是个非常考验协调能力的事情。尤其当公司规模较大部门协作困难时,简直是噩梦,完全不可接受。

    优点是简单粗暴,只要能推的动执行的下去,是最没有后患的策略,永远不用担心向后兼容的问题。因此当API设计不合适的时候,可以相对比较自由的修订和改善。当API使用范围小而可控时,使用这个版本策略比较简单容易推进。

  • 点对点

    点对点,即平台的API版本带有版本号,根据需要部署多个版本。用户根据自己的需求选择使用对应的API版本,然后连接到对应版本的服务器。当客户端需要使用新的API特性,用户必须自己升级。

    这种方式是对客户端影响稍微小一点的版本策略,在新版本发布之后,旧有版本的用户还可以继续使用原有版本,然后不同的客户端可以在不同的时间内各自升级到新版本,因此不存在同一时间一刀切的升级难问题。由于存在多版本,因此有一定的空间留给新版本做API改良,对版本演进有好处。

    缺点是容易造成服务器端版本众多,尤其当服务器端版本升级频繁时,需要同时维护多个版本的服务器。典型问题在于当发现有严重bug必须修复时,不得不在所有版本上都修复并更新,维护代码高昂。

  • 全兼容

    和无版本的策略类似,平台只有一个最新版本,但是这个最新版本需要兼容以前所有版本的行为。

    优点特别明显,升级完全没有阻力,无论是客户端还是服务器端都可以自己升级新版本。

    最大的挑战在于初期版本的API设计,是否足以覆盖到之后的后续发展,要求没有明显缺陷并可以有足够的扩展。如果能做到这种策略是最理想的,但是如果没能做到,初始设计不理想,后期为了兼容就不得不忍受初期的设计,很难有机会进行改善。这也是这种策略最大的缺点:画虎不成就有憋成犬的风险。

我们的思路

以上几种策略方案,优缺点都很明朗,可以说每个策略在它适用的区域都是不错的选择,但是出了这个区域就会出现明显的不适应。

而通常我们在一个公司范围内,对于不同的产品,不同的团队,各个的情况会有极大差异,使得根本不可能出现一套解决方案简单一刀切就可以解决所有人的所有问题。具体说,上述几个方案,不管取其中的哪一个,强制实施到所有产品,可能都会带来很多问题。

因此我们需要考虑的是,如何可以做到用一个统一的策略(或者说制约方式),使得我们的产品在版本控制方面可以做到在上述三者之间自由选择,甚至必要时自有切换为另一个更适合的方式:

  1. 如果产品很简单,使用范围小,可控性非常高,那么使用第一种”无版本”策略可以简单的解决问题。这个策略特别适合同一个团队的同一个产品或者多个产品的内部之间使用,由于开发者和使用者合二为一,因此协调不是问题,绑定升级不是问题。

  2. 如果产品的可控性不够上面这么好,尤其暴露给了外部用户。这个时候可以先优先考虑”全兼容”的策略,在初期API设计时如果设计得当,能实现”全兼容”就尽可能向全兼容靠拢。

  3. 如果开始设计时没有考虑”全兼容”的方案,或者后期产品变化比较大,原有方案hold不住,这个时候可以考虑在适当的时候改用”点对点”方案。在新版本中大幅修改设计,以求得到理想的API应对之后的产品演进,同时保留旧有版本,留下一定的时间窗口给现有客户逐步升级。

  4. “点对点”方案在版本分裂之后,应该尽快让客户端升级到新版本,然后停止旧有版本的支持,重新切换到”全兼容”的轨道,以避开”点对点”方案维护代价高昂的缺陷。

总结说,我们追求的理想的版本控制思路是这样的一个混合体:”无版本”策略特别适合内部使用。而”全兼容”和”点对点”策略适合暴露给外部客户端,两者可以交叉使用,长期目标是尽可能维持”全兼容”,但是在有明显缺陷时不强求,通过转为”点对点”方案寻求在短期之内实现新旧版本过渡,过渡完成后在一个新的更好的基础上继续追求”全兼容”。

Dolphin的策略

Dolphin在版本策略方面延续上面的思路,采取的策略总结有以下三点:

  1. 大版本隔离

    对于1.×和2.×这样的大版本,采用隔离的方式(即前面的”点对点”策略),确保不同大版本的客户端和服务器端不会直接通讯。

  2. 小版本兼容

    对于1.1和1.2这样的小版本,采用兼容方案,让低版本的客户端可以访问到可向后兼容的高版本服务器端。但是高版本的客户端是不能访问低版本的服务器端。

  3. 多版本并存

    容许在线上部署各种版本的多台服务器,在部署上不做限制。客户端在遵循”大版本隔离”和”小版本兼容”的基础上可以自由的选择版本兼容的服务器。

实现细节

这里我们将介绍dolphin中版本控制的实现细节。

版本定义

在Dolphin中,我们采用的是业界最为通用的版本定义方式,版本分成标准的三大部分:

  1. major

    大版本,当大版本变化时,意味着API出现了不兼容的情况,此时不同大版本客户端和服务器已经无法通讯

  2. minor

    小版本,当小版本变化时,意味着API出现变化,但是可以向后兼容,典型场景如增加新的接口方法。此时大版本相同但是小版本不同的客户端和服务器端可以兼容(注意只是是低版本的客户端和高版本的服务器端兼容,反之不行)

  3. patch

    补丁版本,用于bug fix,当发布补丁版本时,意味着只有功能修复,API没有任何变化,理所当然的向后兼容。

定义的方式,以下版本都是dolphin认可的:

1
2
3
4
5
1
1.0
1.0.0
1.3.5
2.0

注意以上版本中,如果小版本和patch版本没有给出,dolphin会自动用零补全。即版本”1”和”1.0”都会补全为”1.0.0”, 这三种写法在dolphin中是等价的(实际注册在consul中只会使用补全之后的完整的格式)。

为了简单期间,dolphin不支持其他格式的版本方式(主要是考虑到没有必要把事情搞复杂),这些方式包括:

  • v1.0: dolphin中不必带前缀,直接写1.0即可
  • 1.0-RELEASE: spring喜欢用的风格,dolphin中同样不必带后缀,直接写1.0即可
  • A: 非数字型的版本,dolphin不支持,因为难于定义它和其他版本之间的兼容界限
  • 无版本: dolphin要求必须给版本,实际实现时如果不填写版本会默认为”1.0.0”

版本兼容规则

Dolphin版本兼容的判断规则前面已经有文字描述,这里我们给出内部实现的实际java代码,相信更容易让大家理解透彻:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public boolean isBackCompatibleWith(ServiceVersion targetVersion) {
// 1. 先比较大版本
if (this.major != targetVersion.major) {
// 大版本必须一致,否则直接返回false
// 举例:如果服务区端是2.*,则客户端必须也是2.*
return false;
}
// 2. 大版本相同之后继续比较小版本
if (this.minor < targetVersion.minor) {
// 2.1 如果小版本比目标版本小,直接返回false
// 举例:如果服务区端是2.5.*,则客户端是更高的2.6.*则肯定不兼容,不需要继续比较patch
return false;
}
if (this.minor > targetVersion.minor) {
// 2.2 如果小版本比目标版本大,直接返回true,无需比较patch
// 举例:如果服务区端是2.5.*,则客户端是2.4.*/2.3.*则肯定兼容,不需要继续比较patch
return true;
}
// 2.3 只剩下major和minor都相等的情况了,需要继续比较patch
// if (this.minor == targetVersion.minor) { } // 这行就不必写了,能到这里肯定是 == 了。
return this.patch >= targetVersion.patch;
}

注:以上代码仅为参考,细节以git仓库为准,具体代码可以见dolphin-registry项目中的ServiceVersion类。

版本兼容实例

以下表格中,列举了当版本从1.0.×到2.0版本中,各个版本之间的兼容性:

服务器端版本 1.0.0 1.0.1 1.0.8 1.1.6 2.0.5
客户端版本 1.0.0 Y Y Y Y N
客户端版本 1.0.1 N Y Y Y N
客户端版本 1.0.8 N N Y Y N
客户端版本 1.1.6 N N N Y N
客户端版本 2.0.5 N N N N Y

第一行是服务器端的版本,下面的每行对应一个客户端版本,然后我们罗列了它和各个服务器端的兼容情况。

特别要注意的是:

  1. 对于patch版本,当大版本/小版本都同样时,dolphin中也是要求必须服务器端patch要大于等于客户端版本。即服务器端1.0.8版本可以兼容客户端的1.0.1版本,但是反之服务器端的1.0.1版本是不能兼容客户端1.0.8版本的。
  2. 如果大版本已经发现不兼容,则忽略小版本和patch版本
  3. 如果大版本相同,小版本不同就可以直接确认兼容或者不兼容,不需要比较patch,例如服务器端的1.1.6版本就可以兼容客户端的1.0.8。

Dophin中设置版本

在dolphin中,服务器端在注册服务和客户端查找需要访问的服务时,都需要设置对应的版本号。

具体的设置方式请见后面的”服务器”和”客户端”的具体章节。

如果您觉得文章不错,可以打赏我喝一杯咖啡!