工作总结——RestTemplate请求时间过长问题

出现场景

项目使用微服务,将每个数据源拆分成了一个服务,并通过Eureka注册,web服务通过配置的不同数据源的url调用各个数据源的服务从而获取相应数据。

但近日部署后在跑全量更新缓存的过程中,发现了一个严重问题。缓存更新不完整,通过日志信息定位到,每次在调用MongoDB数据源微服务时,会发生无响应,导致更新任务无法继续进行下去,耗费大量时间。

而调用各个服务的接口正是使用RestTemplate实现的,但经过查看,RestTemplate的Bean并未进行超时配置。这就很方了,RestTemplate本身在不配置超时的时候,是不存在超时机制的,只能通过tcp协议的响应超时来结束连接,那要等多久?等不了的,系统等不了,我更等不了。

解决方案

嗯,反正问题是很好解决的,如下:

        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setConnectionRequestTimeout(10*1000);
        httpRequestFactory.setConnectTimeout(10*1000);
        httpRequestFactory.setReadTimeout(10*1000);
        return new RestTemplate(httpRequestFactory);

完事儿,在加载RestTemplate的bean的过程中,注入超时,具体的超时时间,是根据normal情况下系统跑出来的耗时时长进行的设置。

分析

借此机会,准备好好分析下里面的配置项,从而达到,定义出一个合理的RestTemplate的Bean。

  • setConnectTimeout(int timeout)方法

设置基础的HttpClient连接超时。

	public void setConnectTimeout(int timeout) {
		Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
		this.requestConfig = requestConfigBuilder().setConnectTimeout(timeout).build();
		setLegacyConnectionTimeout(getHttpClient(), timeout);
	}
  • setBufferRequestBody(boolean bufferRequestBody)

该设置的默认值是ture,含义是使用内部缓存将请求的Body体进行缓冲处理。这里呢,如果通过POST或PUT发送大量数据时,建议将此属性更改为false,以免内存不足。

	public void setBufferRequestBody(boolean bufferRequestBody) {
		this.bufferRequestBody = bufferRequestBody;
	}
  • setConnectionRequestTimeout(int connectionRequestTimeout)

设置使用基础HttpClient从连接管理器请求连接时使用的超时(以毫秒为单位)。可以看到,这里的超时时间是允许为0的,那么问题来了,如果为零会发生什么事情:嗯没错,就是无限超时。

	public void setConnectionRequestTimeout(int connectionRequestTimeout) {
		this.requestConfig = requestConfigBuilder().setConnectionRequestTimeout(connectionRequestTimeout).build();
	}
  • setHttpClient(HttpClient httpClient)

哎哟,看到这个方法,我有一个大胆的想法。。。可以自实现一个自定义的HttpClient了。嗯嗯不错

	public void setHttpClient(HttpClient httpClient) {
		this.httpClient = httpClient;
	}
  • setReadTimeout(int timeout)

该超时设置的是Socket读取超时时间。

	public void setReadTimeout(int timeout) {
		Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
		this.requestConfig = requestConfigBuilder().setSocketTimeout(timeout).build();
		setLegacySocketTimeout(getHttpClient(), timeout);
	}
  • setLegacyConnectionTimeout

将指定的连接超时应用于已弃用的HttpClient实现。

	private void setLegacyConnectionTimeout(HttpClient client, int timeout) {
		if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) {
			client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
		}
	}
  • setLegacySocketTimeout

将指定的Socket超时应用于已弃用的HttpClient实现。

	private void setLegacySocketTimeout(HttpClient client, int timeout) {
		if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) {
			client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
		}
	}

注意:这里的已弃用的HttpClient的实现主要是指HttpClient 4.3版本之前的实现。

 

 

 

 

 

 

 

 

