服务接口网关API技术一览
2023-01-23 10:44:17 阿炯

什么是网关
网关通俗理解
为什么需要网关
网关与服务器集群

网关设计思路
1. 请求路由
2. 服务注册
3. 负载均衡
4. 弹力设计
5. 安全方面

网关设计重点
1. 高性能
2. 高可用
3. 高扩展

网关设计注意事项
流量网关
业务网关

常见网关对比
1. OpenResty
2. Kong
3. Zuul1.0
4. Zuul2.0
5. Spring Cloud Gateway

几种网关的对比

本文准备围绕七个点来讲网关,分别是网关的基本概念、网关设计思路、网关设计重点、流量网关、业务网关、常见网关对比,对基础概念熟悉的朋友可以根据目录查看自己感兴趣的部分。互联网系统设计三高指标,那就是高并发、高性能、高可用。

什么是网关

网关在很多地方将网关比如成门,没什么问题,但是需要区分网关与网桥的区别,网桥工作在数据链路层,在不同或相同类型的LAN之间存储并转发数据帧,必要时进行链路层上的协议转换。可连接两个或多个网络,在其中传送信息包。网关是一个大概念,不具体特指一类产品,只要连接两个不同的网络都可以叫网关,网桥一般只转发信息,而网关可能进行包装。API网关是一个服务器,是系统的唯一入口。封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、协议转换、限流熔断、静态响应处理。其核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。

网关通俗理解

根据网关的特性,举个例子:假如你要去找集团老板(这儿只是举个例子),大家都知道老板肯定不是谁想见就能见的,也怕坏人嘛,那么你去老板所在的办公楼,假如是集团总部,大楼这个门就充当了网关的角色,大门一般都有看门员 ,看门员会做哪些事情呢?

首先所有想见老板的人肯定都得从这个门进(统一入口),这个门相当于将办公室和外界隔离了,主要为了保护里面的安全以及正常工作,来到这个门之后,门卫肯定会让你出示相关证件(鉴权检验),意思就是判断你要见老板这个请求是否合理,如果不合理直接就拒绝了,让你回家等消息,如果鉴权之后,发现你找老板其实只是为了和他谈谈两元店的生意,门卫会跟你说这个用不着找老板,你去集团投资部就行了(动态路由,将请求路由到不同的后端集群中),此时会对你进行一些包装,例如给出具一个访问证类似的,然后告诉路该怎么走等等。

网关的作用是不是就是这三个,最终目的就是减少你与集团的耦合,具体到计算机上就是减少客户端与服务端的耦合,如果没有网关意味着所有请求都会直接调用服务器上的资源,这样耦合太强了,服务器出了问题,客户端会直接报错,例如老板换工作的地方了,如果没有网关你直接去原来的地方找,肯定会被告知老板不在这儿。

为什么需要网关

当使用单体应用程序架构时,客户端(Web 或移动端)通过向后端应用程序发起一次 REST 调用来获取数据。负载均衡器将请求路由给 N 个相同的应用程序实例中的一个。然后应用程序会查询各种数据库表,并将响应返回给客户端。微服务架构下,单体应用被切割成多个微服务,如果将所有的微服务直接对外暴露,势必会出现安全方面的各种问题,另外内外耦合严重。

客户端可以直接向每个微服务发送请求,其问题主要如下:
客户端需求和每个微服务暴露的细粒度 API 不匹配。
部分服务使用的协议不是Web友好协议。可能使用 Thrift 二进制 RPC,也可能使用 AMQP 消息传递协议。
微服务难以重构。如果合并两个服务,或者将一个服务拆分成两个或更多服务,这类重构就非常困难了。

服务端的各个服务直接暴露给客户端调用势必会引起各种问题。同时服务端的各个服务可扩展和伸缩性很差。API 网关是微服务架构中的基础组件,位于接入层之下和业务服务层之上,如前所述的这些功能适合在 API 网关实现。

网关与服务器集群

回到我们服务器上,下面图介绍了网关(Gateway)作用,可知 Gateway 方式下的架构,可以细到为每一个服务的实例配置一个自己的网关,也可以粗到为一组服务配置一个,甚至可以粗到为整个架构配置一个接入的 Gateway。于是,整个系统架构的复杂度就会变得简单可控起来。


这张图展示了一个多层 Gateway 架构,其中有一个总的 Gateway 接入所有的流量(流量网关),并分发给不同的子系统,还有第二级 Gateway 用于做各个子系统的接入 Gateway(业务网关)。可以看到,网关所管理的服务粒度可粗可细。通过网关,我们可以把分布式架构组织成一个星型架构,由网络对服务的请求进行路由和分发。下面来聊聊好的网关应该具备哪些功能,也就是网关设计模式。

网关设计思路

一个网关需要有以下的功能:

1. 请求路由
网关一定要有请求路由的功能。这样一来,对于调用端来说,也是一件非常方便的事情。因为调用端不需要知道自己需要用到的其它服务的地址,全部统一地交给 Gateway 来处理。

2. 服务注册
为了能够代理后面的服务,并把请求路由到正确的位置上,网关应该有服务注册功能,也就是后端的服务实例可以把其提供服务的地址注册、取消注册。一般来说,注册也就是注册一些 API 接口。比如,HTTP 的 Restful 请求,可以注册相应 API 的 URI、方法、HTTP 头。这样,Gateway 就可以根据接收到的请求中的信息来决定路由到哪一个后端的服务上。

3. 负载均衡
因为一个网关可以接收多个服务实例,所以网关还需要在各个对等的服务实例上做负载均衡策略。简单点就是直接 Round-Robin 轮询,复杂点的可以设置上权重进行分发,再复杂一点还可以做到 session 粘连。

4. 弹力设计
网关还可以把弹力设计中的那些异步、重试、幂等、流控、熔断、监视等都可以实现进去。这样,同样可以像 Service Mesh 那样,让应用服务只关心自己的业务逻辑(或是说数据面上的事)而不是控制逻辑(控制面)。

5. 安全方面
SSL 加密及证书管理、Session 验证、授权、数据校验,以及对请求源进行恶意攻击的防范。错误处理越靠前的位置就是越好,所以,网关可以做到一个全站的接入组件来对后端的服务进行保护。当然,网关还可以做更多更有趣的事情,比如:灰度发布、API聚合、API编排。

灰度发布:网关完全可以做到对相同服务不同版本的实例进行导流,还可以收集相关的数据。这样对于软件质量的提升,甚至产品试错都有非常积极的意义。

API 聚合:使用网关可以将多个单独请求聚合成一个请求。在微服务体系的架构中,因为服务变小了,所以一个明显的问题是,客户端可能需要多次请求才能得到所有的数据。这样一来,客户端与后端之间的频繁通信会对应用程序的性能和规模产生非常不利的影响。于是,我们可以让网关来帮客户端请求多个后端的服务(有些场景下完全可以并发请求),然后把后端服务的响应结果拼装起来,回传给客户端(当然,这个过程也可以做成异步的,但这需要客户端的配合)。

API 编排:同样在微服务的架构下,要走完一个完整的业务流程,我们需要调用一系列 API,就像一种工作流一样,这个事完全可以通过网页来编排这个业务流程。我们可能通过一个 DSL 来定义和编排不同的 API,也可以通过像 AWS Lambda 服务那样的方式来串联不同的 API。


网关的主要功能

微服务网关作为微服务后端服务的统一入口,它可以统筹管理后端服务,主要分为数据平面和控制平面:
数据平面主要功能是接入用户的HTTP请求和微服务被拆分后的聚合。使用微服务网关统一对外暴露后端服务的API和契约,路由和过滤功能正是网关的核心能力模块。另外,微服务网关可以实现拦截机制和专注跨横切面的功能,包括协议转换、安全认证、熔断限流、灰度发布、日志管理、流量监控等。控制平面主要功能是对后端服务做统一的管控和配置管理。例如,可以控制网关的弹性伸缩;可以统一下发配置;可以对网关服务添加标签;可以在微服务网关上通过配置Swagger功能统一将后端服务的API契约暴露给使用方,完成文档服务,提高工作效率和降低沟通成本。

路由功能:路由是微服务网关的核心能力。通过路由功能微服务网关可以将请求转发到目标微服务。在微服务架构中,网关可以结合注册中心的动态服务发现,实现对后端服务的发现,调用方只需要知道网关对外暴露的服务API就可以透明地访问后端微服务。

负载均衡:API网关结合负载均衡技术,利用Eureka或者Consul等服务发现工具,通过轮询、指定权重、IP地址哈希等机制实现下游服务的负载均衡。

