Skip to content

分布式API设计 上

About 3475 wordsAbout 12 min

API架构RESTgRPC

2025-02-06

img

  • 当作为一个架构师,选择分布式 API 框架,你需要注意哪些事项
  • 当你作为一个服务提供者,提供分布式 API, 你需要注意哪些事项,至少有 10 点。
  • 当你需要调用一个分布式 API,你需要注意哪些事项,至少有 10 点。
  • 当调用出现故障或者性能优化问题,有哪些思路解决

分布式 API

软件世界,是通过 API 互相链接,这些 API 或者是进程内的 API,或者是分布式 API,通过这些 API,构造出精彩的复杂的企业应用,互联网世界,物联网世界。学习完本节后,当你是调用一个分布式 API 的时候,你会掌握 API 调用最佳实践,当你提供一个分布式 API 时候,你会清楚 API 设计原则,本章覆盖如下内容

  • 分布式 API 的技术选型
  • REST API 设计原则 (参考 远程调用 API 设计 下:REST,待定)

面向架构质量的设计

我们知道,架构主要目标是对软件系统分解成较小更容易实现的元素,如模块或者子系统,并能让这些元素协同完成业务需求,对于通常的程序员视角来说,架构貌似就是画几个框,然后连上线即可。

如下是一个分布式系统最简单的架构。看着很简单的俩框一线,但架构师却需要考虑的非常多,这也是架构师和普通程序员区别

img

A 服务的架构师需要考虑

  • 如果服务 B 不可用,服务 A 如何保证高可用。比如宕机,故障,虚机漂移,网络故障
  • 如果服务 B 出现阻塞,性能下降,服务 A 如何保性能不受影响
  • 服务 A 调用服务 B,是否一定需要等待服务 B 的响应,能否解耦 A 和 B 调用,避免前面 2 个问题
  • 服务 A 如果是通过其寻址服务到 B,如果寻址服务不可用,如何调用服务 B
  • 如果服务 B 更新了 API,服务 A 未同步更新(兼容)

B 服务作为服务提供者,架构师需要考虑的更多

  • 如何保证服务 B 支持大并发的操作,包括自身支持大并发,以及依赖的下游支持
  • 如何保证服务 B 支持大流量操作,甚至是不正常突发高流量下仍然可用,比如断网恢复后的高流量
  • 如果服务 B 的下游不可用,如何给服务 A 提供可用接口
  • 服务 B 的更新重启,会对服务 A 产生什么样的影响
  • 如何可观测服务 B 的调用
  • 如果服务 B 是公网服务,如何保证安全,数据被授权用户获取
  • 如果服务 B 更新了 API,服务 A 未及时更新(兼容考虑)

B 服务架构师还需要考虑意外情况,如服务 A 的 BUG

  • 服务 A 应该只调用服务 B 一次,但实际服务 A 调用了多次
  • 服务 A 调用频率应该是 1 分钟一次,但实际 1 秒一次
  • 服务 A 应该先后顺序调用 B 俩次不同接口,但调用顺序相反。这种可能是 A 的 Bug,或者是事件驱动架构里顺序问题。

对这种 “俩框一线” 如此简单的架构,可以看到架构师相比于初级程序员,需要考虑较多,需要考虑 10 + 种情况。这种考虑其实也不是架构师挠秃头发想到的,而是基于技术架构的架构质量考虑的,有种说法,说是架构质量驱动了架构设计(ADD)。

分布式 API 的架构

分布式 API 建立在俩类协议基础上:

  • TCP 协议,使用这类协议的分布式 API 有 Dubbo,RMI(EJB),gRPC,Thrift 等,这列分布式 API 具有良好的性能,延迟都在 1 毫秒级,适用于系统内部微服务之间调用。其缺点是不易被观察。
  • 基于 HTTP 协议,比如 REST,SOAP 建立在 HTTP 协议基础上,HTTP 协议高可用和高性能,内置安全,并且支持跨内外网调用。而且 HTTP 上的调用易于被观测,如,通过 HTTP 代理观测,或者 HTTP 日志进行业务统计。因此基于 HTTP 的 REST 等 API 技术框架逐渐成为现在流行的分布式调用方式,适用于系统之间,用户和系统之间的调用。 基于 REST 调用延迟通常相对 TCP 方式的协议较大,即使内网之间调用,延迟在 10 + 毫秒。对于需要毫秒级延迟的场景,REST 协议不合适

