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);
}
}