统一鉴权:一般而言,无论对内网还是外网的接口都需要做用户身份认证,而用户认证在一些规模较大的系统中都会采用统一的单点登录(Single Sign On)系统,如果每个微服务都要对接单点登录系统,那么显然比较浪费资源且开发效率低。API网关是统一管理安全性的绝佳场所,可以将认证的部分抽取到网关层,微服务系统无须关注认证的逻辑,只关注自身业务即可。

协议转换:API网关的一大作用在于构建异构系统,API网关作为单一入口,通过协议转换整合后台基于REST、AMQP、Dubbo等不同风格和实现技术的微服务,面向Web Mobile、开放平台等特定客户端提供统一服务。

指标监控:网关可以统计后端服务的请求次数,并且可以实时地更新当前的流量健康状态,可以对URL粒度的服务进行延迟统计,也可以使用Hystrix Dashboard查看后端服务的流量状态及是否有熔断发生。

限流熔断:在某些场景下需要控制客户端的访问次数和访问频率,一些高并发系统有时还会有限流的需求。在网关上可以配置一个阈值,当请求数超过阈值时就直接返回错误而不继续访问后台服务。当出现流量洪峰或者后端服务出现延迟或故障时,网关能够主动进行熔断,保护后端服务,并保持前端用户体验良好。

黑白名单:微服务网关可以使用系统黑名单,过滤HTTP请求特征,拦截异常客户端的请求,例如DDoS攻击等侵蚀带宽或资源迫使服务中断等行为,可以在网关层面进行拦截过滤。比较常见的拦截策略是根据IP地址增加黑名单。在存在鉴权管理的路由服务中可以通过设置白名单跳过鉴权管理而直接访问后端服务资源。

灰度发布:微服务网关可以根据HTTP请求中的特殊标记和后端服务列表元数据标识进行流量控制,实现在用户无感知的情况下完成灰度发布。

流量染色:和灰度发布的原理相似,网关可以根据HTTP请求的Host、Head、Agent等标识对请求进行染色,有了网关的流量染色功能,我们可以对服务后续的调用链路进行跟踪,对服务延迟及服务运行状况进行进一步的链路分析。

文档中心:网关结合Swagger,可以将后端的微服务暴露给网关,网关作为统一的入口给接口的使用方提供查看后端服务的API规范,不需要知道每一个后端微服务的Swagger地址,这样网关起到了对后端API聚合的效果。

日志审计:微服务网关可以作为统一的日志记录和收集器,对服务URL粒度的日志请求信息和响应信息进行拦截。


网关设计重点

网关设计重点主要是三个,高性能、高可用、高扩展:

1. 高性能
在技术设计上,网关不应该也不能成为性能的瓶颈。对于高性能,最好使用高性能的编程语言来实现,如 C、C++、Go 和 Java。网关对后端的请求,以及对前端的请求的服务一定要使用异步非阻塞的 I/O 来确保后端延迟不会导致应用程序中出现性能问题。C 和 C++ 可以参看 Linux 下的 epoll 和 Windows 的 I/O Completion Port 的异步 IO 模型,Java 下如 Netty、Spring Reactor 的 NIO 框架。

2. 高可用
因为所有的流量或调用经过网关,所以网关必须成为一个高可用的技术组件,它的稳定直接关系到了所有服务的稳定。网关如果没有设计,就会成变一个单点故障。因此一个好的网关至少要做到以下几点:

1).集群化。网关要成为一个集群,其最好可以自己组成一个集群,并可以自己同步集群数据,而不需要依赖于一个第三方系统来同步数据。

2).服务化。网关还需要做到在不间断的情况下修改配置,一种是像 Nginx reload 配置那样,可以做到不停服务,另一种是最好做到服务化。也就是说,得要有自己的 Admin API 来在运行时修改自己的配置。

3).持续化。比如重启,就是像 Nginx 那样优雅地重启。有一个主管请求分发的主进程。当我们需要重启时,新的请求被分配到新的进程中,而老的进程处理完正在处理的请求后就退出。

后文对此还有较为细致的描述。

3. 高扩展
因为网关需要承接所有的业务流量和请求,所以一定会有或多或少的业务逻辑。而我们都知道,业务逻辑是多变和不确定的。比如,需要在网关上加入一些和业务相关的东西。因此,一个好的 Gateway 还需要是可以扩展的,并能进行二次开发的。当然,像 Nginx 那样通过 Module 进行二次开发的固然可以。

另外,在运维方面,网关应该有以下几个设计原则。

业务松耦合,协议紧耦合。在业务设计上,网关不应与后面的服务之间形成服务耦合,也不应该有业务逻辑。网关应该是在网络应用层上的组件,不应该处理通讯协议体,只应该解析和处理通讯协议头。另外,除了服务发现外,网关不应该有第三方服务的依赖。

应用监视,提供分析数据。网关上需要考虑应用性能的监控,除了有相应后端服务的高可用的统计之外,还需要使用 Tracing ID 实施分布式链路跟踪,并统计好一定时间内每个 API 的吞吐量、响应时间和返回码,以便启动弹力设计中的相应策略。

用弹力设计保护后端服务。网关上一定要实现熔断、限流、重试和超时等弹力设计。如果一个或多个服务调用花费的时间过长,那么可接受超时并返回一部分数据,或是返回一个网关里的缓存的上一次成功请求的数据。你可以考虑一下这样的设计。

DevOps。因为网关这个组件太关键了,所以需要 DevOps 这样的东西,将其发生故障的概率降到最低。这个软件需要经过精良的测试,包括功能和性能的测试,还有浸泡测试。还需要有一系列自动化运维的管控工具。

网关设计注意事项

不要在网关中的代码里内置聚合后端服务的功能,而应考虑将聚合服务放在网关核心代码之外。可以使用 Plugin 的方式,也可以放在网关后面形成一个 Serverless 服务。

网关应该靠近后端服务,并和后端服务使用同一个内网,这样可以保证网关和后端服务调用的低延迟,并可以减少很多网络上的问题。这里多说一句,网关处理的静态内容应该靠近用户(应该放到 CDN 上),而网关和此时的动态服务应该靠近后端服务。

网关也需要做容量扩展,所以需要成为一个集群来分担前端带来的流量。这一点,要么通过 DNS 轮询的方式实现,要么通过 CDN 来做流量调度,或者通过更为底层的性能更高的负载均衡设备。

对于服务发现,可以做一个时间不长的缓存,这样不需要每次请求都去查一下相关的服务所在的地方。当然,如果你的系统不复杂,可以考虑把服务发现的功能直接集成进网关中。

为网关考虑 bulkhead 设计方式。用不同的网关服务不同的后端服务,或是用不同的网关服务前端不同的客户。另外因网关是为用户请求和后端服务的桥接装置,所以需要考虑一些安全方面的事宜。具体如下:

1).加密数据。可以把 SSL 相关的证书放到网关上,由网关做统一的 SSL 传输管理。

2).校验用户的请求。一些基本的用户验证可以放在网关上来做,比如用户是否已登录,用户请求中的 token 是否合法等。但是,我们需要权衡一下,网关是否需要校验用户的输入。因为这样一来,网关就需要从只关心协议头,到需要关心协议体。而协议体中的东西一方面不像协议头是标准的,另一方面解析协议体还要耗费大量的运行时间,从而降低网关的性能。对此,我想说的是,看具体需求,一方面如果协议体是标准的,那么可以干;另一方面,对于解析协议所带来的性能问题,需要做相应的隔离。

3).检测异常访问。网关需要检测一些异常访问,比如在一段比较短的时间内请求次数超过一定数值;还比如,同一客户端的 4xx 请求出错率太高……对于这样的一些请求访问,网关一方面要把这样的请求屏蔽掉,另一方面需要发出警告,有可能会是一些比较重大的安全问题,如被黑客攻击。

流量网关

流量网关,顾名思义就是控制流量进入集群的网关,有很多工作需要在这一步做,对于一个服务集群,势必有很多非法的请求或者无效的请求,这时候要将请求拒之门外,降低集群的流量压力。


定义全局性的、跟具体的后端业务应用和服务完全无关的策略网关就是上图所示的架构模型——流量网关。流量网关通常只专注于全局的Api管理策略,比如全局流量监控、日志记录、全局限流、黑白名单控制、接入请求到业务系统的负载均衡等,有点类似防火墙。Kong 就是典型的流量网关,下面是kong的架构图,来自其官网。


这里需要补充一点的是,业务网关一般部署在流量网关之后、业务系统之前,比流量网关更靠近业务系统。通常API网指的是业务网关。有时候我们也会模糊流量网关和业务网关,让一个网关承担所有的工作,所以这两者之间并没有严格的界线。

