当前位置: 首页 > news >正文

Spring cloud负载均衡 @LoadBalanced注解原理

接上一篇文章,案例代码也在上一篇文章的基础上。

在上一篇文章的案例中,我们创建了作为Eureka server的Eureka注册中心服务、作为Eureka client的userservice、orderservice。

orderservice引入RestTemplate,加入了@LoadBalanced注解,代码如下:

package com;@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication {public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class);}@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}}

从而,我们实现了基于Eureka注册中心的微服务治理框架,在orderservice调用userservice的时候通过加了@LoadBalanced注解的RestTemplate实现了负载均衡。

今天的目标是:深入研究@LoadBalanced生效的底层原理。

@LoadBalanced是怎么实现负载均衡的?

我们要回答的第一个问题是,为什么@LoadBalanced能实现负载均衡?

我们从代码的源头一路追查下去…

orderservice通过RestTemplate实现对userservice的调用代码:

@Service
public class OrderService {@Autowiredprivate RestTemplate restTemplate;public String getOrder(){//通过userService获取user信息String url="http://userservice/user/getUser";System.out.println("url"+url);User user=restTemplate.getForObject(url,User.class);System.out.println(user);return user.getName();}
}

很容易的,我们需要有一个认知:这里访问的地址http://userservice/user/getUser只可能在Spring cloud服务治理环境下有意义,最终能访问到我们发布到本机上的userservice的如下服务:

1. http://localhost:8080
2. http://localhost:8081
3. http://localhost:8082

必定需要借助Spring Cloud的某一机制将http://userservice转换为上述地址之一。这个转换过程,也就是Spring Cloud的负载均衡机制。

跟踪getForObject:

	@Override@Nullablepublic <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);HttpMessageConverterExtractor<T> responseExtractor =new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);}

调用到execute方法:

	@Override@Nullablepublic <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,@Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {URI expanded = getUriTemplateHandler().expand(url, uriVariables);return doExecute(expanded, method, requestCallback, responseExtractor);}

doExecute方法:

@Nullableprotected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {Assert.notNull(url, "URI is required");Assert.notNull(method, "HttpMethod is required");ClientHttpResponse response = null;try {ClientHttpRequest request = createRequest(url, method);if (requestCallback != null) {requestCallback.doWithRequest(request);}response = request.execute();handleResponse(url, method, response);return (responseExtractor != null ? responseExtractor.extractData(response) : null);}catch (IOException ex) {String resource = url.toString();String query = url.getRawQuery();resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);throw new ResourceAccessException("I/O error on " + method.name() +" request for \"" + resource + "\": " + ex.getMessage(), ex);}finally {if (response != null) {response.close();}}}

createRequest方法:

	protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {ClientHttpRequest request = getRequestFactory().createRequest(url, method);initialize(request);if (logger.isDebugEnabled()) {logger.debug("HTTP " + method.name() + " " + url);}return request;}

最关键的部分来了,就是这个 getRequestFactory()方法,在RestTemplate的父类InterceptingHttpAccessor中定义:

    private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();public ClientHttpRequestFactory getRequestFactory() {List<ClientHttpRequestInterceptor> interceptors = getInterceptors();if (!CollectionUtils.isEmpty(interceptors)) {ClientHttpRequestFactory factory = this.interceptingRequestFactory;if (factory == null) {factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);this.interceptingRequestFactory = factory;}return factory;}else {return super.getRequestFactory();}}public List<ClientHttpRequestInterceptor> getInterceptors() {return this.interceptors;}

首先通过getInterceptor()方法检查是否有有拦截器,拦截器interceptors是由ClientHttpRequestInterceptor组成的一个list。如果有的话,就会创建InterceptingClientHttpRequestFactory、并且将拦截器interceptors送给InterceptingClientHttpRequestFactory工厂之后,返回工厂InterceptingClientHttpRequestFactory。

然后,方法调用返回到createRequest方法中,调用InterceptingClientHttpRequestFactory的createRequest方法,最终会调用到:

	@Overrideprotected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);}

方法最终返回的是InterceptingClientHttpRequest,并且,会将工厂InterceptingClientHttpRequestFactory持有的interceptors传递给InterceptingClientHttpRequest对象:

protected InterceptingClientHttpRequest(ClientHttpRequestFactory requestFactory,List<ClientHttpRequestInterceptor> interceptors, URI uri, HttpMethod method) {this.requestFactory = requestFactory;this.interceptors = interceptors;this.method = method;this.uri = uri;}

之后返回到doExecute方法中,会调用InterceptingClientHttpRequest的父类AbstractClientHttpRequest类的execute方法、之后又转回到InterceptingClientHttpRequest类的executeInternal方法:

	@Overrideprotected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();return requestExecution.execute(this, bufferedOutput);}

