Spring Cloud Alibaba Seata + Nacos + Jooq + RestTemplate实现分布式事务

Spring Cloud Alibaba Seata + Nacos + Jooq + RestTemplate实现分布式事务

示例代码

本文所有代码均可在github克隆使用:clyoudu/spring-cloud-seata-demo

环境

  • macOS 10.14.6(unix/linux)
  • JDK 1.8
  • seata(client) 0.9.0
  • seata server 0.7.1
  • nacos server 1.3.0
  • jooq 3.11.12
  • Spring Cloud Alibaba 2.1.0.RELEASE
  • Spring Cloud Hoxton SR5
  • Spring Boot 2.1.15.RELEASE
  • MySQL 8.0.20

Seata

Seata(Simple Extensiable Autonomous Transaction Architecture,简单的、可扩展的、自治的事务架构)是一款阿里开源的分布式事务解决方案,前身叫fescar,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

github地址:seata/seata
官方网站:seata.io

Nacos

Nacos(Dynamic Naming and Configuration Service)是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos是阿里云中间件团队开源的一个项目,简单理解就是Spring Cloud Eureka+Config Server,但是功能比Spring Cloud Eureka+Config Server更加强大。

github地址:alibaba/nacos
官方网站:nacos.io

JOOQ

JOOQ(Java Object Oriented Querying)是基于Java访问关系型数据库的工具包,轻量,简单,并且足够灵活,可以轻松的使用Java面向对象语法来实现各种复杂的sql。对于写Java的码农来说ORMS再也熟悉不过了,不管是Hibernate或者Mybatis,都能简单的使用实体映射来访问数据库。

号称是The easiest/best way to write SQL in Java,

github地址:jOOQ/jOOQ
官方网站:jooq.org

Getting Started

seata理论上可以和很多注册中心配合使用,但这里直接使用和Spring Cloud Alibaba配套的nacos,相对来说不会特别容易出错,毕竟自家产品。

seta的原理和模式可以到github或官网查看,讲的还是比较详细。

选用JOOQ而不是Mybatis或其他,一是官方示例用的是mybatis + dubbo rpc,我想试试jooq + TestTemplate,加深对整个分布式事务的理解。

部署Nacos

Nacos和Eureka一样,有两种部署方式:源码和jar包,这里直接使用jar包部署:
下载nacos:/alibaba/nacos/releases
这里选择1.3.0版本:nacos-server-1.3.0.tar.gz
接下来启动nacos server:

cd /path/to/nacos-server-1.3.0.tar.gz
tar -zvxf nacos-server-1.3.0.tar.gz
cd nacos/bin
sh startup.sh

启动后稍等一下访问:http://127.0.0.1:8848/nacos,输入nacos/nacos即可看到nacos的管理界面。

部署seata server

下载seata server,关于seata server版本为何要选0.7.1这么低,现在最新版本已是1.2.0,因为Spring Cloud Alibaba目前在spring官网上最新版本是2.1.0.RELEASE,其中依赖的seata版本就是0.7.1,因此也选择了对应版本的server,不过后来发现0.7.1的seata有bug,替换成了0.9.0,这个后面会提到。
下载seata server:seata-server-0.7.1.zip
接下来准备nacos配置:

cd /path/to/seata-server-0.7.1.zip
unzip seata-server-0.7.1.zip
cd seata-server-0.7.1/bin
# 拷贝conf目录下的nacos-config.sh和nacos-config.txt到bin目录
cp ../conf/nacos-config.sh	.
cp ../conf/nacos-config.txt .
# 修改nacos-config.txt,添加下面两行配置到对应位置
# service.default.grouplist=127.0.0.1:8091
# service.disableGlobalTransaction=false
vi nacos-config.txt
# 保存
esc wq
# 同步配置到nacos
sh nacos-config.sh 127.0.0.1
# 最后会输出
# init nacos config finished, please start seata-server. 
# 如果输出:init nacos config fail. 请检查前面的输出并排查

在这里插入图片描述
在这里插入图片描述
打开nacos管理界面,会有如下内容:
在这里插入图片描述
接着修改配置文件:

vi ../conf/registry.conf
# 修改注册方式和配置获取方式,全部修改为nacos
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
}
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
}

最后启动

sh seata-server.sh

稍等一下,nacos注册中心可以看到seata server已注册成功:
在这里插入图片描述