业务网关

当一个单体应用被拆分成许许多多的微服务应用后,也带来了一些问题。一些与业务非强相关的功能,比如权限控制、日志输出、数据加密、熔断限流等,每个微服务应用都需要,因此存在着大量重复的代码实现。而且由于系统的迭代、人员的更替,各个微服务中这些功能的实现细节出现了较大的差异,导致维护成本变高。另一方面,原先单体应用下非常容易做的接口管理,在服务拆分后没有了一个集中管理的地方,无法统计已存在哪些接口、接口定义是什么、运行状态如何。

网关就是为了解决上述问题。作为微服务体系中的核心基础设施,一般需要具备接口管理、协议适配、熔断限流、安全防护等功能,各种开源的网关产品(比如 zuul)都提供了优秀高可扩展性的架构、可以很方便的实现我们需要的一些功能、比如鉴权、日志监控、熔断限流等。与流量网关相对应的就是业务网关,业务网关更靠近我们的业务,也就是与服务器应用层打交道,那么有很多应用层需要考虑的事情就可以依托业务网关,例如在线程模型、协议适配、熔断限流,服务编排等。下面看看业务网关体系结构:


从这个途中可以看出业务网关主要职责以及所做的事情,目前业务网关比较成熟的 API 网关框架产品有三个 分别是:Zuul1、Zuul2 和 SpringCloud Gateway,后面再进行对比。

常见网关对比

既然对比,就先宏观上对各种网关有一个了解,后面再挑一些常用的或者说应用广泛的详细了解。目前常见的开源网关大致上按照语言分类有如下几类:
Nginx+lua:OpenResty、Kong、Orange、Abtesting gateway 等
Java:Zuul/Zuul2、Spring Cloud Gateway、Kaazing KWG、gravitee、Dromara soul 等
Go:Janus、fagongzi、Grpc-gateway
Dotnet:Ocelot
NodeJS:Express Gateway、Micro Gateway

按照使用数量、成熟度等来划分,主流的有 5个:
OpenResty
Kong
Zuul、Zuul2
Spring Cloud Gateway

1. OpenResty

OpenResty是一个流量网关,根据前面对流量网关的介绍就可以知道流量网关的职责。其基于 Nginx与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用服务和动态网关。通过融和众多设计良好的 Nginx 模块,OpenResty 有效地把 Nginx 服务器转变为一个强大的 Web 应用服务器,基于它开发人员可以使用 Lua 编程语言对 Nginx 核心以及现有的各种 Nginx C 模块进行脚本编程,构建出可以处理一万以上并发请求的极端高性能的 Web 应用。

OpenResty 最早是顺应 OpenAPI 的潮流做的,所以 Open 取自“开放”之意,而Resty便是 REST 风格的意思。虽然后来也可以基于 ngx_openresty 实现任何形式的 web service 或者传统的 web 应用。也就是说 Nginx 不再是一个简单的静态网页服务器,也不再是一个简单的反向代理了。第二代的 openresty 致力于通过一系列 nginx 模块,把nginx扩展为全功能的 web 应用服务器。

ngx_openresty 是用户驱动的项目,后来也有不少国内用户的参与,从 openresty.org 的点击量分布上看,国内和国外的点击量基本持平。目前有两大应用目标:

通用目的的 web 应用服务器。在这个目标下,现有的 web 应用技术都可以算是和 OpenResty 或多或少有些类似,比如 Nodejs,PHP 等等。ngx_openresty 的性能(包括内存使用和 CPU 效率)算是最大的卖点之一。

Nginx 的脚本扩展编程,用于构建灵活的 Web 应用网关和 Web 应用防火墙,类似 NetScaler。其优势在于 Lua 编程带来的巨大灵活性。Nginx 在启动后,会有一个 Master 进程和多个 Worker 进程,Master 进程和 Worker 进程之间是通过进程间通信进行交互的。Worker 工作进程的阻塞点是在像 select()、epoll_wait() 等这样的 I/O 多路复用函数调用处,以等待发生数据可读/写事件。Nginx 采用了异步非阻塞的方式来处理请求。

2. Kong

Kong基于OpenResty开发,也是流量层网关,是一个云原生、快速、可扩展、分布式的Api 网关。继承了OpenResty的高性能、易扩展性等特点。Kong通过简单的增加机器节点,可以很容易的水平扩展。同时功能插件化,可通过插件来扩展其能力。而且在任何基础架构上都可以运行。具有以下特性:
提供了多样化的认证层来保护Api。
可对出入流量进行管制。
提供了可视化的流量检查、监视分析Api。
能够及时的转换请求和相应。

主要有三个组件:
Kong Server:基于Nginx的服务器,用来接收API请求;
Apache Cassandra/PostgreSQL:用来存储操作数据;
Kong dashboard:官方推荐UI管理工具,也可以使用 restfull 方式管理admin api。

Kong采用插件机制进行功能定制,插件集(可以是0或N个)在API请求响应循环的生命周期中被执行。插件使用Lua编写,目前已有几个基础功能:HTTP基本认证、密钥认证、CORS(Cross-Origin Resource Sharing,跨域资源共享)、TCP、UDP、文件日志、API请求限流、请求转发以及Nginx监控。


具有以下的特性:
可扩展性: 通过简单地添加更多的服务器,可以轻松地进行横向扩展,这意味着您的平台可以在一个较低负载的情况下处理任何请求;
模块化: 可以通过添加新的插件进行扩展,这些插件可以通过RESTful Admin API轻松配置;
在任何基础架构上运行: Kong网关可以在任何地方都能运行。可以在云或内部网络环境中部署Kong,包括单个或多个数据中心设置,以及public,private 或invite-only APIs。

提供log解决方案
可通过api调用Serverless函数。

Kong解决了什么问题
当我们决定对应用进行微服务改造时,应用客户端如何与微服务交互的问题也随之而来,毕竟服务数量的增加会直接导致部署授权、负载均衡、通信管理、分析和改变的难度增加。面对以上问题,API GATEWAY是一个不错的解决方案,其所提供的访问限制、安全、流量控制、分析监控、日志、请求转发、合成和协议转换功能,可以解放开发者去把精力集中在具体逻辑的代码,而不是把时间花费在考虑如何解决应用和其他微服务链接的问题上。



可以看到Kong解决的问题。专注于全局的Api管理策略,全局流量监控、日志记录、全局限流、黑白名单控制、接入请求到业务系统的负载均衡等。

Kong的优点以及性能
在众多 API GATEWAY 框架中,Mashape 开源的高性能高可用API网关和API服务管理层——KONG(基于 NGINX+Lua)特点尤为突出,它可以通过插件扩展已有功能,这些插件(使用 lua 编写)在API请求响应循环的生命周期中被执行。与此同时,KONG本身提供包括 HTTP 基本认证、密钥认证、CORS、TCP、UDP、文件日志、API请求限流、请求转发及 NGINX 监控等基本功能。目前 Kong 在 Mashape 管理了超过 15,000 个 API,为 200,000 开发者提供了每月数十亿的请求支持。

Kong架构
Kong提供一些列的服务,这就不得不谈谈内部的架构:


首先最底层是基于Nginx,这是高性能的基础层,一个良好的负载均衡、反向代理器,然后在此基础上增加Lua脚本库,形成了OpenResty,拦截请求,响应生命周期,可以通过Lua编写脚本,所以插件比较丰富。

关于Kong的一些插件库以及如何配置,可以参考简书:开源API网关系统(Kong教程)入门到精通

3. Zuul1.0

Zuul是所有从设备和web站点到Netflix流媒体应用程序后端请求的前门。作为一个边缘服务应用程序,Zuul被构建来支持动态路由、监视、弹性和安全性。它还可以根据需要将请求路由到多个Amazon自动伸缩组。它使用了一系列不同类型的过滤器,使我们能够快速灵活地将功能应用到服务中。

过滤器

过滤器是Zuul的核心功能。它们负责应用程序的业务逻辑,可以执行各种任务:
Type:通常定义过滤器应用在哪个阶段;
Async:定义过滤器是同步还是异步;
Execution Order:执行顺序;
Criteria:过滤器执行的条件;
Action:如果条件满足,过滤器执行的动作。

Zuul提供了一个动态读取、编译和运行这些过滤器的框架。过滤器之间不直接通信,而是通过每个请求特有的RequestContext共享状态。

