不是后端也应该知道的「web 服务、子服务、服务的部署」

web 服务是什么

1. 定义

我们先来看一个很通俗的定义,来自于wiki。

A web service is a service offered by an electronic device to another electronic device, communicating with each other via the World Wide Web.

Web service 指的是,一个平台通过 web 向其它平台来提供服务。

更专业一点的定义怎么说呢?我们来看一下 W3C 对 web service 的定义。

A web service is a software system designed to support interoperable machine-to-machine interaction over a network.

Web service 是一个软件系统,使得不同机器可以在网络间进行互动操作。

2. 要素

想要实现一个平台在网络间调用另一个平台的服务,至少需要明确三点:

  • 如何将平台上的代码作为服务暴露出去供其它平台调用;
  • 使用什么样的网络协议通信;
  • 使用什么样的格式作为通信内容。
从 WSDL 理解 web service 的要素

要回答以上问题,我们可以先简单的了解一下什么是 WSDL,Web Services Description Language,网络服务描述语言。我们知道服务的提供方其实本质上是由代码编写而成,而服务的调用方通过发起一个网络请求来调用服务。那么通俗的说,WSDL 做的事情就是,描述了如何根据调用方发送的网络请求,找到服务提供方,进而找到要运行哪一段代码,从而得到结果返回给调用方。

WSDL 是基于 XML 格式的文档,包括两部分,抽象定义和具体定义。

WSDL的抽象定义

WSDL 的抽象定义,独立于提供服务的平台和服务实现的语言,定义了该服务通过什么样的网络协议、使用什么样的消息格式与调用方通信。网络协议是不受限制的,可以是 http、ftp、smtp 等其它网络传输协议,不过大部分情况下我们使用的是 http 协议。消息格式也是多种多样的,最初唯一被广泛使用的消息格式是基于 XML 格式的 SOAP,简单对象访问协议,后来 REST 流行了起来,出现了基于 REST + XML 的消息格式,再后来发展为 REST + JSON 的消息格式,也就是我们现在应用最广泛的一种。

WSDL 的具体定义

WSDL 的具体定义,与平台和语言相关,定义了一个具体的服务调用,请求参数和返回参数是怎么样的、以及通过哪一部分代码的运行可以得到结果等等。

咖啡馆的类比

我们使用一个咖啡馆来类比 WSDL 的工作原理,咖啡馆是服务提供方,提供了下单、取餐、付款等服务,咖啡馆的员工手册则相当于提供服务的代码,顾客是服务调用方。WSDL 的抽象定义,定义了顾客如何找到咖啡馆的位置,以及顾客和咖啡馆的员工使用哪国语言进行交流等等;而 WSDL 的具体定义,则定义了每一个具体的服务,如下单服务,顾客需要提供什么,工作人员在员工手册的哪一页可以找到下单的操作流程,以及工作人员会返回什么给顾客,等等。

WSDL 文档可以由服务的实现代码自动生成,反之也可以通过定义好的 WSDL 文档生成代码框架。

3. 应用方式

最常见的两种 web service 的组织形式是:RPC 远程过程调用,REST 表述性状态转移。从本质上来说,两者定义的都是规范,一个是面向过程的远程调用规范,一个是面向资源的远程调用规范。

RPC 远程过程调用

RPC,Remote Procedure Call,远程过程调用,定义了平台与平台之间面向过程进行服务调用的规范。它的本质思想是,将一个平台上的多个函数过程,作为一个服务,提供给另一个平台调用。所以以 RPC 为规范的服务,需要关心的是「我要做一件什么事」。RPC 规范是协议无关的,可以使用各种网络协议实现。

REST 表述性状态传递

那么 REST 又是什么?不知道 REST 没关系,如果你接触过 GET、POST、PUT、DELETE 这样的请求,不要怀疑,这种我们通常意义上所说的 http 请求大部分都是基于 REST 规范而来的。基于 REST 规范设计的 api 也称之为是 RESTful 的 api,这样的 api 的主题必须是资源,它关心的是「我要对某个资源进行什么样的操作」。为什么 REST 可以流行起来呢?这就跟我们为什么要用面向对象的思想进行编程是一个道理,万物皆对象,外物皆资源。这里推荐一篇非常通俗的讲解 REST 规范的文章 如何给老婆解释什么是RESTful

RPC 与 REST 的比较

总的来说,PRC 与网络协议无关,关心的是过程;REST 基于 http 协议,关心的是资源。下图演示了针对相同的有关用户的操作,REST 形式的服务(左边)和 RPC 形式的服务(右边)设计上的区别。
------_99b55016-aefd-46d3-b34c-c1be1a15758b

