微服务设计模式-外部api
外部API设计的相关问题
一个微服务系统需要提供给外部调用的api,这些api可能有如下一些类型:
- web应用,例如,erp后台管理系统等。
- 运行在浏览器上的js应用。
- 手机端:app、小程序等。
- 第三方应用。
web应用通常运行在防火墙内,所以会在高宽带、低延迟的LAN上访问服务。其他运行在防护墙外,一般通过低宽带、高延迟的因特网或者移动 网络访问服务。
一种API策略是让客户端直接访问服务。是传统的整体应用调用的方式,但是这种在微服务中很少用,有如下缺点:
- 微服务的api都是细粒度的,这就要求客户端发起多个请求获取他们需要的数据,这样做会降低请求效率,从而影响用户体验。
- 没有封装导致客户端直接对接服务和API,这让服务的架构和API改变困难。
- 服务间的通讯协议可能对客户端不友好。
移动端设计问题
- 频繁的请求导致用户体验变差:因特网相比 LAN有着更高的延迟,通常要比LAN高100倍,移动网络更糟。频繁请求会导致请求时间更长。 此外, 更多的网络请求也会消耗电量, 让移动设备耗电过快。
- 缺少封装让前端开发者和后端开发者同步修改代码:对于移动端,后台的改变可能会导致前端代码对应地改变,而发布新版本的审核流程 十分不便。
- 服务间通信可能是客户端不友好的通信协议:如服务间可能使用的gRPC,这对移动端非常不友好。
其他类型客户端设计的问题
-
web应用的设计:web应用也可以使用浏览器不友好的协议访问服务,开发web应用的人员通常和开发后端服务的人员保持密切的协作。 所以web应用可以随着后台的变化而改变,所以web应用直接访问后端服务是可行的。
-
面向浏览器js的应用:浏览器的js程序需要随着后端服务的改变而改变,并且js程序在调用细粒度的API时,也会存在高延迟的问题。
-
第三方应用:微服务的组合api模式也会让第三方调用效率低下,但是更关键的是,第三方通常需要一个稳定的API,很难让第三方开发者去 升级到新版本的API。并且让后端开发人员长期保持API向前兼容对一个组织是一个很大的负担。
API网关模式
API网关是外部通向系统的进入点。它的责任是请求路由、API组合、身份认证等。API网关和设计模式中的facade(门面模式)类似。它封装了 应用的内部结构并提供API接口给客户端。它还拥有其他的功能,如鉴权,监控,限速等。其结构如下:
路由请求
api网关主要功能之一就是路由请求。当api网关接收到一个请求,API网关查询路由映射,将请求路由到指定服务。这个功能能和nginx等 web服务器提供的方向代理功能相同。
API组合
api网关也扮演了api组合的功能,如获取订单详情如下:
协议转换
如提供一个RESTful API给外部客户端,而内部使用gRPC。
API 为每个客户端提供特定的客户端特定API。
每种客户端需要的API不一样,如第三方客户端需要订单完整的信息,而移动端只需要返回订单简短信息。这时,最好的办法是为每个客户端 提供它自己的api。
实现边缘功能(edge)
一个应用可能有如下边缘功能:认证、授权、限流、缓存、请求统计、请求记录日志等。这些功能一般会有三个地方可以实现。第一,后端 服务。这些后端也可以做,但是在到达后端服务前处理更加安全。第二,在API网关之前建立一个独立edge 服务,好处是功能分离,缺点是 增加了网络延迟,并增加了应用的复杂性。第三种就是在API网关中实现edge服务。
一个API网关通常由两部分组成,API层和通用层。API层由多个独立的API模块组成,每个API模块映射一个特定的客户端。通用层实现共享 功能,如鉴权。如下图:
一些api直接将请求映射到服务,而另一些则使用api组合从多个服务中获取数据并合并结果。
API网关归属模式
一个重要的问题,是谁负责API网关的开发。一种方案就像SOA的ESB一样,由一个独立的团队去管理,如果一个移动端的开发者想要访问一个 接口,那么他需要向API网关组提交一个申请并等待API网管组提供API。这种中心化的组织拥有瓶颈,不适合微服务松耦合的团队结构。
一个更好的方案是由Netflix提出的,让每个客户端组拥有API模块,并发布API。而API网关组负责开发通用层和网关操作相关功能。如下图:
但是api网关的责任依然是模糊不清的,多个团队向代码库提供代码,API网关组负责它的管理和操作。这种模糊的划分和“谁创建,谁负责” 的原则违背。解决方案是一种Backends for frontends (BFF) 模式。每个客户端团队独立开发和管理API模块和独立通用模块, 如下图所示:
这种方式也有一个缺点,就是独立管理通用模块可能导致代码重复。理想情况下:所有的API网关应该使用共享的库,并由API网关组管理。
API网关的优缺点
- api网关的优点:封装应用内部结构,减少网络延迟,并简化客户端代码。
- 缺点:需要维护一个高可用的独立组件,带来开发、部署、管理成本。因此可能成为开发瓶颈。
API网关设计
设计一个api网关时,需要考虑如下几点:
性能和扩展
网关时应用的门户,所有的外部调用都要经过网关,一个影响性能和扩展的重要因素是API网关使用同步还是异步I/O。异步IO可以极大地提高 请求效率,JVM可以使用NIO的某种框架,如Netty或者Spring Reactor等。但是异步IO更加复杂,代码也更加难写,事件处理器必须快速返回 来避免线循环利用的程阻塞。
使用reactive流
api组合模式调用多个服务,等待返回并组装结果。通常每个服务的调用都是顺序执行的,这样的缺点是接口响应的总时间是每个服务响应的 事件总和。为了减少调用事件,服务调用可以并发执行。
传统的并发代码是使用回调,异步,事件驱动IO都是基于回调的扩展。可以通过ExecutorService.submitCallable()来实现。该方法返回 一个Future,是一个阻塞的API。一种更好的扩展方法是api组合器调用ExecutorService.submit (Runnable),为每一个Runnable设置一个 Callback方法,用于获取每个请求的返回结果。
传统的异步回调方法和容易导致问题,并且很难理解。尤其当并行和串行请求混合在一起时。更好的方法是使用一个reactive框架定义声明式 的API组合代码。JVMreactive抽象有如下例子:
- java 8
CompletableFutures
- Rxjava
Observables
- Scala
Futures
API网关的实现
有多种方式实现API网关,如:
- 现成的API网关服务:这种需要很少的开发或者不需要开发,但是不灵活。如它不支持api组合。
- 使用一个API网关框架或者web框架自己开发:十分灵活,需要更多的开发工作。
现成的API网关服务
现成的API网关很多,如AWS API网关。还有基于NGINX HTTP服务器的Kong。和用golang编写的Traefik。
自建API网关
如果需要实现API组合功能,就需要自己开发API网关。构建一个API网关,有两个问题需要解决:
- 定义一个路由规则的机制来简化代码。
- 正确地实现HTTP代理功能,如如何处理HTTP headers等。
开发API网关通常需要使用对应的框架,减少大量的代码。我们先看Zuul,然后再看Spring Cloud GateWay。
Zuul
Zuul框架使用filter的概念,用一些列filter过滤http请求,调用后端服务,然后转化结果返回给客户端。尽管可以直接使用Zuul,但是使用 Spring Cloud Zuul更方便。Zuul有一个很大的限制是它只能处理基于路径的路由,如它不能将GET /ORDERS和POST /ORDERS路由到不同的服务。
Spirng Cloud GateWay
Spring Cloud GateWay是在Spring FrameWork 5、SpringBoot2、Spring WebFlux等框架的基础上构建的。它简答,并且易于理解,提供 如下功能:
- 路由请求到后端服务。
- 实现请求处理器,执行API组合。
- 处理edge功能,如权限验证。
具体实现可以参考代码:
基于GraphQL
具体实现参考代码。