下面是Zuul的一些过滤器:
Incoming:在请求被代理到Origin之前执行。这通常是执行大部分业务逻辑的地方。例如:认证、动态路由、速率限制、DDoS保护、指标;
Endpoint:负责基于incoming过滤器的执行来处理请求。Zuul有一个内置的过滤器(ProxyEndpoint),用于将请求代理到后端服务器,因此这些过滤器的典型用途是用于静态端点。例如:健康检查响应,静态错误响应,404响应;
Outgoing:在从后端接收到响应以后执行处理操作。通常情况下,它们更多地用于形成响应和添加指标,而不是用于任何繁重的工作。例如:存储统计信息、添加/剥离标准标题、向实时流发送事件、gziping响应。

过滤器类型

下面是与一个请求典型的生命周期对应的标准的过滤器类型:
PRE:路由到Origin之前执行;
ROUTING:路由到Origin期间执行;
POST:请求被路由到Origin之后执行;
ERROR:发生错误的时候执行。

这些过滤器帮助我们执行以下功能:
身份验证和安全性:识别每个资源的身份验证需求,并拒绝不满足它们的请求;
监控:在边缘跟踪有意义的数据和统计数据,以便给我们一个准确的生产视图;
动态路由:动态路由请求到不同的后端集群;
压力测试:逐渐增加集群的流量,以评估性能;
限流:为每种请求类型分配容量,并丢弃超过限制的请求;
静态响应处理:直接在边缘构建一些响应,而不是将它们转发到内部集群。

Zuul 1.0 请求生命周期


Netflix宣布了通用API网关Zuul的架构转型。Zuul原本采用同步阻塞架构,转型后叫作Zuul2,采用异步非阻塞架构。Zuul2和Zuul1在架构方面的主要区别在于,Zuul2运行在异步非阻塞的框架上,比如Netty。Zuul1依赖多线程来支持吞吐量的增长,而Zuul 2使用的Netty框架依赖事件循环和回调函数。

4. Zuul2.0

Zuul 2.0 架构图


上图是Zuul2的架构,和Zuul1没有本质区别,两点变化:

前端用Netty Server代替Servlet,目的是支持前端异步。后端用Netty Client代替Http Client,目的是支持后端异步。

过滤器换了一下名字,用Inbound Filters代替Pre-routing Filters,用Endpoint Filter代替Routing Filter,用Outbound Filters代替Post-routing Filters。

Inbound Filters:路由到 Origin 之前执行,可以用于身份验证、路由和装饰请求

Endpoint Filters:可用于返回静态响应,否则内置的ProxyEndpoint过滤器将请求路由到Origin

Outbound Filters:从Origin那里获取响应后执行,可以用于度量、装饰用户的响应或添加自定义header

有两种类型的过滤器:sync 和 async。因为Zuul是运行在一个事件循环之上的,因此从来不要在过滤中阻塞。如果你非要阻塞,可以在一个异步过滤器中这样做,并且在一个单独的线程池上运行,否则可以使用同步过滤器。

上文提到过Zuul2开始采用了异步模型。

优势是异步非阻塞模式启动的线程很少,基本上一个CPU core上只需启一个事件环处理线程,它使用的线程资源就很少,上下文切换(Context Switch)开销也少。非阻塞模式可以接受的连接数大大增加,可以简单理解为请求来了只需要进队列,这个队列的容量可以设得很大,只要不超时,队列中的请求都会被依次处理。

不足之处理是异步模式让编程模型变得复杂。一方面Zuul2本身的代码要比Zuul1复杂很多,Zuul1的代码比较容易看懂,Zuul2的代码看起来就比较费劲。另一方面异步模型没有一个明确清晰的请求->处理->响应执行流程(call flow),它的流程是通过事件触发的,请求处理的流程随时可能被切换断开,内部实现要通过一些关联id机制才能把整个执行流再串联起来,这就给开发调试运维引入了很多复杂性,比如你在IDE里头调试异步请求流就非常困难。另外ThreadLocal机制在这种异步模式下就不能简单工作,因为只有一个事件环线程,不是每个请求一个线程,也就没有线程局部的概念,所以对于CAT这种依赖于ThreadLocal才能工作的监控工具,调用链埋点就不好搞(实际可以工作但需要进行特殊处理)。

总体上,异步非阻塞模式比较适用于IO密集型(IO bound)场景,这种场景下系统大部分时间在处理IO,CPU计算比较轻,少量事件环线程就能处理。运行在异步和无阻塞框架上,每个 CPU 核一个线程,处理所有的请求和响应,请求和响应的生命周期是通过事件和回调来处理的,这种方式减少了线程数量,因此开销较小。

Zuul 与 Zuul 2 性能对比

Netflix给出了一个比较模糊的数据,大致Zuul2的性能比Zuul1好20%左右,这里的性能主要指每节点每秒处理的请求数。为什么说模糊呢?因为这个数据受实际测试环境,流量场景模式等众多因素影响,你很难复现这个测试数据。即便这个20%的性能提升是确实的,其实这个性能提升也并不大,和异步引入的复杂性相比,这20%的提升是否值得是个问题。Netflix本身在其博文22和ppt11中也是有点含糊其词,甚至自身都有一些疑问的。

5. Spring Cloud Gateway

SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。其目标不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。底层使用了高性能的通信框架Netty。官方对其特征介绍如下:
1).基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
2).集成 Hystrix 断路器
3).集成 Spring Cloud DiscoveryClient
4).Predicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters
5).具备一些网关的高级功能:动态路由、限流、路径重写

从以上的特征来说,和Zuul的特征差别不大。SpringCloud Gateway和Zuul主要的区别,还是在底层的通信框架上。简单说明一下上文中的三个术语:
1).Filter(过滤器):和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。

2).Route(路由):网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。

3).Predicate(断言):这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。

6. Traefik

Træfɪk 是一个为了让部署微服务更加便捷而诞生的现代HTTP反向代理、负载均衡工具,采用Go语言开发,它支持多种后台 (Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB, Rest API, file…) 来自动化、动态的应用它的配置文件设置。


特性:
非常快,无需安装其他依赖,通过Go语言编写的单一可执行文件;
多种后台支持:Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd;
支持支持Rest API、Websocket、HTTP/2、Docker镜像;
监听后台变化进而自动化应用新的配置文件设置;
配置文件热更新,无需重启进程;
后端断路器、负载均衡、容错机制;
清爽的前端页面,可监控服务指标。


几种网关的对比



浅谈服务接口的高可用设计

作为一个后端研发人员,开发服务接口不管是面向前端 HTTP 或者是供其他服务 RPC 远程调用的,都绕不开一个共同的话题就是“高可用”,接口开发往往看似简单,但保证高可用这块实现起来却不并没有想想的那么容易,用一句简单的话来概就是我们的系统具不具备应对和规避风险的能力。

1. 程序都是有人开发的,在开发过程中会犯错从而导致线上事故的发生;
2. 系统运行依赖各种运行环境:CPU、内存、硬盘、网络等等,而这些都有可能损坏;
3. 业务拉新用户正在注册账号,结果注册接口挂了用户体验受影响;
4. 大促时节大量用户下单,结果下单服务接口挂了GMV受影响等;
5. 其他未知因素等等。

总之为了应对这些不可控因素的发生,就必须要做高可用。

高可用的关键点

高可用的本质是系统是否具备应对和规避风险的能力,那么从这个角度出发来设计高可用接口的有以下几个关键因素:Dependence(依赖)、Probability(概率)、Time(时长)、Scope(范围)。

1. 依赖的资源相对少
2. 风险的概率足够低
3. 影响的范围足够小
4. 影响时长足够短

接口高可用设计的几个原则

结合这些关键点,我们来看一下具体具体注意事项。

1、控制依赖
能少依赖就少依赖,能不强依赖就不强依赖。少依赖,例如日常每分钟10个请求,查询Mysql数据即可满足,此时盲目引入Redis中间件,不仅浪费资源而且增加系统复杂性。弱依赖,例如用户注册服务强依赖新用户优惠券发放服务,当优惠券发放服务故障后,整个注册不可用,好的方式是采用弱依赖,使用异步化的方式,这样优惠券发送服务不可用时,不会影响注册链路。

2、避免单点
避免单点故障的核心是通过备份或者冗余快速的进行容错:
1. 我们采用多机房多实力部署我们应用来保障故障风险分摊,一旦有一台服务器出现问题,其他服务仍然能够继续支撑我们的服务
2. 每次上线我们都会保留上一次上线发布版本,这样一旦上线的程序出现问题我们能够快速回滚到上一版本
3. 每个接口至少保障2人知道相关业务,一旦线上服务出现问题,其中任何一人一个能够快速处理相关线上问题
4. 不管是Mysql还是Redis等中间件都支持数据主备机群部署

