查漏补缺!OpenFeign整合Resilience4j,你真的会用吗?

文摘   科技   2024-11-07 08:04   新疆  

最新实战案例锦集:《Spring Boot3实战案例合集》持续更新,每天至少更新一篇文章,订阅后将赠送文章最后展示的所有MD文档(学习笔记)。

环境:SpringBoot2.7.18 + SpringCloud2021.0.9



1. 简介

OpenFeign是一个Java到HTTP客户端的绑定器,灵感来自于Retrofit、JAXRS-2.0和WebSocket。Resilience4j是一个轻量级的容错库,受到Netflix Hystrix的启发,并采用JDK8函数式编程。

Resilience4j的断路器装饰器可以在服务调用过程中检测故障,并在故障发生时自动触发断路器,防止请求继续传递给故障服务,从而避免故障扩散。限流器装饰器可以在高并发场景下限制服务调用的速率,防止系统过载。

通过整合OpenFeign和Resilience4j,可以有效地保护微服务系统,提高系统的可用性和稳定性。此外,Resilience4j还提供了其他容错模式,如基于信号量的隔离、缓存、限时和请求重启等,可以根据具体需求选择适合的模式进行整合。

有关Resilience4j的详细请查看下面这篇文章:

保障系统稳定,提升接口安全性 》这篇文章详细的介绍了关于Resilience4j的使用,强烈建议学习下。

2. 实战案例

2.1 环境依赖

<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>

注意:这里我并没有引入Resilience4j。是为了说明下面的问题。

目标服务接口

