使用netty构建API网关实践之路
随着互联网的快速发展,当前以步入移动互联、物联网时代。用户访问系统入口也变得多种方式,由原来单一的PC客户端,变化到PC客户端、各种浏览器、手机移动端及智能终端等。同时系统之间大部分都不是单独运行,经常会涉及与其他系统对接、共享数据的需求。所以系统需要升级框架满足日新月异需求变化,支持业务发展,并将框架升级为微服务架构。“API网关”核心组件是架构用于满足此些需求
很多互联网平台已基于网关的设计思路,构建自身平台的API网关,国内主要有京东、携程、唯品会等,国外主要有Netflix、Amazon等。
业界为了满足这些需求,已有相关的网关框架。
1、基于nginx平台实现的网关有:kong、umbrella等
2、自研发的网关有:zuul1、zuul2等
但是以上网关框架只能是满足部分需求,不能满足企业的所有要求,就我而言,我认为最大的问题是没有协议转换及OPS管理控制平台
另外:对于微服务架构下,如果基于HTTP REST传输协议,API网关还承担了一个内外API甄别的功能,只有在API网关上注册了的API还能是真正的堆外API
整个网关系统由三个子系统组成:
说明:
1) 整个网关基于Netty NIO来实现同步非阻塞是HTTP服务,网关是外部API请求的HTTP服务端,同时是内部服务的客户端,所以有Netty Server Handler和Netty Client Handler的出现;
2)对于Netty Server Handler来说,当一个HTTP请求进来时,他会把当前连接转化为ClientToProxyConnection,它是线程安全的,伴随当前此HTTP请求的生命周期结束,它也负责ClientToProxyConnection的生命周期的维护;
3)对于Netty Client Handler来说,当ClientToProxyConnection需要传递请求到内部服务时,会新建(或者获取原来已建)的ProxyToServerConnection来进行内部的请求,它也是线程安全的;
4)对于Filter来说,他运行在ClientToProxyConnection上,插入请求进来及收到后端请求之间;
从以上分析,网关选择同步非阻塞方式是一个合适的选择
其中转化的过程如下:
2:根据FileDescriptorSet获取gRPC的入参和出参描述符,然后再创建gRPC所需要的MethodDescriptor方法描述对象
2) HTTP ---->dubbo
在dubbo的框架设计中,其中已经包含了泛化调用的设计,所以在这块,基本上就延用了dubbo的泛化调用来实现http转dubbo的协议,而关于dubbo的参数部分,可以指定参数映射规范,利用参数裁剪的技术对http请求参数进行抽取,如果dubbo的接口是java类型,则直接抽取,如果是pojo,按照dubbo的用户文档,把他组成一个Map的数据结构即可,而操作这一步需要映射规则
整个网关目前基本完成并且也开源到GitHub上,欢迎拍砖及使用
tesla
服务编排/数据聚合 指的是可以通过一个请求来依次调用多个微服务,并对每个服务的返回结果做数据处理,最终整合成一个大的结果返回给前端。
例如一个服务是“查询用户预定的酒店”,前端仅需要传一个订单ID,后端会返回整个订单的信息,包括用户信息、酒店信息和房间信息等。
这个服务背后可能对应着以下几个操作:
微服务架构上对功能做了解耦,使用服务编排可以快速从各类服务上获取需要的数据,对业务实现快速响应。总的来说,编排有以下几点优势:
Goku API Gateway (中文名:悟空 API 网关)是一个基于 Golang 开发的微服务网关,能够实现高性能 HTTP API 转发、服务编排、多租户管理、API 访问权限控制等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行 API 服务治理、提高 API 服务的稳定性和安全性。
Goku API Gateway支持一个编排API对应多个后端服务,每个后端服务的请求参数可以使用前端传入的参数,也可以在编排里自定义(写静态参数或从返回数据里获得)。每个后端服务的返回数据支持过滤、删除、移动、重命名、拆包和封包等操作;编排API能够设定编排失败时的异常返回。
Goku API Gateway 的社区版本(CE)同时拥有完善的使用指南和二次开发指南,内置的插件系统也能够让企业针对自身业务进行定制开发。
项目地址: https://github.com/eolinker/goku-api-gateway
官网地址: https://www.eolinker.com
我们将编排的整个操作放到网关进行,由网关对数据做处理与转换,这样无需对后端服务做改动。一个请求到达网关,网关调用多个后端服务,并且在网关上对各个服务的返回数据做处理(操作有过滤、移动、重命名、封包、拆包,后面会对各操作做详细解释),最后由网关将数据整合好返回给前端。
网关将编排过程中对 API的转发处理过程 (转发->获取返回数据->数据处理)称为一个 Step 。
添加一个转发服务,该服务为 查询订单详情API,配置相应的转发地址、传入的参数、对返回数据做何种处理等。
由于篇幅原因,后续的Step(查询用户详情、查询酒店详情、查询房间详情)就不一一展示了。
网关将编排过程中对 API的转发处理过程 (转发->获取返回数据->数据处理)称为一个 Step。
我们将处理查询订单详情API称为 Step1 ,其中Step1的返回数据有:用户ID、酒店ID、房间ID。同理,将查询用户信息这步称为 Step2 ,将查询酒店信息称为 Step3 ,将查询房间信息称为 Step4 。
传参规则:
以下为转发路径的传参写法:
Step2中需要接收Step1里返回的userID作为参数,同时需要接收前端传入的Authorization参数
在网关里Step2的请求参数配置如下所示,请求参数存在多个的话用换行表示:
1.查询订单详情的API,返回数据称为json1,内容如下:
2.查询用户详情的API,返回数据称为json2,内容如下:
3.查询酒店详情的返回数据,称为json3,内容如下:
4.查询房间详情的返回数据,称为json4,内容如下:
5.可以在每一个Step里对返回Json做处理,网关会将处理过的数据最后整合起来,再返回前端,例如这是通过网关返回的最终数据:
这里以查询酒店详情API的返回数据json3为例,讲解网关如何在编排过程中对返回数据做处理。
查询酒店详情API返回的原始数据如下:
从网关返回给前端的数据中截取酒店信息的数据如下:
从原始数据到处理后的数据需要经过以下操作:
字段黑名单的作用是排除某些字段,支持数组形式。
在网关的Step3里配置如下:
经过网关处理后,实际的返回数据如下,可以看到data对象里的id字段已经被过滤掉:
拆包是指将指定对象的内容提取出来作为该步骤(step)的返回结果。其中匹配目标只能为object,匹配目标为空时,结果为 {},可用于清除数据。
在网关的Step里配置如下:
经过网关处理后,实际的返回数据如下,可以看到data对象被拆开,最终数据仅保留了data对象里面的字段:
字段封包会将当前的数据整体打包为最终返回数据中的一个对象,不支持*,不支持数组。
在网关的Step里配置如下:
经过网关处理后,实际的返回数据如下,数据被整体打包为hotelinfo对象:
经过三个步骤,就可以将原始数据变成最终的数据。
本文仅列举了编排过程中部分数据处理的操作,如需了解更多编排细则,可通过文末给出的教程链接。
相关链接
API网关跨一个或多个内部API提供单个统一的API入口点。 通常还包括限制访问速率限制和有关安全性等特点。 诸如Tyk.io的API管理层增加了额外的功能,例如分析,货币化和生命周期管理。
基于微服务的架构可以具有10到100个或更多个服务。 API网关可以为外部消费者提供统一的入口点,而与内部微服务的数量和组成无关。
API网关对于微服务的好处:
1、防止内部关注暴露给外部客户端
API网关将外部公共API与内部微服务API分开,允许添加微服务和更改边界。 其结果是能够在不对外部绑定客户端产生负面影响的情况下重构和适当大小的微服务。 它还通过为您的所有微服务提供单一入口点,对客户端隐藏了服务发现和版本控制详细信息。
2、为您的微服务添加额外的安全层
API网关通过提供一个额外的保护层来防止恶意攻击,例如SQL注入,XML解析器漏洞和拒绝服务(DoS)攻击。
3、支持混合通信协议
虽然面向外部的API通常提供基于HTTP或REST的API,但是内部微服务可以从使用不同的通信协议中受益。 协议可能包括的Protobuf或AMQP ,或者用SOAP,JSON-RPC或XML-RPC系统集成。 API网关可以在这些不同的协议之上提供外部的,统一的基于REST的API,允许团队选择最适合内部架构的API。
4、降低微服务复杂性
如果微服务具有共同的关注点,例如使用API令牌的授权,访问控制实施和速率限制。 每个这些关注可以通过要求每个服务都实现它们,但这为微服务的开发增加更多的时间成本。 API网关将从您的代码中删除这些问题,允许您的微服务关注手头的任务。
5、微服务模拟和虚拟化
通过将微服务API与外部API分离,您可以模拟或虚拟化服务,以验证设计要求或协助集成测试。
API网关的服务对象
API网关可以为Web端、APP提供API访问,也可以给物联网设备提供API接口。另外致力于开发生态的企业还会为一些合作伙伴提供API网关,供其调用通用的微服务。对于可以提供数据或算法服务的企业,可以在云市场的API网关注册自己的API,从而对外提供服务。
假设你正在开发一个电商网站,那么这里会涉及到很多后端的微服务,比如会员、商品、推荐服务等等。
那么这里就会遇到一个问题,APP/Browser怎么去访问这些后端的服务? 如果业务比较简单的话,可以给每个业务都分配一个独立的域名(https://service.api.company.com),但这种方式会有几个问题:
更好的方式是采用API网关,实现一个API网关接管所有的入口流量,类似Nginx的作用,将所有用户的请求转发给后端的服务器,但网关做的不仅仅只是简单的转发,也会针对流量做一些扩展,比如鉴权、限流、权限、熔断、协议转换、错误码统一、缓存、日志、监控、告警等,这样将通用的逻辑抽出来,由网关统一去做,业务方也能够更专注于业务逻辑,提升迭代的效率。
通过引入API网关,客户端只需要与API网关交互,而不用与各个业务方的接口分别通讯,但多引入一个组件就多引入了一个潜在的故障点,因此要实现一个高性能、稳定的网关,也会涉及到很多点。
API 注册
业务方如何接入网关?一般来说有几种方式。
协议转换
内部的API可能是由很多种不同的协议实现的,比如HTTP、Dubbo、GRPC等,但对于用户来说其中很多都不是很友好,或者根本没法对外暴露,比如Dubbo服务,因此需要在网关层做一次协议转换,将用户的HTTP协议请求,在网关层转换成底层对应的协议,比如HTTP ->Dubbo, 但这里需要注意很多问题,比如参数类型,如果类型搞错了,导致转换出问题,而日志又不够详细的话,问题会很难定位。
服务发现
网关作为流量的入口,负责请求的转发,但首先需要知道转发给谁,如何寻址,这里有几种方式:
服务调用
网关由于对接很多种不同的协议,因此可能需要实现很多种调用方式,比如HTTP、Dubbo等,基于性能原因,最好都采用异步的方式,而Http、Dubbo都是支持异步的,比如apache就提供了基于NIO实现的异步HTTP客户端。
因为网关会涉及到很多异步调用,比如拦截器、HTTP客户端、dubbo、redis等,因此需要考虑下异步调用的方式,如果基于回调或者future的话,代码嵌套会很深,可读性很差,可以参考zuul和spring cloud gateway的方案,基于响应式进行改造。
优雅下线
性能
网关作为所有流量的入口,性能是重中之重,早期大部分网关都是基于同步阻塞模型构建的,比如Zuul 1.x。但这种同步的模型我们都知道,每个请求/连接都会占用一个线程,而线程在JVM中是一个很重的资源,比如Tomcat默认就是200个线程,如果网关隔离没有做好的话,当发生网络延迟、FullGC、第三方服务慢等情况造成上游服务延迟时,线程池很容易会被打满,造成新的请求被拒绝,但这个时候其实线程都阻塞在IO上,系统的资源被没有得到充分的利用。另外一点,容易受网络、磁盘IO等延迟影响。需要谨慎设置超时时间,如果设置不当,且服务隔离做的不是很完善的话,网关很容易被一个慢接口拖垮。
而异步化的方式则完全不同,通常情况下一个CPU核启动一个线程即可处理所有的请求、响应。一个请求的生命周期不再固定于一个线程,而是会分成不同的阶段交由不同的线程池处理,系统的资源能够得到更充分的利用。而且因为线程不再被某一个连接独占,一个连接所占用的系统资源也会低得多,只是一个文件描述符加上几个监听器等,而在阻塞模型中,每条连接都会独占一个线程,而线程是一个非常重的资源。对于上游服务的延迟情况,也能够得到很大的缓解,因为在阻塞模型中,慢请求会独占一个线程资源,而异步化之后,因为单条连接所占用的资源变的非常低,系统可以同时处理大量的请求。
如果是JVM平台,Zuul 2、Spring Cloud gateway等都是不错的异步网关选型,另外也可以基于Netty、Spring Boot2.x的webflux、vert.x或者servlet3.1的异步支持进行自研。
缓存
对于一些幂等的get请求,可以在网关层面根据业务方指定的缓存头做一层缓存,存储到Redis等二级缓存中,这样一些重复的请求,可以在网关层直接处理,而不用打到业务线,降低业务方的压力,另外如果业务方节点挂掉,网关也能够返回自身的缓存。
限流
限流对于每个业务组件来说,可以说都是一个必须的组件,如果限流做不好的话,当请求量突增时,很容易导致业务方的服务挂掉,比如双11、双12等大促时,接口的请求量是平时的数倍,如果没有评估好容量,又没有做限流的话,很容易服务整个不可用,因此需要根据业务方接口的处理能力,做好限流策略,相信大家都见过淘宝、百度抢红包时的降级页面。
因此一定要在接入层做好限流策略,对于非核心接口可以直接将降级掉,保障核心服务的可用性,对于核心接口,需要根据压测时得到的接口容量,制定对应的限流策略。限流又分为几种:
稳定性
稳定性是网关非常重要的一环,监控、告警需要做的很完善才可以,比如接口调用量、响应时间、异常、错误码、成功率等相关的监控告警,还有线程池相关的一些,比如活跃线程数、队列积压等,还有些系统层面的,比如CPU、内存、FullGC这些基本的。
网关是所有服务的入口,对于网关的稳定性的要求相对于其他服务会更高,最好能够一直稳定的运行,尽量少重启,但当新增功能、或者加日志排查问题时,不可避免的需要重新发布,因此可以参考zuul的方式,将所有的核心功能都基于不同的拦截器实现,拦截器的代码采用Groovy编写,存储到数据库中,支持动态加载、编译、运行,这样在出了问题的时候能够第一时间定位并解决,并且如果网关需要开发新功能,只需要增加新的拦截器,并动态添加到网关即可,不需要重新发布。
熔断降级
熔断机制也是非常重要的一项。若某一个服务挂掉、接口响应严重超时等发生,则可能整个网关都被一个接口拖垮,因此需要增加熔断降级,当发生特定异常的时候,对接口降级由网关直接返回,可以基于Hystrix或者Resilience4j实现。
日志
由于所有的请求都是由网关处理的,因此日志也需要相对比较完善,比如接口的耗时、请求方式、请求IP、请求参数、响应参数(注意脱敏)等,另外由于可能涉及到很多微服务,因此需要提供一个统一的traceId方便关联所有的日志,可以将这个traceId置于响应头中,方便排查问题。
隔离
比如线程池、http连接池、redis等应用层面的隔离,另外也可以根据业务场景,将核心业务部署带单独的网关集群,与其他非核心业务隔离开。
网关管控平台
这块也是非常重要的一环,需要考虑好整个流程的用户体验,比如接入到网关的这个流程,能不能尽量简化、智能,比如如果是dubbo接口,我们可以通过到git仓库中获取源码、解析对应的类、方法,从而实现自动填充,尽量帮用户减少操作;另外接口一般是从测试->预发->线上,如果每次都要填写一遍表单会非常麻烦,我们能不能自动把这个事情做掉,另外如果网关部署到了多个可用区、甚至不同的国家,那这个时候,我们还需要接口数据同步功能,不然用户需要到每个后台都操作一遍,非常麻烦。
这块个人的建议是直接参考阿里云、aws等提供的网关服务即可,功能非常全面。
其他
其他还有些需要考虑到的点,比如接口mock,文档生成、sdk代码生成、错误码统一、服务治理相关的等,这里就不累述了。
目前的网关还是中心化的架构,所有的请求都需要走一次网关,因此当大促或者流量突增时,网关可能会成为性能的瓶颈,而且当网关接入的大量接口的时候,做好流量评估也不是一项容易的工作,每次大促前都需要跟业务方一起针对接口做压测,评估出大致的容量,并对网关进行扩容,而且网关是所有流量的入口,所有的请求都是由网关处理,要想准确的评估出容量很复杂。可以参考目前比较流行的ServiceMesh,采用去中心化的方案,将网关的逻辑下沉到sidecar中,
sidecar和应用部署到同一个节点,并接管应用流入、流出的流量,这样大促时,只需要对相关的业务压测,并针对性扩容即可,另外升级也会更平滑,中心化的网关,即使灰度发布,但是理论上所有业务方的流量都会流入到新版本的网关,如果出了问题,会影响到所有的业务,但这种去中心化的方式,可以先针对非核心业务升级,观察一段时间没问题后,再全量推上线。另外ServiceMesh的方案,对于多语言支持也更友好。