类似的例子很多这里就不再一一列举了。

3、负载均衡
将风险进行分摊避免分险扩散。例如:无论是Ngnix或者JSF的,其负载均衡目的就是尽量的将流量分散到不同的服务器节点上,这样可以有效的保障单节点因系统瓶颈问题而引发一系列的风险。 像上面这个例子我想每个研发人员都知道也都会这么做,但是是不是所有的场景我们都考虑到均衡这个问题?

例如:通常为了提高读并发的能力,我们会把数据缓存到JIMDB中,但是因为缓存的key出现了热点数据导致JIMDB单分片负载过高,恰好这个分片上也缓存了其他数据,但是因为CPU负载过高,导致查询性能变差,大量的超时,影响了业务。所以在接口设计的时候,假如遇到类似场景,也要充分考虑数据存储的均衡性,同时针对热点数据做好监控,随时支持动态均衡。

4、资源隔离
隔离的目的将风险控制在可控范围内,避免风险扩散。例如接口部署之间服务部署物理上是相互隔离的,避免单机房或者单服务器出现故障影响整个服务;或在存储业务数据的时候会将数据分库分表,数据通过不同分片存储,这样就不会导致某个服务器挂掉影响到整个服务。

5、接口限流
限流是一种保护措施,目的是将风险控制在可控范围内。在开发接口的时候,一定要结合业务流量情况进行限流措施,限流一方面处于对自身服务资源的保护,同时也是对依赖资源的一种保护措施。

6、服务熔断
熔断也是一种保护措施,目的是将风险控制在可控范围内,避免风险扩散。例如经常服务A会同时调用B、C、D多个服务,当我们依赖的服务其中一个出现故障或者性能下降的时候,就是导致整体服务可用率下降,所以我们在开发此类服务的时候,一定要注意接口之间的隔离。我们可以利用类似Hystrix组件实现,也可以借助DUCC进行手动隔离。其实熔断也是一种控制资源依赖的一种,将强依赖降级为弱依赖。

7、异步处理
将同步操作转为异步操作。例如用户页面领取一些权益,针对领取这个服务在大促期间因为用户流量较大,为了避免系统负载,此时采用MQ异步接收用户领取请求然后进行优惠券发放,这样不仅极大的减少了事故的影响范围,也减少问题发生概率。

8、降级方案
服务降级属于一种问题发生后的补救措施,通过服务降级可以减少一部分风险影响范围。对于重要的服务接口我们都要具备完善的降级方案,这里需要说明的是,降级有损的,我们一定要在系统开发前就要考虑各种问题发生的可能,降级的前提是通过降级非核心业务保证核心业务运行。例如大促峰值期间,一般会提前降级掉很多功能,同时限流,主要是为了保护峰值绝大部分人的交易支付体验。

9、灰度发布
通过灰度发布降低风险影响范围。例如上线一个新服务,通过一定的灰度策略,让用户先行体验新版本的应用,通过收集这部分用户对新版本应用的反馈以及对新版本功能、性能、稳定性等指标进行评论,进而决定继续放大新版本投放范围直至全量升级或回滚至老版本。根据线上反馈结果,做到查漏补缺,发现重大问题,可回滚“旧版本”。

10、混沌工程
通过提前对系统进行一些破坏性的手段,提前发现潜在问题。例如一个复杂接口系统依赖了太多的服务和组件,这些组件随时随地都可能会发生故障,而一旦它们发生故障,会不会如蝴蝶效应一般造成整体服务不可用呢,因此我们可以借助泰山平台混沌工程进行演练,针对发生的场景制定各种预案,将风险控制在可控范围内。

综合网上的结论,供参考:
1.性能:Nginx+Lua形式必然是高于Java语言实现的网关的,Java技术栈里面Zuul1.0是基于Servlet实现的,剩下都是基于webflux实现,性能是高于基于Servlet实现的。

2.可维护性和扩展性:Nginx+Lua这个组合掌握的人不算多,如果团队有大神,大佬们就随意了,当没看到这段话,对于一般团队来说的话,选择自己团队擅长的语言更重要。

3.高可用:对于网关高可用基本都是统一的策略都是采用多机器部署的方式,前面挂一个负载,监控一定要做到位就基本可行了。


API 管理发展趋势

本节探讨了 API 管理在数字化转型中的重要性,以及 API 管理面临的挑战和发展机遇。重点介绍了十大 API 管理发展趋势,包括 API 安全性、API 标准化、云端 API 管理解决方案、低代码 API 平台、API 市场、新兴 API 协议、人工智能与 API、开发者体验、API 分析和无服务器架构等。通过关注这些趋势,企业能更好地应对未来挑战,抓住发展机遇,实现业务的持续增长和创新。原作者郑玩星发表于2023年4月中旬。

什么是 API 与 API 管理

近期,AIGC(AI Generated Content,生成式人工智能)在各行业的应用日趋普及。AIGC 服务提供商通过 API 向外部提供其内容生成能力,使得用户能够便捷地获取 AIGC 应用相关内容。显然 API 成为 AIGC 应用的重要支柱,那么究竟什么是 API 呢?

API(Application Programming Interface,应用程序接口)是一系列预先定义的规则和约定,目的是用于不同软件应用之间的通信。它使得一个软件应用程序(即客户端)能够请求另一个软件应用程序(即服务器)的功能与数据,从而促成不同系统间的互动和数据共享。借助 API,开发者能够利用其他应用程序的功能,从而更快速地构建和发布新应用。

API 管理包括创建和发布 API、制定使用策略、控制访问权限、培养用户社区、搜集与分析使用统计数据以及报告性能等过程,通常包含 API 网关、开发者门户等组件。其中,API 网关作为关键组件,负责处理和转发请求,同时执行安全和性能策略,而开发者门户则是一个在线平台,为开发者提供 API 文档、密钥管理和其他相关资源。

随着企业日益依赖 API 推进数字化转型,API 管理的重要性空前提升。在简要介绍了相关概念之后,接下来将探讨 API 管理的十大发展趋势。

一、API 安全性越来越重要

API 安全性是指保护应用程序和系统之间通过 API 交换数据和功能的过程。主要目标是确保数据和功能的正确性、可靠性和私密性,防止未经授权的访问和潜在的恶意攻击。对于现代应用和企业服务至关重要,因为它们大量依赖于 API 进行数据交换和集成。以下是 API 安全性重要的几点原因:
1.数据保护:API 通常用于传输敏感数据,如用户信息、交易细节和支付信息。确保 API 安全性可以防止数据泄露、篡改和丢失,保障用户和企业的信息安全。

2.系统完整性:通过确保 API 只能被合法用户和合规应用访问,可以维护系统完整性。这有助于防止恶意攻击者通过 API 破坏或控制系统。

3.信任和声誉:一个安全的 API 可以提高用户对企业服务的信任度,有助于建立良好的品牌声誉。相反,如果 API 安全性不足,可能导致企业声誉受损,用户流失。

为了保障 API 的安全性,通常我们可以利用 API 网关来管理安全功能,例如身份验证和访问控制,以保护 API 免受未经授权的访问和攻击。市场上已有许多 API 网关提供这些功能,其中之一便是 Apache APISIX。其为 Apache 软件基金会下的云原生 API 网关,兼具动态、实时、高性能等特点。它提供了一系列安全功能,以确保 API 的安全性。例如 Apache APISIX 支持通过 key-auth、jwt-auth 等插件进行身份验证,以及通过 consumer-restriction 等插件进行访问控制。这些功能帮助企业防止数据泄露,保护用户隐私及企业利益。

二、API 标准化日益重要

随着 API 的广泛应用,其标准化变得越来越重要,以下是 API 标准化的几大好处:
1.促进组织内部的协作和沟通:让不同团队和部门遵循统一的设计原则和规范,提高开发效率和质量。
2.增强 API 的安全性和稳定性:通过定义清晰的接口、数据结构和协议,防止错误或滥用的情况发生。
3.提升 API 的可扩展性和互操作性:通过遵循行业或社区认可的设计指南或最佳实践,使 API 能够适应不同的场景和需求。

在 API 标准化过程中,常见的 API 标准规范有 OpenAPI Specfication。许多工具和平台都支持这种规范,以方便用户导入和管理 API。例如,Apache APISIX Dashboard 就可以通过 OpenAPI 文档进行导入相关路由数据。利用这些标准规范,团队可以轻松地在不同平台和工具之间共享和管理 API,进一步提高协作效率和 API 的可维护性。

三、云端 API 管理解决方案的普及

