API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。
背景
移动互联网时代的挑战
移动互联时代迭代速率对于后端开发带来了一些挑战。
在我们公司的商品详情页上,包含了商品信息、价格信息、促销信息和推荐列表四个部分。
在开发过程中,我们想要把这四个信息在一个接口访问中全部吐出来,尝试给出一个万能接口。
事实上,我们在第一次这样做的时候或许是靠谱的,但是当产品发生变更的时候,比如产品想把推荐列表换成热销榜,那么之前做的万能接口就已经不满足这个业务场景了,只能新开一个V2版本。
这样重复必然会导致接口的膨胀以及维护成本越来越高。
所以虽然我们都在追求一个万能的解,但这个“解”也许并不存在。
微服务时代的挑战
我们商品详情页的数据来自于商品系统、价格系统、推荐系统和营销系统。而对于客户端或用户而言,其实没有必要知道每个接口由哪个微服务提供的,只需得到数据即可。
所以我们面临的问题就是怎样避免让客户端感知微服务边界的存在,不同的后端、前端团队需要统一的接口设计、接入规范。
需求
使用API网关构建微服务
API网关是挡在所有微服务之前的一个透明层,是请求进入系统的唯一节点。基于这一点,一方面解决了对调用方隐藏微服务的系统边界问题,另一方面负责服务请求路由及协议转换。它可能还具有其它职责,如身份验证、权限控制、负载均衡、“请求整形”与管理。
实践落地
从HTTP到RPC
在我们的APP和WEB上,所有请求都基于HTTP,RPC服务是基于DUBBO的RPC框架来做。
API网关做的最简单的一件事就是能够让用户发起的请求通过API网关转成对RPC服务的调用,再回到用户的APP上。
主要解决了HTTP请求到RPC调用实例的映射,以及把无类型的参数转换为带类型的参数。
实现了一个轻量级的MVC框架,将请求转换为对RPC实例的调用。
从HTTP到RPC——定义好一个接口
我认为一个设计良好的接口一定包含了明确的异常编码,以及这个异常编码在什么业务场景上出现,这个异常编码怎样在客户端得到合适的处理。
还需要有一个明确的调用权限说明,和清晰的参数列表、返回结果。
示例
如图,这上面有一个ApiGroup,来描述业务模块的属性。
这是我们某个特定的方法,和control有些相似。包含了API的方案名、SecurityType是权限认证的level。
Designed ErrorCode是用来对客户端接口明确报出异常。
入参描述有ApiAutowired和ApiParameter两种类型。
从HTTP到RPC——API注册
在API注册流程中,我们首先做了ApiParser,用于解析API信息,解析完之后会得到接口的完备信息,包含了接口的描述及整个业务异常编码的描述。
第二步是要创建一个RPC调用实例,也就是RpcInvoker。
接下来需要创建一个服务的动态代理,来解决无类型参数到RPC接口上的有类型参数的转换。
最后是把proxy做一个缓存。
从HTTP到RPC——proxy实例
这是一个动态生成proxy的例子。
我们用ASM字节码框架在运行期动态生成proxy。图中函数是把string类型参数转换为强类型参数。
从HTTP到RPC——处理请求
第一步解析请求,在URL中需要描述清楚HTTP调用哪些接口,解析请求就是要pass出这些信息。
然后要做安全验证,以保证什么样的请求才可以被路由到微服务的请求。
第三步构建API调用的上下文。上下文里包含了用户ID等信息,这些信息来自于用户访问令牌解密之后。
接下来就是做代理的执行,最后对结果做序列化。
从HTTP到RPC——安全策略
做安全策略的大体上的处理方法就是设备识别、数字签名,也包括HTTPS。
设备识别主要是用于确认身份,依赖于一个叫token的访问令牌来做。
在APP上用户如果发起了登录,首先经过网关,然后到用户服务,登录完成后下发appSecret和token。
APP在调用API之前要做数字签名,需要一个签名的密钥,也就是下发的appSecret。
API到了网关,网关可以解析出用户身份对应的appSecret,所以它可以验证这个请求是不是正常的请求。验证通过后就到了业务系统。
中间人攻击是在APP发起请求离开设备以后,在网络传输过程中如果被第三方窃听,它会去尝试篡改请求。
中间人窃取到网络传输中的请求报文,虽然获得了token等关键信息,但由于签名密钥appSecret无法通过监听网络请求获得,所以中间人篡改请求却无法得到一个正确的数字签名。
组合式调用
虽然万能接口是不存在的,但我们尝试实现减轻一个接口中同时返回来自于不同微服务的信息的需求。基于这个需求,我们在API网关扩展出一个组合式调用的协议。
简单来说,这个协议是在一次HTTP请求中对RPC服务发起多次调用,在API网关做响应报文的整合,最后做返回。
组合式调用——Façade设计
用Façade设计模式为多个RPC服务的接口做了Façade,在Façade上把API做组装,统一暴露给客户端,让API网关成为API的Façade。
但我们之前尝试做的万能接口跟不上需求的变化,最终它带来的恶果就是代码难以维护。如果在API网关上去不停地为接口做Façade,API网关的代码必然也是很难维护的。
为了解决这个问题,我们让客户端去定义Façade,API网关只负责组装。
组合式调用——并行处理
使用异步并行方式,将组合式调用串行变并行。
首先是请求前置处理,实际调用则采用了dubbo async调用,是dubbo原生的调用方式。最后做请求后置处理。
但是Dubbo异步调用标识会通过attachment向下传递,污染调用链,导致后续调用链路都变成了异步方式。而且dubbo filter链机制在异步调用中具有局限性。
这两个问题都是最后改了dubbo的源码来解决的。
降低接入门槛
提供在线API文档和接入SDK。
但是变化总是存在,我们该如何解放生产力呢?
扩大API的影响力,基于API信息生成敏捷开发工具;强类型约束的SDK,及时暴露违背“契约”的行为。
这是我们做的一些敏捷开发的工具。
这是在线文档工具和调试工具。
定位请求
Dubbo attachment是一种隐式传参机制,具有传递性。
基于这个机制可以把请求标识在调用链路上一直做传递,调用链路上增加AOP、向日志上下文中传递traceid。
请求定位——elk+zabbix
在API网关落下的所有请求日志都会有相应的错误编码,可以做zabbix基于这个服务的远程报错。
我理解的API网关
从技术设计的角度上来说,API是一种抽象,它隔离了我们的使用以及实现;从开发管理的角度上来说,API是一种契约。
API网关是一种微服务的架构解决方案,服务于API“契约”精神,并尽可能的扩大这种契约的影响力,构建一种围绕API开发的“生态”。
展望
API网关前面是HTTP的传输,在这过程中不一定需要中心化的存储,可以尝试用CDN来做边界缓存。
热发布服务于所有微服务,微服务接口的变更怎样做到热重启,是一个比较有挑战性的事情。
我们之前做的事情只是日志定位,谈不上调用链路跟踪,调用链路跟踪有更专业的解决方案。
API网关目前做的只是权限验证,还没有和风控系统结合起来。
限流降级也还没有做。
在网关上做ab测试会是比较有意义的。
我今天的分享就到这里,谢谢大家!