在微服务架构中,Feign 调用可能会因为服务间的依赖关系导致 循环调用(即服务 A 调用服务 B,服务 B 又调用服务 A),从而引发无限递归、系统崩溃等问题。为了避免这种情况,可以通过以下方式实现 Feign 调用的循环调用检测:
1. 使用请求头传递调用链信息
通过在请求头中传递调用链信息,可以检测是否存在循环调用。
(1)实现步骤
- 定义调用链标识:
- 在每次 Feign 调用时,生成一个唯一的调用链标识(如 UUID),并将其添加到请求头中。
- 传递调用链信息:
- 在 Feign 客户端拦截器中,将调用链信息添加到请求头中。
- 检测循环调用:
- 在服务端拦截器中,检查请求头中的调用链信息,如果发现当前服务已经在调用链中,则抛出异常。
(2)代码示例
Feign 客户端拦截器
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 生成调用链标识
String callChainId = UUID.randomUUID().toString();
// 将调用链标识添加到请求头
template.header("Call-Chain-Id", callChainId);
}
}
服务端拦截器
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashSet;
import java.util.Set;
public class CallChainInterceptor implements HandlerInterceptor {
private static final ThreadLocal<Set<String>> callChainIds = ThreadLocal.withInitial(HashSet::new);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String callChainId = request.getHeader("Call-Chain-Id");
if (callChainId != null) {
Set<String> ids = callChainIds.get();
if (ids.contains(callChainId)) {
throw new RuntimeException("Detected cyclic call: " + callChainId);
}
ids.add(callChainId);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
String callChainId = request.getHeader("Call-Chain-Id");
if (callChainId != null) {
Set<String> ids = callChainIds.get();
ids.remove(callChainId);
}
}
}
注册拦截器
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CallChainInterceptor());
}
}
2. 使用分布式链路追踪工具
分布式链路追踪工具(如 Zipkin、SkyWalking)可以自动记录服务调用链,并检测循环调用。
(1)实现步骤
- 集成链路追踪工具:
- 在项目中集成 Zipkin 或 SkyWalking,记录服务调用链。
- 检测循环调用:
- 在链路追踪工具中配置规则,检测是否存在循环调用。
- 告警与处理:
- 当检测到循环调用时,触发告警并记录日志。
(2)代码示例
集成 Zipkin
在 application.yml
中配置 Zipkin:
spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1.0 # 采样率
检测循环调用
在 Zipkin 或 SkyWalking 的 UI 界面中,查看服务调用链,检查是否存在循环调用。
3. 使用服务注册中心
通过服务注册中心(如 Eureka、Nacos)记录服务间的依赖关系,检测循环调用。
(1)实现步骤
- 记录服务依赖:
- 在服务注册中心中记录每个服务的依赖关系。
- 检测循环调用:
- 在服务调用时,检查依赖关系是否存在循环。
- 告警与处理:
- 当检测到循环调用时,触发告警并记录日志。
(2)代码示例
使用 Nacos 记录依赖关系
在 application.yml
中配置 Nacos:
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
检测循环调用
在 Nacos 的 UI 界面中,查看服务依赖关系,检查是否存在循环调用。
4. 总结
实现 Feign 调用的循环调用检测可以通过以下方式:
- 请求头传递调用链信息:在请求头中传递调用链标识,检测循环调用。
- 分布式链路追踪工具:使用 Zipkin 或 SkyWalking 自动记录和检测循环调用。
- 服务注册中心:通过 Eureka 或 Nacos 记录服务依赖关系,检测循环调用。
通过以上方式,可以有效避免 Feign 调用中的循环调用问题,提升系统的稳定性和可靠性。