executeInternal方法创建了InterceptingClientHttpRequest内部类InterceptingRequestExecution对象之后,调用内部对象的execute方法。

查看内部类InterceptingRequestExecution,不难发现,他拿到了宿主类InterceptingClientHttpRequest的拦截器interceptors的迭代器:

private class InterceptingRequestExecution implements ClientHttpRequestExecution {private final Iterator<ClientHttpRequestInterceptor> iterator;public InterceptingRequestExecution() {this.iterator = interceptors.iterator();}@Overridepublic ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {if (this.iterator.hasNext()) {ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();return nextInterceptor.intercept(request, body, this);}else {HttpMethod method = request.getMethod();Assert.state(method != null, "No standard HTTP method");ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));if (body.length > 0) {if (delegate instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));}else {StreamUtils.copy(body, delegate.getBody());}}return delegate.execute();}}}

然后在execute方法中首先遍历该迭代器iterator并调用迭代器对象ClientHttpRequestInterceptor的intercept方法。

拦截器ClientHttpRequestInterceptor就是实现负载均衡的关键所在,Spring正是在拦截器ClientHttpRequestInterceptor的intercept方法中,完成了负载均衡的实现:将请求中的服务名称比如本案例中的userservice、替换成了由Eureka注册中心下发下来的具体的userservice服务器(比如http://127.0.0.1:8081)

我们发现最关键的部分就是RestTemplate对象中的拦截器interceptors。

接下来的问题就是:interceptors是什么时候、怎么创建出来的?

拦截器的初始化

从RestTemplate的父类InterceptingHttpAccessor简单追踪一下,不难发现他的interceptors是通过setInterceptors方法赋值的,然后借助开发工具的帮助:
在这里插入图片描述
发现LoadBalanceAutoConfiguration调用了setInterceptors方法。

这个LoadBalanceAutoConfiguration的命名方式是不是很熟悉啊?我们前面分析过SpringBoot的自动配置,就是各种xxxxAutoConfiguration命名的类负责具体的自动配置任务的。

简单了解了下,LoadBalanceAutoConfiguration就是SpringBoot的自动配置类。我们找到LoadBalanceAutoConfiguration类在spring-cloud-commons包下,按图索骥,我们找到了包下的/META-INF/spring.factories文件:
在这里插入图片描述
因此我们知道,SpringBoot的自动配置机制会通过调用LoadBalanceAutoConfiguration类来完成LoadBalance的相关初始化工作。
所以我们接下来的工作就是要研究LoadBalanceAutoConfiguration。

LoadBalanceAutoConfiguration

第一步,从LoadBalanceAutoConfiguration类的源码知道他要通过restTemplateCustomizer方法加载一个RestTemplateCustomizer对象,方法需要一个参数LoadBalancerInterceptor:

		@Bean@ConditionalOnMissingBeanpublic RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {return restTemplate -> {List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());list.add(loadBalancerInterceptor);restTemplate.setInterceptors(list);};}

先看一眼RestTemplateCustomizer类,只有一个customize方法,该方法有一个参数RestTemplate:

public interface RestTemplateCustomizer {void customize(RestTemplate restTemplate);}

这个customize方法已经在restTemplateCustomizer方法中通过lamda表达式定义出来了,代码逻辑很简单:

就是要将LoadBalancerInterceptor拦截器对象通过调用RestTemplate的setInterceptors方法加入到RestTemplate的interceptors中!

似乎快要摸到开关了!!!

那么我们现在又冒出了以下几个问题:

  1. 这个LoadBalancerInterceptor从哪里来?
  2. RestTemplate从哪里来?
  3. 什么时候调用这个已经注入到Spring Ioc容器中的RestTemplateCustomizer的customize方法?

第1、第2两个问题其实很简单。看代码:

    @Configuration(proxyBeanMethods = false)@Conditional(RetryMissingOrDisabledCondition.class)static class LoadBalancerInterceptorConfig {@Beanpublic LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);}...
}

LoadBalancerAutoConfiguration类中有一个静态配置类LoadBalancerInterceptorConfig,通过loadBalancerInterceptor方法注入了LoadBalancerInterceptor 对象,创建对象的时候通过参数注入了loadBalancerClient和LoadBalancerRequestFactory。

第2个问题,RestTemplate的注入,其实是我们从应用层通过@Bean注入的,同时加了@LoadBalanced注解。

现在我们来回答第3个问题:

什么时候调用这个已经注入到Spring Ioc容器中的RestTemplateCustomizer的customize方法?

