假设你正在开发一个电商网站,那么这里会涉及到不少后端的微服务,好比会员、商品、推荐服务等等。git
那么这里就会遇到一个问题,APP/Browser怎么去访问这些后端的服务? 若是业务比较简单的话,能够给每一个业务都分配一个独立的域名(https://service.api.company.com
),但这种方式会有几个问题:github
更好的方式是采用API网关,实现一个API网关接管全部的入口流量,相似Nginx的做用,将全部用户的请求转发给后端的服务器,但网关作的不只仅只是简单的转发,也会针对流量作一些扩展,好比鉴权、限流、权限、熔断、协议转换、错误码统1、缓存、日志、监控、告警等,这样将通用的逻辑抽出来,由网关统一去作,业务方也可以更专一于业务逻辑,提高迭代的效率。
经过引入API网关,客户端只须要与API网关交互,而不用与各个业务方的接口分别通信,但多引入一个组件就多引入了一个潜在的故障点,所以要实现一个高性能、稳定的网关,也会涉及到不少点。
web
业务方如何接入网关?通常来讲有几种方式。redis
第一种采用插件扫描业务方的API,好比Spring MVC
的注解,并结合Swagger
的注解,从而实现参数校验、文档&&SDK生成等功能,扫描完成以后,须要上报到网关的存储服务。spring
手动录入。好比接口的路径、请求参数、响应参数、调用方式等信息,但这种方式相对来讲会麻烦一些,若是参数过多的话,前期录入会很费时费力。
数据库
内部的API多是由不少种不一样的协议实现的,好比HTTP、Dubbo、GRPC等,但对于用户来讲其中不少都不是很友好,或者根本无法对外暴露,好比Dubbo服务,所以须要在网关层作一次协议转换,将用户的HTTP协议请求,在网关层转换成底层对应的协议,好比HTTP -> Dubbo
, 但这里须要注意不少问题,好比参数类型,若是类型搞错了,致使转换出问题,而日志又不够详细的话,问题会很难定位。后端
网关做为流量的入口,负责请求的转发,但首先须要知道转发给谁,如何寻址,这里有几种方式:api
网关因为对接不少种不一样的协议,所以可能须要实现不少种调用方式,好比HTTP、Dubbo等,基于性能缘由,最好都采用异步的方式,而Http、Dubbo都是支持异步的,好比apache就提供了基于NIO实现的异步HTTP客户端。
由于网关会涉及到不少异步调用,好比拦截器、HTTP客户端、dubbo、redis等,所以须要考虑下异步调用的方式,若是基于回调或者future的话,代码嵌套会很深,可读性不好,能够参考zuul和spring cloud gateway的方案,基于响应式进行改造。缓存
优雅下线也是网关须要关注的一个问题,网关底层会涉及到不少种协议,好比HTTP、Dubbo,而HTTP又能够继续细分,好比域名、注册中心等,有些自身就支持优雅下线,好比Nginx自身是支持健康监测机制的,若是检测到某一个节点已经挂掉了,就会把这个节点摘掉,对于应用正常下线,须要结合发布系统,首先进行逻辑下线,而后对后续Nginx的健康监测请求直接返回失败(好比直接返回500),而后等待一段时间(根据Nginx配置决定),而后再将应用实际下线掉。另外对于注册中心的其实也相似,通常注册中心是只支持手动下线的,能够在逻辑下线阶段调用注册中心的接口将节点下线掉,而有些不支持主动下线的,须要结合缓存的配置,让应用延迟下线。另外对于其余好比Dubbo等原理也是相似。
网关做为全部流量的入口,性能是重中之重,早期大部分网关都是基于同步阻塞模型构建的,好比Zuul 1.x。但这种同步的模型咱们都知道,每一个请求/链接都会占用一个线程,而线程在JVM中是一个很重的资源,好比Tomcat默认就是200个线程,若是网关隔离没有作好的话,当发生网络延迟、FullGC、第三方服务慢等状况形成上游服务延迟时,线程池很容易会被打满,形成新的请求被拒绝,但这个时候其实线程都阻塞在IO上,系统的资源被没有获得充分的利用。另一点,容易受网络、磁盘IO等延迟影响。须要谨慎设置超时时间,若是设置不当,且服务隔离作的不是很完善的话,网关很容易被一个慢接口拖垮。
而异步化的方式则彻底不一样,一般状况下一个CPU核启动一个线程便可处理全部的请求、响应。一个请求的生命周期再也不固定于一个线程,而是会分红不一样的阶段交由不一样的线程池处理,系统的资源可以获得更充分的利用。并且由于线程再也不被某一个链接独占,一个链接所占用的系统资源也会低得多,只是一个文件描述符加上几个监听器等,而在阻塞模型中,每条链接都会独占一个线程,而线程是一个很是重的资源。对于上游服务的延迟状况,也可以获得很大的缓解,由于在阻塞模型中,慢请求会独占一个线程资源,而异步化以后,由于单条链接所占用的资源变的很是低,系统能够同时处理大量的请求。
若是是JVM平台,Zuul 二、Spring Cloud gateway等都是不错的异步网关选型,另外也能够基于Netty、Spring Boot2.x的webflux、vert.x或者servlet3.1的异步支持进行自研。
对于一些幂等的get请求,能够在网关层面根据业务方指定的缓存头作一层缓存,存储到Redis等二级缓存中,这样一些重复的请求,能够在网关层直接处理,而不用打到业务线,下降业务方的压力,另外若是业务方节点挂掉,网关也可以返回自身的缓存。
限流对于每一个业务组件来讲,能够说都是一个必须的组件,若是限流作很差的话,当请求量突增时,很容易致使业务方的服务挂掉,好比双十一、双12等大促时,接口的请求量是平时的数倍,若是没有评估好容量,又没有作限流的话,很容易服务整个不可用,所以须要根据业务方接口的处理能力,作好限流策略,相信你们都见过淘宝、百度抢红包时的降级页面。
所以必定要在接入层作好限流策略,对于非核心接口能够直接将降级掉,保障核心服务的可用性,对于核心接口,须要根据压测时获得的接口容量,制定对应的限流策略。限流又分为几种:
稳定性是网关很是重要的一环,监控、告警须要作的很完善才能够,好比接口调用量、响应时间、异常、错误码、成功率等相关的监控告警,还有线程池相关的一些,好比活跃线程数、队列积压等,还有些系统层面的,好比CPU、内存、FullGC这些基本的。
网关是全部服务的入口,对于网关的稳定性的要求相对于其余服务会更高,最好可以一直稳定的运行,尽可能少重启,但当新增功能、或者加日志排查问题时,不可避免的须要从新发布,所以能够参考zuul的方式,将全部的核心功能都基于不一样的拦截器实现,拦截器的代码采用Groovy编写,存储到数据库中,支持动态加载、编译、运行,这样在出了问题的时候可以第一时间定位并解决,而且若是网关须要开发新功能,只须要增长新的拦截器,并动态添加到网关便可,不须要从新发布。
熔断机制也是很是重要的一项。若某一个服务挂掉、接口响应严重超时等发生,则可能整个网关都被一个接口拖垮,所以须要增长熔断降级,当发生特定异常的时候,对接口降级由网关直接返回,能够基于Hystrix或者Resilience4j实现。
因为全部的请求都是由网关处理的,所以日志也须要相对比较完善,好比接口的耗时、请求方式、请求IP、请求参数、响应参数(注意脱敏)等,另外因为可能涉及到不少微服务,所以须要提供一个统一的traceId方便关联全部的日志,能够将这个traceId置于响应头中,方便排查问题。
好比线程池、http链接池、redis等应用层面的隔离,另外也能够根据业务场景,将核心业务部署带单独的网关集群,与其余非核心业务隔离开。
这块也是很是重要的一环,须要考虑好整个流程的用户体验,好比接入到网关的这个流程,能不能尽可能简化、智能,好比若是是dubbo接口,咱们能够经过到git仓库中获取源码、解析对应的类、方法,从而实现自动填充,尽可能帮用户减小操做;另外接口通常是从测试->预发->线上,若是每次都要填写一遍表单会很是麻烦,咱们能不能自动把这个事情作掉,另外若是网关部署到了多个可用区、甚至不一样的国家,那这个时候,咱们还须要接口数据同步功能,否则用户须要到每一个后台都操做一遍,很是麻烦。
这块我的的建议是直接参考阿里云、aws等提供的网关服务便可,功能很是全面。
其余还有些须要考虑到的点,好比接口mock,文档生成、sdk代码生成、错误码统1、服务治理相关的等,这里就不累述了。
目前的网关仍是中心化的架构,全部的请求都须要走一次网关,所以当大促或者流量突增时,网关可能会成为性能的瓶颈,并且当网关接入的大量接口的时候,作好流量评估也不是一项容易的工做,每次大促前都须要跟业务方一块儿针对接口作压测,评估出大体的容量,并对网关进行扩容,并且网关是全部流量的入口,全部的请求都是由网关处理,要想准确的评估出容量很复杂。能够参考目前比较流行的ServiceMesh,采用去中心化的方案,将网关的逻辑下沉到sidecar中, sidecar和应用部署到同一个节点,并接管应用流入、流出的流量,这样大促时,只须要对相关的业务压测,并针对性扩容便可,另外升级也会更平滑,中心化的网关,即便灰度发布,可是理论上全部业务方的流量都会流入到新版本的网关,若是出了问题,会影响到全部的业务,但这种去中心化的方式,能够先针对非核心业务升级,观察一段时间没问题后,再全量推上线。另外ServiceMesh的方案,对于多语言支持也更友好。