分布式 API 通常有如下协议栈

img

  • Client API ,以 Java 接口形式体现
  • Client Stub,扮演了客户端的 Gateway,通常会将调用的目标类,参数等封装成指定协议的报文,调用底层 transport。Client Stub 有负责寻址,调用分布式服务端的节点,并等待返回结果返回调用者
  • Client Transport: 负责传输部分,客户端报文到服务器端
  • Server Transport :负责接收客户端报文
  • Server Skeleton:负责把报文反序列化成调用参数,并调用实际的实现类 Service Impl
  • Service Impl:接口实现类

分布式 API 设计通用考虑

下面列出了系统常使用的分布式 API 技术的公共特性

API 技术基础协议报文格式语言支持性能内网 / 外网可观测通信方式API Schema负载均衡
RESTHTTPJSON通用一般内网 / 外网容易同步支持Nginx 等 HTTP 代理
Spring FeighHTTPJSON通用一般内网 / 外网容易同步支持客户端
RMITCP/HTTPRMIJava一般内网 / 外网二次开发同步支持客户端
gRPCTCP/ HTTPProtobuf常用语言支持内网 / 外网二次开发同步,异步,服务端推送支持客户端或者代理
DubboTCP/HTTP多种常用语言支持内网 / 外网二次开发同步,异步客户端或者代理
SOAPHTTPXML通用一般内网 / 外网容易同步,异步支持Nginx 等 HTTP 代理
WebSocketHTTP文本或者二进制,XML/JSON通用内网 / 外网容易同步,异步Nginx 等 HTTP 代理
  • 基础协议: 通常区分 TCP 或者 HTTP,构建在 HTTP 基础上的分布式 API,适用于系统之间或者内外网之间调用,直接构建在 TCP 上的协议通常性能更好,适用于内网之间调用,构建在 TCP 协议上的分布式 API 通常有更好的性能。目前基于 TCP 的分布式 API 都开始支持基于 HTTP,以方便互联网调用而不是企业内网的调用。如 RMI,Dubbo,gRPC
  • 报文格式:通常文本格式更方便阅读,二进制格式性能更好。JSON 相比与 XML 文本格式,性能也更好。报文格式以影响兼容性,这点非常重要,XML 和 JSON 方式天然能实现向前,向后兼容,二进制则依赖序列化协议是否支持兼容。比如 Hession,gPRC 支持兼容,fury 依赖配置以是否支持兼容。兼容性是选择序列化协议的一个考虑点
  • 编程语言支持: 由于 HTTP 协议是一种非常成熟协议,因此所有语言都支持。gRpc 和 Dubbo 等 API 框架,流行语言都支持,RMI 则只有 Java 支持。广泛的编程语言支持,能保证异构系统之间的互相调用。
  • 性能:当考虑到性能的时候,推荐基于 TCP 实现的分布式 API 框架。随着 HTTP/2 和 HTTP/3 的推出,HTTP 协议也有较高的性能
  • 内网 / 外网:如果分布式 API 暴漏给外网,则需要考虑到能穿透防火墙,通常 HTTP 协议比较适合,这也是 REST,SOAP 比较适合对外提供 API 或者系统 (云)之间的调用
  • 可观测,基于 HTTP 协议的分布式 API 有较好的可观测,通过网关,或者 HTTP 日志。基于 TCP 的分布式 API 则需要框架支持或者二次开发支持。
  • 通信方式,包括同步和异步调用,以及服务端推送。比如 gPRC 同时支持这三种方式
  • API Schema: 通常分布式 API 都会提供 Schema 来说明 API 的调用地址 endpoint,入参,出参,以及参数校验规则。这些 Schema 除了能当做 API 文档外,还能生成客户端或者服务端代码,或者生成客户端或者服务测试代码和数据。Open API 提供了 REST 调用的 Schema。WSDL 提供了 SOPA 调用的 Schema,gRPC 则使用 protobuf
  • 网关支持: API 网关可以提高架构的高可用和可观测,大部分布式 API 都支持网关,以 HTTP 协议为基础的 API 天然支持通过 Apache,Nginx,HAProxy 等作为网关,或者 Spring Cloud Gateway 。 对于 Dubbo,则可以使用 Apache Shengyu 网关, 或者二次开发 Spring Cloud Gateway,将 REST 请求转化为 Dubbo 请求。