@GetMapping("/info/{id}")public Object info(@PathVariable("id") Integer id, @RequestParam Map<String, Object> params) throws Exception {  // 模拟耗时  TimeUnit.SECONDS.sleep(3) ;  Map<String, Object> result = new HashMap<>() ;  result.put("code", 0) ;  result.put("data", id) ;  result.put("message", "success") ;  result.putAll(params) ;  return result ;}@PostMapping(value = "/map")public Object map(@RequestBody Map<String, Object> params) {  System.out.printf("receive data: %s%n", params) ;  Map<String, Object> res = new HashMap<>() ;  res.put("code", 0) ;  res.put("data", params) ;  // 手动抛出异常  System.out.println(1 / 0) ;  return res ;}

接下来,进行Feign接口的定义。

2.2 Feign接口定义

@FeignClient(    url = "http://localhost:8088/demos",     name = "demoService",     configuration = DemoFeignConfiguration.class,    fallback = DemoFeignFallback.class    primary = true)public interface DemoFeign {  @GetMapping("/info/{id}")  public Object info(@PathVariable("id") Integer id) ;  @PostMapping("/map")  public Object map(@RequestBody Map<StringObject> params) ;}

这里并没有使用服务发现机制,但是上面的name属性是必须的。

自定义配置类

public class DemoFeignConfiguration {  @Bean  public Logger.Level loggerLevel() {    return Logger.Level.FULL ;  }  @Bean  public DemoFeignFallback demoFeignFallback() {    return new DemoFeignFallback() ;  }}

Fallback类

public class DemoFeignFallback implements DemoFeign {
public Object info(Integer id) { return "default - " + id; }
@Override public Object map(Map<String, Object> params) { return "default - map" ;  }}

测试/map接口

fallback并没有生效。这也说明了,要使用断路器那就必须要引入相应断路器的实现。

2.3 引入断路器

<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId></dependency>

Spring Cloud提供了断路器的抽象,而resilience4j是具体的实现。如果你只是引入这个依赖还是不能生效,你还的手动开启断路器功能。

feign:  circuitbreaker:    enabled: true #默认是false

继续测试

成功!

接下来针对断路器进行常用的一些配置测试

2.4 自定义断路器配置

配置超时时间

resilience4j:  timelimiter:    configs:      default:        timeout-duration: 4s

访问/info接口

调整超时时间为2s后再次测试

输出了fallback中的默认信息。

以上是默认配置,所有的feign接口都将是统一的配置,如何针对具体的接口进行配置呢?下面分别对info和map接口进行自定义

feign:  circuitbreaker:    enabled: true    alphanumeric-ids:      enabled: trueresilience4j:  timelimiter:    configs:      default:        timeout-duration: 2s    instances:      # /info接口      DemoFeigninfoInteger:        timeout-duration: 4s      # /map接口      DemoFeignmapMap:        timeout-duration: 2s

测试

自定义的配置生效了。这里的DemoFeigninfoInteger、DemoFeignmapMap就是circuitName(断路器名称)。那这里的名称到底是根据什么规则生成的呢?上面feign.circuitbreaker.alphanumeric-ids.enabled 设置为 true,这个配置属性会将名称中的特殊字符如: #() ,过滤掉。通过名称也能发现这里的circuitName的规则是:className+methodName + parameterType(参数类型)组成。如果这些写比较麻烦而且容器错误,我们可以借助feign的工具类生成。

Method method = DemoFeign.class.getDeclaredMethod("info", Integer.class) ;String configKey = Feign.configKey(DemoFeign.class, method).replaceAll("[^a-zA-Z0-9]", "") ;System.out.println(configKey) ;

通过上面的方法就能正确的成才circuitName。而feign内部也是用的Feign#configKey生成。

注意:上面配置了feign.circuitbreaker.alphanumeric-ids.enabled=true,如果你没有配置,那么这里你就不能执行replaceAll("[^a-zA-Z0-9]", "")。

断路器默认名称生成方法如下:

public static String configKey(Class targetType, Method method) {  StringBuilder builder = new StringBuilder();  builder.append(targetType.getSimpleName());  builder.append('#').append(method.getName()).append('(');  for (Type param : method.getGenericParameterTypes()) {    param = Types.resolve(targetType, targetType, param);    builder.append(Types.getRawType(param).getSimpleName()).append(',');  }  if (method.getParameterTypes().length > 0) {    builder.deleteCharAt(builder.length() - 1);  }  return builder.append(')').toString();}

openfeign接口调用时部分源码如下:

class FeignCircuitBreakerInvocationHandler implements InvocationHandler {  private final CircuitBreakerFactory factory;  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {    // ...    // 生成断路器名称    String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);    CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName)        : factory.create(circuitName);    // 目标方法调用封装到Supplier    Supplier<Object> supplier = asSupplier(method, args);    if (this.nullableFallbackFactory != null) {      Function<Throwable, Object> fallbackFunction = throwable -> {        Object fallback = this.nullableFallbackFactory.create(throwable);        try {          return this.fallbackMethodMap.get(method).invoke(fallback, args);        }        catch (Exception exception) {          unwrapAndRethrow(exception);        }        return null;      };      return circuitBreaker.run(supplier, fallbackFunction);    }    return circuitBreaker.run(supplier);  }}

总结:OpenFeign与Resilience4j的整合为微服务开发提供了一种强大而灵活的解决方案。提高了系统的可用性和稳定性。在微服务架构中,这有助于降低故障风险、优化系统性能。

以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

如此强大的REST Client API为什么都不用?

考察你对 Spring 基本功掌握能力

Controller 接口支持多达26种参数解析方式

干货!详解Spring Boot中各种场景下的类型转换

强大!基于Spring Boot动态注册 / 删除Controller接口(支持动态上传)

基于Spring Boot实现不一样的通用DAO,值得学习

真香!Forest让HTTP请求如此简单

优雅!SpringBoot详细记录SQL执行耗时情况

当心!SpringBoot错误的数据绑定带来安全隐患

基于SpringBoot支持任意文件在线预览

完美!SpringBoot + HTML模板高效生成PDF文档

SpringBoot自带Controller接口监控,赶紧用起来

请牢记SpringBoot这7个强大的隐藏Bean

SpringBoot多租户3种架构实现方案详解

Spring全家桶实战案例源码
spring, springboot, springcloud 案例开发详解
 最新文章