服务与服务之间的调用

石果子2022-07-07  21

本文简要描述了我所了解到的解决“服务调用”相关的技术演进历史,纯属科普文章。

图片来自Pexels

本文重点讨论演进过程中每一步的原因和内容,尽量不要过于深入技术细节。

服务的三个要素

一般来说,网络服务包括以下三个要素:

地址:调用者根据地址访问网络接口。地址包括以下元素:IP地址、服务端口和服务协议(TCP、UDP等)。协议格式:指协议的字段,由接口提供者与协议调用方协商后确定。协议:或协议类型,因为在同一个服务监控端口上,可能会同时提供多个接口为调用者服务。这时就需要协议类型(名称)来区分不同的网络接口。注意,在服务地址中:

IP地址提供了在互联网上查找该计算机的凭据。以及协议服务端口,提供凭据以查找在此机器上提供服务的进程。

这些都属于TCP/IP协议栈的知识点,这里就不赘述了。这里,需要解释一些与服务相关的术语:

服务实例:服务对应的IP地址加上端口的简称。当您需要访问一个服务时,您需要在建立访问连接之前,确定该服务的每个运行实例的地址和端口。注册:一个服务实例声明它提供哪些服务,也就是一个IP地址+端口提供哪些服务接口。服务发现:调用者通过某种方式找到服务提供者,也就是知道服务运行的IP地址和端口。基于IP地址的呼叫

初始网络服务通过原始IP地址向调用者公开。这种方法存在以下问题:

IP地址很难记,没有意义。另外,从上面的服务三要素可以看出,IP地址其实是一个很底层的概念,直接对应一个机器上的网络接口。如果直接用IP地址寻址,换机会很麻烦。“尽量不要用太低级的概念来提供服务”是这个进化过程中的一个重要原则。比如今天就很少看到直接用汇编语言写代码的场景了。

反而抽象越来越多。本文展示了服务调用领域在这一过程中的演变过程。

现在除非是在测试阶段,否则无法直接以IP地址的形式提供服务。

域名系统

之前的IP地址是主机作为路由器地址的数字标识,不好记。

这时域名系统就应运而生了。与简单地提供IP地址相比,域名系统更容易记住,因为它使用有意义的域名来标识服务。此外,还可以更改域名对应的IP地址,为换机提供了便利。

拥有域名后,当调用方需要访问某个网络服务时,先去域名地址服务,根据DNS协议将域名解析成对应的IP地址,然后根据返回的IP地址访问服务。

从这里可以看出,因为多了一个查询IP地址到域名地址服务映射过程的步骤,所以多了一个解析的步骤。为了减少这一步的影响,调用者会缓存解析后的结果,在一段时间内不会过期,从而节省了这一步查询的成本。

协议的接收和解析

上述域名系统解决了服务IP地址难记的问题。我们来看看协议格式解析的演变。

一般来说,网络协议包括两部分:

包头:协议的元信息存储在这里,可能包括协议类型、报体长度、协议格式等。需要注意的是,包头一般是固定大小或者有明确的边界(比如终止符),否则无法知道包头何时结束。

协议主体:具体的协议内容。无论是HTTP协议还是自定义二进制网络协议,一般都由这两部分组成。

很多情况下,一口气接收到客户端的协议数据是不可能的,所以在接收协议数据时,一般使用状态机来接收协议数据:

收到网络数据后,协议分析停滞了很久。一个协议有多个字段,这些不同的字段有不同的类型。简单的raw类型(比如integer、string)说起来容易,但是遇到字典、数组等复杂类型就比较麻烦了。

当时常见的手段如下:

使用json或xml等数据格式。优点是可视性强,方便表达以上复杂类型。缺点是容易被破解,过往数据传输量大。自定义二进制协议。随着每个公司越做越大,这个领域必然会有几个类似的轮子。我见过典型的TLV格式(类型-长度-值)。自定义二进制格式的最大问题发生在调试和协商协议时。由于可视性较弱,可能这里缺一个字段,那里加一个字段,给调试过程带来麻烦。

上述问题直到Google的协议缓冲区(以下简称PB)的出现才得到很大的改善。

PB出现后,类似的技术还有很多,比如Thrift,MsgPack等。,这里就不赘述了。所有这些技术都将被描述为PB。

与前两种方法相比,PB具有以下优点:

Proto格式文件用于定义协议格式。proto文件是典型的DSL(域特定语言)文件,描述了协议的具体格式,每个字段是什么类型,哪些是可选字段,哪些是必填字段。有了proto文件,C\S的两端通过这个文件相互通信,而不是具体的技术细节。

