目录
项目初始配置:
项目模块:
订单模块order、商品模块goods
order模块关键代码
goods模块关键代码
依赖:
seata版本:
seata-server-1.6.1
子项目pom:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
父pom:
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- SpringBoot 依赖配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
</dependencies>
</dependencyManagement>
-
概述:
由请求关键字,触发异常(可由关键字确定是goods\order中心抛出异常);
测试请求:由订单中心抛出异常,看是否商品中心能回滚成功(说明:商品远程事务先执行成功)
场景复现:
由postman发起请求后,订单中心抛出异常。order表无数据但goods-chang-log表有数据,不一致
排查思路:
seata的分布式事务,在一个业务请求连路中,多个分布式业务模块共用一个事务id,即TX_XID,和全局数据一致性相关。
添加日志后,发现到商品中心的请求头有TX_XID,但seata上下文RootContext中getXID()为null。
所以初步思路,在进入子模块前,获取请求中的TX_XID,并设置到RootContext该上下文中
解决方案:
import io.seata.common.util.StringUtils;
import io.seata.core.context.RootContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.stereotype.Component;
@Component
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class MyFilter implements WebFilter {
private static final Logger log = LoggerFactory.getLogger(MyFilter.class);
/**
* 处理过程参照其servlet实现:
* @see com.alibaba.cloud.seata.web.SeataHandlerInterceptor
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String xid = RootContext.getXID();
String rpcXid = exchange.getRequest().getHeaders().getFirst(RootContext.KEY_XID);
if (log.isDebugEnabled()) {
log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid);
}
if (StringUtils.isBlank(xid) && rpcXid != null) {
RootContext.bind(rpcXid);
if (log.isDebugEnabled()) {
log.debug("bind {} to RootContext", rpcXid);
}
}
Mono<Void> mono = chain.filter(exchange);
return mono.then(Mono.defer(() -> {
if (StringUtils.isNotBlank(RootContext.getXID())) {
if (StringUtils.isNotEmpty(rpcXid)) {
String unbindXid = RootContext.unbind();
if (log.isDebugEnabled()) {
log.debug("unbind {} from RootContext", unbindXid);
}
if (!rpcXid.equalsIgnoreCase(unbindXid)) {
log.warn("xid in change during RPC from {} to {}", rpcXid, unbindXid);
if (unbindXid != null) {
RootContext.bind(unbindXid);
log.warn("bind {} back to RootContext", unbindXid);
}
}
}
}
return Mono.empty();
}));
}
}
结果:
商品中心成功获取到XID(debug时,未执行bind方法,getXID()的值完全由MyFilter设置而来)
出现回滚日志
数据一致(用例前,已清空两表)
场景用例:
- 正常调用:其它正常接口不受影响
- 异常调用:异常信息留存正常