<think>嗯,用户现在想了解Feign的工作原理,特别是底层是否使用RestTemplate和Ribbon进行服务调用和负载均衡。用户之前已经讨论过JDK和CGLIB的区别,现在转向了微服务调用组件的问题,说明用户可能在深入学习Spring Cloud相关技术。 从用户提供的引用来看,用户已经掌握了一些基础:知道Feign整合了Ribbon和Eureka[^2],也了解RestTemplate是Spring提供的HTTP客户端[^3]。但用户可能被Feign的声明式接口迷惑,不确定底层是否真的复用这些组件。 需要重点解释几个技术衔接点: 1. Feign的接口动态代理机制(这里可以和之前讨论的JDK/CGLIB关联起来) 2. 如何通过Ribbon实现负载均衡(结合用户提到的轮询策略[^1]) 3. 为什么实际开发中不需要显式使用RestTemplate(因为Feign封装了) 用户可能存在的深层疑问: - 既然底层还是RestTemplate+Ribbon,为什么还要用Feign? - Feign的封装到底带来了什么便利? - 性能上是否有损耗? 在回答时要特别注意: 1. 明确肯定用户猜测——Feign确实基于这些组件 2. 用分层结构展示封装关系 3. 强调声明式编程的收益(代码简化、维护性) 4. 提及新版替代方案(如WebClient)避免知识过时 用户引用的资料提到“Feign整合了Ribbon和Eureka”[^2],这个描述很准确,可以重点展开。另外用户注意到依赖配置问题[^4],说明可能正在实践,可以补充配置注意事项。</think>### Feign底层工作原理:与RestTemplate和Ribbon的关系 #### 1. **核心结论** **Feign底层确实通过集成RestTemplate和Ribbon实现服务调用与负载均衡**,但通过声明式接口进行了高度封装,开发者无需手动编写相关代码[^2][^4]。 --- #### 2. **工作流程与组件关系** ```mermaid graph TD A[Feign客户端接口] -->|动态代理生成| B(Feign代理对象) B --> C{调用方法} C -->|封装HTTP请求| D[底层RestTemplate] D -->|负载均衡| E[Ribbon] E -->|选择服务实例| F[Eureka注册中心] F -->|返回实例地址| E E -->|发送请求| G[服务提供者] ``` ##### 关键步骤解析: 1. **动态代理生成**(基于JDK/CGLIB) Feign在启动时通过动态代理技术(默认JDK动态代理)为声明式接口(如`@FeignClient("service-name")`)生成代理对象[^2]。 2. **HTTP请求封装** 代理对象将接口方法转化为HTTP请求,**底层默认使用RestTemplate处理HTTP通信**(Spring Cloud OpenFeign 1.x~2.x版本)[^3][^4]。 3. **负载均衡执行** RestTemplate的调用被**Ribbon拦截**,自动从Eureka获取服务实例列表,并应用负载均衡策略(如轮询)选择目标实例[^1][^2]。 4. **服务调用** 最终由Ribbon将请求发送到选定的服务实例,完成调用。 --- #### 3. **技术栈验证** | 组件 | 作用 | 是否被Feign集成 | |---------------|----------------------------------------------------------------------|-----------------| | **RestTemplate** | 执行HTTP请求,处理请求/响应序列化 | ✔️ 默认底层使用 | | **Ribbon** | 服务发现、负载均衡(从Eureka获取实例列表 + 选择策略) | ✔️ 原生整合 | | **Eureka** | 服务注册与发现,提供可用实例信息 | ✔️ 依赖注入 | > **注**:Spring Cloud 2020.0(Feign 3.x)后逐步用`LoadBalancerClient`替代Ribbon,但负载均衡逻辑不变[^1]。 --- #### 4. **Feign的封装优势** 虽然底层依赖RestTemplate+Ribbon,但Feign通过声明式接口提供显著优化: - **代码简化** 无需重复编写RestTemplate调用代码,只需定义接口: ```java @FeignClient(name = "user-service") public interface UserService { @GetMapping("/users/{id}") User getUser(@PathVariable("id") Long id); } ``` - **负载均衡透明化** 开发者无需关注Ribbon策略配置,默认轮询(可自定义)[^1]。 - **整合容错机制** 天然支持Hystrix断路器(通过`fallback`属性)[^2]。 --- #### 5. **配置依赖关系** Feign的依赖已包含Ribbon,无需单独引入: ```xml <!-- pom.xml --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> ``` > 即使显式添加`spring-cloud-starter-ribbon`也不冲突,但冗余[^1][^4]。 --- ### 总结 1. **底层机制**:Feign通过**RestTemplate执行HTTP请求** + **Ribbon实现负载均衡**。 2. **封装价值**:将分布式调用简化为“类本地方法调用”,提升开发效率。 3. **演进趋势**:新版Spring Cloud中,底层可能迁移至`LoadBalancerClient`,但抽象层行为一致[^1]。 --- ### 相关问题 1. **Feign如何实现动态代理?与JDK/CGLIB代理有何关联?** (探讨接口方法到HTTP请求的转换机制) 2. **在Spring Cloud最新版本中,Feign是否仍依赖Ribbon?** (分析`LoadBalancerClient`替代方案及兼容性) 3. **如何自定义Feign的HTTP客户端(如替换RestTemplate为WebClient)?** (讨论配置`feign.httpclient`或自定义`Client`实现) 4. **Feign与OpenFeign有何区别?** (对比Spring Cloud Feign和Netflix Feign的演进) 5. **Feign的日志增强机制如何实现?** (解析`FeignLoggerFactory`配置与请求/响应日志捕获)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值