使用Seata实现分布式事务处理

以畅购商城项目为例子(day15)

undolog表结构导入

核心在于对业务sql进行解析,转换成undolog,所以只要支持Fescar分布式事务的微服务数据都需要导入该表结构,我们在每个微服务的数据库中都导入下面表结构:

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=200 DEFAULT CHARSET=utf8;

Fescar工程搭建

在所有微服务工程中,不一定所有工程都需要使用分布式事务,我们可以创建一个独立的分布式事务工程,指定微服务需要支持分布式事务的时候,直接依赖独立的分布式工程即可。

搭建一个changgou-transaction-fescar提供fescar分布式事务支持。

pom.xml依赖


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>changgou-gmallartifactId>
        <groupId>com.changgougroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <groupId>com.changgougroupId>
    <artifactId>changgou-transaction-fescarartifactId>

    <description>
        分布式事务处理
    description>
    <properties>
        <fescar.version>0.4.2fescar.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>com.changgougroupId>
            <artifactId>changgou-commonartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
        <dependency>
            <groupId>com.alibaba.fescargroupId>
            <artifactId>fescar-tmartifactId>
            <version>${fescar.version}version>
        dependency>
        <dependency>
            <groupId>com.alibaba.fescargroupId>
            <artifactId>fescar-springartifactId>
            <version>${fescar.version}version>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starterartifactId>
        dependency>
    dependencies>

project>

fescar配置文件文件夹中的所有配置文件拷贝到resources工程下

在这里插入图片描述其中file.conf有2个配置

使用Seata实现分布式事务处理_第1张图片
service.vgroup_mapping.my_test_tx_group 映射到相应的 Fescar-Server 集群名称,然后再根据集群名称.grouplist 获取到可用服务列表。

TM和ProxyDataSource

核心在于对业务sql进行解析,转换成undolog,并同时入库,此时需要创建一个代理数据源,用代理数据源来实现。

要想实现全局事务管理器,需要添加一个@GlobalTransactional注解,该注解需要创建一个解析器,GlobalTransactionScanner,它是一个全局事务扫描器,用来解析带有@GlobalTransactional注解的方法,然后采用AOP的机制控制事务。

每次微服务和微服务之间相互调用,要想控制全局事务,每次TM都会请求TC生成一个XID,每次执行下一个事务,也就是调用其他微服务的时候都需要将该XID传递过去,所以我们可以每次请求的时候,都获取头中的XID,并将XID传递到下一个微服务。

4.3.1 TM和ProxyDataSource实现

创建FescarAutoConfiguration类,代码如下:

@Configuration
public class FescarAutoConfiguration {

    public static final String FESCAR_XID = "fescarXID";

    /***
     * 创建代理数据库
     * @param environment
     * @return
     */
    @Bean
    public DataSource dataSource(Environment environment){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(environment.getProperty("spring.datasource.url"));
        try {
            dataSource.setDriver(DriverManager.getDriver(environment.getProperty("spring.datasource.url")));
        } catch (SQLException e) {
            throw new RuntimeException("can't recognize dataSource Driver");
        }
        dataSource.setUsername(environment.getProperty("spring.datasource.username"));
        dataSource.setPassword(environment.getProperty("spring.datasource.password"));
        return new DataSourceProxy(dataSource);
    }

    /***
     * 全局事务扫描器
     * 用来解析带有@GlobalTransactional注解的方法,然后采用AOP的机制控制事务
     * @param environment
     * @return
     */
    @Bean
    public GlobalTransactionScanner globalTransactionScanner(Environment environment){
        String applicationName = environment.getProperty("spring.application.name");
        String groupName = environment.getProperty("fescar.group.name");
        if(applicationName == null){
            return new GlobalTransactionScanner(groupName == null ? "my_test_tx_group" : groupName);
        }else{
            return new GlobalTransactionScanner(applicationName, groupName == null ? "my_test_tx_group" : groupName);
        }
    }

