不引入任何中间件,实现丐版分布式事务

1 简介

上篇文章介绍了使用RocketMQ的事务消息,来实现分布式事务。其实假如项目初期,业务量比较小。服务差分的也不多。需要用到分布式事务的函数也没几个。为了一两个函数而添加MQ中间件,成本是比较高的。其实也可以做一个带重试功能的任务队列,可以定义函数调用顺序,正向执行,出现异常逆向调用补偿函数。就可以实现丐版的分布式事务。

可以定义一个执行器,将要执行的函数,抽象成两种执行任务,添加到执行器的集合中。添加完成之后调用执行器的执行函数。按照添加的顺序,遍历集合,正向执行任务。当前任务出现异常,出发回滚,逆向调用预设的补偿函数。正向和逆向的调用都附带重试机制。

2 实现

要执行的函数可以抽象成有返回值Callable和没有返回值Runable两种。两种函数以表达式的形式传入执行任务中。

示例的代码有的异常直接使用了RuntimeException,项目中应替换成合适的异常。没有定义异常处理的代码,根据实际场景,如果需要的话后续可以加上重试异常,和最终重试失败之后的处理逻辑。定义成接口,可由调用方根据实际形况扩展。

既然要接受表达式, 就需要定义函数式接口。

2.1 定义Callable和Runnable接口

package com.example.model.rocketmqdemo.retryqueue;

/**
 * @author liu
 * @date 2023/11/24
 * description: 带返回值
 */
@FunctionalInterface
public interface TaskCallable<V,R> {

    /**
     * 要执行的操作
     * @param arg 参数
     */
    R call(V arg);

}
package com.example.model.rocketmqdemo.retryqueue;

/**
 * @author liu
 * @date 2023/11/24
 * description: 没有返回值
 */
@FunctionalInterface
public interface TaskRollable<V> {

    void rollback(V arg);

}

2.2 定义重试策略

定义重试策略接口,目前提供了固定时间间隔,固定重试次数的策略, 可以在创建执行任务的时候使用,并且指定任务的重试间隔和重试次数。

package com.example.model.rocketmqdemo.retryqueue;


/**
 * @author liu
 * @date 2023/11/24
 * description: 重试策略
 */
@FunctionalInterface
public interface RetryStrategy {

    /**
     * 判断是否可以重试
     *
     * @param retryTimes 当前重试次数
     * @param lastExecuteTimestamp 上次执行的时间戳
     * @return {@link boolean } 是否可以重试, true=可以重试/false=不能重试
     * @author liuzhiyong
     * @date 2023/11/24
     */
    boolean izRetry(int retryTimes, long lastExecuteTimestamp);

}
package com.example.model.rocketmqdemo.retryqueue;

import lombok.SneakyThrows;

import java.time.LocalDateTime;

/**
 * @author liu
 * @date 2023/11/24
 * description: 固定时间间隔, 固定次数重试
 */
public class FixTimeStrategy implements RetryStrategy {

    /**
     * 重试间隔
     * 单位ms
     */
    private final long interval;

    /**
     * 重试次数
     */
    private final int times;


    public FixTimeStrategy(long interval, int times) {
        this.interval = interval;
        this.times = times;
    }


    @Override
    @SneakyThrows
    public boolean izRetry(int retryTimes, long lastExecuteTimestamp) {
        if (retryTimes == 1) {
            return true;
        }
        // 超过重试次数, 不能重试
        if (retryTimes > times) {
            return false;
        }

        // 当前时间戳
        long currentTimeMillis = System.currentTimeMillis();
        // 延时的时间接节点
        long intervalTime = lastExecuteTimestamp + interval;
        // 当前时间超过了延时的时间节点 返回true, 可以重试
        if (currentTimeMillis >= intervalTime) {
            return true;
        }
        // 没有超过重试时间节点, 睡眠到重试的时间 返回可以重试
        Thread.sleep(intervalTime - currentTimeMillis);
        return  true;
    }
}

2.3 定义执行任务

package com.example.model.rocketmqdemo.retryqueue;

/**
 * @author liuzhi
 * @date 2023/11/24
 * description: 任务参数类型
 */