传统的 API 管理解决方案通常侧重于在本地部署和管理 API,这意味着企业需要购买、部署和维护硬件和软件资源,以支持 API 的开发、发布和监控。然而随着业务的发展和云计算技术的普及,传统的 API 管理解决方案在可伸缩性、成本效益和跨平台集成方面面临一定的挑战。

与此同时,云端 API 管理解决方案应运而生。这类解决方案充分利用了云计算的弹性、按需付费和跨平台特性,为企业提供了一种更为灵活、高效和可靠的 API 管理方式;解决方案通常包括 API 网关、安全功能、监控和分析等组件,以支持企业在混合云和多云环境中实现 API 的统一管理。相比于传统的 API 管理解决方案,云端 API 管理解决方案拥有以下优势:
1.高可用性:得益于云端 API 管理解决方案提供弹性的负载均衡与自动扩展功能、以及自动化的故障切换和灾难恢复能力,使得云端 API 管理解决方案具备更高的可用性。
2.降低成本:云端 API 管理解决方案可以降低 API 的开发、部署和维护成本,让企业专注于业务创新而无需担心基础设施的管理。
3.跨平台支持:云端 API 管理解决方案支持混合云和多云环境,实现跨平台的 API 集成和管理,让企业可以轻松地在不同云服务提供商之间迁移和扩展其 API。

四、使用低代码 API 平台方便创建发布 API

低代码 API 平台是一种允许用户通过简单的图形界面和预构建的模块创建、发布和管理 API 的工具。这些平台旨在简化 API 开发过程,降低开发门槛,提高开发效率。一个具体的例子是,Apache APISIX Dashboard 创建路由的时候,无需手动编写代码,可以使用拖拽方式进行插件编排组合不同的插件。

五、API 市场的发展

随着 API 的普及,API 市场逐渐成为企业从各种供应商发现、评估和购买 API 的一种方式,可以帮助企业加速创新,降低开发成本:
1.对于 API 供应商,API 市场可以提高他们的 API 的可见性和吸引力,增加他们的收入和客户群,以及利用市场的分析功能来优化他们的 API 策略和设计。
2.对于 API 消费者,API 市场可以提供一个方便的一站式服务,让他们能够轻松地找到并使用各种高质量的 API 来满足他们的业务需求,并且节省了自己开发或维护这些 API 的时间和资源。
3.对于整个 API 生态系统,API 市场可以促进多方之间的协作和创新,激发新的用例和价值。

六、更多 API 协议崛起

随着下一代 API 协议,如 GraphQL 和 gRPC,与当前主导但逐渐衰落的 REST API 展开竞争,越来越多的 API 协议得到了广泛应用。

GraphQL 是由 Facebook 开发的一种数据查询和操作语言。它允许客户端根据其需求明确请求所需数据,并在一个请求中获取多个资源。这有助于减少数据传输量和提高性能。与 REST API 相比,GraphQL 的优势包括:
1.灵活的数据请求:客户端可以指定所需的数据,避免了过度或不足的数据传输。
2.更高效的请求处理:通过单个请求获取多个资源,有助于减少网络往返次数。
3.实时数据更新:GraphQL 支持实时数据更新,可以及时响应客户端的数据变更需求。

gRPC 是由 Google 开发的一种高性能、开源的远程过程调用(RPC)框架。它允许客户端像调用本地方法一样调用服务端的方法。gRPC 使用 Protocol Buffers(Protobuf)作为接口定义语言和数据序列化格式,以实现高效的数据传输。与 REST API 相比,gRPC 的优势包括:
1.使用 Protobuf 进行数据序列化:相较于 JSON 格式,具有更高的性能和更小的数据体积。
2.基于 HTTP/2 协议,支持双向流式传输,多路复用和内置的 TLS 安全性:比 REST API 基于 HTTP/1.1 协议的单向请求响应模式更快,更灵活,更安全。
3.基于 Protobuf 定义 API:提供了原生的代码生成功能,可以自动生成客户端和服务器端的代码,支持多种编程语言,比 REST API 需要使用第三方工具如 Swagger 生成代码更方便,更一致。

为了适应这些新兴协议的需求,Apache APISIX 提供了一系列相关插件,以支持不同协议的处理。以下插件可以处理这些新兴 API 协议:
1.grpc-transcode:grpc-transcode 用于在 HTTP 和 gRPC 请求之间进行转换;
2.grpc-web:grpc-web 是一个代理插件,可以处理从 JavaScript 客户端到 gRPC Service 的 gRPC Web 请求;
3.degraphql:degraphql 插件用于支持将 RESTful API 解码为 GraphQL。

七、人工智能与 API

API 管理平台正在利用机器学习和人工智能自动化任务,例如 API 发现、安全威胁检测和异常检测。这可以帮助企业减轻其 IT 团队的负担,并提高其 API 管理流程的效率和准确性。

安全威胁检测:机器学习和人工智能可以帮助 API 管理平台实时监控并分析 API 流量,以便于及时发现并阻止任何恶意或异常的请求。

异常检测:机器学习和人工智能可以帮助 API 管理平台预测并诊断任何可能影响 API 性能或可用性的问题,以便于及时修复并优化。

八、更加关注开发者体验

随着 API 变得越来越集中于业务运营,开发者体验变得越来越重要。API 管理平台正在增加更多的开发者友好功能,例如文档、测试工具和 SDK,以使开发者更容易使用 API:
1.文档:文档是开发者了解和学习 API 的主要途径,因此文档应该清晰、完整、准确、及时地描述 API 的功能、参数、示例和错误码等信息。文档还应该提供交互式的控制台或沙盒,让开发者能够快速地测试和调试 API。
2.测试工具:测试工具是开发者验证和优化 API 的重要手段,因此测试工具应该方便、可靠、灵活地支持各种测试场景和需求。测试工具还应该提供实时的反馈和报告,让开发者能够及时地发现并解决问题。
3.SDK:SDK 是开发者集成和使用 API 的便捷方式,因此 SDK 应该覆盖各种主流的编程语言和平台,并且保持与 API 的同步更新。SDK 还应该遵循最佳实践和规范,让开发者能够轻松地理解和调用。

九、API 分析的兴起

API 分析(API Analytics)是一种用于收集、分析和解释 API 使用情况数据的技术。随着 API 在软件和互联网行业的普及,API 分析应运而生,成为一种关键的管理和优化手段。以下是 API 分析兴起的几点原因:随着云计算、大数据、物联网等技术的发展,API 已成为企业和开发者之间交换数据和功能的重要工具。这导致了对 API 分析的需求不断增长,以便更好地了解和优化 API 的性能。现代软件开发越来越多地采用微服务架构,将复杂的应用程序分解为多个独立的、可扩展的服务。这些服务通过 API 相互通信,因此对 API 分析的需求在这种架构下更加明显,可以帮助检测潜在的安全漏洞和违反合规性的行为,从而降低风险。

十、更多的 API 通过无服务器(Serverless)架构提供服务

无服务器架构是一种云计算模式,它允许开发者在不管理服务器的情况下,部署和运行应用程序。要通过无服务器架构提供 API 服务,你只需要以下几个步骤:
1.选择一个无服务器平台,编写你的 API 逻辑代码,使用无服务器平台提供的编程语言和框架。
2.在平台上配置你的 API 触发器,如 HTTP 请求,定时器,事件等。
3.使用平台提供的相关工具部署你的 API 代码到无服务器平台,并测试其功能和性能。

使用无服务器架构拥有以下优势:
1.让 API 开发者专注于业务逻辑,而不用担心基础设施、部署、缩放等问题。
2.根据 API 请求量自动调整资源,避免资源浪费或不足。
3.提高 API 的响应速度和可靠性,因为它可以利用分布式的边缘计算节点来处理请求

Apache APISIX 在这方面也有所支持,包括 serverless,openfunction 等插件。

小结

API 管理作为数字化转型战略的重要组成部分,正面临着诸多挑战和发展机遇。通过关注 API 管理的这十大趋势,企业可以更好地应对未来的挑战,抓住发展机遇,并实现业务的持续增长和创新。


并发和并行

再开始讲并发之前回顾一些旧知识。

什么是并发?什么是并行...

并发(Concurrency)
早期计算机的 CPU 都是单核的,一个 CPU 在同一时间只能执行一个进程/线程,当系统中有多个进程/线程等待执行时,CPU 只能执行完一个再执行下一个。

计算机在运行过程中,有很多指令会涉及 I/O 操作,而 I/O 操作又是相当耗时的,速度远远低于 CPU,这导致 CPU 经常处于空闲状态,只能等待 I/O 操作完成后才能继续执行后面的指令。为了提高 CPU 利用率,减少等待时间,人们提出了一种 CPU 并发工作的理论。