    /***
     * 每次微服务和微服务之间相互调用
     * 要想控制全局事务,每次TM都会请求TC生成一个XID,每次执行下一个事务,也就是调用其他微服务的时候都需要将该XID传递过去
     * 所以我们可以每次请求的时候,都获取头中的XID,并将XID传递到下一个微服务
     * @param restTemplates
     * @return
     */
    @ConditionalOnBean({RestTemplate.class})
    @Bean
    public Object addFescarInterceptor(Collection<RestTemplate> restTemplates){
        restTemplates.stream()
                .forEach(restTemplate -> {
                    List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
                    if(interceptors != null){
                        interceptors.add(fescarRestInterceptor());
                    }
                });
        return new Object();
    }

    @Bean
    public FescarRMRequestFilter fescarRMRequestFilter(){
        return new FescarRMRequestFilter();
    }

    @Bean
    public FescarRestInterceptor fescarRestInterceptor(){
        return new FescarRestInterceptor();
    }
}

注意:如果自定义fescar.group.name需要和file.conf中的名字保持一致。

创建FescarRMRequestFilter,给每个线程绑定一个XID,代码如下;

public class FescarRMRequestFilter extends OncePerRequestFilter {

    private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(FescarRMRequestFilter.class);

    /**
     * 给每次线程请求绑定一个XID
     * @param request
     * @param response
     * @param filterChain
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String currentXID = request.getHeader(FescarAutoConfiguration.FESCAR_XID);
        if(!StringUtils.isEmpty(currentXID)){
            RootContext.bind(currentXID);
            LOGGER.info("当前线程绑定的XID :" + currentXID);
        }
        try{
            filterChain.doFilter(request, response);
        } finally {
            String unbindXID = RootContext.unbind();
            if(unbindXID != null){
                LOGGER.info("当前线程从指定XID中解绑 XID :" + unbindXID);
                if(!currentXID.equals(unbindXID)){
                    LOGGER.info("当前线程的XID发生变更");
                }
            }
            if(currentXID != null){
                LOGGER.info("当前线程的XID发生变更");
            }
        }
    }
}

创建FescarRestInterceptor过滤器,每次请求其他微服务的时候,都将XID携带过去。

public class FescarRestInterceptor implements RequestInterceptor, ClientHttpRequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        String xid = RootContext.getXID();
        if(!StringUtils.isEmpty(xid)){
            requestTemplate.header(FescarAutoConfiguration.FESCAR_XID, xid);
        }
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        String xid = RootContext.getXID();
        if(!StringUtils.isEmpty(xid)){
            HttpHeaders headers = request.getHeaders();
            headers.put(FescarAutoConfiguration.FESCAR_XID, Collections.singletonList(xid));
        }
        return execution.execute(request, body);
    }
}

分布式事务测试

分布式事务测试

微服务添加依赖

因为所有微服务都有可能使用分布式事务,所以我们可以在每个微服务工程中添加fescar的依赖,当然,搜索工程排除,因为它不需要依赖数据库,代码如下:


<dependency>
    <groupId>com.changgougroupId>
    <artifactId>changgou-transaction-fescarartifactId>
    <version>1.0-SNAPSHOTversion>
dependency>

测试

在订单微服务的OrderServiceImpl的add方法上增加@GlobalTransactional(name = “add”)注解,代码如下:

使用Seata实现分布式事务处理_第2张图片

这里涉及到几个微服务的调用,我们先查询下数据库数据,然后再测试一次,如果输出添加订单完成库存减少完毕则表明订单微服务和商品微服务的事务已经完成,这时候我们在添加积分的方法中制造一个异常,如果积分添加异常,而商品微服务中数据没发生变化,则表明分布式事务控制成功。

修改用户微服务,在添加用户积分的地方制造异常,代码如下:

使用Seata实现分布式事务处理_第3张图片

启动Fescar-server,打开seata包/fescar-server-0.4.2/bin,双击fescar-server.bat启动fescar-server,如下:
使用Seata实现分布式事务处理_第4张图片

测试前后结果一致

你可能感兴趣的:(使用Seata实现分布式事务处理)