public enum ArgStrategy {
    NONE, // 不需要参数
    BY_USER, // 用户设置
    LAST_RESULT //依赖于上一个任务的结果
}
package com.example.model.rocketmqdemo.retryqueue;

/**
 * @author liu
 * @date 2023/11/24
 * description: 重复执行的任务, 定义执行能力
 */
public interface RetryTask {

    default void doRetryRun() {
        throw new RuntimeException("不支持的方法");
    }

    default <R> R doRetryCall() {
        throw new RuntimeException("不支持的方法");
    }

    default void doRollBack() {
        throw new RuntimeException("不支持的方法");
    }

}
package com.example.model.rocketmqdemo.retryqueue.transactional;

import com.example.model.rocketmqdemo.retryqueue.*;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * @author liu
 * @date 2023/11/24
 * description: 带有回滚和返回值的执行任务
 */
@Slf4j
@Builder
@Data
public class CallWithRollBackTask<V, R> implements RetryTask {

    public CallWithRollBackTask(TaskCallable<V, R> taskCall, TaskRollable<V> taskRollback, RetryStrategy retryStrategy, int retryTimes, long lastExecuteTimestamp, V arg, ArgStrategy argStrategy) {
        this.taskCall = taskCall;
        this.taskRollback = taskRollback;
        this.retryStrategy = retryStrategy;
        // 初始值设置为1, 这么写是因为用了@Builder注解,不自动一构造函数初始值会被@Builder提供的功能覆盖
        this.retryTimes = 1;
        this.lastExecuteTimestamp = lastExecuteTimestamp;
        this.arg = arg;
        this.argStrategy = argStrategy;
    }

    /**
     * 任务函数
     */
    private final TaskCallable<V, R> taskCall;

    /**
     * 回滚函数
     */
    private TaskRollable<V> taskRollback;

    /**
     * 重试策略函数
     */
    private final RetryStrategy retryStrategy;

    /**
     * 当前重试次数
     * 初始值为1
     */
    private int retryTimes;

    /**
     * 上一次执行的时间戳
     */
    private long lastExecuteTimestamp;

    /**
     * 参数
     */
    private V arg;

    /**
     * 参数策略
     */
    private final ArgStrategy argStrategy;

    /**
     * 带重试策略的调用call函数
     */
    public R doRetryCall() {
        Exception err = null;
        int callRetryTimes = this.retryTimes;
        while (retryStrategy.izRetry(callRetryTimes, lastExecuteTimestamp)) {
            try {
                return taskCall.call(arg);
            } catch (Exception e) {
                log.warn("出现异常重试");
                err = e;
                callRetryTimes += 1;
            } finally {
                this.lastExecuteTimestamp = System.currentTimeMillis();
            }
        }
        throw new RuntimeException(err);
    }

    /**
     * 带重试的回滚
     */
    public void doRollBack() {
        if (taskRollback == null) {
            return;
        }
        Exception err = null;
        int rollBackRetryTimes = this.retryTimes;
        while (retryStrategy.izRetry(rollBackRetryTimes, lastExecuteTimestamp)) {
            try {
                taskRollback.rollback(arg);
                return;
            } catch (Exception e) {
                log.warn("回滚出现异常");
                err = e;
                rollBackRetryTimes += 1;
            } finally {
                this.lastExecuteTimestamp = System.currentTimeMillis();
            }
        }
        throw new RuntimeException(err);
    }

}
package com.example.model.rocketmqdemo.retryqueue.transactional;

import com.example.model.rocketmqdemo.retryqueue.*;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * @author liu
 * @date 2023/11/24
 * description: 带有回滚和没有返回值的执行任务
 */
@Slf4j
@Builder
@Data
public class RunWithRollBackTask<V> implements RetryTask {

    public RunWithRollBackTask(TaskRunnable<V> taskRun, TaskRollable<V> taskRollback, RetryStrategy retryStrategy, int retryTimes, long lastExecuteTimestamp, V arg, ArgStrategy argStrategy) {
        this.taskRun = taskRun;
        this.taskRollback = taskRollback;
        this.retryStrategy = retryStrategy;
        // 初始值设置为1, 这么写是因为@Builder注解
        this.retryTimes = 1;
        this.lastExecuteTimestamp = lastExecuteTimestamp;
        this.arg = arg;
        this.argStrategy = argStrategy;
    }

