背景
-
当作为一个架构师,选择分布式API框架,你需要注意哪些事项
-
当你作为一个服务提供者,提供分布式API,你需要注意哪些事项,至少有10点。
-
当你需要调用一个分布式API,你需要注意哪些事项,至少有10点。
-
当调用出现故障或者性能优化问题,有哪些思路解决
分布式API
软件世界,是通过API互相链接,这些API或者是进程内的API,或者是分布式API,通过这些API,构造出精彩的复杂的企业应用,互联网世界,物联网世界。学习完本节后,当你是调用一个分布式API的时候,你会掌握API调用最佳实践,当你提供一个分布式API时候,你会清楚API设计原则,本章覆盖如下内容
-
分布式API的技术选型
-
REST API 设计原则 (参考 远程调用API设计 下:REST,待定)
面向架构质量的设计
我们知道,架构主要目标是对软件系统分解成较小更容易实现的元素,如模块或者子系统,并能让这些元素协同完成业务需求,对于通常的程序员视角来说,架构貌似就是画几个框,然后连上线即可。
如下是一个分布式系统最简单的架构。看着很简单的俩框一线,但架构师却需要考虑的非常多,这也是架构师和普通程序员区别
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 通常有如下协议栈
-
Client API ,以Java接口形式体现
-
Client Stub,扮演了客户端的Gateway,通常会将调用的目标类,参数等封装成指定协议的报文,调用底层transport。Client Stub有负责寻址,调用分布式服务端的节点,并等待返回结果返回调用者
-
Client Transport: 负责传输部分,客户端报文到服务器端
-
Server Transport :负责接收客户端报文
-
Server Skeleton:负责把报文反序列化成调用参数,并调用实际的实现类Service Impl
-
Service Impl:接口实现类
分布式API设计通用考虑
下面列出了系统常使用的分布式API技术的公共特性
API技术
|
基础协议
|
报文格式
|
语言支持
|
性能
|
内网/外网
|
可观测
|
通信方式
|
API Schema
|
负载均衡
|
REST
|
HTTP
|
JSON
|
通用
|
一般
|
内网/外网
|
容易
|
同步
|
支持
|
Nginx等HTTP代理
|
Spring Feigh
|
HTTP
|
JSON
|
通用
|
一般
|
内网/外网
|
容易
|
同步
|
支持
|
客户端
|
RMI
|
TCP/HTTP
|
RMI
|
Java
|
一般
|
内网/外网
|
二次开发
|
同步
|
支持
|
客户端
|
gRPC
|
TCP/ HTTP
|
Protobuf
|
常用语言支持
|
好
|
内网/外网
|
二次开发
|
同步,异步,服务端推送
|
支持
|
客户端或者代理
|
Dubbo
|
TCP/HTTP
|
多种
|
常用语言支持
|
好
|
内网/外网
|
二次开发
|
同步,异步
|
无
|
客户端或者代理
|
SOAP
|
HTTP
|
XML
|
通用
|
一般
|
内网/外网
|
容易
|
同步,异步
|
支持
|
Nginx等HTTP代理
|
WebSocket
|
HTTP
|
文本或者二进制,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不直接支持
|
批量调用API
|
API是否还有批量调用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
|
负载均衡
|
SSE
|
HTTP
|
同WebSocket
|
通用
|
好
|
内网/外网
|
难
|
服务器推送
|
无
|
Nginx等HTTP代理
|
GraphQL
|
HTTP
|
JSON
|
通用
|
一般
|
内网/外网
|
容易
|
同步
|
支持
|
Nginx等HTTP代理
|
MQTT
|
TCP
|
MQTT
|
通用
|
好
|
外网
|
难
|
异步
|
无
|
否
|
Thrift
|
TCP
|
二进制
|
通用
|
好
|
内网
|
难
|
同步,异步
|
支持
|
Nginx,HAProxy,客户端自研
|
参考
2 各个框架的官网功能特性
3
可观测性工程