持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
网关解决什么问题?
作为微服务网关,以下几个场景迫切需要网关
- 请求每个服务都需要认证,需要将认证的逻辑放到网关
- 每个应用都是集群部署,如何统一做负载均衡
- 灰度路由,to B产品需要根据企业维度灰度
网关的测试用例
- 路由转发
- 自定义灰度
- 针对微服务限流
网关包含了哪些子系统及其作用
- 协议层:作为http协议的网关,肯定有http request、http response
- hook层:对外提供hook能够自定义开发
- 路由层,提供基本的url路由hook、限流hook
网关基本实现原理
协议解析
目前仅实现一个http网关即可,所以我们也只需要解析http的报文协议,可以有以下的方案。
方案一
借助于servlet容器解析http协议,我们只需要聚焦httpServletRequest、httpServletResponse的处理。
优点:不需要处理复杂的报文解析
缺点:性能可能不高,比如tomcat是BIO的线程模型
方案二
自己实现http报文解析
优点:可用自己采用NIO实现,性能更加强大
缺点:协议解析复杂一些
方案对比
图简单可以直接用方案一,比如zuul1,但是演进方向肯定是方案二,比如zuul2。
Handler的api设计
对于这种socket一问一答的需求,责任链必须要安排上。那么Handler的API应该如何设计呢?基于我的一些经验,hook的使用需要能够满足以下几点要求:
- 链式执行,这个是基本要求,实现肯定会想到列表循环,但有时你不需要执行后续的hook,所以类似netty的fHandlere机制对自定义更加友好 2.Handler的顺序,这个可以定义个接口
- 我可以用Handler在路由之前处理逻辑,比如http header增加一个kv
- 我可以用Handler在路由之后处理逻辑,比如拿到路由之后的http response header做一下操作
- Handler可以根据request或者response判断是否执行
如果有了解过netty,它的ChannelPipeline非常能够满足我们的要求。
Handler的生命周期
handler基本不会占用系统资源,所以生命周期中不需要close阶段。
测试用例功能的实现
上面说了一些整体的设计,下面开始围绕需求进行设计。
路由转发
转发路由之前需要定义路由规则,还有个注意点是需要从注册中心获取微服务的IP地址,然后通过负载均衡策略选择一个IP,建立三次握手直接调用。
当然每次的三次握手也是十分耗时,你可能会想到使用连接池,没错是个好办法。假设微服务有10个,每个微服务有3个pod,对于每个节点假设连接池缓存5个socket 连接不关闭。此时常态下一个网关pod需要建立。链接数量=10 * 3 * 5=150,emmmm,好像也不是很多,但我感觉如果日后集群扩大,这里会成为日后演进的一个瓶颈。
还有个注意点是需要从注册中心获取微服务的IP列表,这个肯定也是需要缓存到本地,对应的也是需要注册中心提供一个notify的机制,并且也是由注册中心提供服务探活,具体可以参考注册中心设计与实现。
负载均衡策略也是容易被忽略的点,负载均衡主要是解决服务负载不均的问题。轮训、权重、一致性哈希。理论原理你可能深入于心,但是优秀的代码可能需要参考参考(cv大法),遇到面试更是得背诵一番,不同的负载均衡,难点在于如何写出高性能且线程安全的代码。
软件架构 4+1 视图
逻辑视图
逻辑视图需要说明整个网关的分层设计。根据实现原理,整个网关分为:
- 协议层:用于将byte流解析为HTTP请求,将HTTP响应转为byte流发送,具体可以通过Netty实现
- handler层,这一层分为InboundHandler与OuboundHandler,并且需要管理Handler的整个生命周期
- 基本的Handler组件:比如路由转发Handler,限流Handler
过程视图
过程视图中我们通过时序图描述关键测试用例的步骤。在最前面提到过,我们目前网关的测试用例只有3个:
- 路由转发
- 自定义灰度
- 针对微服务限流
对于2、3需求比较简单,就不做时序图描述了,仅描述路由转发时序图。
物理视图
网关的部署通常比较简单,网关的前面可能是一个硬件负载均衡(F5)或者软件负载均衡(Nginx),部署时需要配置对应的路由策略。
另外一个在部署时需要注意的就是高可用和灰度怎么做。
高可用
什么?你说不用做高可用? 没有高可用的服务你敢上线?
网关的高可用还是比较简单,毕竟是一个无状态的服务。而无状态的服务高可用通常套路都一样:
- 冗余部署:比如主备部署,部署一个Master网关,再部署一个Slave
- 异常检测:监控Master是否正常,主要是服务是否正常
- 节点切换:比如Master节点网络不好或者节点挂了,需要将对Master节点的请求,转为对Slave机器的请求
这个三板斧可以解决任何无状态的服务高可用,高可用具体可以看看我的其它文章。 对于网关来说,我提一个主备方案。
方案一:脚本检测
- 冗余部署:网关服务搞两台服务器A,B,均部署网关服务,Nginx只路由到服务器A
- 异常检测:自己写一个shell脚本部署到Nginx所在服务器,定时执行,ping服务器A
- 如果ping不通,则立即修改Nginx的配置,将对服务器A的路由IP切换到服务器B的IP,然后重新加载Nginx配置文件
优点:很简单,完全不需要再引入其它软件,实施起来很快就能上线。 缺点:只能应对 Nginx -> 服务器A网络异常,或者服务器A挂掉的情况。并且最大故障时间=定时任务执行间隔 + nginx reload时间
灰度
什么?你说不用做灰度? 没有灰度的服务你敢上线?你敢保证的代码没有bug?我相信世界上的任何一个coder都不能保证,毕竟只要是人,都会犯错。
网关的灰度发布基本是根据来源IP进行灰度,比如仅允许公司办公网的IP可以路由到灰度机器。具体实现也是依赖于Nginx或者硬件的负载均衡。
开发视图
在开发视图中,我们通过类图描述具体类依赖关系与结构,协议层负责编解码http消息,HandlerManager具体执行Handler,他们两个都依赖HttReqeust、HttResponse,这俩类可以直接使用Netty的http模块提供的类。但如果要支持不同的网络通信框架,这俩类也得自己封装一下。
场景(测试用例)
场景就是测试用例,我们当前的一切设计都是为了实现测试用例,一定不要偏移主题,如果最终写出的代码连测试用例都无法完成,做方案设计的人一定要背这个锅。
测试用例:
- 协议层:作为http协议的网关,肯定有http request、http response
- hook层:对外提供hook能够自定义开发
- 路由层,提供基本的url路由hook、限流hook
测试
到这一步,设计阶段基本完成了,下一步不是考虑安排人手开发,而是考虑开发后、上线后,如何验证网关的正确性(能否正确路由)、高性能(支持的最大并发量多少)、高可用(有故障还能正常工作?),当然笼统的说网关也不对,因为正常情况下没有资源对网关的每个用例都做验证,需要针对具体的用例来验证对应的,正确性、高性能、高可用。
对于网关来说,路由转发是核心功能,需要全面验证。正确性验证就是配置下路由,看tomcat.log能不能收到转发的请求,高性能就是找个路由转发的接口,拿jmeter跑一跑,等cpu达到70%算是并发峰值。高可用就是把网关给挂掉(比如缩容道0个),手动请求下是否在tomcat.log还是能收到请求日志。
可观测性
当用了一段时间后,看某个服务的请求量多少也是尤为的重要,所以可观测性的建设是一个重要但不紧急的事情,不必在开发阶段思考。这个时候需要列出监控的指标,比如某个服务的请求量,http status=5xx的数量等等
总结
到这个里网关的设计基本就结束了,你可以看到每个点都需要考虑。