    /**
     * 任务函数
     */
    private final TaskRunnable<V> taskRun;

    /**
     * 回滚函数
     */
    private TaskRollable<V> taskRollback;

    /**
     * 重试策略函数
     */
    private final RetryStrategy retryStrategy;

    /**
     * 当前重试次数
     * 初始值设置为1
     */
    private int retryTimes;

    /**
     * 上一次执行的时间戳
     */
    private long lastExecuteTimestamp;

    /**
     * 参数
     */
    private V arg;

    /**
     * 参数策略
     */
    private final ArgStrategy argStrategy;

    /**
     * 带重试策略的调用call函数
     */
    public void doRetryRun() {
        Exception err = null;
        int runRetryTimes = this.retryTimes;
        while (retryStrategy.izRetry(runRetryTimes, lastExecuteTimestamp)) {
            try {
                taskRun.run(arg);
                return;
            } catch (Exception e) {
                log.warn("出现异常重试");
                err = e;
                runRetryTimes += 1;
            } finally {
                this.lastExecuteTimestamp = System.currentTimeMillis();
            }
        }
        throw new RuntimeException(err);
    }

    /**
     * 带重试的回滚
     */
    public void doRollBack() {
        if (taskRollback == null) {
            return;
        }
        Exception err = null;
        int rollBackRetryTimes = this.retryTimes;
        while (retryStrategy.izRetry(rollBackRetryTimes, lastExecuteTimestamp)) {
            try {
                taskRollback.rollback(arg);
                return;
            } catch (Exception e) {
                log.warn("回滚出现异常");
                err = e;
                rollBackRetryTimes += 1;
            } finally {
                this.lastExecuteTimestamp = System.currentTimeMillis();
            }
        }
        throw new RuntimeException(err);
    }

}

2.4 定义执行器

执行器函数还可以继续优化,写的demo比较省事。

package com.example.model.rocketmqdemo.retryqueue.transactional;

import com.example.model.rocketmqdemo.retryqueue.ArgStrategy;
import com.example.model.rocketmqdemo.retryqueue.RetryTask;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @author liu
 * @date 2023/11/24
 * description: 事务任务执行器
 */
@Data
@Slf4j
public class TransactionalTaskExecutor {

    private TransactionalTaskExecutor() {
        // 不允许外部使用构造函数创建此对象
    }

    /**
     * 存储任务的集合
     */
    private final List<RetryTask> taskList = new ArrayList<>();

    /**
     * 执行任务队列
     */
    public Object execute() {
        // 记录执行失败时的集合索引
        int failIndex = -1;
        // 记录最最后一次执行call函数的返回值
        Object lastRes = null;
        // 记录异常
        Exception err = null;

        // 遍历任务集合正向执行
        for (int i = 0; i < taskList.size(); i++) {
            try {
                RetryTask retryTask = taskList.get(i);
                if (retryTask instanceof CallWithRollBackTask callTask) {
                    ArgStrategy argStrategy = Optional.ofNullable(callTask.getArgStrategy()).orElse(ArgStrategy.NONE);
                    lastRes = switch (argStrategy) {
                        case NONE -> {
                            callTask.setArg(null);
                            yield callTask.doRetryCall();
                        }
                        case BY_USER -> {
                            yield callTask.doRetryCall();
                        }
                        case LAST_RESULT -> {
                            callTask.setArg(lastRes);
                            yield callTask.doRetryCall();
                        }
                    };
                }
                if (retryTask instanceof RunWithRollBackTask runTask) {
                    ArgStrategy argStrategy = Optional.ofNullable(runTask.getArgStrategy()).orElse(ArgStrategy.NONE);
                    switch (argStrategy) {
                        case NONE -> {
                            runTask.setArg(null);
                            runTask.doRetryRun();
                        }
                        case BY_USER -> {
                            runTask.doRetryRun();
                        }
                        case LAST_RESULT -> {
                            runTask.setArg(lastRes);
                            runTask.doRetryRun();
                        }
                    };
                }
            } catch (Exception e) {
                log.warn("事务队列执行出现异常, 进入回滚流程");
                err = e;
                // 从失败的前一个节点开始回滚
                failIndex = i - 1;
                break;
            }
        }
        // 说明有异常, 进入回滚流程
        if (failIndex != -1) {
            try {
                executeRollBack(failIndex);
            } catch (Exception e) {
                log.warn("事务回滚出现异常");
                err = e;
            }
        }

        // 说明有异常, 抛出异常, 走全局异常处理
        if (err != null) {
            throw new RuntimeException(err);
        }
        // 返回最后一的结果
        return lastRes;
    }

