面试官:高并发下重启服务,接口调用老是超时,你有什么解决办法?
今天文章内容来自一位朋友出去面试碰到的问题:
「了解 Dubbo 服务预热过程吗?详细聊聊它的原理。」
这个问题朋友没有很好答出来,因为之前也没了解过。说实话一开始我只是大概知道这块预热的代码位于何处,但是原理什么的还是没有仔细去了解。
所以这次仔细去看了下代码,查了一些 Github 这块代码提交记录,终于搞明白这块的原理,跟大家一起分享下。
预热
首先我们来看下什么是服务预热?
先举一个生活的中的例子,买过新车的同学应该知道新车都有一个磨合期的,大概开个一两千公里之后,才能达到最佳的状态。
其实服务预热也是这个意思,服务刚启动的时候将存在一段「磨合期」,这段期间服务运行状态没有达到最佳,如果一下子将服务流量提升到平常的状态,可能会存在大量的请求超时或者瞬间将系统压垮。
所以服务刚启动的时候我们要慢慢增加的流量,直到一段时间后增加到阈值的上限,给系统一个「预热过程」,让其运行状态到达最佳。
那为什么服务刚启动的时候系统状态没有到达最佳状态?
大概原因其实如下:
Java 应用存在一个类加载的过程,而这个过程是按需加载的。即服务刚启动时候,JVM 只加载了启动过程必需的类。
我们自己所需要的类,直到服务被调用之后才会被真正的加载。
另外对于一些「热点代码」,JVM 将会使用 JIT 编译器编译成本地代码,提高运行速度。
上面两个过程是出于 JVM 系统层面的影响。
除此之外,我们服务系统中可能会需要一些缓存资源。刚启动的时候,由于资源不存在,服务需要去加载这些资源。
Dubbo 预热实现方式
好了,了解完预热是咋回事后,我们回到正题,来看下 Dubbo 是如何实现预热的。
首先我们来看下 Dubbo 服务模型:
服务提供者启动之后将会把节点相关信息注册到注册中心,服务消费者通过注册中心就可以及时获取所有的服务节点。
当服务消费者调用服务时,内部将会通过负载均衡组件选择一个节点,进行服务调用。
如上图所示,假设 B 节点服务刚启动,其需要一个预热过程,这就需要服务消费者逐渐将流量分发给 B 节点。
下面我们就从 Dubbo 源码出发,观察服务预热具体实现方式,具体源码位于 AbstractLoadBalance#getWeight
ps: 当前源码 Dubbo 版本为2.7.4,低于这个版本代码实现存在少量差异,详情见下文。
这段代码主要分为三步:
获取服务提供者启动时间
timestamp
使用当前时间减去服务提供者启动时间,计算服务提供者已运行时间
uptime
根据已运行时间动态计算服务预热过程的权重
第三步动态权重计算方法如下:
这里计算方式其实很简单,简单来说服务运行时间越久,权重越高,直到正常权重。
假如服务提供者已运行 1 分钟,那么 weight 最终结果为 10 。
假如服务提供者已运行 5 分钟,那么 weight 最终结果为 50 。
假如服务提供者已运行 11 分钟,超过默认预热时间的阈值 10分 钟,那么将不会再计算,直接返回 weight 默认权重。
这里我们需要注意的是,Dubbo 默认提供五种负载均衡的策略:
Random LoadBalance :「加权随机」策略
RoundRobin LoadBalance:「加权轮询」策略
LeastActive LoadBalance:「最少活跃调用数」策略
ConsistentHash LoadBalance:「一致性 Hash」 策略
ShortestResponse LoadBalance:「最短响应时间」策略
「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
类型存储时间戳计算结果,最终优化代码如下:
总结
今天的文章主要介绍了服务预热的作用,以及 Dubbo 服务预热的实现方式。
这个实现方式整体来说不是很难,简单来说就是随着运行时间逐渐提高权重,从而增加服务节点的流量。
如果你当前使用框架并没有这个功能,而你正需要服务预热,可以参考 Dubbo 的实现方式。
另外由于 Dubbo 历史版本存在一些 Bug,如果各小伙伴需要使用服务预热功能,需要注意避免使用以下版本:
「Dubbo 2.5.5 之前的版本」
「Dubbo 2.7.2/ 2.7.3」
相关资料
https://github.com/apache/dubbo/issues/6242
https://github.com/apache/dubbo/issues/306
https://github.com/apache/dubbo/pull/4870
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️
面试官:高并发下重启服务,接口调用老是超时,你有什么解决办法?相关推荐
- postfix和dovecot服务异常,重启服务后又会自动停掉的解决办法
1.首先排除/var/log/mailog日志,但是发现是空的. 2.查看rsyslog日志配置文件,主要注意$IncludeConfig和mail这两行. [root@gxm log]# vi /e ...
- 高并发下如何保证接口的幂等性?
前言 接口幂等性问题,对于开发人员来说,是一个跟语言无关的公共问题.本文分享了一些解决这类问题非常实用的办法,绝大部分内容我在项目中实践过的,给有需要的小伙伴一个参考. 不知道你有没有遇到过这些场景: ...
- 面试:高并发下的流量控制
面试:高并发下的流量控制 这个时候如果不做任何保护措施,服务器就会承受很大的处理压力,请求量很高,服务器负载也很高,并且当请求超过服务器承载极限的时候,系统就会崩溃,导致所有人都不能访问. 为了应用服 ...
- Hystrix面试 - 基于 timeout 机制为服务接口调用超时提供安全保护
Hystrix面试 - 基于 timeout 机制为服务接口调用超时提供安全保护 一般来说,在调用依赖服务的接口的时候,比较常见的一个问题就是超时.超时是在一个复杂的分布式系统中,导致系统不稳定,或者 ...
- 手把手教你搭建SpringCloud项目(九)集成OpenFeign服务接口调用
Spring Cloud全集文章目录: 零.什么是微服务?一看就会系列! 一.手把手教你搭建SpringCloud项目(一)图文详解,傻瓜式操作 二.手把手教你搭建SpringCloud项目(二)生产 ...
- python的坐标代码_基于Python的地图坐标服务接口调用代码实例
代码描述:基于Python的地图坐标服务接口调用代码实例 关联数据:地图坐标服务 接口地址:http://www.juhe.cn/docs/api/id/32 #!/usr/bin/python # ...
- linux mysql端口启动失败怎么办,Linux下apache mysql等服务修改默认端口后无法正常启动解决办法...
Linux下apache mysql等服务修改默认端口后无法正常启动解决办法 linux下 apache 等服务修改默认端口后无法正常启动解决办法 服务器上装了两个webserver,一个是nginx ...
- Maya2016.5、2018、2019更高版启动卡住(附带很多acwebbrower.exe程序)的解决办法)
Maya2016.5.2018.2019更高版启动卡住(附带很多acwebbrower.exe程序)的解决办法 问题原因 解决方法 问题原因 Maya在2016版本后增加了Cloud登入的方法,这个方 ...
- 面试官:聊一聊 Spring Boot 服务监控机制
欢迎关注方志朋的博客,回复"666"获面试宝典 任何一个服务如果没有监控,那就是两眼一抹黑,无法知道当前服务的运行情况,也就无法对可能出现的异常状况进行很好的处理,所以对任意一个服 ...
最新文章
- 联合南京大学,爱奇艺智能论文入选顶会CVPR 2021
- webpack入门学习手记(一)
- 安卓开发遇到Error:Execution failed for task ':app:transformClassesWithDexForDebug'.
- 《趣题学算法》—第0章0.3节算法的伪代码描述
- binder 进程间通讯关于handle一点疑问
- JAVA月数输入24回车后变成12_Java语言程序设计(一)自考2012年10月真题
- win11网络无法连接怎么办 Windows11连不上网的解决方法
- [论文笔记]DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter
- SQLyog 注册码(包含企业版注册码)
- css做计算器,CSS3 纯CSS计算器
- Apache 绿色版本官方版本下载
- 计算机程序员crc算法,CRC-8校验原理及软件实现
- Vue中v-model和checkbook的使用
- 详细Ubuntu系统修改默认软件下载源
- 处理猪舌须知小窍门-滋阴润燥好良方-菜椒滑猪舌
- 泰勒展开式求sinx近似值
- ORA-01002 “fetch out of sequence”关于cursor的一个bug
- 仓储机器人的3位鼻祖
- 被遗忘的设计模式——空对象模式(转载)
- pwm调速流程图小车_求智能小车 PWM调速?
热门文章
- mysql 安装telnat_yum安装telnet服务
- php导出csv带图片,PHP导出CSV文件:刚测试过,这个导出CSV可以
- Java游戏触屏处理,非触屏java游戏转换为触屏游戏工具使用方法
- lacp可以在access接口吗_【思唯网络学院】【干货】LACP与PAGP是什么?有何区别?...
- 三,位操作类指令:包括逻辑运算指令,测试指令和移位指令
- Linux下动态库(.so)和静态库(.a)
- USACO-Section2.2 Party Lamps
- 学习推荐《精通Python网络爬虫:核心技术、框架与项目实战》中文PDF+源代码
- 数据结构之树状数组(候补)
- 我的博客园css样式