比前两个问题稍稍复杂了一点:

	@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {return () -> restTemplateCustomizers.ifAvailable(customizers -> {for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {for (RestTemplateCustomizer customizer : customizers) {customizer.customize(restTemplate);}}});}

首先看到标注了@Autowire和@LoadBalanced注解的RestTemplates列表的注入,意思是: 如果有加了@LoadBalanced注解的RestTemplates bean的话,就自动装配到restTemplates 变量中。

之后,通过loadBalancedRestTemplateInitializerDeprecated方法注入了一个SmartInitializingSingleton,我们知道SmartInitializingSingleton在装配到Spring Ioc之后会调用他的afterSingletonsInstantiated()方法。这里注入的SmartInitializingSingleton通过lamda实现了afterSingletonsInstantiated()方法,代码逻辑:通过方法参数打包注入到Ioc容器中的RestTemplateCustomizer(前面讲过了,他已经注入到IoC容器中了)到restTemplateCustomizers中,然后遍历restTemplates(这个list在前面说过了,已经把我们通过@Bean和@LoadBalanced注解的RestTemplate对象注入进来了)、针对每一个RestTemplate再遍历restTemplateCustomizers中的RestTemplateCustomizer对象,逐个调用他的customize方法。

OK!对于@LoadBalanced注解从应用、到初始化、生效机制,我们就分析清楚了。

最后还遗留两个小问题,初始化和应用两端各一个:初始化过程中的装配到loadBalancerInterceptor对象中的LoadBalancerClient具体是什么对象、什么时候注入的?应用端最终的负载均衡策略、负载均衡实现逻辑,我们还没具体分析。

下一篇文章解决上述两个问题。

相关文章:

Spring cloud负载均衡 @LoadBalanced注解原理

接上一篇文章&#xff0c;案例代码也在上一篇文章的基础上。 在上一篇文章的案例中&#xff0c;我们创建了作为Eureka server的Eureka注册中心服务、作为Eureka client的userservice、orderservice。 orderservice引入RestTemplate&#xff0c;加入了LoadBalanced注解&#x…...

C#when关键字

在C#中&#xff0c;when关键字用于在模式匹配表达式中添加条件。它允许您在模式匹配的过程中指定额外的条件&#xff0c;以进一步过滤匹配的模式。当模式匹配和附加条件都为真时&#xff0c;相关的代码块将被执行。 以下是when关键字的详细解释以及示例说明&#xff1a; 语法…...

华为政企无线局域网产品集

产品类型产品型号产品说明 室内接入点AirEngine 5760-51AirEngine 5760-51是华为发布的支持Wi-Fi 6&#xff08;802.11ax&#xff09;标准的新一代室内AP&#xff0c;适合部署在企业办公、零售、制造等场景。 通过软件定义射频&#xff0c;能够在双频、三频模式灵活切换&a…...

解释 RESTful API

RESTful API是一种基于HTTP协议的API设计风格&#xff0c;它的核心思想是将每个资源&#xff08;如用户、订单等&#xff09;抽象成一个URI&#xff08;统一资源标识符&#xff09;&#xff0c;通过HTTP协议定义的方法&#xff08;如GET、POST、PUT、DELETE等&#xff09;对资源…...

青翼科技-国产化ARM系列TES720D-KIT

板卡概述 TES720D-KIT是专门针对我司TES720D&#xff08;基于复旦微FMQL20S400的全国产化ARM核心板&#xff09;的一套开发套件&#xff0c;它包含1个TES720D核心板&#xff0c;加上一个TES720D-EXT扩展底板。 FMQL20S400是复旦微电子研制的全可编程融合芯片&#xff0c;在单…...

Tomcat为什么支持线程池?

Tomcat作为一个Java Servlet容器&#xff0c;支持线程池是因为它能够处理多个并发请求。这些请求可以是对Web应用程序的HTTP请求、Servlet的请求&#xff0c;或其他支持的协议。 支持线程池的主要原因包括&#xff1a; 并发处理能力&#xff1a; 提高性能&#xff1a; 使用线程…...

Mac安装VMware

去官网下载一下VMware Download VMware Fusion | VMware | SG 下载完成之后&#xff0c;打开直接闪退&#xff0c;参考这篇文章解决 解决macOS13安装Fusion13闪退的问题-CSDN博客 然后即可成功顺行...

项目部署文档

申请SSL证书 先申请,用免费的 下载证书 先将下载下来的保存起来 服务器安装JDK: 创建develop目录 mkdir /usr/local/develop/ 把JDK压缩包上传到/usr/local/develop/目录 解压安装包 并且将安装到指定目录 tar -zxvf /usr/local/develop/jdk-8u191-linux-x64.tar.gz -C /us…...