那么在具体的使用场景下,对于两种设计规范,我们应该如何选择呢?我觉得二者的取舍,可以类比于函数式编程与面向对象的编程,各自有各自适合的场景,甚至在某些场景下,使用二者皆可且各有利弊。重要的是要理解这两个设计规范的本质和初衷,并根据实际场景和个人的使用习惯最初抉择。

更加深入的 RPC 与 REST 的解释和比较,可以参考这篇文章Understanding RPC Vs REST For HTTP APIs

web service 与子服务

在谈子服务之前,我们来继续之前咖啡馆的假设,从而理解什么是子服务以及我们为什么需要子服务。

为什么我们需要子服务

设想一个咖啡馆的正常运作,需要以下职能人员的参与。

  • 前台:负责创建、修改、删除客户的订单
  • 收银员:收取订单相应的费用、找零、管理咖啡馆的日常支出
  • 服务生:为客户配送咖啡到相应的座位上,回收餐具
  • 清洁工:维护店内清洁、桌椅摆放、空调灯光等硬件设施
  • 经理:保证店铺正常运行,解决问题和异常情况

对于这些职能人员来说,核心要素有三点:

  1. 他们所做的工作有明确的界限划分;
  2. 他们互相之间可能需要进行交流;
  3. 他们共同维护了咖啡馆的运作。

为什么咖啡馆不是由一个非常厉害的全能的人承担所有的工作呢?这个很容易理解:

  • 首先厉害的人比普通人更加难找到;
  • 而且要同时兼顾这么多的工作内容是更加容易出错的;
  • 还有最重要的一点是,如果他生病了,整个店就完全没有办法运作下去。

那么将咖啡馆的例子映射到 web 服务上,提供一个单一的 web 服务来支持整个咖啡馆的运作自然也是不合理的:

  • 想要维护好一个大型的系统比维护好一个小型的系统更加困难;
  • 业务逻辑冗杂的系统更容易出错;
  • 如果这个系统的一小块内容出现问题很容易导致整个系统的崩盘。

那么如何拆分一个服务系统呢,答案就是子服务了。我们将整个系统根据职能的划分拆分成5个子服务,分别对应到上文的5种职能人员。

  • 订单管理服务
  • 账户管理服务
  • 餐具管理服务
  • 店内环境管理服务
  • 性能监控与异常处理服务

同样的这些子服务的核心要素如下:

  1. 这5个子服务所提供的接口有明确的界限划分;
  2. 子服务之间可以互相调用;
  3. 共同保证了整个咖啡馆的运作。

理解了子服务的概念以及 web service 为什么需要子服务之后,新的问题出现了:子服务如何进行合理的拆分?如何管理多个子服务?子服务间如何通信?

这里就不得不提到 SOA 了。

通过 SOA 架构组织子服务

SOA,Service Oriented Architecture,是一个面向服务的架构设计,通俗的说它也是一个规范,定义了如何管理服务的集合及他们之间的通讯方式。它本质上和 web service 以及子服务都没有绝对的依赖关系,它甚至比 web service 出现的更加早。然而人们在 web service 上发现了它的用武之地,也就是说 SOA 刚好可以在 web service 的管理上体现它的价值。于是乎,造成了几乎所有 SOA 的应用场景都与 web service 相关这样的现状,也导致了这两个概念一定程度上发生了混淆。

既然 SOA 框架是对服务的集合的管理,那么它究竟比单纯的服务拆分多做了哪些事情呢?

我们来看一下下图这个简单的例子。假设我们要将整个系统拆分成4个子服务:ACCOUNT、C2D、ASK、DESIGN。左边为单纯的服务拆分,右边为基于 SOA 框架的服务拆分。

------_75f0ad12-0400-4b73-b48b-aec9f93d4701

  • 左边:单纯的进行了服务拆分,形成了4个互独立的服务。这里其实就出现了两个问题:客户端需要关心我请求的 api 到底是属于哪个服务的,然后再往相应的服务端发送请求;虽然服务做了拆分,但是如果其中一个服务出现问题挂掉了,那么整个架构中的服务都不可用。
  • 右边:将这4个子服务作为一个服务的集合,并简单地应用了 SOA 架构。可以看到除了四个子服务之外,最上层还多了一个 gateway,而最下层也多了三个底层模块。最下层的三个底层模块很好理解,有一些工作是每个子服务都需要做的,比如版本控制、性能监控等,底层就是抽出了这样的公共模块以便子服务复用。最上层的 gateway,网关,顾名思义,你们如果想访问我管理的这些子服务,直接访问我就好了;也就是说客户端只需要向 gateway 发送请求,gateway 会根据所配置的规则将请求转发到正确的子服务上,这也就解决了上文所述左边的设计中遇到的两个问题。

