Spring-Cloud-Alibaba之Seata

微服务中不可避免的会发生服务间的调用,这就一定会涉及到事务相关的问题,在单体项目中我们可以直接很方便的实现事务回滚,但是在分布式系统中就不能像以前那么做了,因为各个服务是独立的一套系统; 而要实现跨服务的事务管理系统的复杂度必然会大大增加,因此我们应当尽可能的避免使用分布式事务;对于那种要求不是很严格的可以考虑忽略掉事务的问题,只对重要的数据才做分布式事务。下面我们使用spring-cloud-alibaba套件Seata来实现分布式事务的功能。

Seata简介

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。

相关文档

功能特性如下

  • 微服务框架支持:目前已支持 Dubbo、Spring Cloud、Sofa-RPC、Motan 和 grpc 等RPC框架,其他框架持续集成中;
  • TCC模式:支持 TCC 模式并可与 AT 混用;
  • XA模式:支持已实现 XA 接口的数据库的 XA 模式;
  • AT模式:提供无侵入自动补偿的事务模式,目前已支持 MySQL、 Oracle 、PostgreSQL和 TiDB的AT模式,H2 开发中;
  • 高可用:支持基于数据库存储的集群模式,水平扩展能力强;
  • SAGA模式:为长事务提供有效的解决方案;

Seata的使用

Seata支持本地文件模式和远程配置中心模式,下面我们分别介绍相关的使用方式。注意示例中使用的是spring-cloud-alibaba的套件;下面是代码示例:

使用file模式

添加maven依赖:

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

spring-cloud-starter-alibaba-seata这个依赖中只依赖了spring-cloud-alibaba-seata,所以在项目中添加spring-cloud-starter-alibaba-seata和spring-cloud-alibaba-seata是一样的

seata配置文件

seata的配置参数官方文档 https://seata.io/zh-cn/docs/user/configurations.html

在application.yml里面配置seata需要的信息

spring:
  cloud:
    alibaba:
      seata:
        # 这里定义seata服务分组名称,必须和下面的seata.service.vgroup-mapping对应,否则将无法获取seata服务端IP信息
        tx-service-group: seata-dubbo-b-seata-service-group
seata:
  registry:
    type: file
  service:
    # seata服务端的地址和端口信息,多个使用英文分号分隔
    grouplist:
      default: 192.168.56.101:8091
    vgroup-mapping:
      seata-dubbo-b-seata-service-group: default

上面的配置可以去看 io.seata.spring.boot.autoconfigure.properties.client.ServicePropertiesio.seata.discovery.registry.FileRegistryServiceImpl 这2个类你就明白了为啥这样配置了。

创建数据库表

在每一个业务库里面创建一个undo_log的表,这里表里面会记录事务信息,用于seata后面回滚数据使用。

CREATE TABLE `undo_log`
(
    `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT,
    `branch_id`     BIGINT(20)   NOT NULL,
    `xid`           VARCHAR(100) NOT NULL,
    `context`       VARCHAR(128) 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`),
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8

开启全局事务

在需要开启全局事务的方法上添加 @GlobalTransactional 注解即可;只需要在起始的调用方法上加即可;注意对应异常情况想要回滚,直接抛出异常即可,否则将无法触发全局事务的回滚。 代码示例如下:

服务A

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @DubboReference(interfaceClass = GoodsService.class, check = false)
    private GoodsService goodsService;
    /**
     * 预定
     */
    @GlobalTransactional
    @Override
    public String booking(Long goodsId, Integer num) throws SQLException {

        Order order = new Order();
        order.setOrderNo(String.valueOf(System.currentTimeMillis()));
        order.setUid(1L);
        order.setGoodsId(goodsId);
        order.setIntegral(num*50);

        int count = orderMapper.insert(order);
        if (count!=1){
            System.out.println("订单创建失败");
            return "订单创建失败";
        }
        boolean res = this.goodsService.deductInventory(goodsId, num);
        if(!res){
            throw new SQLException("库存不足");
        }

        return order.getOrderNo();
    }
}

服务B

@DubboService
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private GoodsMapper goodsMapper;
    @DubboReference(interfaceClass = IntegralService.class, check = false)
    private IntegralService integralService;

    @Override
    public boolean deductInventory(Long id, int num) throws SQLException {
        Goods goods = goodsMapper.selectById(id);
        int count = goodsMapper.deductInventory(id, num);
        if (count!=1){
            throw new SQLException("库存不足");
        }
        boolean res = this.integralService.deductIntegral(id, num*goods.getIntegral());
        System.out.println("积分扣除结果:"+res);
        if(!res){
            throw new SQLException("积分不足");
        }
        return true;
    }
}

服务C

@DubboService
public class IntegralServiceImpl implements IntegralService {
    @Autowired
    private MemberMapper memberMapper;
    
    @Override
    public boolean deductIntegral(Long id, int integral) {
        int count = memberMapper.deductIntegral(id, integral);
        return count==1;
    }
}

使用注册中心nacos进行集群

之前我们的seata是没有集群的,要集群的话那么就不能使用文件模式了,这里我们使用nacos来实现seata集群间的通信;注意这里使用的是nacos-1.x,在实际测试中使用nacos-2.x的时候会偶发出现dubbo服务无法调用的问题。

修改application.yml的配置,将上面seata部分的配置改为如下所示:

seata:
  registry:
    type: nacos
    nacos:
      serverAddr: 192.168.56.1:8848
      application: seata-server
      group: SEATA_GROUP
  service:
    vgroup-mapping:
      # 这个必须和上面的匹配,同时最大长度为32;否则需要修改创建seata库中的global_table表的transaction_service_group的长度限制
      seata-dubbo-b-seata-service-group: default

其他的无需改动;直接即可使用;服务启动成功后,seata服务那边也会打印相关信息。最后不得不吐槽下加入分布式事务组件后系统的响应就变慢,因此不到万不得已最好不用分布式事务,哪怕是通过后期手动处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容