问题:在项目整合了集成了seata来做分布式事务,后面整合sleuth来做服务追踪后发现消费者使用feign调不通提供者服务了,当时检查了提供者服务都是否启动了,提供者是否注册nacos成功了,也检查了feign调用的提供者的在nacos上注册的服务名,这些都没有问题,就是使用feign调不通服务,就像提供者服务没有启动一样,我引入的依赖是spring-cloud-alibaba-seata和spring-cloud-starter-zipkin,一直报错:
Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: 192.168.248.1
at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:184) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
at rx.Observable.subscribe(Observable.java:10423) ~[rxjava-1.3.8.jar:1.3.8]
at rx.Observable.subscribe(Observable.java:10390) ~[rxjava-1.3.8.jar:1.3.8]
at rx.observables.BlockingObservable.blockForSingle(BlockingObservable.java:443) ~[rxjava-1.3.8.jar:1.3.8]
at rx.observables.BlockingObservable.single(BlockingObservable.java:340) ~[rxjava-1.3.8.jar:1.3.8]
at com.netflix.client.AbstractLoadBalancerAwareClient.executeWithLoadBalancer(AbstractLoadBalancerAwareClient.java:112) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:83) ~[spring-cloud-openfeign-core-2.2.2.RELEASE.jar:2.2.2.RELEASE]
... 120 common frames omitted
后面在seata官网的issue发现使用sleuth和seata同时存在,会导致feign调用报错,seata官网issue链接:
https://github.com/seata/seata/issues/2544
官网建议:
Spring Cloud Sleuth默认通过TraceFeignClientAutoConfiguration提供feign的集成,可以设置spring.sleuth.feign.enabled为false来使其无效
但是这种修改方式把sleuth关闭了,导致在zipkin控制台看到的接口调用栈都是没有任何关联的,
如果你需要使用seata,但是又不想关闭sleuth,则本篇博客就来解决这个问题。
分布式系统中seata如何传递xid:
在seata的官方文档中业务系统集成seata客户端,有五个步骤。在最后一个步骤:
步骤五:实现xid跨服务传递
手动 参考源码integration文件夹下的各种rpc实现 module
自动 springCloud用户可以引入spring-cloud-alibaba-seata,内部已经实现xid传递
这个意思也就是:需要把全局事务的xid,透传到构成全局事务的各个子服务中。
自动透传xid,可以直接引用 spring-cloud-alibaba-seata (由于这个包和sleuth冲突,所以我们主要看手动透传xid的方案。)
手动透传xid,可以参考官方源码的实现: https://github.com/seata/seata/tree/develop/integration
解决思路:
这里我就以http的实现思路来简单说一下:
如果构成全局事务的各个自服务是使用http协议的,比如使用的是SpringCloud。对于http协议透传xid的方式很简单:
在consumer端,只需要获取到xid然后把xid放到请求头中;
在provider端解析http的请求头,然后使用RootContext.bind(xid)绑定xid即可。
解决问题:
在consumer端:
1.修改启动类,加(exclude = {SeataFeignClientAutoConfiguration.class})
@SpringBootApplication(exclude = {SeataFeignClientAutoConfiguration.class})
2.可以在每个feign请求头中将全局事务的xid传过去,本人推荐使用全局请求的头中加上xid,如:
import feign.RequestInterceptor;
import feign.RequestTemplate;
import io.seata.core.context.RootContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SeataFeignInterceptor implements RequestInterceptor {
private static final Logger logger = LoggerFactory.getLogger(SeataFeignInterceptor.class);
@Override
public void apply(RequestTemplate request) {
String xid = RootContext.getXID();
if (xid != null && !xid.trim().isEmpty()) {
logger.info("seata - feign add seata-xid http request header [{}]", xid);
request.header(RootContext.KEY_XID, xid);
}
}
}
注册该拦截器
import com.db.cloud.serviceconsume.common.handle.SeataFeignInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CommonConfig {
@Bean
public SeataFeignInterceptor seataFeignInterceptor() {
return new SeataFeignInterceptor();
}
}
在provider端,是否实现了WebMvcConfigurer,如果是,请参考com.alibaba.cloud.seata.web.SeataHandlerInterceptorConfiguration#addInterceptors的方法.把SeataHandlerInterceptor加入到你的拦截链路中.因为在consumer在每个请求头加入了全局事务的xid,所以需要在拦截器中使用该xid,而seata使用http请求时使用的是SeataHandlerInterceptor,此时需在拦截链路中加入该拦截器,如何使用xid可以看看SeataHandlerInterceptor的源码。
WebMvcConfigurer配置添加SeataHandlerInterceptor如下:
import com.alibaba.cloud.seata.web.SeataHandlerInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns(new String[]{"/**"});
}
}
参考链接:
https://github.com/seata/seata/issues/2544
https://seata.io/zh-cn/docs/overview/faq.html
https://github.com/seata/seata/tree/develop/integration/http/src/main/java/io/seata/integration/http