Biz-SIP金融级分布式事务的实现原理和实战

Biz-SIP金融级业务中台是一套基于领域驱动设计(DDD),用于快速构建金融级云原生架构的服务整合中间件,包含了在金融场景里锤炼出来的最佳实践。

分布式事务是分布式系统架构设计中的一个技术难点,特别是在这几年越来越火的微服务架构中,服务拆分所带来的跨服务数据一致性问题亟待解决,而在金融系统中,这个问题更是备受关注。

在Biz-SIP金融业务中台中,是利用基于队列的延迟服务,来提供对分布式事务的支持,主要场景有:

  1. 重试通知:通知对方系统,直到对方给出明确的回复响应。
  2. 向前补偿:调用第三方服务后,超时没有响应,系统会后续发起查询上笔交易状态的查询交易,根据查询交易状态来决定继续完成后续服务步骤还是对以前服务步骤发起补偿(冲正)操作。
  3. 向后补偿:调用第三方服务后,超时没有响应,系统会立即发起针对此交易的补偿交易,补偿成功后,会对以前服务步骤依次发起补偿(冲正)操作;如果补偿失败,会置交易失败,由人工介入处理。

一、Biz-SIP分布式事务的实现原理

基于队列的延迟服务的运行机制,如下图所示:
在这里插入图片描述

  1. 适配层调用App服务后,会执行指定的聚合服务;
  2. 在Java编写的聚合服务中,会首先约定一个聚合服务调用的接口xxxDelayInterface,会声明接口类型、调用延迟间隔时间和调用次数;
  3. 在Java编写的服务中,可以通过前面声明的xxxDelayInterface接口,发起延迟服务的调用;
  4. 发起延迟消息后,在平台内部实现机制上,是通过发送RabbitMQ延迟消息的方式来实现的;
  5. 事务管理器在延迟服务接口约定的延迟时间后,会收到RabbitMQ消息,调起聚合服务(子服务)的处理;
  6. 如果执行成功,则延迟服务顺利完成,父服务和子服务都标记为成功;
  7. 如果在执行中抛出BizTimeOutException异常,则会结束当前延迟服务,等待延迟服务的再次唤起;
  8. 如果在执行中抛出其它BizException异常,则会结束当前延迟服务,标记父服务和子服务为失败,等待人工后续处理;
  9. 延迟服务唤起次数超限,也会会结束当前延迟服务,标记父服务和子服务为失败,等待人工后续处理。

二、实战

下面以具体的向前补偿和向后补偿二个场景案例,来介绍如何来开发支持分布式事务的代码。
sampleCompensate服务是一个app服务,依次调用了sink53、sink51、sink54这三个sink服务,其中sink51服务是调用第三方系统的sink服务,sink53和sink54是纯内部交易的sink服务:
在这里插入图片描述

1、向前补偿

我们来看一下向前补偿的场景,向前补偿是在第3步sink51服务调用外部第三方系统时,发现消息超时没有返回后,app服务会根据约定的时延和重试次数发起向前补偿的延迟服务(第4步),首先,会发起查询上笔交易状态的查询交易(第5步),然后根据查询交易状态,来决定继续完成后续服务步骤(左边的第6步),还是对以前服务步骤发起补偿(冲正)操作(右边的第6步)。:
在这里插入图片描述

下面是app服务的相关代码:

public class SampleCompensate implements SampleCompensateInterface {
    // sink51为调用第三方外部系统的sink服务
    private Sink51Interface sink51Interface = AppClientFactory
            .getSinkClient(Sink51Interface.class, "sink51");
    // sink53为内部交易处理的sink服务
    private Sink53Interface sink53Interface = AppClientFactory
            .getSinkClient(Sink53Interface.class, "sink53");
    // sink54为内部交易处理的sink服务
    private Sink54Interface sink54Interface = AppClientFactory
            .getSinkClient(Sink54Interface.class, "sink54");
    // sampleCompensate作为app服务,既能被source服务所调用,
    // 也能申明为延迟服务接口,被延迟服务所调用
    private SampleCompensateInterface delaySampleCompensateInterface
            = AppClientFactory.getDelayAppServiceClient(
                    SampleCompensateInterface.class, "sampleCompensate",
                    1000, 2000, 4000, 8000, 16000, 32000);

    @Override
    public String tx01(String tranId,String data) throws BizException {
        // 调用sink53(内部交易处理)
        this.sink53Interface.tx01(tranId,data);
        try {
            // 调用sink51(调用第三方系统)
            this.sink51Interface.tx01(tranId, data);
        } catch (BizTimeOutException e) {
            // 超时异常后,调用延迟服务进行向前补偿
            this.delaySampleCompensateInterface.forwardCompensateTx01(tranId,data);
            return "tx01交易向前补偿中...";
        }
        // 成功后调用sink54(内部交易处理)
        this.sink54Interface.tx01(tranId,data);
        return "tx01交易成功";
    }

    @Override
    public void forwardCompensateTx01(String tranId,String data) throws BizException {
        // 调用sink51的查询交易状态接口(调用第三方系统),超时直接抛出BizTimeOutException异常
        boolean successFlag = this.sink51Interface.queryTx01(tranId);
        if (successFlag) {
            // 返回原调用第三方系统交易成功,继续后续调用sink54
            this.sink54Interface.tx01(tranId,data);
            return;
        } else {
            // 返回原调用第三方系统交易失败,对上步sink53的交易进行冲正
            this.sink53Interface.compensateTx01(tranId,data);
            return;
        }
    }
    ......
}

