这是奇点云全新技术专栏「StartDT Tech Lab」的第 9 期。

在这里,我们聚焦数据技术,分享方法论与实战。一线的项目经历,丰富的实践经验,真实的总结体会…滑到文末,可以看到我们的往期内容。

本篇由奇点云 Java 开发工程师「长夜」带来:

作者:长夜

阅读时间:8 分钟

背景

在某个阳光明媚的早上,开发同学们还在吃着早饭摸着鱼。突然,我们收到了一个客户的反馈,说在他们生产环境上的页面出现了几次报错,影响到了使用体验,希望我们尽快排查解决。

大家迅速放下手中的鱼,赶紧上客户生产环境上排查了一下问题,发现我们使用的开源网关 Apache ShenYu (Incubating) 代理的其中一个服务宕机了,但网关却没有做任何处理,依然会去尝试访问该宕机的实例。但由于客户现场实例较多,所以该问题只会偶尔发生,增加了排查难度。

针对该问题,我们调研并设计了不同方案,下面就来看看不同方案的优劣势及改进点。

网关请求转发原理

在阐述几种解决方案前,我们需要了解 ShenYu 网关目前代理的工作形式,我们来看看下面的图。(基于 ShenYu 2.1.0 版本)

图 1(图源:长夜)

可以看出,在网关中维护了一份全量的实例列表,而我们当时的问题就出在第 5 步:当 Service-1 或 Service-2 任意一台宕机了之后,网关的负载均衡依然会将他们作为正常的实例进行选择,然后交给插件做请求转发,就导致了上文所出现的问题。因此,我们要针对这种情形提供一种解决方案。

基于Redis的服务健康检查方案

该方案为我们当时首先提出来能够尽快解决客户问题并保证客户生产环境可用性的方案。我们看看下面的流程图。本图省略了和上面图(图 1)中重复的步骤,只抽出了图 1 中第 5 步和第 6 步之间新增的步骤。

图 2(图源:长夜)

该方案中,我们判断实例是否健康的标准为查看请求时间是否超过 3 秒,并且新增了 Redis 依赖用作实例健康状态存储,该状态信息会默认在 Redis 中存储 100 秒。

可以看出该方案易于实现,但是也同时存在以下几个问题:

1. 网关服务高度依赖于 Redis,若两个中间件任意一个不可用,会导致整个应用挂掉,接口无法访问;

2. 每次健康检查后,若因为网络波动等原因导致实例状态异常,会使机器在长时间内被标记为异常状态;

3. 该检查位于主线程,会影响接口请求时间。若实例数量为n个,请求正常的情况下主线程最长额外时耗为 3s * (n – 1) + n * (Redis RTT) + (Service RTT),而异常情况下最长额外时耗为 n * (Redis RTT + 3s),这显然是不合适的;

4. Redis 超时时间过于固定,若设置得过小则会有大量健康检查逻辑阻塞实际请求,若设置得过大则会导致实例即使恢复健康后一段时间内依然不会被请求,大量压力堆积在其他实例上。

开源健康检查方案

在交付了上述方案后,客户现场基本可用,但是该方案依然存在某些问题。为了探索问题解决思路,我们去调研了 ShenYu 网关最新版本的方案(此时为 2.3.0 版本)。

调研后我们发现了最新版本提供了 2 套方案进行健康检查。其中 1 套方案较为简单,并且默认为关闭状态,所以此处不进行讲解,主要看看另 1 套默认开启的方案:

图 3(图源:长夜)

在这套方案中,网关启动了异步线程进行健康检查,不阻塞主线程请求。并且新增了不健康列表存储异常实例。

值得一提的是,这套方案中,网关会将不健康实例从数据源中删除,并且存入程序缓存中。若此时管理员查看控制台页面,则会发现配置的实例在控制台页面中无法显示。

并且在检查次数超过一定次数后,若实例依然不健康,则会将配置永久删除。

此处是我觉得网关设计不好的一个点,我个人认为网关仅需关注并将请求转发至健康实例即可,而不应将不健康实例从配置中删除,该行为会使用户在使用时产生疑惑。

综上,这套设计的不足之处在于:

1. 健康检查不应在控制台程序中执行,而应该在网关服务中负载均衡所在的插件执行;

2. 网关不应主动删除实例的配置信息,应由用户自行处理。

异步心跳及重试方案

针对以上两个方案的优缺点,我们开发人员进行了后续设计:

01

优化异步健康检查任务

1. 将健康检查任务移入网关服务中;

2. 取消网关对配置的删除行为;

3. 新增维护三个列表,全量列表、健康实例列表、不健康实例列表;

4. 新增四个参数,响应超时时间、检测间隔时间、健康阈值、不健康阈值;

5. 将健康实例变为不健康实例,需要对应的状态维持的时间超过“不健康阈值 * 检测时间间隔”后才可改变,反之亦然。

02

新增调用时保障

1. 新增调用重试,若某次请求调用失败,则会将实例暂时移除健康实例列表后,重新进行一次负载均衡后再调用;

2. 若健康实例列表中没有实例,则使用所有实例进行负载均衡。

具体请看下图,图中保留了主要流程,省去了一些异常判断过程。

图 4(图源:长夜)

上述优化可以保证在异步线程中始终能检测到健康的实例,并且即使由于网络波动等原因导致的某服务实例暂时不可用,在新增的重试方案中亦能保证较高的可用性,并且已在线上环境稳定运行。

参与开源社区讨论

后续通过提交了对应优化过的 PR,以及对应的 Issue 的讨论,我参与到了开源社区中,与开源方案中健康检查功能的设计者交流后,我进一步了解到了方案中删除配置的缘由:即某些用户会使用 Docker 作为服务提供者,而 Docker 重启后会有 IP 漂移问题,删除配置是为了满足这部分用户的诉求。

但是在讨论中也提出了另一个疑虑,作为非 Docker 用户,删除配置这个行为可能会导致预期之外的程序异常。所以后续与开源社区进一步优化了这一部分的设计:

1. 由用户手动选择实例配置是否为动态 IP,若为动态 IP,则帮助用户删除配置,否则保留配置。

2. 抽象健康检查代码,作为公共方法供网关其他有需要的组件使用。

该设计对应的 PR 的改动目前正在进行中,预计会在 8 月份同步到开源侧。

相关推荐