2.OpenFeign 入门与使用
- 1.什么是 OpenFeign?
- 2.OpenFeign 基础使用
- 2.1 添加依赖
- 2.2 配置 Nacos 服务端信息
- 2.3 项目中开启 OpenFeign
- 2.4 编写 OpenFeign 调用代码
- 2.5 调用 OpenFeign 接口代码
- 3.超时重试机制
- 3.1 配置超时重试
- 3.2 覆盖 Retryer
- 4.自定义超时重试机制
- 4.1 自定义超时重试类
- 4.2 设置配置文件
- 5.超时重试底层实现
- 5.1 超时底层实现
- 5.2 重试底层实现
1.什么是 OpenFeign?
OpenFeign 的全称为 Spring Cloud OpenFeign(下文简称 OpenFeign),是 Spring Cloud 团队开发的一款基于Feign 的框架,声明式 Web 服务客户端。
Feign 是 Netfix 开源的一个声明式的 Web 服务客户端,它简化了基于 HTTP 的服务调用,使得服务间的通信变得更加简单和灵活。Feiqn 通过定义接口、注解和动态代理等方式,将服务调用的过程封装起来,开发者只需要定义服务接口,而无需关心底层的 HTTP 请求和序列化等细节。
OpenFeign 功能升级
OpenFeign 在 Feign 的基础上提供了以下增强和扩展功能:
- 更好的集成 Spring Cloud 组件:OpenFeign 与 Spring Cloud 其他组件(如服务发现、负载均衡等)紧密集成,可以无缝地与其他 Spring Cloud 组件一起使用。
- 支持 @Feignclient 注解:0penFeign 引入了 @FeignClient 注解作为 Feign 客户端的标识,可以方便地定义和使用远程服务的声明式接口。
- 错误处理改进:OpenFeiqn 对异常的处理做了增强,提供了更好的错误信息和异常处理机制,使得开发者可以3更方便地进行错误处理。例如 OpenFeiqn 提供的错误解码器(DefaultErrorDecoder)和回退策略(当服务端返回错误响应或请求失败时,OpenFeiqn 会调用回退策略中的逻辑,提供一个默认的处理结果)。
- 更丰富的配置项:OpenFeiqn 提供了丰富的配置选项,可以对 Feiqn 客户端的行为进行灵活的配置,例如超时设置、重试策略等。
2.OpenFeign 基础使用
2.1 添加依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
2.2 配置 Nacos 服务端信息
spring:application:name: nacos-consumer-democloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosregister-enabled: false # 消费者(不需要将此服务注册到nacos)openfeign:client:config:default:connect-timeout: 1000 # 连接超时时间read-timeout: 1000 # 读取超时时间retryer: com.example.consumer.config.CustomRetryer
server:port: 8080
2.3 项目中开启 OpenFeign
@SpringBootApplication
@EnableFeignClients // 开启 OpenFeign
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}}
2.4 编写 OpenFeign 调用代码
@Service
@FeignClient("nacos-discovery-demo") // 表示调用 nacos 中的 nacos-discovery-demo 服务
public interface UserService {@RequestMapping("/user/getnamebyid") // 调用生产者的"/user/getnamebyid"接口public String getNameById(@RequestParam("id") int id);}
2.5 调用 OpenFeign 接口代码
@RestController
public class BusinessController {@Autowiredprivate UserService userService;@RequestMapping("/getnamebyid")public String getNameById(Integer id){return userService.getNameById(id);}}
3.超时重试机制
在微服务架构中,服务之间是通过网络进行通信的,而网络是非常复杂性和不稳定的,所以在调用服务时可能会失败或超时,那么在这种情况下,我们就需要给 OpenFeign 配置超时重试机制了。
什么是超时重试?
答:超时重试是一种在网络通信中常用的策略,用于处理请求在一定时间内未能得到响应或得到超时响应的情况。当发起请求后,如果在规定的时间内没有得到预期的响应,就会触发超时重试机制,重新发送请求。超时重试的主要目的是提高请求的可靠性和稳定性,以应对网络不稳定、服务不可用、响应延迟等不确定因素OpenFeign 默认情况下是不会自动开启超时重试的,所以想要开启超时重试,需要通过以下2 步来实现:
- 配置超时重试,
- 覆盖 Retryer 对象
3.1 配置超时重试
spring:application:name: nacos-consumer-democloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosregister-enabled: false # 消费者(不需要将此服务注册到nacos)openfeign:client:config:default:connect-timeout: 1000 # 连接超时时间read-timeout: 1000 # 读取超时时间retryer: com.example.consumer.config.CustomRetryer
server:port: 8080
3.2 覆盖 Retryer
@Configuration // 将当前对象存储在 IoC 容器
public class RetryerConfig {@Beanpublic Retryer retryer(){return new Retryer.Default(1000,1000,3);}
}
4.自定义超时重试机制
自定义超时重试机制的实现分为以下两步:
- 自定义超时重试类(实现 Retryer 接口,并重写 continueOrPropagate 方法)
- 设置配置文件。
4.1 自定义超时重试类
常见的超时重试策略有以下三种:
- 固定间隔重试:每次重试之间的时间间隔固定不变,例如每次重试之间相隔1秒。
- 指数重试:每次重试之间的时间间隔按指数递增。例如,初始间隔为1秒,每次重试后加倍,即第一次1 秒,第二次 2 秒,第三次 4秒,以此类推。
- 随机间隔重试:每次重试之间的时间间隔是随机的,通过引入随机性来防止多个失败请求同时发生。例如,每3.次重试的时间间隔在一定范围内随机选择。
package com.example.consumer.config;import feign.RetryableException;
import feign.Retryer;import java.time.LocalDateTime;/*** 自定义超时重传类*/
public class CustomRetryer implements Retryer {private final int maxAttempts; // 最大尝试次数private final long backoff; // 超时间隔时间int attempt; // 当前尝试次数public CustomRetryer() {this.maxAttempts = 3;this.backoff = 1000L;this.attempt = 0;}@Overridepublic void continueOrPropagate(RetryableException e) {if (attempt++ >= maxAttempts) {throw e;}long interval = this.backoff; // 重试间隔时间System.out.println(LocalDateTime.now() + " | 执行一次重试:" + interval);try {Thread.sleep(interval * attempt);} catch (InterruptedException ex) {throw new RuntimeException(ex);}}@Overridepublic Retryer clone() {return new CustomRetryer();}
}
4.2 设置配置文件
spring:application:name: nacos-consumer-democloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosregister-enabled: false # 消费者(不需要将此服务注册到nacos)openfeign:client:config:default:connect-timeout: 1000 # 连接超时时间read-timeout: 1000 # 读取超时时间retryer: com.example.consumer.config.CustomRetryer
server:port: 8080
5.超时重试底层实现
5.1 超时底层实现
OpenFeign 超时的底层实现是通过配置底层的 HTTP 客户端来实现的。Openfeign 允许你在请求连接和读取数据阶段设置超时时间,具体的超时配置可以通过设置 HTP 客户端的连接超时(connectTimeout)和读取超时(readTimeout)来实现,你可以在配置文件中设置超时参数。OpenFeiqn 底层的 HTTP 客户端,可以使用 Apache HttpClient 或 OkHtpClient 来实现,默认使用的是 ApacheHttpClient 实现的。
5.2 重试底层实现
@Overridepublic Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);Retryer retryer = this.retryer.clone();try {if (methodInfo.isAsyncReturnType()) {return executeAndDecode(template, options, retryer);} else {return executeAndDecode(template, options, retryer).join();}} catch (CompletionException e) {throw e.getCause();}}private CompletableFuture<Object> executeAndDecode(RequestTemplate template,Options options,Retryer retryer) {CancellableFuture<Object> resultFuture = new CancellableFuture<>();executeAndDecode(template, options).whenComplete((response, throwable) -> {if (throwable != null) {if (!resultFuture.isDone() && shouldRetry(retryer, throwable, resultFuture)) {if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}resultFuture.setInner(executeAndDecode(template, options, retryer));}} else {resultFuture.complete(response);}});return resultFuture;}
/** Copyright 2012-2022 The Feign Authors** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except* in compliance with the License. You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software distributed under the License* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express* or implied. See the License for the specific language governing permissions and limitations under* the License.*/
package feign;import static java.util.concurrent.TimeUnit.SECONDS;/*** Cloned for each invocation to {@link Client#execute(Request, feign.Request.Options)}.* Implementations may keep state to determine if retry operations should continue or not.*/
public interface Retryer extends Cloneable {/*** if retry is permitted, return (possibly after sleeping). Otherwise propagate the exception.*/void continueOrPropagate(RetryableException e);Retryer clone();class Default implements Retryer {private final int maxAttempts;private final long period;private final long maxPeriod;int attempt;long sleptForMillis;public Default() {this(100, SECONDS.toMillis(1), 5);}public Default(long period, long maxPeriod, int maxAttempts) {this.period = period;this.maxPeriod = maxPeriod;this.maxAttempts = maxAttempts;this.attempt = 1;}// visible for testing;protected long currentTimeMillis() {return System.currentTimeMillis();}public void continueOrPropagate(RetryableException e) {if (attempt++ >= maxAttempts) {throw e;}long interval;if (e.retryAfter() != null) {interval = e.retryAfter().getTime() - currentTimeMillis();if (interval > maxPeriod) {interval = maxPeriod;}if (interval < 0) {return;}} else {interval = nextMaxInterval();}try {Thread.sleep(interval);} catch (InterruptedException ignored) {Thread.currentThread().interrupt();throw e;}sleptForMillis += interval;}/*** Calculates the time interval to a retry attempt. <br>* The interval increases exponentially with each attempt, at a rate of nextInterval *= 1.5* (where 1.5 is the backoff factor), to the maximum interval.** @return time in milliseconds from now until the next attempt.*/long nextMaxInterval() {long interval = (long) (period * Math.pow(1.5, attempt - 1));return interval > maxPeriod ? maxPeriod : interval;}@Overridepublic Retryer clone() {return new Default(period, maxPeriod, maxAttempts);}}/*** Implementation that never retries request. It propagates the RetryableException.*/Retryer NEVER_RETRY = new Retryer() {@Overridepublic void continueOrPropagate(RetryableException e) {throw e;}@Overridepublic Retryer clone() {return this;}};
}
所以,OpenFeign 的重试功能是通过其内置的 Retryer 组件和底层的 HTTP 客户端实现的。Retryer 组件提供了重试策略的逻辑实现,而远程接口则通过 HTTP 客户端来完成调用。