以前玩游戏的时候,总是希望在高手后面看他怎么操作,以提高自己的水平。当时悟出了一个道理:菜鸟看老鸟,只觉得每一步都顺理成章。但是菜鸟自己上时,老鸟却是看得频频摇头。今天这个标题讲的就是这么个理所当然的架构,源于我的同事对当前系统的思考。

单体系统→微服务→平台

随着IT的不断发展,软件能解决的问题越来越多也越来越大。在大型软件开发中,人们发现要与一大坨代码共舞变得越来越困难,于是微服务架构兴起并持续火热,许多单体系统也纷纷挤上风口随之演进,不管是新服务单提出去还是老服务逐步拆分,总之他们都渐渐走到分布式系统的大道上了。服务的数量上去了,便带来了一个问题:你的服务跟别人的服务应该如何集成呢?最简单粗暴直截了当的做法就是直接调用。但是这样缺少定制化支持。对方有什么,你可能就只能凑合着用什么。要不,就得等对方把你的需求排上档期。

总这样不行呀,缺胳膊少腿的服务和只能傻傻等待着跟不上节奏的服务没法儿工作呀。为了满足不同客户的多方面需求,服务的平台化就成了一个自然而然的选择。作为平台方,不止提供服务,而且提供客户在平台上的定制化功能,客户成为了平台的租户。一般来说,租户需要在平台那边写点儿为自己的需求而定制的代码或是DSL,以便支持自己的业务场景。

如果你是一个平台的所有者,如何才能让别人在你的平台上顺利安营扎寨,而你无需提供太多的支持工作呢?

构建组合型平台

首先要问自己一下,你的平台,也就是你拥有的服务,面向的客户是什么人呢?从技术维度上来说,无非是这几类:

  • 非技术人员:你的服务只对非技术人员开放,对方想要的是一个大而全的系统,而不希望在不同的服务间来回奔波。以操作系统为例的话,那就是需要一个Windows。
  • 技术人员:你的服务只对技术人员开放,对方想要的是更加灵活,别人的烂代码不要影响到自己的代码。Unix/Linux就是一个典型的例子。
  • 混合型人员:你的服务对技术人员及非技术人员开放,大家各取所需,当然对你的要求也比较高了。举个例子就是OSX。

出于简单起见,我们先考虑前两种类型。实际情况是,第一类其实就是许多第二类的聚合。你可能有许多微服务,但是作为网关的微服务其实数量不多。这样,我们便把要解决的大部分平台问题归类于对技术人员开放的服务了。第三类的混合型也很类似。其实对于微服务来说,我根本不在乎对方的实现方式、架构方式,能提供稳定的服务就可以了。但是,一旦它是个平台,并且我需要在其上定制代码,那我也就不得不关心一下了。还是先分两个类:

  • 非组合型软件架构:平台代码和租户代码的耦合比较紧,类比的话,就是EJB。平台升级很可能需要租户代码一起改。于是,能不升级就不升级,因为租户很多,升级可能会很繁琐。
  • 组合型软件架构:可能由多种服务组成,对于每种服务而言,SRP是最重要的事,把一件简单的事情做好。其实“简单”是相对而言的,不管内部有多复杂,但是开放出去的接口,或者是允许租户在其中实现的能力,都应该只专注于一件事情。举个例子,亚马逊AWS的S3。作为使用者而言,还是非常简单的。虽然程序员们都知道要维护这样一个服务其实是相当复杂的。这些服务之间是松耦合的,像Linux的管道一样,灵活组合,对外提供平台的能力。

如果一个租户的代码挂掉了,从而影响到其他的租户甚至是你的平台,那这个平台就是很有问题的。这句话看起来理所当然,但确实有不少所谓的平台并不是这样设计的。在这种情况下,租户要往这个平台提交代码,就需要接受平台的代码审查,以免待提交的代码影响到其他人。而平台方就不得不耗费人力审查代码、部署代码,当租户的数量多起来时,平台根本就无法提供足够的可伸缩性,势必需要排优先级。于是各种抱怨就纷至沓来,自己的技术人员也苦不堪言。所以,我们希望租户拥有对自己代码的审查和部署的所有权,不希望当租户代码变化时需要平台人员人力审核。

如果你需要开发一个平台,如何能够做到让租户之间不相互影响呢?最佳的租户隔离方式是向租户提供服务级别的隔离。一个租户服务的倒掉不至于影响到其他租户的服务,只是影响已损坏服务的分支业务自己而已(自己的代码挂掉,当然自己承担喽)。如果由于某些原因无法为所有租户都提供服务级别的隔离,那么也可以尝试进程级别的隔离,例如Docker。一个损坏的进程在许多情况下都不太至于影响到平台为其他租户进程提供的服务。如果很不幸的是不得不与租户在同一个进程里,那么起码还可以用DSL来限制租户的能力,让租户的代码不能有太大的破坏性。要是租户也能用平台的语言,还在同一个进程中,那么搞砸平台的服务只是时间问题罢了。

在DevOps的光茫照耀下,我们当然要提倡自动化,这是平台和租户双方的责任。平台提供的是从代码提交到部署测试环境乃至生产环境的自动化能力,而租户需要实现的是自动化测试。一个典型的应用场景就是:平台上的租户代码提交,触发了该租户的持续交付流水线,于是在测试环境上自动部署平台的租户代码,接下来运行租户自己实现的自动化测试,一旦通过,租户便可以决定是否要在平台的生产环境上自动部署这次提交的代码。

最后,作为平台,提供的是能力。不要强迫租户使用你的能力,而要提供稳定的服务,让租户心甘情愿地使用你的能力。向Unix那样,把一个个稳定的服务组合起来,以提供整体的服务;同时,允许租户自由替换其中的某些服务以实现灵活性。能做到这一点的平台就相当成熟了。