准备数据库

创建3个schema:balance_db、order_db、stock_db。
balance_db库结构和数据如下:

DROP TABLE IF EXISTS `balance_tb`;
CREATE TABLE `balance_tb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `balance` decimal(16,2) DEFAULT '0.00',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `balance_tb` VALUES (1, 'cd083092-b3c4-445d-8e77-76fb927a02fc', 1000000.00);

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

order_db库结构和数据如下:

DROP TABLE IF EXISTS `order_tb`;
CREATE TABLE `order_tb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `product_code` varchar(255) DEFAULT NULL,
  `count` int DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

stock_db库结构和数据如下:

DROP TABLE IF EXISTS `stock_tb`;
CREATE TABLE `stock_tb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `product_code` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `description` text,
  `price` decimal(10,2) DEFAULT '0.00',
  `count` int DEFAULT '0',
  `user_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`,`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;

INSERT INTO `stock_tb` VALUES (21, '00001', '未来架构: 从服务化到云原生', '{\n  \"description\": \"<div><p>互联网架构不断演化,经历了从集中式架构到分布式架构,再到云原生架构的过程。云原生因能解决传统应用升级缓慢、架构臃肿、无法快速迭代等问题而成了未来云端应用的目标。《未来架构:从服务化到云原生》首先介绍架构演化过程及云原生的概念,让读者对基础概念有一个准确的了解,接着阐述分布式、服务化、可观察性、容器调度、Service Mesh、云数据库等技术体系及原理,并介绍相关的SkyWalking、Dubbo、Spring Cloud、Kubernetes、Istio等开源解决方案,最后深度揭秘开源分布式数据库生态圈ShardingSphere的设计、实现,以及进入Apache基金会的历程,非常适合架构师、云计算从业人员阅读、学习。</p><div><b>张亮</b><p>京东数科数据研发负责人,Apache Sharding-Sphere发起人兼PPMC成员。热爱分享,拥抱开源,主张代码优雅化,擅长以Java为主的分布式架构及以Kubernetes和Mesos为主的云平台的构建。ShardingSphere已进入Apache软件基金会,是京东集团首个进入Apache的开源项目,也是Apache首个分布式数据库中间件。</p><b>吴晟</b><p>Apache SkyWalking创始人及PPMC成员,Apache ShardingSphere原型作者及PPMC成员,Apache Zipkin贡献者,Apache孵化器导师,CNCF基金会OpenTracing标准化委员会成员,W3C Trace Context规范贡献者。擅长分布式架构、性能监控与诊断、分布式追踪、云原生监控等领域。</p><b>敖小剑</b><p>具有十七年软件开发经验,资深码农,微服务专家,Cloud Native拥护者,敏捷实践者,ServiceMesh布道师,ServicelMesher中文社区联合创始人。专注于基础架构建设,对微服务、云计算等相关技术有着深入研究和独到见解。</p><b>宋净超</b><p>蚂蚁金服云原生布道师,ServiceMesher中文社区联合创始人,Kubemetes社区成员,Istio社区成员,《Cloud Native Go》《Python云原生》《云原生Java》等图书译者。</p></div></div>\",\n  \"author\": \"张亮, 吴晟, 敖小剑, 宋净超\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/1.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647166160&Signature=xCS%2FpJtY8%2FVcdbLqfjHUp6z%2FoOw%3D\"\n}\n', 99.00, 10000, 'cd083092-b3c4-445d-8e77-76fb927a02fc');
INSERT INTO `stock_tb` VALUES (22, '00002', 'Cloud Native Go: 构建基于Go和React的云原生Web应用与微服务', '{\n  \"description\": \"<div><p>本书旨在向开发人员展示如何构建适用于大流量、高并发场景下的云原生Web应用。本书从搭建开发测试环境开始,逐步介绍使用Go语言构建微服务的方法,通过引入CI/CD流程和Wercker、Docker等工具将应用推送到云中。结合微服务构建中的后端服务、数据服务、事件溯源和CQRS模式、基于React和Flux的UI设计等,本书最后构建了一个基于Web的RPG游戏World of FluxCraft,可以作为使用Go构建云原生Web应用的参考,适合于云计算与Go语言编程从业者们阅读。</p><div><b>1.云原生是云计算时代的发展趋势和必然结果</b><p>《Cloud Native Go:构建基于Go和React的云原生Web应用与微服务》通过一个云原生应用项目的构建,为大家介绍了云原生的道与术,引导读者了解云原生理念的产生、应用场景、优势。</p><b>2.集现今诸多热点技术之大成</b><p>《Cloud Native Go:构建基于Go和React的云原生Web应用与微服务》在构建云原生项目时,涉及Docker、持续集成、微服务、DevOps、事件溯源与CQRS等众多备受关注的技术热点,无疑会让读者受益匪浅。</p><b>3.Go语言助理云开发完美实现</b><p>Go语言以其简单优雅、快速安全、支持高并发等特性,成为云计算时代的最优语言。《Cloud Native Go:构建基于Go和React的云原生Web应用与微服务》将带领读者正确认识Go语言,掌握用Go构建应用程序的方法。</p><b>4.流程完整,示例具体详细</b><p>《Cloud Native Go:构建基于Go和React的云原生Web应用与微服务》从搭建平台开始,逐步带领读者开发一个完整的云上项目。其中的每一环节都有详细讲解。示例具有代表性,代码详细,帮助读者轻松掌握云原生开发的关键。</p></div></div>\",\n  \"author\": \"Kevin Hoffman, 宋净超\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/2.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647166182&Signature=U4ep6Dsh4w8TqW6tLlDLoEopIm4%3D\"\n}\n', 69.00, 10000, 'cd083092-b3c4-445d-8e77-76fb927a02fc');
INSERT INTO `stock_tb` VALUES (23, '00003', '云原生Java: Spring Boot、Spring Cloud与Cloud Foundry弹性系统设计', '{\n  \"description\": \"<div><p>无论是传统IT行业,还是互联网行业,都正处于行业历史上最剧烈的变革中 :大量的系统正在从传统的IT架构转向基于云的架构, 开发模式也正在从开发和运维分工的传统模式,逐渐转向统一的“DevOps”模式。Java技术已经进入了新的生命周期,大量被用于构建现代的、基于云的应用程序。 本书详细阐述了开发云原生应用程序的机遇和挑战,明确指出了成功实现的方向,并且重点介绍了微服务框架Spring Boot。Spring Boot可以轻松创建任何粒度的 Spring服务,并部署到现代的容器环境中。本书主要面向正在使用 Spring Boot、SpringCloud和Cloud Foundry, 以便更快、更好地构建软件的Java/JVM 开发人员。本书一共分为4个部分共15章。第1章和第2章介绍了云原生思想产生的背景,然后介绍了Spring Foundry。第3章介绍了如何配置Spring Boot应用程序。第4章介绍了如何测试Spring应用程序,从如何测试最简单的组件到测试分布式系统。第5章介绍了可以将应用程序迁移到Cloud Foundry等云平台的轻量级重构方式。第6章介绍了如何使用Spring构建HTTP和RESTful服务。第7章介绍了在分布式系统中控制请求进出的常用方法。第8章介绍了如何构建一个响应外部请求的服务。第9章介绍了如何使用Spring Data在Spring中管理数据。这为领域驱动的思想奠定了基础。第10章介绍了如何使用Spring中事件驱动、消息中心化的能力,来集成分布式服务和数据。第11章介绍了如何利用云平台(如Cloud Foundry)的能力来处理长期运行的工作。第12章介绍了在分布式系统中管理状态的一些方法。第13章介绍了如何构建具备可观测性和可操作性的系统。第14章介绍了如何构建类似于Cloud Foundry平台的服务代理。第15章介绍了持续交付背后的思想。</p><div><b>1. 基础知识</b><p>了解云原生思维背后的动机;配置和测试Spring Boot应用程序;将您的传统应用程序迁移至云端</p><b>2. 微服务</b><p>使用Spring构建HTTP和RESTful服务;在分布式系统中路由请求;建立更接近数据的边缘服务</p><b>3. 数据整合</b><p>使用Spring Data管理数据,并将分布式服务与——Spring对事件驱动、以消息传递为中心架构的支持——集成起来</p><b>4. 生产</b><p>让您的系统可观察;使用服务代理来连接有状态的服务;了解持续交付背后的重要思想</p></div></div>\",\n  \"author\": \"Josh Long, 张若飞, 宋净超\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/3.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647166193&Signature=WfYCQ1S8XLuqj7oCGxHYTH3VNdc%3D\"\n}\n', 128.00, 10000, 'cd083092-b3c4-445d-8e77-76fb927a02fc');
INSERT INTO `stock_tb` VALUES (24, '00004', 'Python云原生: 构建应对海量用户数据的高可扩展Web应用', '{\n  \"description\": \"<div><p>《Python云原生:构建应对海量用户数据的高可扩展Web应用》以一个应用开发贯穿始终,从云原生和微服务的概念原理讲起,使用Python构建云原生应用,并使用React构建Web视图。为了应对大规模的互联网流量,使用了Flux构建UI和事件溯源及CQRS模式。考虑到Web应用的安全性,《Python云原生:构建应对海量用户数据的高可扩展Web应用》对此也给出了解决方案。书中对于关键步骤进行了详细讲解并给出运行结果。读者可以利用Docker容器、CI/CD工具,敏捷构建和发布本书示例中的应用到AWS、Azure这样的公有云平台上,再利用平台工具对基础设施和应用的运行进行持续监控。</p><div><b>云原生是云计算时代的发展趋势和必然结果</b><p>云原生将持续领航云时代架构理念</p><b>用Python语言进行开发</b><p>易如门,易掌握,集现今诸多热点技术之大成</p><b>流程完整,示例具体详细</b><p>一个实际开发案例贯穿始终,全面开放代码</p></div></div>\",\n  \"author\": \"Manish Sethi, 宋净超\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/4.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647178695&Signature=9l4JHy7eMQVj3T3mXgRpAN9wdPI%3D\"\n}\n', 89.00, 10000, 'cd083092-b3c4-445d-8e77-76fb927a02fc');
INSERT INTO `stock_tb` VALUES (25, '00005', '深入浅出Istio: Service Mesh快速入门与实践', '{\n  \"description\": \"<div><p>Google联合IBM、Lyft推出的Istio,一经问世就受到了人们的普遍关注,其热度迅速攀升,成为Service Mesh(服务网格)方案的代表项目。本书整理了Istio中的部分概念和案例,以快速入门的形式,对Istio的基础用法一一进行讲解,并在书末给出一些试用方面的建议。<br/>在本书中,前3章从微服务和服务网格的简短历史开始,讲述了服务网格的诞生过程、基本特性及Istio的核心功能,若对这些内容已经有所了解,则可以直接从第4章开始阅读;第4、5章分别讲解了Istio的配置和部署过程;第6章至第9章,通过多个场景来讲解Istio的常用功能;第10章结合了笔者的实践经验,为读者提供了Istio的一系列试用建议。本书没有采用官方复杂的Book Info应用案例,而是采用客户端+简单HTTP服务端的案例,读者随时都能在短时间内启动一个小的测试。<br/>本书面向对服务网格技术感兴趣,并希望进一步了解和学习Istio的中高级技术人员,假设读者已经了解Kubernetes的相关概念并能够在Kubernetes上熟练部署和管理微服务。若希望全面、深入地学习Kubernetes,可参考《Kubernetes 权威指南:从Docker到Kubernetes实践全接触》和《Kubernetes 权威指南:企业级容器云实战》。</p><div><b>快速入门Service Mesh和实践</b><p>手把手快速入门Service Mesh和实践,并根据Istio 1.1版本的升级,将源码及内容同步更新至GitHub</p><b>作者为Kubernetes 权威指南作者之一</b><p>作者为Kubernetes 权威指南作者之一,Istio、Kubernetes项目成员,Istio.io主要贡献者之一</p><b>知名大咖热评</b><p>知名大咖敖小剑、马全一、张琦及《Kubernetes 权威指南》作者龚正等热评!</p></div></div>\",\n  \"author\": \"崔秀龙\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/5.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647179151&Signature=LIY%2F56jF8Out5eHsxAU1hkUOp7o%3D\"\n}\n', 79.00, 9998, 'cd083092-b3c4-445d-8e77-76fb927a02fc');

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

