请求的流程
一个HTTP请求发送到网关并完成整个生命周期通常包括以下步骤:
客户端请求: 请求始于客户端,客户端通过HTTP请求(例如GET、POST等)发送请求到API网关的入口点。
API网关接收: API网关作为请求的第一个接收点,它会监听来自客户端的请求,通常在端口80(HTTP)或443(HTTPS)上。
请求路由: API网关会根据请求的目标路径或域名信息将请求路由到适当的后端服务。这可以是基于配置的路由规则,也可以是根据请求中的路径进行动态路由。
请求验证: 在路由之前,API网关可能会执行请求验证,包括身份验证、API密钥验证、访问令牌验证等。这确保只有授权的客户端能够访问后端服务。
请求转换: API网关可能需要转换请求的数据格式,以确保请求与后端服务期望的数据格式匹配。这可以涉及数据转换或重新映射请求参数。
负载均衡: 如果后端服务有多个实例,API网关可以执行负载均衡,以选择一个合适的后端服务实例来处理请求。这确保请求被均匀分发到后端服务。
请求代理: 一旦路由和验证完成,API网关将请求代理到选定的后端服务。这通常涉及将请求重新发送到后端服务的HTTP端点。
后端处理: 后端服务接收请求,并根据请求的内容执行相应的操作。这可以是查询数据库、处理业务逻辑、生成响应等。
响应转换: 后端服务生成响应并将其返回给API网关。API网关可能需要将响应转换为客户端期望的格式,进行数据转换等操作。
响应传递: 最后,API网关将经过处理的响应发送回客户端。
响应验证: 在将响应发送给客户端之前,API网关还可以执行响应验证,包括授权检查、响应头的添加或修改以及安全性检查。
响应传输: 最终,API网关将响应传递给客户端,客户端收到响应并执行相应的操作。
架构设计
参考目前主流的网关的设计,有SpringCloud Gateway以及Zuul,他们的底层都大量使用了异步编程的思想,并且也都有非常重要的网络通信上的设计。 比如我当初看SpringCloudGateway的源码的时候就看到了大量的对Netty的使用。
由于我们的网关是自研的,也就是他自己本身就是一个单独的服务,因此我们并不需要使用到SpringBoot这种框架,我们可以直接使用原生Java框架来编写各种重要代码。
网络通信上毋庸置疑的使用Netty即可。
同时,在第一篇文章中也提到了,网关是需要用到注册中心的,因为我们的请求具体最后要转发到那个路由,是需要从注册中心中拉取服务信息的,目前注册中心有: Zookeeper,Eureka,Nacos,Apollo,etcd,Consul。 他们各有优劣势,比如Zk保证的是CP而不是AP,我们知道,网关是应用的第一道门户,我们使用Dubbo的时候会使用Zk,但是对于网关,可用性大于一致性,因此Zk我们不选。 而Eureka都和SpringCloud生态有比较紧密的联系,因此如果我们使用它,就会增加我们的网关和SpringCloud的耦合,不太符合我们自研的初衷,所以也不选。 Etcd虽然是通用的键值对分布式存储系统,可以很好的应用于分布式系统,但是依旧没有很好的优势,当然,他很轻量级。所以这里暂不考虑。 Consul和Etcd差不多,所以这里也不考虑Consul。 这里我选用Nacos作为注册中心,Nacos首先支持CP和AP协议,并且提供了很好的控制台方便我对服务进行管理。同时,Nacos的社区相对来说非常活跃,网络上的资料也更加的多,同时,我也看过Nacos的源码,编写优雅且相对易懂。同时我相信会使用Nacos的人肯定更多,因此在这里选择Nacos作为注册中心。 当然,上面的几种注册中心都可以使用,没有特别明显的优劣势,他们也都有各自合适的场合,具体场合具体分析,主要是要分析自己的团队更加了解或者适合哪一种注册中心。 而配置中心方面,有SpringCloud Config,Apollo,Nacos。
这里很明显,依旧选择Nacos,因为Nacos不仅仅是注册中心也是配置中心。因此选用Nacos我们可以减少引入不必要的第三方组件。 因此在技术栈上,选用如下技术:
- Java
- Netty
- Nacos
设计要点
设计高性能网关时需要考虑的一些重要设计要点
串行化 使用并行自然有其好处,能加快一些业务的处理,但是由于涉及到对操作系统以及线程层面的操作,因此对于一些场景,合理的使用串行化也可以优化性能。 比如对于耗时较少,性能要求高的场景,我们就可以使用串行化。 而对于耗时较久,任务之间没有依赖关系,比如RPC远程调用的场景,我们就可以使用并行。
异步化 使用异步处理能够提高性能,允许网关同时处理多个请求而不被阻塞。这可以通过采用异步框架、事件驱动的编程模型或多线程/多进程处理来实现。 我们可以从如下几个地方考虑进行异步化,分别是: 请求转发异步化、请求响应异步化、插件过滤异步化。 **同时异步的模式可以考虑单异步模式或者双异步模式。**这意味着发送者接受和发送数据的时间不必完全同步。其中单异步模式指的是同一时间只有一个方向可以进行数据传输,而双异步指的是两个方向都可以进行数据传输,那么数据传输的速度也会大大提高。 我们可以考虑在插件过滤使用单异步模式,在请求响应的时候使用双异步模式。 并且双异步模式适合应用在下游服务器性能不是很高的场景。 不是很了解单异步和双异步的可以先Google简单了解一下。
缓存 缓存常用的响应数据,以降低后端服务的负载。合理的缓存策略可以显著提高响应时间和减少资源使用。可以考虑使用Caffeine这种缓存,网关推荐使用内存级缓存。
吞吐量 在大流量的时刻,我们一般需要进行一些削峰限流。比如使用一些消息中间件如RocketMQ,不过在自研缓存中,使用消息中间件将会增大我们系统的耦合度,并且消息中间件也会需要网络传输,会提示我们系统的RT时间。 因此我们需要考虑使用本地缓冲区,比如使用Disruptor。
线程合理配置 我们都知道有CPU密集型和IO密集型任务。 所以合理的配置线程数量,也可以提升我们的项目性能。 比如常见的CPU密集型任务我们会设置线程数量为n+1,而IO密集型任务我们会设置线程数量为2n。这些都是为了在某种程度上提升我们的性 能,
项目架构
Common:维护公共代码,比如枚举 Client:客户端模块,方便我们其他模块接入网关 Register Center:注册中心模块 Config Center:配置中心模块 Container:包含核心功能 Context:请求上下文,规则 FilterChain:通过责任链模式,链式执行过滤器 FlowFilter:流控过滤器 LoadBalanceFilter:负载均衡过滤器 RouterFilter:路由过滤器 TimeoutFilter:超时过滤器 OtherFilter:其他过滤器 NettyHttpServer:接收外部请求并在内部进行流转 Processor:后台请求处理 Flusher:性能优化 MPMC:性能优化 SPI Loader:扩展加载器 Plugin Loader:插件加载器 Dynamic Loader:动态配置加载器 Config Loader:静态配置加载器
流程设计
有了上面这张比较详细的处理流程,我们就大概可以开始准备编写代码了。 请期待下文,下文将开始真正的编写代码。