    /**
     * 执行回滚
     */
    private void executeRollBack(int failIndex) {
        for (int i = failIndex; i > -1; i--) {
            RetryTask retryTask = taskList.get(i);
            retryTask.doRollBack();
        }
    }

    /**
     * 创建建造者对象
     */
    public static TransactionalTaskExecutorBuilder builder() {
        return new TransactionalTaskExecutorBuilder();
    }

    /**
     * 建造类,创建TransactionalTaskExecutor对象
     */
    public static class TransactionalTaskExecutorBuilder {

        private final TransactionalTaskExecutor instance;

        public TransactionalTaskExecutorBuilder() {
            instance = new TransactionalTaskExecutor();
        }

        /**
         * 将任务添加进队列
         */
        public TransactionalTaskExecutorBuilder addTaskList(RetryTask retryTask) {
            instance.getTaskList().add(retryTask);
            return this;
        }

        public TransactionalTaskExecutor build() {
            return instance;
        }

    }


}

3 测试

package com.example.model.rocketmqdemo.controller;

import com.example.framework.common.model.resp.Result;
import com.example.model.rocketmqdemo.mq.product.RocketMqProducer;
import com.example.model.rocketmqdemo.retryqueue.ArgStrategy;
import com.example.model.rocketmqdemo.retryqueue.FixTimeStrategy;
import com.example.model.rocketmqdemo.retryqueue.transactional.CallWithRollBackTask;
import com.example.model.rocketmqdemo.retryqueue.transactional.RunWithRollBackTask;
import com.example.model.rocketmqdemo.retryqueue.transactional.TransactionalTaskExecutor;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author liu
 * @date 2023/11/23
 * description:
 */
@Slf4j
@Tag(name = "mq消息接口")
@RestController
@RequestMapping
@RequiredArgsConstructor
public class TestController {