注意每个库里都有undo_log表,这个是记录每个分布式事务参与者的本地事务undo_log,用于记录本地事务日志信息,用于处理回滚、悬挂、空回滚等等。模拟场景下,表里基本不会有数据,单个事务的数据将会在对应的整个分布式事务提交或回滚后删除。

构建服务

场景:
简单的商品付款场景

  • 页面点击购买
  • 账户服务执行用户扣款操作
  • 库存服务扣除库存
  • 订单服务生成订单

所有操作必须都成功才算成功,任何一个服务失败,其他已经执行的操作都必须全部回滚。

服务结构如下:
spring-cloud-seata-demo
├── account
├── business
├── order
└── storage

  • account:账户服务
  • business:业务入口,作为全局事务的发起方
  • order:订单服务
  • storage:库存服务

这里只以businees和account作为例子记录,order和storage服务与account几乎一样。
首先修改父项目spring-cloud-seata-demo的pom文件,统一管理依赖的版本:

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>github.clyoudu</groupId>
    <artifactId>spring-cloud-seata-demo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>business</module>
        <module>storage</module>
        <module>order</module>
        <module>account</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.15.RELEASE</version>
    </parent>

    <properties>
        <seata.version>0.9.0</seata.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>${seata.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

主要统一管理了spring cloud、spring cloud alibaba和seata的版本,并且统一引入了lombok、spring-boot-maven-plugin等相关依赖和插件。

然后新建businrss子模块,pom如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>spring-cloud-seata-demo</artifactId>
        <groupId>github.clyoudu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>business</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seta-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

注意这里替换了spring-cloud-alibaba-seata中seata-all的版本。
business模块的结构如下:
business
├── pom.xml
└── src
└── main
├── resources
│ ├── registry.conf
│ ├── bootstrap.properties
│ └── application.properties
└── java.github.clyoudu.business
├── dto
│ └── ResultDto.java
├── config
│ └── RestTemplateConfig.java
├── controller
│ └── PurchaseController.java
├── service
│ ├── impl
│ │ └── PurchaseServiceImpl.java
│ └── PurchaseService.java
└── BusinessApplication.java

有两个关键的类,一个是RestTemplateConfig,注意加@Loadbalanced注解,否则会报找不到服务:

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate () {
        return new RestTemplate();
    }

}