分布式 API 设计其他考虑

在选择 API 框架,以及使用 API 过程中,还需要如下考虑

特性说明
安全调传输协议是否支持安全调传输协议,系统之间,或者是内外网之间可以采用 SSL 传输协议。基于 HTTP 的 API 框架天然支持 SSL
安全支持安全支持,API 框架是否允许携带额外的安全信息,并进行认证和鉴权。比如 REST 通常使用 JWT 协议,在 HTTP Header 中携带加密的用户,角色等信息,REST 服务端会解密这些信息并认证或者授权用户是否能调用此 API
降级说明API 可能不会按照预期调用,比如因为限流,或者下游不可用,应该提供降级的指示,并要求客户端稍后按照规定的方式重试,最常见的是退避指数间隔重试。有的分布式 API 框架内置限流降级功能,有的则需要服务提供者实现,或者按照业务优先级区分限流降级
性能说明API 应该通过文档申明哪些参数会影响性能
幂等说明API 一般要求幂等,既同样参数,多次调用的结果应该是一样的。比如订单 API,用户多次点击,应该只生成一个订单,而不是多次订单。 如果提供的 API 不能实现幂等,需要在 API 文档中说明多次调用的结果
是否支持泛化调用分布式 API 框架通常通过 Schema 生成客户端 SDK(client stub),客户端依赖 SDK 调用微服务,但有些情况,强依赖 SDK 并不是个好主意,比如测试平台,或者 API 网关。 这些系统不会使用客户端 SDK。那么就要求 API 分布式框架支持泛化调用,即客户端可以提供类名和方法名,以及 JSON 作为参数。dubbo 支持泛化调用,而 gRpc,Thrift 不直接支持
批量调用 APIAPI 是否还有批量调用 API,比如,查询单个设备是否在线和批量查询多个设备是否在,查询单个商品库存,和批量查询商品库存。批量 API 有助于提升性能
传输数据压缩API 框架是否支持传输数据压缩,压缩有助于提高性能。比如 HTTP 协议天然支持报文压缩,有些分布框架采用的序列化协议也支持压缩功能
API 超时设定API 框架默认的超时时间都比较保守,gRCP,Thrift 默认超时时间不设置,dubbo 为例子则默认为 1S。建议为每一个 API 设定超时时间
重试策略大部分 API 框架的重试间隔时间都采用退避指数,以避免频繁重拾导致服务器压力过大,甚至无法恢复到正常状态,linux 的 TCP 重传间隔 1S,2S,4S,8S,16S。 如果你作为客户端,调用分布式 API,失败情况下想重试,应该考虑重试策略

我参与物联云系统遇到过 3 个跟重试相关问题

1 jedis 早期版本的发送数据到 redis,会不间隔重试。导致迅速失败

2 弱网环境,操作系统使用退避指数间隔重试,导致弱网通信耗时较长

3 主网关和接入网关在重启后,访问量较大,导致所有访问都失败。怀疑设备是不断重试

有了如上知识,可以分析 SSE,GraphQL 和 MQTT,Thrift

API 技术基础协议报文格式语言支持性能内网 / 外网可观测通信方式API Schema负载均衡
SSEHTTP同 WebSocket通用内网 / 外网服务器推送Nginx 等 HTTP 代理
GraphQLHTTPJSON通用一般内网 / 外网容易同步支持Nginx 等 HTTP 代理
MQTTTCPMQTT通用外网异步
ThriftTCP二进制通用内网同步,异步支持Nginx,HAProxy, 客户端自研

参考

1 架构修炼之道 —— 亿级网关、平台开放、分布式、微服务、容错等核心技术修炼实践

2 各个框架的官网功能特性

3 可观测性工程

知行合一