    @Operation(summary = "事务队列测试接口")
    @GetMapping("transactional-queue-demo")
    public Result<Object> transactionalQueueDemo() {
        log.info("事务队列测试接口");

        // 测试run
//        TransactionalTaskExecutor.builder()
//                .addTaskList(RunWithRollBackTask.builder()
//                        .taskRun(this::run1)
//                        .taskRollback(this::rollBack1)
//                        .retryStrategy(new FixTimeStrategy(2000L, 3))
//                        .argStrategy(ArgStrategy.BY_USER).arg("param....测试参数")
//                        .build())
//                .addTaskList(RunWithRollBackTask.builder()
//                        .taskRun(this::run2)
//                        .taskRollback(this::rollBack2)
//                        .retryStrategy(new FixTimeStrategy(2000L, 3))
//                        .argStrategy(ArgStrategy.BY_USER).arg("param....测试参数")
//                        .build())
//                .addTaskList(RunWithRollBackTask.builder()
//                        .taskRun(this::run3)
//                        .taskRollback(this::rollBack3)
//                        .retryStrategy(new FixTimeStrategy(2000L, 3))
//                        .argStrategy(ArgStrategy.BY_USER).arg("param....测试参数")
//                        .build())
//                .addTaskList(RunWithRollBackTask.builder()
//                        .taskRun(this::run4)
//                        .taskRollback(this::rollBack4)
//                        .retryStrategy(new FixTimeStrategy(2000L, 3))
//                        .argStrategy(ArgStrategy.BY_USER).arg("param....测试参数")
//                        .build())
//                .build()
//                .execute();

        // 测试call
//        Object res = TransactionalTaskExecutor.builder()
//                .addTaskList(CallWithRollBackTask.builder()
//                        .taskCall(this::call1)
//                        .taskRollback(this::rollBack1)
//                        .retryStrategy(new FixTimeStrategy(2000L, 3))
//                        .argStrategy(ArgStrategy.BY_USER).arg(1)
//                        .build())
//                .addTaskList(CallWithRollBackTask.builder()
//                        .taskCall(this::call2)
//                        .taskRollback(this::rollBack2)
//                        .retryStrategy(new FixTimeStrategy(2000L, 3))
//                        .argStrategy(ArgStrategy.LAST_RESULT)
//                        .build())
//                .addTaskList(CallWithRollBackTask.builder()
//                        .taskCall(this::call3)
//                        .taskRollback(this::rollBack3)
//                        .retryStrategy(new FixTimeStrategy(2000L, 3))
//                        .argStrategy(ArgStrategy.LAST_RESULT)
//                        .build())
//                .addTaskList(CallWithRollBackTask.builder()
//                        .taskCall(this::call4)
//                        .taskRollback(this::rollBack4)
//                        .retryStrategy(new FixTimeStrategy(2000L, 3))
//                        .argStrategy(ArgStrategy.LAST_RESULT)
//                        .build())
//                .build()
//                .execute();
//        log.info("结果: res: {}", res);

        // run 和 call 混合
        Object res = TransactionalTaskExecutor.builder()
                .addTaskList(CallWithRollBackTask.builder()
                        .taskCall(this::call1)
                        .taskRollback(this::rollBack1)
                        .retryStrategy(new FixTimeStrategy(2000L, 3))
                        .argStrategy(ArgStrategy.BY_USER).arg(1)
                        .build())
                .addTaskList(RunWithRollBackTask.builder()
                        .taskRun(this::run2)
                        .taskRollback(this::rollBack2)
                        .retryStrategy(new FixTimeStrategy(2000L, 3))
                        .argStrategy(ArgStrategy.LAST_RESULT)
                        .build())
                .addTaskList(CallWithRollBackTask.builder()
                        .taskCall(this::call3)
                        .taskRollback(this::rollBack3)
                        .retryStrategy(new FixTimeStrategy(2000L, 3))
                        .argStrategy(ArgStrategy.LAST_RESULT)
                        .build())
                .addTaskList(RunWithRollBackTask.builder()
                        .taskRun(this::run4)
                        .taskRollback(this::rollBack4)
                        .retryStrategy(new FixTimeStrategy(2000L, 3))
                        .argStrategy(ArgStrategy.LAST_RESULT)
                        .build())
                .build()
                .execute();
        log.info("结果: res: {}", res);


        return Result.OK();
    }


    private void run1(Object arg) {
        log.info("----------------run1  arg:{}", arg);
    }

    private void run2(Object arg) {
        log.info("----------------run2  arg:{}", arg);
    }

    private void run3(Object arg) {
        log.info("----------------run3  arg:{}", arg);
    }

    private void run4(Object arg) {
        log.info("----------------run4  arg:{}", arg);
        int i = 4 / 0;
    }

    private int call1(Object arg) {
        log.info("----------------call1  arg:{}", arg);
        return (int) arg + 1;
    }

    private int call2(Object arg) {
        log.info("----------------call2  arg:{}", arg);
        return (int) arg + 1;
    }

    private int call3(Object arg) {
        log.info("----------------call3  arg:{}", arg);
        return (int) arg + 1;
    }

    private int call4(Object arg) {
        log.info("----------------call4  arg:{}", arg);
//        int i = 4 / 0;
        return (int) arg + 1;
    }

    private void rollBack1(Object arg) {
        log.info("----------------rollback1  arg:{}", arg);
//        int i = 1 / 0;
    }

    private void rollBack2(Object arg) {
        log.info("----------------rollback2  arg:{}", arg);
    }

    private void rollBack3(Object arg) {
        log.info("----------------rollback3  arg:{}", arg);
    }

    private void rollBack4(Object arg) {
        log.info("----------------rollback4  arg:{}", arg);
    }


}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值