架构02-访问远程服务

18 阅读11分钟

架构02-访问远程服务

1、远程服务调用

(1)RPC 的起源和概念

  • 历史背景:RPC(Remote Procedure Call,远程服务调用) 在计算机科学中有超过 40 年的历史。
  • 关注度:尽管历史悠久,但仍然受到广泛关注。
  • 原因分析:
    • 微服务风潮的推动。
    • 开发者对 RPC 认知的模糊。
  • RPC 的定义:
    • 定义:RPC 是一种语言级别的通讯协议,允许程序在一台计算机上调用另一台计算机上的程序。
    • 施乐 Palo Alto 研究中心:首次提出远程服务调用的定义。

(2)本地方法调用的过程

  • 伪代码示例:
public static void main(String[] args) {
    System.out.println("hello world");
}
  • 步骤:
    1. 传递方法参数:将字符串 hello world 的引用压栈。
    2. 确定方法版本:根据方法签名确定执行版本。
    3. 执行被调方法:从栈中获取参数并执行逻辑。
    4. 返回执行结果:将结果压栈并恢复指令流。

(3)进程间通讯(IPC)

  • 障碍:
    • 参数传递:不同进程没有共享的栈内存。
    • 方法版本选择:不同语言实现的程序难以确定方法版本。
  • 解决办法:
    • 管道(Pipe)/具名管道(Named Pipe):用于进程间传递字符流或字节流。
    • 信号(Signal):通知目标进程有事件发生。
    • 信号量(Semaphore):用于进程间的同步协作。
    • 消息队列(Message Queue):适合传递大量信息。
    • 共享内存(Shared Memory):效率最高的进程间通讯形式。
    • 本地套接字接口(IPC Socket):适用于不同机器间的进程通信。

(4)通信的成本

  • 早期目标:将 RPC 作为 IPC 的特例,实现远程调用与本地调用的一致性。
  • 问题:
    • 通信成本被忽视,导致性能下降。
    • 服务端和客户端的角色划分。
    • 异常处理和多线程竞争。
    • 网络利用效率和连接复用。
    • 参数和返回值的表示。
    • 网络可靠性和故障处理。
  • Andrew Tanenbaum 的观点:透明的调用形式增加了程序员的工作复杂度。

(5)RPC 框架要解决的三个基本问题

  • 如何表示数据?
    • 包括传递给方法的参数和方法的返回值。
    • 解决方法:将数据转换为某种中立的数据流格式(序列化),再转换回不同语言中的数据类型(反序列化)。
    • 常见的序列化协议:JSON、XML、Protocol Buffers、Thrift 等。
  • 如何传递数据?
    • 通过网络在两个服务 Endpoint 之间相互操作、交换数据。
    • 解决方法:使用应用层协议(如 HTTP、gRPC)和传输层协议(如 TCP、UDP)。
    • 常见的 Wire Protocol:HTTP、gRPC、Thrift、JSON-RPC 等。
  • 如何表示方法?
    • 在不同语言中表示和找到方法。
    • 解决方法:使用接口描述语言(IDL)为每个方法规定一个通用且不重复的编号(如 UUID)。
    • 常见的 IDL:CORBA IDL、Thrift IDL、gRPC IDL 等。

(6)统一的RPC

  • DCE/RPC
    • 由惠普和 Apollo 提出,面向 Unix 系统。
    • 限制:仅支持 Unix 系统,不支持对象传递。
  • ONC RPC
    • 由 Sun Microsystems 提出,基于 TCP/IP 网络,支持 C 语言。
    • 限制:不支持对象传递。
  • CORBA
    • 由 OMG 发布,支持多语言,面向对象。
    • 失败原因:设计过于复杂,实现互不兼容。
  • DCOM
    • 由微软提出,支持多语言,但受限于 Windows 系统。
    • 失败原因:操作系统限制。
  • Web Service
    • 以 XML 为基础,支持多语言。
    • 失败原因:性能差,协议过于复杂。

(7)分裂的RPC

  • 简单、普适、高性能这三点,一直没有一个同时满足以上三点的“完美RPC协议”出现,所以远程服务器调用这个小小的领域,逐渐进入百家争鸣的战国时代
    • RMI(Sun/Oracle):面向 Java 语言,支持远程对象调用。
    • Thrift(Facebook/Apache):支持多语言,高性能,基于 TCP 协议。
    • Dubbo(阿里巴巴/Apache):支持多语言,高性能,插件化设计。
    • gRPC(Google):支持多语言,高性能,基于 HTTP/2 协议。
    • Motan2(新浪):支持多语言,高性能。
    • Finagle(Twitter):支持多语言,高性能。
    • brpc(百度):支持多语言,高性能。
    • .NET Remoting(微软):面向 .NET 语言,支持远程对象调用。
    • Arvo(Hadoop):面向 Hadoop 生态,支持多语言。
    • JSON-RPC 2.0(公开规范):简单易用,适合 Web 浏览器。现代 RPC 框架的发展趋势
  • 现代 RPC 框架的发展趋势:高层次与插件化
    • 不再仅限于调用远程服务,还管理远程服务。
    • 设计为扩展点,实现核心能力的可配置。
    • 代表框架:Thrift、Dubbo。