PB可以通过proto文件生成对应各种语言的序列化和反序列化代码,为跨语言调用提供了便利。PB可以压缩特定类型的数据,减少数据量。服务网关

有了之前的进化,写一个简单的单机服务器并不难。但是,随着访问量的增加,一台机器不足以支持所有请求,因此需要向外扩展并添加更多的业务服务器。

但是之前通过域名访问服务的架构遇到了一个问题:如果有多个服务实例可以提供相同的服务,就需要在DNS域名解析中将域名与多个地址绑定。

这种方案存在以下问题:

如何检查这些实例的健康状况,并在发现问题时添加或删除服务实例地址?所谓的服务高可用性问题。向外部网络公开这些服务实例地址会涉及安全问题吗?即使安全问题可以解决,那么每台机器都需要制定一个安全策略。由于DNS协议的特点,添加和删除服务实例不是实时的,有时会影响业务。

为了解决这些问题,引入了反向代理网关组件。它提供以下功能:

负载平衡功能:根据某些算法将请求分派给服务实例。管理功能:运维管理员可以添加或删除服务实例。因为它决定了服务请求流量的方向,所以它还可以做更多的其他功能:灰色引流、安全抵御攻击(比如访问黑白名单、卸载SSL证书)等。负载均衡软件有四层和七层,其中第四层引入LVS,第七层引入Nginx。

上图是一个简单的TCP/IP协议栈的层次图,其中LVS分四层工作,即当一个请求来到LVS时,根据四层协议确定该请求最终去往哪个服务实例。

Nginx在七层中工作,主要由协议本身用来确定请求的方向。

需要注意的是,Nginx也可以在四楼工作,但是这样使用的地方并不多。可以参考Nginx的Stream模块。

LVS作为四层负载平衡

由于LVS有几种工作模式,我并不完全了解。下面的语句只针对全NAT模式,下面的语句可能是错误的。

LVS有以下组成部分:

直接服务器(Direct Server,以下简称DS):前端向客户端公开的服务器,用于负载均衡。虚拟IP地址(以下简称VIP):DS暴露的IP地址,作为客户端请求的地址。直接IP地址(以下简称DIP):DS用来与真实服务器交互的IP地址。Real(以下简称RS):真正在后端工作的服务器,可以横向扩展。IP地址(以下简称RIP):RS的地址。客户端IP地址(以下简称CIP):客户端的地址。当客户端发出请求时,流程如下:

使用VIP地址访问DS,其中地址二进制为

在开始讨论之前,我们需要先简单说一下正向代理和反向代理。

所谓前向代理,我理解就是客户端的代理。比如浏览器中可以配置访问某些网站的代理是正代理,但一般来说不是正代理而是代理,也就是默认代理是正的。

而反向代理是站在服务器前面的代理。例如,LVS前面的DS服务器属于反向代理。

为什么需要反向代理?一般原因如下:

负载均衡:我希望在这个反向代理服务器中,请求被均匀地分配到下面的服务器。安全性:不想暴露太多服务器地址给客户端,可以统一访问这个反向代理服务器,在这里做限流和安全控制。由于对客户端请求的统一访问,可以在反向代理的访问层制定更多的控制策略,如灰度流量释放、权重控制等。我觉得反向代理和所谓的网关、网关等没有太大区别。只是叫的名字不一样,做的事情差不多。

Nginx应该是现在用的最多的效果了。

比如:

上游hello {服务器A:11001;服务器B:11001;} location/{ root html;索引index.html index.htm;proxy_pass的配置修改需要人工干预,效率会比较慢。

是时候提一个名词叫DevOps了。我的理解是开发各种自动化运维工具的工程师。

有了上面的分析,此时,一个提供七层HTTP提供者的服务架构大致是这样的:

服务发现和RPC

单机服务器对外提供服务的大部分问题,之前已经解决了。让我们简单回顾一下:

域名系统解决了记忆复杂数字IP地址的问题。类PB软件库的出现,解决了协议定义分析的痛点。网关组件解决了一系列问题,如客户端访问和服务器横向扩展。然而,一项服务通常不仅仅是由它自己提供的。服务过程还可能涉及查询其他服务的过程,比如MySQL、Redis等数据服务。

这种在服务内部进行调用和查询的服务称为内部服务,通常不直接对外公开。

面向公共网络的服务通常以域名的形式提供给外部呼叫者。但是,域名形式对于服务内部的相互调用是不够的。原因如下:

DNS服务发现的粒度太粗,只能达到IP地址级别,服务端口需要用户自己维护。对于服务的健康检查,DNS检查是不够的,还需要运维的参与。缺少对DNS服务状态的收集,服务状态应该反过来影响被调用的服务。DNS的更改需要人工参与,不智能,不自动。综上所述,内网之间的服务调用通常自行实现一套“服务发现”系统,包括以下组件:

服务发现系统:用于提供服务的寻址和注册能力,并对服务状态进行统计汇总,根据服务情况改变服务的调用情况。例如,如果一个服务实例的响应很慢,分配给该实例的流量响应会更慢。

因为这个系统可以提供服务的寻址能力,这里可以做一些寻址策略,比如灰度,某些流量只能到达某些实例,比如可以配置每个实例的流量权重。

与此服务系统一起使用的一组RPC库。RPC库提供了以下功能:

服务提供者:使用RPC库向服务发现系统注册自己的服务,并报告自己的服务。服务调用方:使用RPC库进行服务寻址,实时从服务发现系统获取最新的服务调度策略。它提供了协议的序列化和反序列化功能、负载均衡的调用策略、熔丝限流等安全访问策略,适用于服务提供者和调用方。

有了这个服务发现系统和与之一起使用的RPC库,让我们看看当前的服务调用是什么样子的:

如果你写的是业务逻辑,就不用关注服务地址、协议解析、服务调度、自助报告等等,只需要关注业务逻辑就可以了。发现系统一般有配套的管理后台界面,通过该界面可以修改和查看服务策略。还将有一个服务监控系统,这是一个实时服务数据收集和计算系统。有了这个系统,服务质量一目了然。完全自动检查服务的健康状况,在服务状况不佳时降级,人工干预更少,智能化、自动化程度更高。现在,服务架构已经发展成这样:

服务网格

发展到上述水平的架构,其实已经能够解决大部分问题。近两年又出现了一个火热的概念:Service Mesh,中文翻译过来就是“服务网格”。看看它能解决什么问题。

在以前的服务发现系统中,需要匹配的RPC库,但是会存在以下问题:

如果需要支持多种语言,该怎么办?每种语言都实现了相应的RPC库吗?库的升级很麻烦。比如RPC库本身就有安全漏洞,比如需要升级版本。一般来说,推动业务端做这个升级是非常困难的,尤其是系统变大以后。可以看到,RPC库是嵌入在进程中的一个组件,所以上面的问题很麻烦,于是设计了一个解决方案:把原来的进程拆分成两个进程。

如下图所示:

在服务网格之前,服务调用者实例通过它自己的内部RPC库与服务提供者实例通信。

在服务被网格化之后,一个本地代理,也就是服务网格的代理,将被部署在与服务调用者相同的机器上。

此时服务调用的流量会先去这个代理,然后它会完成原来的RPC库响应。

至于如何劫持这个流量,答案是使用Iptables,将特定端口的流量转发给代理。

随着这一层的拆分,业务服务从负责RPC库的代理中分离出来,以上两个痛点变成了每台物理机上Mesh代理的升级和维护。

多语言不是问题,因为所有的RPC通信都是通过网络调用完成的,而不是在这个过程中使用RPC库。

然而,这个方案并不是没有任何问题。最大的问题是,增加这层调用后,原来的响应时间势必会受到影响。

时至今日(2019年6月),服务网格仍然是一个概念大于实际的产品。

从上面的进化史可以看出,所谓的“中间层理论”,即“计算机科学中的任何问题都可以通过另一层间接解决”,在这个过程中被广泛使用。

比如为了解决IP地址难记的问题,引入域名系统,比如引入网关解决负载均衡的问题,等等。

但是,每引入一个中间层,必然会带来另一种影响。例如,从服务网格到代理的另一个调用是另一个问题。

另外,回到最初的服务三要素,我们可以看到,整个进化史也是逐渐屏蔽较低级成分的过程,比如:

域名的外观掩盖了IP地址。发现服务系统屏蔽协议和端口号。PB类序列化库屏蔽了用户自己对协议的分析。可以看出,演进过程使得业务开发人员更加专注于业务逻辑。这种演变过程不仅发生在今天,也不会发生在今天。类似的进化在未来还会发生。

作者:codedump

简介:从事互联网服务器后台开发多年。可以访问作者的博客:/,阅读更多文章。

作者:codedump来源:高可用架构

转载请注明原文地址:https://juke.outofmemory.cn/read/628578.html

最新回复(0)