另一个是业务实现类PurchaseServiceImpl

@Service
public class PurchaseServiceImpl implements PurchaseService {

    @Autowired
    private RestTemplate restTemplate;

    @Override
    @GlobalTransactional(timeoutMills = 300000, name = "business")
    public ResultDto purchase(Integer userId, String username, String productCode, Integer count, BigDecimal amount) {
        restTemplate.getForEntity("http://account/debit?userId=" + userId + "&amount=" + amount, ResultDto.class).getBody();
        restTemplate.getForEntity("http://storage/deduct?productCode=" + productCode + "&count=" + count, ResultDto.class).getBody();
        restTemplate.getForEntity("http://order/create?username=" + username + "&productCode=" + productCode + "&count=" + count + "&amount=" + amount, ResultDto.class).getBody();
        return new ResultDto(200, "支付成功", null);
    }
}

作为分布式事务的发起方,需要在对应的方法上添加GlobalTransactional注解,可以指定超时时间。

接着新建account子模块,pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>spring-cloud-seata-demo</artifactId>
        <groupId>github.clyoudu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>account</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seta-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jooq</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.jooq</groupId>
                <artifactId>jooq-codegen-maven</artifactId>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>

                <configuration>
                    <jdbc>
                        <driver>com.mysql.cj.jdbc.Driver</driver>
                        <url>jdbc:mysql://localhost:3306/balance_db?useUnicode=true&amp;allowMultiQueries=true&amp;useSSL=false&amp;characterEncoding=utf8</url>
                        <username>root</username>
                        <password>root@leichen</password>
                    </jdbc>
                    <generator>
                        <database>
                            <name>org.jooq.meta.mysql.MySQLDatabase</name>
                            <includes>.*</includes>
                            <inputSchema>balance_db</inputSchema>
                        </database>

                        <target>
                            <packageName>github.clyoudu.account.jooq</packageName>
                            <directory>src/main/java</directory>
                        </target>
                    </generator>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