所谓并发,就是通过一种算法将 CPU 资源合理地分配给多个任务,当一个任务执行 I/O 操作时,CPU 可以转而执行其它的任务,等到 I/O 操作完成以后,或者新的任务遇到 I/O 操作时,CPU 再回到原来的任务继续执行。

下图展示了两个任务并发执行的过程:


图片来源自C语言中文网-并发和并行的区别

虽然 CPU 在同一时刻只能执行一个任务,但是通过将 CPU 的使用权在恰当的时机分配给不同的任务,使得多个任务在视觉上看起来是一起执行的。CPU 的执行速度极快,多任务切换的时间也极短,用户根本感受不到,所以并发执行看起来才跟真的一样。

并行(Parallelism)
并发是针对单核 CPU 提出的,而并行则是针对多核 CPU 提出的。和单核 CPU 不同,多核 CPU 真正实现了“同时执行多个任务”。

多核 CPU 内部集成了多个计算核心(Core),每个核心相当于一个简单的 CPU,如果不计较细节,可以认为给计算机安装了多个独立的 CPU。

多核 CPU 的每个核心都可以独立地执行一个任务,而且多个核心之间不会相互干扰。在不同核心上执行的多个任务,是真正地同时运行,这种状态就叫做并行。

例如,同样是执行两个任务,双核 CPU 的工作状态如下图所示:


图片来源自C语言中文网-并发和并行的区别

双核 CPU 执行两个任务时,每个核心各自执行一个任务,和单核 CPU 在两个任务之间不断切换相比,它的执行效率更高。

这里讲了关于并发、并行概念与我们的接口最大并发处理数之间有什么关系?放心这都是铺垫,为了更好消化后面的内容。

这里结下并发与并行的概念:

并发:在同一时间段内,多个任务都在执行,但不一定是同时执行。这意味着这些任务在轮流使用 CPU 的时间片,给人一种它们在“同时”运行的错觉。并发的真正含义在于,尽管多个程序或进程可能正在运行,但每次只有一个进程或线程实际上正在使用 CPU。

并行:指在同一时刻,多个任务都在执行。这通常需要多个处理器或多核的 CPU 来实现,因为只有这样,每个处理器或核心才能在同一时刻执行一个任务。例如,当音乐软件和 IDEA 同时运行时,如果计算机有两个以上的 CPU 核心,那么这两个应用程序就可以真正地并行运行。

注意:并发强调的是在同一段时间内,它是一个时间范围的概念。并行强调的在同一时刻。

线程个数 * 单线程最大并发数,就代表接口的最大并行处理数。

那么根据公式可得出一个结论,通过增大线程数或者减少接口响应时间可以增大接口的最大并发处理数。


重试机制

1.循环重试
这是最简单也最直接的一种方式。在请求接口的代码块中加入循环,如果请求失败则继续请求,直到请求成功或达到最大重试次数。

2.使用递归结构
除了循环,还可以使用递归来实现接口的请求重试。递归是我们都比较熟悉的编程技巧,在请求接口的方法中调用自身,如果请求失败则继续调用,直到请求成功或达到最大重试次数。

3.使用网络工具的内置重试机制
我们常用的一些HTTP客户端通常内置了一些重试机制,只需要在创建对应的客户端实例的时候进行配置即可。

4.使用Spring Retry库
当在Spring项目中使用重试机制时,可以使用Spring Retry库来实现,其提供了一组注解和工具类,可以方便地为方法添加重试功能。

5.使用Resilience4j库
Resilience4j是一个轻量级的,易于使用的容错库,提供了重试、熔断、限流等多种机制。

6.自定义重试工具类
如果说我们不想在项目里额外地引入一些重试的框架,自己定义一个重试工具类也是可以的。

7.并发框架异步重试
比如使用线程池ThreadPoolExecutor,把请求接口转化成一个异步任务,将任务放入线程池中异步执行,并发地重试请求接口。可以在任务执行完成后,判断任务执行结果,如果失败则继续重试。

8.消息队列重试
在某些情况下希望尽可能保证重试的可靠性,不会因为服务中断,而导致重试任务的丢失,可以引入消息队列直接把消息投递到消息队列里,通过对消息的消费,来实现重试机制。通过使用消息队列(如MQ)来实现重试机制,可以提高系统的可靠性和稳定性。即使在服务中断的情况下,重试任务也不会丢失,而是等待服务恢复后再次进行处理。

在请求重试的时候,我们也要注意一些关键点,以免因为重试,引发更多的问题:
1.合理设置重试次数和重试间隔时间,避免频繁地发送请求,同时也不要设置过大的重试次数,以免影响系统的性能和响应时间。

2.考虑接口幂等性:如果请求是写操作,而且下游的服务不保证请求的幂等性,那么在重试时需要谨慎处理,可以通过查询等幂等的方式进行重试。

3.在重试过程中,需要考虑并发的问题。如果多个线程同时进行重试,可能会导致请求重复发送或请求顺序混乱等问题。可以使用锁或者分布式锁来解决并发问题。

4.在处理异常时,需要根据具体的异常类型来进行处理。有些异常是可以通过重试来解决的,例如网络超时、连接异常等;而有些异常则需要进行特殊的处理,例如数据库异常、文件读写异常等。

5.在使用重试机制时,需要注意不要陷入死循环。如果请求一直失败,重试次数一直增加,可能会导致系统崩溃或者资源耗尽等问题。


性能优化

1.索引
接口性能优化大家第一个想到的可能是:优化索引。

没错,优化索引的成本是最小的。可通过查看线上日志或者监控报告,查到某个接口用到的某条sql语句耗时比较长。可能有如下的情况:没加索引、索引没生效、选错索引。索引失效的常见原因:



2. sql优化
如果优化了索引之后效果甚微。

接下来试着优化一下sql语句,因为它的改造成本相对于代码来说也要小得多。常见sql优化的小技巧:


3. 远程调用
需要在某个接口中,调用其他服务的接口。比如有这样的业务场景:在用户信息查询接口中需要返回:用户名称、性别、等级、头像、积分、成长值等信息。而用户名称、性别、等级、头像在用户服务中,积分在积分服务中,成长值在成长值服务中。为了汇总这些数据统一返回,需要另外提供一个对外接口服务。

于是,用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后汇总数据统一返回。显然这种串行调用远程接口性能是非常不好的,调用远程接口总的耗时为所有的远程接口耗时之和。

3.1 并行调用
需要借用使用线程池。但要注意为了防止高并发场景下,出现线程过多的问题。

3.2 数据异构
上面说到的用户信息查询接口需要调用用户查询接口、积分查询接口 和 成长值查询接口,然后汇总数据统一返回。

可以考虑将把数据冗余一下,把用户信息、积分和成长值的数据统一存储到一个地方,比如:redis,存的数据结构就是用户信息查询接口所需要的内容。然后通过用户id,直接从redis中查询数据出来;在高并发的场景下,为了提升接口性能,远程接口调用大概率会被去掉,而改成保存冗余数据的数据异构方案。


但代价可能是如果使用了数据异构方案,就可能会出现数据一致性问题。

用户信息、积分和成长值有更新的话,大部分情况下,会先更新到数据库,然后同步到redis。但这种跨库的操作,可能会导致两边数据不一致的情况产生。

4. 重复调用
重复调用在的日常工作代码中可以说随处可见,但如果没有控制好,会非常影响接口的性能。

4.1 循环查数据库
有时需要从指定的用户集合中,查询出有哪些是在数据库中已经存在的。

4.2 死循环或无效循环
出现死循环,大概率是开发人员人为的bug导致的,不过这种情况很容易被测出来。另外还有一种隐藏的比较深的死循环,是由于代码写的不太严谨导致的。如果用正常数据,可能测不出问题,但一旦出现异常数据,就会立即出现死循环。

4.3 无限递归
建议写递归方法时,设定一个递归的深度,比如:分类最大等级有4级,则深度可以设置为4。然后在递归方法中做判断,如果深度大于4时,则自动返回,这样就能避免无限循环的情况。

5. 异步处理
有时接口性能优化,需要重新梳理一下业务逻辑,看看是否有设计上不太合理的地方。

比如有个用户请求接口中,需要做业务操作,发站内通知,和记录操作日志。为了实现起来比较方便,通常我们会将这些逻辑放在接口中同步执行,势必会对接口性能造成一定的影响。


这个接口表面上看起来没有问题,但如果仔细梳理一下业务逻辑,会发现只有业务操作才是核心逻辑,其他的功能都是非核心逻辑。

