如何解决高并发下重启服务接口调用老是超时的问题

发布时间:2021-10-23 15:57:56 作者:iii
来源:亿速云 阅读:233

这篇文章主要讲解了“如何解决高并发下重启服务接口调用老是超时的问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何解决高并发下重启服务接口调用老是超时的问题”吧!

预热

首先我们来看下什么是服务预热?

先举一个生活的中的例子,买过新车的同学应该知道新车都有一个磨合期的,大概开个一两千公里之后,才能达到最佳的状态。

其实服务预热也是这个意思,服务刚启动的时候将存在一段「磨合期」,这段期间服务运行状态没有达到最佳,如果一下子将服务流量提升到平常的状态,可能会存在大量的请求超时或者瞬间将系统压垮。

如何解决高并发下重启服务接口调用老是超时的问题

所以服务刚启动的时候我们要慢慢增加的流量,直到一段时间后增加到阈值的上限,给系统一个「预热过程」,让其运行状态到达最佳。

如何解决高并发下重启服务接口调用老是超时的问题

那为什么服务刚启动的时候系统状态没有到达最佳状态?

大概原因其实如下:

Java 应用存在一个类加载的过程,而这个过程是按需加载的。即服务刚启动时候,JVM 只加载了启动过程必需的类。

我们自己所需要的类,直到服务被调用之后才会被真正的加载。

另外对于一些「热点代码」,JVM 将会使用 JIT 编译器编译成本地代码,提高运行速度。

上面两个过程是出于 JVM 系统层面的影响。

除此之外,我们服务系统中可能会需要一些缓存资源。刚启动的时候,由于资源不存在,服务需要去加载这些资源。

Dubbo 预热实现方式

好了,了解完预热是咋回事后,我们回到正题,来看下 Dubbo 是如何实现预热的。

首先我们来看下 Dubbo 服务模型:

如何解决高并发下重启服务接口调用老是超时的问题

服务提供者启动之后将会把节点相关信息注册到注册中心,服务消费者通过注册中心就可以及时获取所有的服务节点。

当服务消费者调用服务时,内部将会通过负载均衡组件选择一个节点,进行服务调用。

如上图所示,假设 B 节点服务刚启动,其需要一个预热过程,这就需要服务消费者逐渐将流量分发给 B 节点。

下面我们就从 Dubbo 源码出发,观察服务预热具体实现方式,具体源码位于 AbstractLoadBalance#getWeight

如何解决高并发下重启服务接口调用老是超时的问题

ps: 当前源码 Dubbo 版本为2.7.4,低于这个版本代码实现存在少量差异,详情见下文。

这段代码主要分为三步:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 获取服务提供者启动时间 timestamp

  3. 使用当前时间减去服务提供者启动时间,计算服务提供者已运行时间 uptime

  4. 根据已运行时间动态计算服务预热过程的权重

第三步动态权重计算方法如下:

如何解决高并发下重启服务接口调用老是超时的问题

这里计算方式其实很简单,简单来说服务运行时间越久,权重越高,直到正常权重。

假如服务提供者已运行 1 分钟,那么 weight 最终结果为 10 。

假如服务提供者已运行 5 分钟,那么 weight 最终结果为 50 。

假如服务提供者已运行 11 分钟,超过默认预热时间的阈值 10分 钟,那么将不会再计算,直接返回 weight 默认权重。

这里我们需要注意的是,Dubbo 默认提供五种负载均衡的策略:

「ShortestResponse LoadBalance」 策略小伙伴们可能会比较陌生,官方文档中并没有提到这个策略。

其实这个是 Dubbo 2.7.7 版本新增的负载均衡策略,官方文档估计还没更新。

如何解决高并发下重启服务接口调用老是超时的问题

ps:感兴趣的小伙伴,可以去修改下官方的文档,增加这个新的负载均衡的策略,为开源献出我们的一份力量。