注意这里替换了spring-cloud-alibaba-seata中seata-all的版本。
account项目的结构如下:

account
├── pom.xml
└── src
    └── main
        ├── resources
        │   ├── registry.conf
        │   ├── bootstrap.properties
        │   └── application.properties
        └── java.github.clyoudu.account
            ├── dto
            │   └── ResultDto.java
            ├── jooq
            │   ├── tables
            │   │   ├── records
            │   │   │   └── BalanceTbRecord.java
            │   │   └── BalanceTb.java
            │   ├── Indexes.java
            │   ├── DefaultCatalog.java
            │   ├── BalanceDb.java
            │   ├── Keys.java
            │   └── Tables.java
            ├── AccountApplication.java
            ├── config
            │   └── DataSourceConfig.java
            ├── dao
            │   ├── BaseDao.java
            │   └── BalanceTbDao.java
            ├── sercice
            │   ├── impl
            │   │   └── BalanceServiceImpl.java
            │   └── BalanceService.java
            └── controller
                └── BalanceController.java

配置文件有仨,registry.conf用于seata-client的注册,application.properties/bootstrap.properties用于SpringBoot Application注册。
registry.conf,主要定义了seata配置来源,这里选择刚刚搭建和完成配置初始化的nacos

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  nacos {
    serverAddr = "localhost"
    namespace = ""
  }
}
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"
  nacos {
    serverAddr = "localhost"
    namespace = ""
  }
}