HTML+CSS阶段知识点梳理

目录 一、简单的网页结构 二、常用标签 三、列表 四、CSS引入方式 五、常用选择器 1、标签&#xff08;元素&#xff09;选择器 2、id选择器 3、class选择器 4、通配选择器 5、复合选择器 6、关系选择器 7、属性选择器 8、伪类选择器 9、a元素的伪类 10、伪元素…...

网易按照作者批量采集新闻资讯软件说明文档

大家好&#xff0c;我是淘小白~ 今天给大家介绍的爬虫软件是网易按照作者采集的软件 1、软件语言&#xff1a; Python 2、使用到的工具 Python selenium库、谷歌浏览器、谷歌浏览器驱动 3、文件说明&#xff1a; 4、配置文件说明&#xff1a; 5、环境配置 安装Python&am…...

SwiftUI 代码调试之都是“变心”惹的祸

0. 概览 这是一段非常简单的 SwiftUI 代码&#xff0c;我们将 Item 数组传递到子视图并在子视图中对其进行修改&#xff0c;修改的结果会立即在主视图中反映出来。 不幸的是&#xff0c;当我们修改 Item 名称时却发现不能连续输入&#xff1a;每次敲一个字符键盘都会立即收起并…...

u20.04安装slam库

git clone https://github.com/strasdat/Sophus.git // 下载的最新版是模板类的 git checkout a621ff // 切换为非模板类的历史版本 模板类Sophus的依赖库是Eigen(版本为3.3.X)和fmt&#xff0c;需提前安装好Eigen库和fmt库 git clone https://github.c…...

齐纳二极管,肖特基二极管,瞬态电压抑制二极管

普通二极管&#xff0c;齐纳二极管&#xff0c;肖特基二极管的符号&#xff1a; 瞬态电压抑制&#xff08;TVS&#xff09;二极管是一种特殊的齐纳二极管&#xff0c;其符号如下&#xff1a; 普通二极管 普通二极管由n类型 的半导体和p类型的半导体结合而成。 硅材料制成的二…...

axios 全局错误处理和请求取消

这两个功能都是用拦截器实现。 前景提要&#xff1a; ts 简易封装 axios&#xff0c;统一 API 实现在 config 中配置开关拦截器 全局错误处理 在构造函数中&#xff0c;添加一个响应拦截器即可。在构造函数中注册拦截器的好处是&#xff0c;无论怎么实例化封装类&#xff0c…...

无法加载文件 C:\Program Files\nodejs\cnpm.ps1,因为在此系统上禁止运行脚本。有

cnpm : 无法加载文件 C:\Program Files\nodejs\cnpm.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Poli cies。 所在位置 行:1 字符: 1 cnpm run debug ~~~~ Categ…...

学电脑编程零基础,计算机编程入门先学什么

学电脑编程零基础&#xff0c;计算机编程入门先学什么&#xff0c;建议先从容易学习的语言入手&#xff0c;比如中文编程。 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&…...

SQL左连接实战案例

要求&#xff1a;用表df1和表df2的数据&#xff0c;得到df3 一、创建表 CREATE TABLE df1 (姓名 varchar(255) DEFAULT NULL,年龄 int DEFAULT NULL,部门 varchar(255) DEFAULT NULL,id int DEFAULT NULL );CREATE TABLE df2 (部门 varchar(255) DEFAULT NULL,年龄 int DEFAU…...

2、Sentinel基本应用限流规则(2)

2.2.1 是什么 Sentinel 是阿里中间件团队开源的&#xff0c;面向分布式服务架构的轻量级高可用流量控制组件&#xff0c;主要以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。 2.2.2 基本概念 • 资源 (需要被保护的东西…...

Qt的事件

2023年11月5日&#xff0c;周日上午 还没写完&#xff0c;不定期更新 目录 事件处理函数的字体特点Qt事件处理的工作原理一些常用的事件处理函数Qt中的事件类型QEvent类的type成员函数可以用来判断事件的类型事件的类型有哪些&#xff1f;有多少种事件类 事件处理函数的字体特…...

MTK联发科天玑9000旗舰5G移动平台处理器_MT6983芯片定制开发

MT6983天玑9000采用台积电4纳米工艺制程&#xff0c;CPU采用“134”三丛集Armv9架构&#xff0c;APU性能提升&#xff0c;ISP处理速度提升&#xff0c;最高支持3.2亿像素摄像头&#xff0c;采用Mali-G710十核GPU&#xff0c;搭载R16 5G调制解调器。 MT6983天玑9000芯片基本概…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...

Linux安全加固:从攻防视角构建系统免疫

Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...