最新实战案例锦集:《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接口定义
(
url = "http://localhost:8088/demos",
name = "demoService",
configuration = DemoFeignConfiguration.class,
fallback = DemoFeignFallback.class
primary = true
)
public interface DemoFeign {
"/info/{id}") (
public Object info( ("id") Integer id) ;
"/map") (
public Object map( Map<String, Object> 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: true
resilience4j:
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的整合为微服务开发提供了一种强大而灵活的解决方案。提高了系统的可用性和稳定性。在微服务架构中,这有助于降低故障风险、优化系统性能。
以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏
推荐文章
强大!基于Spring Boot动态注册 / 删除Controller接口(支持动态上传)
完美!SpringBoot + HTML模板高效生成PDF文档