application.properties,其中的关键配置为:spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group ,这个配置的值要和启动seata-server配置的service.vgroup_mapping.xxx中的xxx一样,比如我这里在nacos中查询出的配置为:
在这里插入图片描述
所以这里spring.cloud.alibaba.seata.tx-service-group的值为my_test_tx_group

server.port=8001
spring.application.name=account

spring.cloud.nacos.discovery.server-addr=localhost:8848
management.endpoints.web.exposure.include=*

spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=localhost:8080

spring.datasource.url=jdbc:mysql://localhost:3306/balance_db
spring.datasource.username=root
spring.datasource.password=root@leichen
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group

bootstrap.properties,指定了nacos config相关的配置:

spring.application.name=account
spring.cloud.nacos.config.server-addr=localhost:8848

关键逻辑为扣款操作,BalanceTbDao和BalanceServiceImpl:

@Repository
public class BalanceTbDao extends BaseDao {

    public int debit(Integer userId, BigDecimal amount) {
        return dslContext.execute("update balance_tb set balance = balance - " + amount + " where id = " + userId);
    }

}
@Service
@Slf4j
public class BalanceServiceImpl implements BalanceService {

    @Autowired
    private BalanceTbDao balanceTbDao;

    @Override
    public ResultDto debit(int userId, BigDecimal amount) {
        log.info("事务ID:" + RootContext.getXID());
        if(balanceTbDao.debit(userId, amount) > 0) {
            return new ResultDto(200, "操作成功", null);
        }
        throw new RuntimeException("账户扣款失败");
    }
}

有一个配置类DataSourceConfig,用于代理增强Spring默认的DataSource,让其支持分布式事务:

@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public DataSource dataSource(DataSourceProperties properties) {
        HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
        if (properties.getName()!=null && properties.getName().length() > 0) {
            dataSource.setPoolName(properties.getName());
        }
        return new DataSourceProxy(dataSource);
    }

    @SuppressWarnings("unchecked")
    protected static <T> T createDataSource(DataSourceProperties properties,
                                            Class<? extends DataSource> type) {
        return (T) properties.initializeDataSourceBuilder().type(type).build();
    }

}

其他几个项目和account类似,具体代码参考:clyoudu/spring-cloud-seata-demo

开始验证

分别启动各个服务,先后顺序无关。

  • 验证事务提交:调用businees服务的purchase接口,返回
    {
    	"code": 200,
    	"msg": "支付成功",
    	"data": null
    }
    
    并且account、order、storage三个服务的日志均输出:Branch commit result: PhaseTwo_Committed,检查各个库表,数据正常逻辑无误,证明分布式事务提交成功。
  • 验证事务回滚:业务的流程是扣款、减库存、创建订单,在任何一个流程制造异常,比如修改对应的接口直接抛出异常,或者修改一下表名等等,调用businees服务的purchase接口,返回错误,并且account、order、storage三个服务的日志均输出:Branch commit result: PhaseTwo_RollBacked,检查各个库表,数据正常逻辑无误,证明分布式事务回滚成功。
  • undo_log相关的验证:可以将分布式事务的超时时间调长一些,并且人为地让某些业务流程处理时间变长,查看undo_log表的记录,或者在业务处理过程中关掉已经完成的业务服务或未完成的业务服务,观察undo_log表记录,观察分布式事务是否能正常回滚,甚至在业务处理过程中停掉seata server,结合seata 相关文档,观察业务系统表现,验证seata相关原理。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值