发布于 

《凤凰架构:构建可靠的大型分布式系统》🪽

有老朽,有消亡,有重生,有更迭,才是生态运行的合理规律

作者:周志明,出版时间:2021-06

凤凰架构:构建可靠的大型分布式系统

架构演变最重要的驱动力,或者说这种“从大到小”的变化趋势的最根本驱动力,始终都是 为了方便某个服务能够顺利地“死去”与“重生”,个体服务的生死更迭,是关系到整个系统能否可靠存续的关键因素。

演进中的架构

graph LR
    A[大型机
Mainframe] -->|演变| B[原始分布式
Distributed] B -->|演变| C[大型单体
Monolithic] C -->|演变| D[面向服务
Service-Oriented] D -->|演变| E[微服务
Microservice] E -->|演变| F[服务网格
Service Mesh] F -->|演变| G[无服务
Serverless] style A fill:#e0e0e0,stroke:#333,stroke-width:1px; style B fill:#c8e6c9,stroke:#333,stroke-width:1px; style C fill:#bbdefb,stroke:#333,stroke-width:1px; style D fill:#ffe0b2,stroke:#333,stroke-width:1px; style E fill:#f8bbd0,stroke:#333,stroke-width:1px; style F fill:#d1c4e9,stroke:#333,stroke-width:1px; style G fill:#f0f4c3,stroke:#333,stroke-width:1px;