2、REST设计风格

(1)REST 与 RPC 的对比

  • 思想上的差异:面向资源 vs 面向过程
    • REST:面向资源编程,抽象目标是资源。
    • RPC:面向过程编程,抽象目标是方法。
  • 概念上的不同
    • REST:不是一种协议,而是一种风格,没有强制性的规范。
    • RPC:是一种远程服务调用协议,有明确的规范和规约文档。
  • 使用范围上的差异
    • REST:
      • 适合浏览器端消费的远程服务。
      • 适合移动端、桌面端或分布式服务端的节点间通信,前提是网络不是性能瓶颈。
    • RPC:
      • 适合分布式对象应用。
      • 适合追求远程服务调用效率的场景。

(2)REST 起源

  • **提出者:**Roy Thomas Fielding
  • **时间:**2000年
  • 全称:Representational State Transfer(表现层状态转化)
  • 背景:
    • Fielding是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。
    • 他的博士论文《Architectural Styles and the Design of Network-based Software Architectures》探讨了软件和网络的交叉点。
    • 目的:理解并评估以网络为基础的应用软件的架构设计,获得功能强、性能好、适宜通信的架构。

(3)REST 核心概念

  • 资源(Resources)
    • 定义:网络上的一个实体或具体信息,可以用URI(统一资源定位符)指向。
    • 示例:文本、图片、服务等。
    • 特点:每个资源对应一个特定的URI,URI是资源的地址或唯一识别符。
  • 表现层(Representation)
    • 定义:资源的具体呈现形式。
    • 示例
      • 文本:txt、HTML、XML、JSON等格式。
      • 图片:JPG、PNG等格式。
    • 重要性:URI只代表资源的位置,具体表现形式应在HTTP请求的头信息中指定(如Accept和Content-Type字段)。
  • 状态转化(State Transfer)
    • 定义:客户端通过HTTP协议操作服务器端资源,实现状态转化。
    • HTTP动词
      • GET:获取资源
      • POST:新建资源(也可用于更新资源)
      • PUT:更新资源
      • DELETE:删除资源

(4)REST 的优缺点

  • 优点:
    • **降低学习成本:**标准的 HTTP 方法,不需要额外学习。
    • **资源的集合与层次结构:**资源是名词,天然具有集合与层次结构。
    • **绑定于 HTTP 协议:**复用 HTTP 协议的语义和基础支持。
  • 缺点:
    • 面向资源的编程思想只适合做 CRUD
      • 观点:HTTP 的基本方法容易让人联想到 CRUD 操作,但 REST 的范围远不止于此。
      • 解决方案:可以使用自定义方法,按 Google 推荐的 REST API 风格来拓展 HTTP 标准方法。
    • REST 与 HTTP 完全绑定,不适用于高性能传输
      • 观点:REST 依赖 HTTP 协议,不适用于需要直接控制传输细节的场景。
      • 解决方案:使用其他协议(如 gRPC)来处理高性能传输。
    • REST 不利于事务支持
      • 观点:REST 不支持刚性 ACID 事务,但可以支持最终一致性。
      • 解决方案:使用分布式事务协议(如 2PC/3PC)来处理强一致性需求。
    • REST 没有传输可靠性支持
      • 观点:HTTP 协议缺乏对传输可靠性的支持。
      • 解决方案:通过幂等性设计和重试机制来提高可靠性。
    • REST 缺乏对资源进行“部分”和“批量”的处理能力
      • 观点:HTTP 协议缺乏对部分和批量操作的支持。
      • 解决方案:使用 GraphQL 等新技术来解决这些问题。

(5)Richardson 成熟度模型

  • Richardson 将 REST 服务接口按照“REST 的程度”从低到高分为 0 至 3 共 4 级:

  • 第 0 级:The Swamp of Plain Old XML
    • 特点:基于 RPC 风格,使用单一的 Endpoint,通过参数和动作来区分不同的操作。
    • 示例:
POST /appointmentService?action=query HTTP/1.1
{
  date: "2020-03-04",
  doctor: "mjones"
}
POST /appointmentService?action=confirm HTTP/1.1
{
  appointment: {
    date: "2020-03-04",
    start: "14:00",
    doctor: "mjones"
  },
  patient: {
    name: "xx",
    age: 30 
  }
}
  • 第 1 级:Resources
    • 特点:引入资源的概念,Endpoint 是名词而非动词,每次请求包含资源 ID。
    • 示例:
POST /doctors/mjones HTTP/1.1
{
  date: "2020-03-04"
}
POST /schedules/1234 HTTP/1.1 
{
  name: "xx",
  age: 30
}
  • 第 2 级:HTTP Verbs
    • 特点:使用 HTTP 标准方法(GET、POST、PUT、DELETE)来操作资源,使用 HTTP 状态码来表示操作结果,处理认证授权。
    • 示例:
GET /doctors/mjones/schedule?date=2020-03-04&status=open HTTP/1.1 
POST /schedules/1234 HTTP/1.1
{
  name: "xx",
  age: 30
}
  • 第 3 级:Hypermedia Controls (HATEOAS)
    • 特点:通过超媒体驱动,响应中包含后续操作的链接,实现客户端和服务端的完全解耦。
    • 示例:
GET /doctors/mjones/schedule?date=2020-03-04&status=open HTTP/1.1
HTTP/1.1 200 OK
{
  schedules: [
    {
      id: 1234,
      start: "14:00",
      end: "14:50",
      doctor: "mjones",
      links: [
        { rel: "confirm schedule", href: "/schedules/1234" }
      ]
    },
    {
      id: 5678,
      start: "16:00",
      end: "16:50",
      doctor: "mjones",
      links: [
        { rel: "confirm schedule", href: "/schedules/5678" }
      ]
    }
  ],
  links: [
    { rel: "doctor info", href: "/doctors/mjones/info" }
  ]
}

(6)常见设计错误

  • **URI包含动词:**URI应为名词,动词应放在HTTP协议中。
    • 错误示例:/posts/show/1
    • 正确示例:/posts/1 + GET方法
  • **动词无法用HTTP动词表示:**将动词转换为名词,作为资源。
    • 错误示例:POST /accounts/1/transfer/500/to/2
    • 正确示例:POST /transaction + from=1&to=2&amount=500.00
  • **URI中加入版本号:**不同版本应视为同一种资源的不同表现形式,版本号应在HTTP请求头信息的Accept字段中指定。
    • 错误示例:
http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
http://www.example.com/app/2.0/foo 
- 正确示例:
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1 
Accept: vnd.example-com.foo+json; version=2.0

3、REST 设计指南

(1)协议

  • **通信协议:**始终使用HTTPS协议。

(2)域名

  • **专用域名:**建议将API部署在专用域名下,例如 https://api.example.com
  • **主域名下:**如果API简单且不会扩展,可以考虑放在主域名下,例如 https://example.org/api/

(3)版本

  • **URL中包含版本号:**推荐将API的版本号放入URL,例如 https://api.example.com/v1/
  • **HTTP头信息:**另一种做法是将版本号放在HTTP头信息中,但不如放入URL方便和直观。GitHub采用这种做法。

(4)路径(Endpoint)

  • **资源表示:**每个网址代表一种资源,网址中不能有动词,只能有名词,且名词通常与数据库的表格名对应。
  • **复数形式:**API中的名词应使用复数形式,例如:
    • https://api.example.com/v1/zoos
    • https://api.example.com/v1/animals
    • https://api.example.com/v1/employees

(5)HTTP动词

  • 常用动词:
    • GET(SELECT):从服务器取出资源。
    • POST(CREATE):在服务器新建一个资源。
    • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
    • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
    • DELETE(DELETE):从服务器删除资源。
  • 不常用动词:
    • HEAD:获取资源的元数据。
    • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

(6)过滤信息(Filtering)

  • ?limit=10:指定返回记录的数量。
  • ?offset=10:指定返回记录的开始位置。
  • ?page=2&per_page=100:指定第几页及每页的记录数。
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序及排序顺序。
  • ?animal_type_id=1:指定筛选条件。

(7)状态码(Status Codes)

  • 200 OK:服务器成功返回用户请求的数据。
  • 201 CREATED:用户新建或修改数据成功。
  • 202 Accepted:请求已进入后台排队(异步任务)。
  • 204 NO CONTENT:用户删除数据成功。
  • 400 INVALID REQUEST:用户请求有错误。
  • 401 Unauthorized:用户没有权限。
  • 403 Forbidden:用户已授权但访问被禁止。
  • 404 NOT FOUND:请求的记录不存在。
  • 406 Not Acceptable:请求的格式不可得。
  • 410 Gone:请求的资源被永久删除。
  • 422 Unprocesable entity:创建对象时发生验证错误。
  • 500 INTERNAL SERVER ERROR:服务器发生错误。

(8)错误处理(Error handling)

  • 返回出错信息:状态码为4xx时,返回的JSON中应包含error键及其对应的出错信息,例如:
{
  "error": "Invalid API key"
}

(9)返回结果

  • GET /collection:返回资源对象的列表(数组)。
  • GET /collection/resource:返回单个资源对象。
  • POST /collection:返回新生成的资源对象。
  • PUT /collection/resource:返回完整的资源对象。
  • PATCH /collection/resource:返回完整的资源对象。
  • DELETE /collection/resource:返回一个空文档。

(10)Hypermedia API

  • 提供链接:返回结果中提供链接,指向其他API方法,使用户不查文档也能知道下一步操作。例如:
{
  "link": {
    "rel": "collection https://www.example.com/zoos",
    "href": "https://api.example.com/zoos",
    "title": "List of zoos",
    "type": "application/vnd.yourformat+json"
  }
}
  • HATEOAS:Hypermedia API的设计原则,GitHub的API采用了这种设计。

(11)其他

  • **身份认证:**API的身份认证应使用OAuth 2.0框架。
  • **数据格式:**服务器返回的数据格式应尽量使用JSON,避免使用XML。