架构02-访问远程服务
1、远程服务调用
(1)RPC 的起源和概念
历史背景 :RPC(Remote Procedure Call,远程服务调用) 在计算机科学中有超过 40 年的历史。关注度 :尽管历史悠久,但仍然受到广泛关注。原因分析:
RPC 的定义:
定义 :RPC 是一种语言级别的通讯协议,允许程序在一台计算机上调用另一台计算机上的程序。施乐 Palo Alto 研究中心 :首次提出远程服务调用的定义。
(2)本地方法调用的过程
public static void main ( String [ ] args) {
System . out. println ( "hello world" ) ;
}
步骤:
传递方法参数 :将字符串 hello world
的引用压栈。确定方法版本 :根据方法签名确定执行版本。执行被调方法 :从栈中获取参数并执行逻辑。返回执行结果 :将结果压栈并恢复指令流。
(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)协议
(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。