在这里有个原则就是:核心逻辑可以同步执行,同步写库。非核心逻辑,可以异步执行,异步写库。

上面这个例子中,发站内通知和用户操作日志功能,对实时性要求不高,即使晚点写库,用户无非是晚点收到站内通知,或者运营晚点看到用户操作日志,对业务影响不大,所以完全可以异步处理。通常异步主要有两种:多线程 和 mq。


5.1 线程池
使用线程池改造之后,接口逻辑如下:


发站内通知和用户操作日志功能,被提交到了两个单独的线程池中。这样接口中重点关注的是业务操作,把其他的逻辑交给线程异步执行,这样改造之后,让接口性能瞬间提升了。

但使用线程池有个小问题就是:如果服务器重启了,或者是需要被执行的功能出现异常了,无法重试,会丢数据。那么这个问题该怎么办呢?

5.2 mq
使用mq改造之后,接口逻辑如下:


对于发站内通知和用户操作日志功能,在接口中并没真正实现,它只发送了mq消息到mq服务器。然后由mq消费者消费消息时,才真正的执行这两个功能。

这样改造之后,接口性能同样提升了,因为发送mq消息速度是很快的,我们只需关注业务操作的代码即可。

6. 避免大事务
下图展现了大事务引发的问题


从图中能够看出,大事务问题可能会造成接口超时,对接口的性能有直接的影响。该如何优化大事务呢?
少用@Transactional注解
将查询(select)方法放到事务外
事务中避免远程调用
事务中避免一次性处理太多数据
有些功能可以非事务执行
有些功能可以异步处理

7. 锁粒度
在某些业务场景中,为了防止多个线程并发修改某个共享数据,造成数据异常。为了解决并发场景下,多个线程同时修改数据,造成数据不一致的情况。通常情况下,通常会:加锁。

但如果锁加得不好,导致锁的粒度太粗,也会非常影响接口性能。

7.1 synchronized
在java中提供了synchronized关键字给我们的代码加锁。通常有两种写法:在方法上加锁 和 在代码块上加锁。但同时它也带来了新的问题:synchronized只能保证一个节点加锁是有效的,但如果有多个节点如何加锁呢?

答:这就需要使用:分布式锁了,引入一个外部锁会大幅缓解内部对锁的需求所带来的复杂性。目前主流的分布式锁包括:redis分布式锁、zookeeper分布式锁 和 数据库分布式锁。

由于zookeeper分布式锁的性能不太好,真实业务场景用的不多,这里先不讲。聊一下redis分布式锁。

7.2 redis分布式锁
在分布式系统中,由于redis分布式锁相对于更简单和高效,成为了分布式锁的首先。redis分布式锁虽说好用,但它在使用时,有很多注意的细节,隐藏了很多坑,如果稍不注意很容易踩中。

7.3 数据库分布式锁
mysql数据库中主要有三种锁:
表锁:加锁快,不会出现死锁。但锁定粒度大,发生锁冲突的概率最高,并发度最低。
行锁:加锁慢,会出现死锁。但锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
间隙锁:开销和加锁时间界于表锁和行锁之间。它会出现死锁,锁定粒度界于表锁和行锁之间,并发度一般。

并发度越高,意味着接口性能越好。

所以数据库锁的优化方向是:优先使用行锁,其次使用间隙锁,再不济使用表锁。

8.分页处理
有时候会调用某个接口批量查询数据,比如:通过用户id批量查询出用户信息,然后给这些用户送积分。但如果一次性查询的用户数量太多了,比如一次查询2000个用户的数据。参数中传入了2000个用户的id,远程调用接口,会发现该用户查询接口经常超时。调用接口从数据库获取数据,是需要经过网络传输的。如果数据量太大,无论是获取数据的速度,还是网络传输受限于带宽,都会导致耗时时间比较长。

这种情况要如何优化呢?
答:分页处理。

将一次获取所有的数据的请求,改成分多次获取,每次只获取一部分用户的数据,最后进行合并和汇总。其实,处理这个问题还要分为两种场景:同步调用 和 异步调用。

9.加缓存
解决接口性能问题,加缓存是一个非常高效的方法。但不能为了缓存而缓存,还是要看具体的业务场景。毕竟加了缓存,会导致接口的复杂度增加,它会带来数据不一致问题。

在有些并发量比较低的场景中,比如用户下单,可以不用加缓存。还有些场景,比如在商城首页显示商品分类的地方,假设这里的分类是调用接口获取到的数据,但页面暂时没有做静态化。如果查询分类树的接口没有使用缓存,而直接从数据库查询数据,性能会非常差。那么如何使用缓存呢?

9.1 redis缓存
通常情况下使用最多的缓存可能是:redis和memcached。

程序先从redis中根据某个key查询是否有菜单数据,如果有则转换成对象,直接返回。如果redis中没有查到菜单数据,则再从数据库中查询菜单数据,有则返回。此外还需要有个job每隔一段时间,从数据库中查询菜单数据,更新到redis当中,这样以后每次都能直接从redis中获取新近的数据,从而减少对数据库的访问了。

9.2 二级缓存
上面的方案是基于redis缓存的,虽说redis访问速度很快。但毕竟是一个远程调用,而且数据很多且在网络传输的过程中来说是有些耗时的。

有没有办法,不经过请求远程,就能直接获取到数据呢?
答:使用二级缓存,即基于内存的缓存。

除了自己手写的内存缓存之后,目前使用比较多的内存缓存框架有:guava、Ehcache、caffine等。

该方案的性能更好,但有个缺点就是,如果数据更新了,不能及时刷新缓存。此外,如果有多台服务器节点,可能存在各个节点上数据不一样的情况。由此可见,二级缓存会带来性能提升的同时,也带来了数据不一致的问题。使用二级缓存一定要结合实际的业务场景,并非所有的业务场景都适用。

10. 分库分表
有时候,接口性能受限的不是别的,而是数据库。当系统发展到一定的阶段,用户并发量大,会有大量的数据库请求,需要占用大量的数据库连接,同时会带来磁盘IO的性能瓶颈问题。

此外,随着用户数量越来越多,产生的数据也越来越多,一张表有可能存不下。由于数据量太大,sql语句查询数据时,即使走了索引也会非常耗时。这时该怎么办呢?
答:需要做分库分表。


图中将用户库拆分成了三个库,每个库都包含了四张用户表。如果有用户请求过来的时候,先根据用户id路由到其中一个用户库,然后再定位到某张表。

路由的算法挺多的:
1.根据id取模,比如:id=7,有4张表,则7%4=3,模为3,路由到用户表3。
2.给id指定一个区间范围,比如:id的值是0-10万,则数据存在用户表0,id的值是10-20万,则数据存在用户表1。
3.一致性hash算法

分库分表主要有两个方向:垂直和水平。垂直方向(即业务方向)更简单。在水平方向(即数据方向)上,分库和分表的作用,其实是有区别的,不能混为一谈。

分库:是为了解决数据库连接资源不足问题,和磁盘IO的性能瓶颈问题。
分表:是为了解决单表数据量太大,sql语句查询数据时,即使走了索引也非常耗时问题。此外还可以解决消耗cpu资源问题。
分库分表:可以解决 数据库连接资源不足、磁盘IO的性能瓶颈、检索数据耗时 和 消耗cpu资源等问题。

如果在有些业务场景中,用户并发量很大,但是需要保存的数据量很少,这时可以只分库,不分表。如果在有些业务场景中,用户并发量不大,但是需要保存的数量很多,这时可以只分表,不分库。如果在有些业务场景中,用户并发量大,并且需要保存的数量也很多时,可以分库分表。

11. 辅助功能
优化接口性能问题,除了上面提到的这些常用方法之外,还需要配合使用一些辅助功能,因为它们真的可以帮我们提升查找问题的效率。

11.1 开启慢查询日志
通常情况下,为了定位sql的性能瓶颈,需要开启mysql的慢查询日志。把超过指定时间的sql语句,单独记录下来,方面以后分析和定位问题。

11.2 加监控
为了出现sql问题时,能够及时发现就需要对系统做监控。需要监控的信息如下:
接口响应时间、调用第三方服务耗时、慢查询sql耗时、cpu使用情况、内存使用情况、磁盘IO使用情况、数据库使用情况等。

11.3 链路跟踪
有时候某个接口涉及的逻辑很多,比如:查数据库、查redis、远程调用接口,发mq消息,执行业务代码等等。该接口一次请求的链路很长,如果逐一排查,需要花费大量的时间,这时候,我们已经没法用传统的办法定位问题了。


安全加固