首先,在app服务代码类中,申明了sink服务和延迟服务的调用接口:

    // sink51为调用第三方外部系统的sink服务
    private Sink51Interface sink51Interface = AppClientFactory
            .getSinkClient(Sink51Interface.class, "sink51");
    // sink53为内部交易处理的sink服务
    private Sink53Interface sink53Interface = AppClientFactory
            .getSinkClient(Sink53Interface.class, "sink53");
    // sink54为内部交易处理的sink服务
    private Sink54Interface sink54Interface = AppClientFactory
            .getSinkClient(Sink54Interface.class, "sink54");
    // sampleCompensate作为app服务,既能被source服务所调用,
    // 也能申明为延迟服务接口,被延迟服务所调用
    private SampleCompensateInterface delaySampleCompensateInterface
            = AppClientFactory.getDelayAppServiceClient(
                    SampleCompensateInterface.class, "sampleCompensate",
                    1000, 2000, 4000, 8000, 16000, 32000);

其中延迟服务接口delaySampleCompensateInterface在申明中,约定了如果调用超时,会最多调用6次,每次间隔分别是1秒、2秒、4秒、8秒、16秒、32秒。

其次,tx01()方法是提供给source层调用的app服务接口,是依次调用sink53、sink51、sink54的sink接口服务,其中在调用sink51超时(抛出BizTimeOutException异常)后,会立即触发当前app服务封装的forwardCompensateTx01()延迟服务接口(此接口是严格按照前面申明的延迟服务接口delaySampleCompensateInterface调用延时和频次来调用的):

@Override
public String tx01(String tranId,String data) throws BizException {
    // 调用sink53(内部交易处理)
    this.sink53Interface.tx01(tranId,data);
    try {
        // 调用sink51(调用第三方系统)
        this.sink51Interface.tx01(tranId, data);
    } catch (BizTimeOutException e) {
        // 超时异常后,调用延迟服务进行向前补偿
        this.delaySampleCompensateInterface.forwardCompensateTx01(tranId,data);
        return "tx01交易向前补偿中...";
    }
    // 成功后调用sink54(内部交易处理)
    this.sink54Interface.tx01(tranId,data);
    return "tx01交易成功";
}

在超时异常后会调用延迟服务forwardCompensateTx01()接口,forwardCompensateTx01()也是封装在sampleCompensate的app服务代码类中,但是通过延迟服务调用的,具体逻辑如下:

@Override
public void forwardCompensateTx01(String tranId,String data) throws BizException {
    // 调用sink51的查询交易状态接口(调用第三方系统),超时直接抛出BizTimeOutException异常
    boolean successFlag = this.sink51Interface.queryTx01(tranId);
    if (successFlag) {
        // 返回原调用第三方系统交易成功,继续后续调用sink54
        this.sink54Interface.tx01(tranId,data);
        return;
    } else {
        // 返回原调用第三方系统交易失败,对上步sink53的交易进行冲正
        this.sink53Interface.compensateTx01(tranId,data);
        return;
    }
}

在forwardCompensateTx01()方法中封装了在调用sink51服务超时异常后,所触发的补偿交易逻辑,会根据原tranId去sink51连接的第三方系统查询原超时交易的最终状态,成功后会继续调用sink54服务,失败后会对sink53触发补偿冲正服务。

2、向后补偿

我们再来看一下向后补偿的场景,向后补偿是在第3步sink51服务调用外部第三方系统时,发现消息超时没有返回后,app服务会根据约定的时延和重试次数发起向前补偿的延迟服务(第4步),首先,会向sink51发起冲正交易(第5步),如果冲正交易成功,则继续完成sink53的冲正交易(第6步),如果对sink51的冲正交易失败,则抛出异常,系统会告警并提示人工介入处理。:
在这里插入图片描述

下面是app服务的相关代码:

@Override
public String tx02(String tranId,String data) throws BizException {
    // 调用sink53(内部交易处理)
    this.sink53Interface.tx02(tranId, data);
    try {
        // 调用sink51(调用第三方系统)
        this.sink51Interface.tx02(tranId, data);
    } catch (BizTimeOutException e) {
        // 超时异常后,调用延迟服务进行向后补偿
        this.delaySampleCompensateInterface.backwardCompensateTx02(tranId, data);
        return "tx02交易向后补偿中...";
    }
    // 成功后调用sink54(内部交易处理)
    this.sink54Interface.tx02(tranId, data);
    return "tx02交易成功";
}

@Override
public void backwardCompensateTx02(String tranId,String data) throws BizException {
    // 调用sink51的冲正交易(调用第三方系统),并返回成功失败结果
    boolean successFlag = this.sink51Interface.compensateTx02(tranId, data);
    if (successFlag) {
        // 冲正交易成功,继续对上一步sink53进行冲正
        this.sink53Interface.compensateTx02(tranId, data);
        return;
    }
    else {
        // 冲正交易失败,抛出异常,提醒人工介入处理
        throw new BizException(BizResultEnum.OTHER_ERROR
                ,"this.sink51Interface.compensateTx02()补偿失败");
    }
}

tx02()方法是提供给source层调用的app服务接口,是依次调用sink53、sink51、sink54的sink接口服务,其中在调用sink51超时(抛出BizTimeOutException异常)后,会立即触发当前app服务封装的backwardCompensateTx02()延迟服务接口(此接口是严格按照前面申明的延迟服务接口delaySampleCompensateInterface调用延时和频次来调用的)。
backwardCompensateTx02()也是封装在sampleCompensate的app服务代码类中,封装了在调用sink51服务超时异常后,所触发的补偿交易逻辑,会通过sink51向连接的第三方系统发起冲正交易,成功后会继续对sink53触发补偿冲正服务,而失败后会抛出BizException异常,此异常可以通过交易日志捕捉并触发告警。

Biz-SIP官方网站:http://bizsip.bizmda.com
Gitee:https://gitee.com/szhengye/biz-sip

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值