子服务及子服务的部署

1. 服务的实现

web 服务是一个软件系统,软件系统是通过代码形成的。那么这样一个软件系统是如何从一大坨代码转化为稳定的、可访问的、可更新的服务的呢?
一个有一定流量的服务一般是由类似这样的结构组成的。

------_e0e3521a-da2e-400d-acea-1ca6161e047d

上层是一个负载均衡器(load balancer),下层是多个相同的节点(node)。

  • 负载均衡器:将针对这个服务的请求,合理的分发到下面的某一个节点上,以尽量达到这样的目的:请求尽可能的被完成(例如其中一个节点没有正常运行不会导致请求失败)、每个节点承担均匀的压力(例如同时有一万个请求,不至于扎堆到同一个节点上去导致节点出现性能问题)。负责均衡器可以通过网络设备、虚拟 ip、nginx 反向代理、甚至仅仅是一段代码来实现。
  • 节点:每个节点都是等同的,每个节点上都运行着相同的服务,等待处理负载均衡器转发过来的请求。节点可以是一个物理机、虚拟机、也可以是一个 docker 容器。

2. 服务的部署

服务的部署,简单来说就是将该服务的软件系统的最新代码克隆到每一个节点上,再在每一个节点上将服务运行起来。那么服务的更新无非就是重新对每一个节点进行一次部署。
但是不要忘记,在一个节点上重新运行服务会导致该节点的服务有一个短暂的罢工,那么如何保证在完成对服务的更新的同时,保证对于客户端来说服务不会出现挂掉的状态?这时候就需要一定的部署策略。

注:下图中绿色节点均表示未更新节点,蓝色节点均表示已更新节点

1)滚动部署

滚动部署,每次只更新 n 个节点,等待前 n 个节点部署好了,再更新下 n 个节点,这样可以保证同一时间只可能最多有 n 个节点处在不可用状态。
下面四张图是一个滚动部署过程的例子,例子中 n 为 1。
首先更新第一个节点。
------_e6c1d90e-c842-4694-b9a6-d9ef870f42eb
待第一个节点更新完毕之后,更新第二个节点。
------_2116f92e-ba3d-4265-91f1-8cc0dcccf56d
待第二个节点更新完毕之后,更新第三个节点。
------_e6c82895-3359-4307-8624-bebfc5fab7ba
待第三个节点更新完毕之后,更新第四个节点。全部的节点都完成了更新。
------_1bfeac4f-6caa-4f2a-bc8a-82d68c16025c
滚动部署的缺点是,在部署过程中,客户端可以同时访问到更新前的服务和更新后的服务。

2)蓝绿部署

蓝绿部署,如下面三张图所示,分为三个步骤。
首先新增四个节点,并将新版的服务部署上去。
------_c7f2eb99-fde9-45f9-be31-c5e48074c9a0
全部部署好之后将负载均衡器指向新的四个节点。
------_0331f734-aa60-4e54-93a9-ed636f32baba
移除原有的旧版本服务的四个节点。
------_bfc141c1-2b07-43c4-a769-e92942b38851
蓝绿部署解决了滚动部署会同时出现新旧服务并存的缺点,但是对资源的要求更高。

3)灰度发布

灰度发布是平滑过渡的一种发布方式。让一部分用户继续用旧版本的服务,一部分用户开始体验新版本的服务,如果用户对新版本没有什么反对意见,那么逐步扩大范围,将所有的旧版本服务更新为新版本服务。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以避免产生无法挽回的影响。灰度发布的实现如下面三张图所示。
首先更新一个节点。
------_7865edc0-1736-42b7-a5de-6b7e7d192464
然后通过负载均衡器按照一定的规则筛选出 20% 的用户(比如用户 id 除以 5 余 0),并将这 20% 的用户所发出的全部请求都转发到已经更新过的节点。
------_f0415278-e36d-4e31-b759-1090c24b41e3
再更新第二个节点,并将包含之前那 20% 的用户的 50% 的用户的请求转发到两个更新过的节点上去。
------_213c6259-2865-41f7-84bc-a5d676023143
以此类推直到所有节点更新完毕,100% 的用户的请求都会请求到新的服务。这里需要注意的是,灰发的用户百分比最好和更新节点的占比相近,这样可以保证每个节点可以承受相似的压力。如果只更新了一个节点,而转发了 90% 的用户的请求到新服务上,那么这个节点很可能会出现性能问题。

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。