原始分布式时代

  • 在 20 世纪 70 年代末期到 80 年代初,计算机科学刚经历了从以 大型机 为主向以 微型机 为主的蜕变
  • 为突破硬件算力的限制,寻找使用多台计算机共同协作来支撑同一套软件系统的可行方案
  • 负责制定 UNIX 系统技术标准的“开放软件基金会”(Open Software Foundation,OSF(邀请当时业界主流的计算机厂商一起参与,共同制订了名为“分布式运算环境”(Distributed Computing Environment,DCE)的分布式技术体系
  • 由于 OSF 本身的 UNIX 背景,当时对这些技术的研究都带着浓厚的 UNIX 设计风格,有一个预设的重要原则是要使分布式环境中的服务调用、资源访问、数据存储等操作尽可能 透明化、简单化,从而使开发人员不必过于关注他们访问的方法或其他资源是位于本地还是远程
  • 尽管“调用远程方法”与“调用本地方法”只有两字之差,但若要兼顾简单、透明、性能、正确、鲁棒、一致等特点,两者的复杂度就完全不可同日而语了,光是“远程”二字带来的网络环境下的新问题,例如:
    • 远程的服务在哪里(服务发现
    • 有多少个(负载均衡
    • 网络出现分区、超时或者服务出错了怎么办(熔断、隔离、降级
    • 方法的参数与返回结果如何表示(序列化协议
    • 信息如何传输(传输协议
    • 服务权限如何管理(认证、授权
    • 如何保证通信安全(网络安全层
    • 如何令调用不同机器的服务返回相同的结果(分布式数据一致性
  • 为解决这样做带来的服务发现、跟踪、通信、容错、隔离、配置、传输、数据一致性和编码复杂度等方面的问题所付出的代价已远远超过了分布式所取得的收益
  • 在那个时代的机器硬件条件下,为了让程序在运行效率上可被用户接受,开发者只能在方法本身运行时间很长、可以相对忽略远程调用成本时的情况下考虑分布式
  • 以上结论是有违 UNIX 设计哲学的,却是当时现实情况下不得不做出的让步。摆在计算机科学面前有两条通往更大规模软件系统的道路:
    • 一条是尽快提升单机的处理能力,以避免分布式带来的种种问题
    • 另一条是找到更完美的、解决如何构建分布式系统的解决方案

原始分布式时代提出的构建符合 UNIX 设计哲学的、如同本地调用一般简单透明的分布式系统的这个目标,是软件开发者对分布式系统最初的美好愿景,但迫于现实,它会在一定时期内被妥协、被舍弃。

在三十多年后的 21 世纪 10 年代,随着分布式架构逐渐成熟、完善,并取代单体成为大型软件的主流架构风格以后,这个美好的愿景终将会重新被开发者拾起。

单体系统时代

  • 单体架构风格的应用也称作“巨石系统”(Monolithic Application),是出现时间最早(小型单体)、应用范围最广、使用人数最多、统治历史最长的一种架构风格,但“单体”这个名称,却是在微服务开始流行之后才“事后追认”所形成的概念
  • 单体系统的不足,必须在软件的性能需求超过了单机、软件的开发人员规模明显超过了“2 Pizza Team”(6-12 人)范畴的前提下才有讨论的价值
  • 使用多个独立的分布式服务共同构建一个更大型系统的设想与实际尝试,反而要比今天大家所了解的 大型单体系统 出现的时间更早
  • 在“拆分”这方面:
    • 从纵向角度来看,分层架构(Layered Architecture)已是现在所有信息系统建设中普遍认可、采用的软件设计方法,无论是单体还是微服务,抑或是其他架构风格
    • 从横向角度来看,单体架构也支持按照技术、功能、职责等维度,将软件拆分为各种模块,以便重用和管理代码
    • 即使是从横向扩展(Scale Horizontally)的角度来衡量,在负载均衡器之后同时部署若干个相同的单体系统副本,以达到分摊流量压力的效果,也是非常常见的需求
  • 单体系统的真正缺陷不在如何拆分,而在拆分之后的 自治与隔离 能力上
    • 由于所有代码都运行在同一个进程内,在获得进程内调用的简单、高效等好处的同时,也意味着如果任何一部分代码出现缺陷,过度消耗了进程空间内的资源,所造成的影响也是全局性的、难以隔离的(“单点故障”)
    • 由于所有代码都共享同一个进程,不能隔离,也就无法(不便)做到单独停止、更新、升级某一部分代码,从 可维护性 来说,单体系统也是不占优势的
    • 由于隔离能力的缺失,单体除了难以阻断错误传播、不便于动态更新程序以外,还面临难以 技术异构的困难,每个模块的代码通常都需要使用一样的程序语言,乃至一样的编程框架去开发
  • 随着软件架构演进,构建可靠系统的观念从“追求尽量不出错”到正视“出错是必然”的转变,以微服务取代单体系统成为潮流趋势的根本原因是:单体系统很难兼容“Phoenix”的特性
  • 为了 允许程序出错,获得 自治与隔离的能力,以及实现可以 技术异构 等目标,是继 性能与算力 之后,再次选择分布式的理由

开发分布式程序也并不意味着一定要依靠今天的微服务架构才能实现,在新旧世纪之交,将一个大的单体系统拆分为若干个更小的、不运行在同一个进程的 独立服务,这些服务拆分方法后来带来了 面向服务架构(Service-Oriented Architecture) 的一段兴盛期

SOA 时代

为了对大型的单体系统进行拆分,让每一个子系统都能独立地部署、运行、更新,三种较有代表性的架构模式:

  • 烟囱式架构(Information Silo Architecture):信息烟囱又名信息孤岛(Information Island),使用这种架构的系统也被称为孤岛式信息系统或者烟囱式信息系统,它指的是一种与其他相关信息系统完全没有互操作或者协调工作的设计模式。
  • 微内核架构(Microkernel Architecture):微内核架构也被称为插件式架构(Plug-in Architecture),
    • 既然在烟囱式架构中,没有业务往来关系的系统也可能需要共享人员、组织、权限等一些公共的主数据,那不妨就将这些主数据,连同其他可能被各子系统用到的公共服务、数据、资源集中到一块,组成一个被所有业务系统共同依赖的核心(Kernel,也称为 Core System)
    • 具体的业务系统以插件模块(Plug-in Module)的形式存在,这样也可提供可扩展的、灵活的、天然隔离的功能特性,即微内核架构
    • 微内核架构也有局限性,它假设系统中各个插件模块之间互不认识,且不可预知系统将安装哪些模块,因此这些插件可以访问内核中一些公共的资源,但不会直接交互
  • 事件驱动架构(Event-Driven Architecture):为了能让子系统互相通信,一种可行的方案是在子系统之间建立一套事件队列管道(Event Queue)
    • 来自系统外部的消息将以事件的形式发送至管道中,各个子系统可以从管道里获取自己感兴趣、能够处理的事件消息
    • 也可以为事件新增或者修改其中的附加信息,甚至可以自己发布一些新的事件到管道队列中去
    • 如此,每一条消息的处理者都是独立的、高度解耦的,但又能与其他处理者(如果存在其他消息处理者的话)通过事件管道进行交互

软件架构来到 SOA 时代,其包含的许多概念、思想都已经能在今天的微服务中找到对应的身影了,譬如服务之间的松散耦合、注册、发现、治理,隔离、编排等,这些在微服务中耳熟能详的概念,大多数也是在分布式服务刚被提出时就已经可以预见的困难点。SOA 针对这些问题,甚至是针对“软件开发”这件事情本身,都进行了更具体、更系统的探索。

  • “更具体”:尽管 SOA 本身还属于抽象概念,而不是特指某一种具体的技术,但它比单体架构和前面所列举的三种架构模式的操作性更强,已经不能简单视为一种架构风格,而是 一套软件设计的基础平台
    • 有领导制定技术标准的组织 Open CSA,有清晰的软件设计的指导原则
    • 明确了采用 SOAP 作为远程调用协议,依靠 SOAP 协议族(WSDL、UDDI 和 WS-*协议)来完成服务的发布、发现和治理
    • 利用企业服务总线(Enterprise Service Bus,ESB)的消息管道来实现各个子系统之间的交互
    • 使用服务数据对象(Service Data Object,SDO)来访问和表示数据
    • 使用服务组件架构(Service Component Architecture,SCA)来定义服务封装的形式和服务运行的容器
  • “更系统”:SOA 的终极目标是希望总结出一套自上而下的软件研发方法论,做到企业只需要跟着 SOA 的思路,就能够一揽子解决掉软件开发过程中的全部问题,譬如该如何挖掘需求、如何将需求分解为业务能力、如何编排已有服务、如何开发/测试/部署新的功能等

经过三十年的技术发展,信息系统经历了巨石、烟囱、插件、事件、SOA 等架构模式,应用受架构复杂度的牵绊却越来越大,已经 距离“透明”二字越来越远 了,这是否算不自觉间忘掉了当年的初心呢?(SOA → 微服务)

微服务时代

“微服务”这个技术名词最早在 2005 年就已经被提出,由 Peter Rodgers 博士在 2005 年的云计算博览会(Web Services Edge 2005)上首次使用,当时的说法是“Micro-Web-Service”,指的是一种专注于单一职责的、与语言无关的细粒度 Web 服务(Granular Web Service),它 最初 可以说是 SOA 发展时催生的产物,这一阶段的微服务是作为 SOA 的一种轻量化的补救方案而被提出的,是 SOA 的一种变体。


2012 年,在波兰克拉科夫举行的“33rd Degree Conference”大会上,Thoughtworks 首席咨询师 James Lewis 做了题为“Microservices-Java, the UNIX Way”的主题演讲,其中提到了 单一服务职责、康威定律、自动扩展、领域驱动设计 等原则,却只字未提 SOA,反而号召应该重拾 UNIX 的设计哲学(As Well Behaved UNIX Service)。

微服务真正崛起是在 2014 年,Martin Fowler 与 James Lewis 合写的文章“Microservices:A Definition of This New Architectural Term”,此文首先给出了现代微服务的概念:“微服务是一种 通过多个小型服务组合来构建单个应用的架构风格,这些服务围绕业务能力而非特定的技术标准来构建,各个服务可以采用 不同的编程语言、不同的数据存储技术,运行在不同的进程之中,服务采取 轻量级的通信机制自动化的部署机制 实现通信与运维。”

此外,文中列举了微服务的 九个核心的业务与技术特征

  • 围绕业务能力构建(Organized around Business Capability)
  • 分散治理(Decentralized Governance)
  • 通过服务来实现独立自治的组件(Componentization via Service)
  • 产品化思维(Product not Project)
  • 数据去中心化(Decentralized Data Management)
  • 强终端弱管道(Smart Endpoint and Dumb Pipe)
  • 容错性设计(Design for Failure)
  • 演进式设计(Evolutionary Design)
  • 基础设施自动化(Infrastructure Automation)

微服务追求的是更加自由的架构风格,摒弃了几乎所有 SOA 里可以抛弃的约束和规定,提倡以“实践标准”代替“规范标准”,对于服务的注册发现、跟踪治理、负载均衡、故障隔离、认证授权、伸缩扩展、传输通信、事务处理等问题,微服务中将 不再有统一的解决方案

  • 仅一个服务间远程调用问题,可以列入解决方案的候选清单的就有:RMI(Sun/Oracle)、Thrift(Facebook)、Dubbo(阿里巴巴)、gRPC(Google)、Motan2(新浪)、Finagle(Twitter)、brpc(百度)、Arvo(Hadoop)、JSON-RPC、REST 等
  • 仅一个服务发现问题,可以选择的就有 Eureka(Netflix)、Consul(HashiCorp)、Nacos(阿里巴巴)、ZooKeeper(Apache)、etcd(CoreOS)、CoreDNS(CNCF)等,其他领域也与此类似

技术架构者的第一职责就是决策权衡,有利有弊才需要决策,有取有舍才需要权衡,如果架构者本身的知识面不足以覆盖所需要决策的内容,不清楚其中利弊,恐怕将无可避免地陷入选择困难症的境遇之中。微服务时代充满着自由的气息,微服务时代充斥着迷茫的选择。

后微服务时代

布式架构中出现的问题,如 注册发现、跟踪治理、负载均衡、传输通信 等,其实在 SOA 时代甚至从原始分布式时代起就已经存在了,只要是分布式架构的系统,就无法完全避免,这些问题一定要由软件系统自己来解决吗?如果不局限于采用软件的方式,这些问题几乎都有对应的硬件解决方案,譬如:

  • 某个系统需要 伸缩扩容,通常会购买新的服务器,部署若干副本实例来分担压力
  • 如果某个系统需要解决 负载均衡 问题,通常会布置负载均衡器,选择恰当的均衡算法来分流
  • 如果需要解决 传输安全 问题,通常会布置 TLS 传输链路,配置好 CA 证书以保证通信不被窃听篡改
  • 如果需要解决 服务发现 问题,通常会设置 DNS 服务器,让服务访问依赖稳定的记录名而不是易变的 IP 地址

在微服务时代,人们之所以选择在软件的代码层面而不是硬件的基础设施层面去解决这些分布式问题,很大程度上是因为由 硬件构成的基础设施跟不上由软件构成的应用服务的灵活性 的无奈之举,软件可以只使用键盘命令就拆分出不同的服务,只通过拷贝、启动就能够实现伸缩扩容服务,硬件难道就不可以通过键盘命令变出相应的应用服务器、负载均衡器、DNS 服务器、网络链路这些设施吗?


微服务时代所取得的成就,本身就离不开以 Docker 为代表的早期容器化技术的巨大贡献。早期的容器只被简单地视为一种可快速启动的服务运行环境,目的是方便程序的分发部署,在这个阶段,针对单个应用进行封装的容器并未真正解决分布式架构问题。

一场持续了三年时间,以 Docker Swarm、Apache Mesos 与 Kubernetes 为主要竞争者的“容器编排战争”,Kubernetes 登基加冕是容器发展中一个时代的终章,也将是软件架构发展下一个纪元的开端。Kubernetes 中提供的 基础设施层面的解决方案 与传统 Spring Cloud 中提供的 应用层面的解决方案 的对比,尽管因为各自出发点不同,解决问题的方法和效果都有所差异,但这无疑是提供了一条全新的、前途更加广阔的解题思路。

功能 Kubernetes Spring Cloud
弹性伸缩 Autoscaling N/A
服务发现 KubeDNS / CoreDNS Spring Cloud Eureka
配置中心 ConfigMap / Secret Spring Cloud Config
服务网关 Ingress Controller Spring Cloud Zuul
负载均衡 Load Balancer Spring Cloud Ribbon
服务安全 RBAC API Spring Cloud Security
跟踪监控 Metrics API / Dashboard Spring Cloud Turbine
降级熔断 N/A Spring Cloud Hystrix

当虚拟化的基础设施从单个服务的容器扩展至由多个容器构成的服务集群、通信网络和存储设施时,软件与硬件的界限便已模糊。一旦虚拟化的硬件能够跟上软件的灵活性,那些与业务无关的技术性问题便有可能从软件层面剥离,悄无声息地在硬件基础设施之内解决,让软件得以只专注业务,真正围绕业务能力构建团队与产品。

软件层面独立应对 分布式架构所带来的各种问题,发展到 应用代码与基础设施软、硬一体,合力应对 架构问题,这个新的时代现在常被媒体冠以“云原生”这个颇为抽象的名字加以宣传。云原生时代追求的目标与此前微服务时代追求的目标并没有本质改变,都是在服务架构演进的历史进程中,称云原生时代为“后微服务时代”

服务网格(Service Mesh)

基础设施是针对整个容器来管理的,粒度相对粗犷,只能到容器层面,对单个远程服务则很难有效管控,类似的,在服务的监控、认证、授权、安全、负载均衡等方面都有可能面临细化管理的需求。为了解决这一类问题,虚拟化的基础设施很快完成了第二次进化,引入了今天被称为“服务网格”(Service Mesh)的“边车代理模式”(Sidecar Proxy)。

在虚拟化场景中的边车指的是由系统自动在服务容器(通常是指 Kubernetes 的 Pod)中注入一个通信代理服务器,以类似网络安全里中间人攻击的方式进行流量劫持,在应用毫无感知的情况下,悄然接管应用所有对外通信。

  • 这个代理除了实现正常的服务间通信外(称为数据平面通信),还接收来自控制器的指令(称为控制平面通信),根据控制平面中的配置,对数据平面通信的内容进行分析处理,以实现熔断、认证、度量、监控、负载均衡等各种附加功能
  • 通过边车代理模式,便实现了既不需要在应用层面加入额外的处理代码,也提供了几乎不亚于程序代码的 精细管理能力

无服务时代

对软件研发而言,不去做分布式无疑是最简单的,如果单台服务器的性能可以是无限的,那架构演进的结果肯定会与今天有很大差别。

绝对意义上的无限性能必然是不存在的,但在云计算落地已有十余年的今天,相对意义的无限性能已经成为现实。

无服务现在还没有一个特别权威的“官方”定义,它只涉及两块内容:后端设施(Backend)和函数(Function):

  • 后端设施 是指 数据库、消息队列、日志、存储 等这类用于支撑业务逻辑运行,但本身无业务含义的技术组件,这些后端设施都运行在云中,在无服务中将它们称为“后端即服务”(Backend as a Service,BaaS)
  • 函数 是指业务逻辑代码,这里函数的概念与粒度都已经很接近于程序编码角度的函数了,其区别是无服务中的函数运行在云端,不必考虑算力问题,也不必考虑容量规划(从技术角度),在无服务中将其称为“函数即服务”(Function as a Service,FaaS)

无服务的愿景是让开发者只需要纯粹地关注业务:

  • 不需要考虑 技术组件,后端的技术组件是现成的,可以直接取用,没有采购、版权和选型的烦恼
  • 不需要考虑如何 部署,部署过程完全托管到云端,由云端自动完成
  • 不需要考虑 算力,有整个数据中心支撑,算力可以认为是无限的
  • 不需要操心 运维,维护系统持续平稳运行是云计算服务商的责任而不再是开发者的责任

在 UC Berkeley 的论文中,把无服务架构下开发者不再关心这些技术层面的细节,类比成 当年软件开发从汇编语言踏进高级语言的发展过程,开发者可以不去关注寄存器、信号、中断等与机器底层相关的细节,从而令生产力得到极大解放。

无服务架构能够降低一些应用的开发和运维环节的成本,但无服务云函数会有冷启动时间,不便依赖服务端状态,响应的性能不太好:

  • 优势:不需要交互的 离线大规模计算、多数 Web 资讯类网站、小程序、公共 API 服务、移动应用服务端等都契合于无服务架构所擅长的短链接、无状态、适合 事件驱动的交互形式
  • 劣势:对于那些信息管理系统、网络游戏等应用,或者说对于 具有业务逻辑复杂、依赖服务端状态、响应速度要求较高、需要长链接等特征的应用,至少目前是相对不那么适合的

如果说微服务架构是分布式系统这条路当前所能做到的极致,那无服务架构,也许就是“不分布式”的云端系统这条路的起点。

将无服务作为技术层面的架构,将微服务视为应用层面的架构,把它们组合起来使用是完全合理可行的。以后,无论是物理机、虚拟机、容器,抑或是无服务云函数,都会是微服务实现方案的候选项之一。 (微服务与无服务并存)

架构师的视角

访问远程服务

远程服务调用

远程服务调用(Remote Procedure Call,RPC)在计算机科学中已经存在超过四十年时间

最初计算机科学家们的想法,就是将 RPC 作为 IPC 的一种特例来看待,这个观点在今天,仅从分类上说也仍然合理,只是到具体操作手段上就不合适了

进程间通信

  • 一个进程内的函数调用依赖 栈内存

  • 不同进程之间交换数据的问题被称为“进程间通信”(Inter-Process Communication,IPC)

    • 管道(Pipe)/ 具名管道(Named Pipe)
      • 普通管道只用于有亲缘关系的进程(由一个进程启动的另外一个进程)间的通信
      • 具名管道允许无亲缘关系的进程间的通信,管道典型的应用就是命令行中的“|”操作符
    • 信号(Signal)用于通知目标进程有某种事件发生,除进程间通信外,进程还可以给自身发送信号
      • kill -9 pid 表示由 Shell 进程向指定 PID 的进程发送 SIGKILL 信号
    • 信号量(Semaphore)用于在两个进程之间同步协作手段,相当于操作系统提供的一个特殊变量
    • 消息队列(Message Queue)
      • 克服了信号承载信息量少、管道只能用于无格式字节流以及缓冲区大小受限等缺点
      • 但实时性相对受限
    • 共享内存(Shared Memory)允许多个进程访问共同一块公共内存空间,这是效率最高的进程间通信形式
    • 本地套接字接口(UNIX/IPC Socket),套接字接口则是更普适的进程间通信机制,可用于不同机器之间的进程通信
      • 仅限于本机进程间通信时,套接字接口被优化,不会经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等操作
      • 将应用层数据从一个进程复制到另一个进程,这种进程间通信方式即本地套接字接口(UNIX Domain Socket / IPC Socket)
      • 由于 Socket 是网络栈的统一接口,它也能支持基于网络的跨机进程间通信

通信的成本

由于 Socket 是各个操作系统都提供的标准接口,完全有可能把远程方法调用的通信细节隐藏在操作系统底层,从应用层面上来看可以做到远程调用与本地的进程间通信在编码上完全一致,在原始分布式时代的早期确实是奔着这个目标去做的

1987 年,在“透明的 RPC 调用”一度成为主流范式的时候,Andrew Tanenbaum 教授曾发表论文“A Critique of The Remote Procedure Call Paradigm”,对这种透明的 RPC 范式提出一系列质问:把本地调用与远程调用当作同样的调用来处理,这是犯了方向性的错误,把系统间的调用透明化,反而会增加程序员工作的复杂度

到 1994 年至 1997 年间,由 ACM 和 Sun 院士 Peter Deutsch、套接字接口发明者 Bill Joy、Java 之父 James Gosling 等一众在 Sun 公司工作的专家们共同总结了通过网络进行分布式运算的八宗罪(8 Fallacies of Distributed Computing)

  • The network is reliable —— 网络是可靠的
  • Latency is zero—— 延迟是不存在的
  • Bandwidth is infinite —— 带宽是无限的
  • The network is secure —— 网络是安全的
  • Topology doesn’t change —— 拓扑结构是一成不变的
  • There is one administrator —— 总会有一个管理员
  • Transport cost is zero —— 不必考虑传输成本
  • The network is homogeneous —— 网络都是同质化的

以上这八条反话被认为是程序员在网络编程中经常忽略的八大问题,潜台词就是如果远程服务调用要透明化,就必须为这些罪过买单,这算是给 RPC 能否等同于 IPC 来 暂时 定下了一个具有公信力的结论

至此,RPC 应该是一种高层次的或者说语言层次的特征,而不是像 IPC 那样,是低层次的或者说系统层次的特征”的观点成为工业界、学术界的主流观点

远程服务调用是指位于互不重合的内存地址空间中的两个程序,在语言层面上,以同步的方式使用带宽有限的信道来传输程序控制信息。——Bruce Jay Nelson,Remote Procedure Call,Xerox PARC,1981

三个基本问题

这几十年所有流行过的 RPC 协议,都不外乎变着花样使用各种手段来解决以下三个基本问题:

  • 如何表示数据,数据包括传递给方法的参数以及方法执行后的返回

    • 对于进程内的方法调用,使用程序语言预置和程序员自定义的数据类型,就很容易解决数据表示问题
    • 对于远程方法调用,则完全可能面临交互双方各自使用不同程序语言的情况
    • 即使只支持一种程序语言的 RPC 协议,在不同硬件指令集/操作系统下,同样的数据类型也可能有不一样的表现细节
    • 有效的做法是将交互双方所涉及的数据转换为某种事先约定好的中立数据流格式来进行传输,将数据流转换回不同语言中对应的数据类型来使用 —— 就是序列化与反序列化,例如:
      • ONC RPC 的外部数据表示(External Data Representation,XDR)
      • CORBA 的通用数据表示(Common Data Representation,CDR)
      • Java RMI 的 Java 对象序列化流协议(Java Object Serialization Stream Protocol)
      • gRPC 的 Protocol Buffers
      • Web Service 的 XML 序列化
      • 众多轻量级 RPC 支持的 JSON 序列化
  • 如何传递数据,如何通过网络,在两个服务的 Endpoint 之间相互操作、交换数据(通常指的是应用层协议数据)

    • 两个服务交互不是只扔个序列化数据流来表示参数和结果就行
    • 许多在此之外的信息,譬如异常、超时、安全、认证、授权、事务等,都可能产生双方需要交换信息的需求
    • 在计算机科学中,专门有一个名词“Wire Protocol”来表示这种两个 Endpoint 之间交换这类数据的行为,常见的如下:
      • Java RMI 的 Java 远程消息交换协议(Java Remote Message Protocol,JRMP,也支持 RMI-IIOP)
      • CORBA 的互联网 ORB 间协议(Internet Inter ORB Protocol,IIOP,是 GIOP 协议在 IP 协议上的实现版本)
      • DDS 的实时发布订阅协议(Real Time Publish Subscribe Protocol,RTPS)
      • Web Service 的简单对象访问协议(Simple Object Access Protocol,SOAP)
      • 如果要求足够简单,双方都是 HTTP Endpoint,直接使用 HTTP 协议也是可以的(如 JSON-RPC)
  • 如何表示方法,一套与语言无关的接口描述语言(Interface Description Language,IDL)

    • 在本地方法调用中,编译器或者解释器会根据语言规范,将调用的方法签名转换为进程空间中子过程入口位置的指针
    • 不过一旦要考虑不同语言,“如何表示同一个方法”“如何找到对应的方法”需要一个统一的跨语言的标准,用于表示方法的协议有:
      • Android 的 Android 接口定义语言(Android Interface Definition Language,AIDL)
      • CORBA 的 OMG 接口定义语言(OMG Interface Definition Language,OMG IDL)
      • Web Service 的 Web 服务描述语言(Web Service Description Language,WSDL)
      • JSON-RPC 的 JSON Web 服务协议(JSON Web Service Protocol,JSON-WSP)

以上 RPC 中的三个基本问题,全部都可以在本地方法调用过程中找到对应的解决方案

统一的 RPC

  • 1998 年,XML 1.0 发布,并成为万维网联盟(World Wide Web Consortium,W3C)的推荐标准
  • 1999 年末,SOAP 1.0(Simple Object Access Protocol)规范的发布,标志着一种被称为“Web Service”的全新的 RPC 协议的诞生
  • Web Service 采用 XML 作为远程过程调用的序列化、接口描述、服务发现等所有编码的载体
    • Web Service 采用 XML 每一次数据交互都包含大量的冗余信息,性能奇差
    • Web Service 希望在一套协议上一揽子解决分布式计算中可能遇到的所有问题,让开发者学习负担沉重
  • 总结
    • 那些面向透明的、简单的 RPC 协议,如 DCE/RPC、DCOM、Java RMI,要么依赖于操作系统,要么依赖于特定语言(约束
    • 那些面向通用的、普适的 RPC 协议,如 CORBA,无法逃过使用 复杂性 的困扰(CORBA 烦琐的 OMG IDL、ORB )
    • 那些意图通过技术手段来屏蔽复杂性的 RPC 协议,如 Web Service,又不免受到 性能 问题的束缚

简单、普适、高性能 这三点,似乎真的很难同时满足

分裂的 RPC

由于一直没有一个同时满足以上三点的“完美 RPC 协议”出现,所以远程服务器调用这个小小的领域,逐渐进入群雄混战、百家争鸣的战国时代,距离“统一”越来越远,并一直延续至今

现在,已经相继出现过许多难以穷举的协议和框架:

  • RMI(sun/Oracle)
  • Thrift(Facebook/Apache)
  • Dubbo(阿里巴巴/Apache)
  • gRPC(Google)
  • Motan1/2(新浪)
  • Finagle(Twitter)
  • brpc(百度/Apache)
  • .NET Remoting(微软)
  • Arvo(Hadoop)
  • JSON-RPC 2.0(公开规范,JSON-RPC 工作组)

这些 RPC 功能、特点不尽相同,有的是某种语言私有,有的支持跨越多种语言,有的运行在应用层 HTTP 协议之上,有的直接运行于传输层 TCP/UDP 协议之上,但并不存在哪一款是“最完美的 RPC”,任何一款具有生命力的 RPC 框架,都不再去追求大而全的“完美”,而是以某个具有针对性的特点作为主要的发展方向:

  • 朝着性能发展,代表为 gRPC 和 Thrift,决定 RPC 性能的主要因素有序列化效率和信息密度

    • 序列化输出结果的容量越小,速度越快,效率自然越高
    • 信息密度则取决于协议中有效负载(Payload)所占总传输数据的比例大小,使用传输协议的层次越高,信息密度就越低
    • gRPC 和 Thrift 都有自己优秀的专有序列化器
    • gRPC 是基于 HTTP/2 的,支持多路复用和 Header 压缩
    • Thrift 则直接基于传输层的 TCP 协议来实现,省去了应用层协议的额外开销
  • 朝着简化发展,代表为 JSON-RPC

    • 牺牲了功能和效率,换来的是协议的简单轻便,接口与格式都更为通用
    • 尤其适合用于 Web 浏览器这类一般不会有额外协议支持、额外客户端支持的应用场合

到了最近几年,RPC 框架有明显向 更高层次(不仅仅负责调用远程服务,还管理远程服务)与 插件化 方向发展的趋势

  • 不再追求独立地解决 RPC 的全部三个问题(表示数据、传递数据、表示方法),而是将一部分功能设计成扩展点
  • 框架聚焦于提供核心的、更高层次的能力,譬如提供负载均衡、服务注册、可观察性等方面的支持

REST 设计风格

事务处理

本地事务

全局事务

共享事务

分布式事务

透明多级分流系统

架构安全性

分布式的基石

不可变基础设施

技术方法论