回到正文,从AbstractLoadBalance#getWeight调用关系可以看到,「ConsistentHash LoadBalance」  实现类是不支持服务预热,这点需要注意一下。

如何解决高并发下重启服务接口调用老是超时的问题

Dubbo 预热历史 bug-反复横跳虽然 Dubbo 预热的相关代码,总体看起来不是很难,但是历史版本还是存在几个 Bug,导致预热失效。

Dubbo 2.5.5 之前的版本

在 Dubbo 2.5.5 之前的版本,AbstractLoadBalance#getWeight实现方式如下:

如何解决高并发下重启服务接口调用老是超时的问题

这个版本跟现在代码一样,都是从节点的 timestamp获取服务启动时间。不过这个版本存在一些问题,Dubbo  没有把服务提供者启动时间传给消费者,导致这里获取 timestamp是消费者启动时间,这样就导致预热失效。

等到 Dubbo 2.5.6 ,修复这个问题,源码如下:

如何解决高并发下重启服务接口调用老是超时的问题

这个版本将服务提供者启动时间单独保存在 remote.timestamp 属性中,源码位于 ClusterUtils#mergeUrl

如何解决高并发下重启服务接口调用老是超时的问题

通过这种方式修复预热失效的问题。

Dubbo 2.7.2 预热又失效了

当 Dubbo 版本升级到 2.7.2 ,这个预热失效 Bug 又回来了。带来这个问题主要原因是ClusterUtils#mergeUrl  源码中清除了remote.timestamp,转而统一使用 timestamp保存服务启动时间。

如何解决高并发下重启服务接口调用老是超时的问题

但是呢,由于修改没有彻底, AbstractLoadBalance#getWeight还是依然使用 remote.timestamp  获取服务启动时间,这就导致预热失效。

预热代码的隐藏 bug

这个 Bug 在Dubbo 2.7.4 版本被彻底修复,另外还顺带优化了代码中存在缺陷。

先看下原先的代码中国缺陷,原先预热代码实现采用如下方式计算服务启动运行的时间。

int uptime = (int) (System.currentTimeMillis() - timestamp);

但是这里存在一个问题,如果服务提供者与消费者两端时钟是不一致,服务提供者启动时间很有可能会大于消费者本地时间。

这种情况,uptime 计算结果为一个负值,这就会导致权重将使用配置的默认值,预热也失效了。

所以针对这种情况 「@aftersss」 提供了修复的方案,加入相关的判断,当 uptime为负值的时候,直接返回权重 1。

不过在 「Code review」 过程中,「@beiwei30」 觉得不用加入额外 if 判断,可以直接使用 Math.max兼容。

如何解决高并发下重启服务接口调用老是超时的问题

不过这样修改,还是存在一个问题:Integer 精度丢失问题。

如何解决高并发下重启服务接口调用老是超时的问题

如果此时 System.currentTimeMillis() = 1566209746000(2019-08-19 18:15:46),而  timestamp = 1561914711000(2019-07-01 01:11:51),当两者差值为:「4295035000」。

这是一个远大于 Integer.MAX_VALUE的值,所以在 long 转为 int 时候精度丢失,导致最后实际得到 int 值为  「67704」。

而这个值小于服务预热的默认时间(10 * 60 * 1000),所以进入动态计算权重环节,最终将得到一个比较小的权重,这就导致「假预热」。

所以最后还是采用 「@aftersss」 修复的方案,采用 long 类型存储时间戳计算结果,最终优化代码如下:

如何解决高并发下重启服务接口调用老是超时的问题

感谢各位的阅读,以上就是“如何解决高并发下重启服务接口调用老是超时的问题”的内容了,经过本文的学习后,相信大家对如何解决高并发下重启服务接口调用老是超时的问题这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. windows下解决pip安装模块超时的问题
  2. Java怎么解决高并发的问题

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

高并发

上一篇:如何用OpenJDK源码执行HelloWorld

下一篇:JVM中类的加载链接和初始化是怎么样的

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》