分布式事务-本地消息表

本地消息表方案核心思路

需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。

方案核心具体实现

核心思想

  1.  利用事务原子性保证业务操作和发送日志同时持久化。
  2.  采用消费者生产者模式。应用读取消息并发送给消息中间件。
    1. 投递成功,修改消息发送状态是成功。
    2. 投递失败,修改消息发送失败是失败。并且给补偿消息发送工人增加一个任务
  3. 补偿消息工人投放一次消息,每间隔一段时间。若超过制定阈值都是失败。将放弃发送并报警。

包括数据模型和核心逻辑

业务规则

定义业务的消息配置如topic 、key、tag

定义业务的重试策略(最大重试次数、报警方式 短信 钉钉) 

数据模型

消息 TransactionMessage

消息内存队列 LinkedBlockedQueue

重试消息内存队列 RetryMessageBlockedQueue

持久层 transaction_message

核心逻辑
本地事务

使用TransactionTemplate  保证业务业务操作和TransactionMessage一并提交事务。

并且分派一个任务给消息消费工人。

注解

对外暴露注解,使用AOP+注解调用本地事务。

消息消费工人 MeesageWorker

1. 负责将本地事务生成的消息保存到消息内存队列。

2. 消费者线程读取消息并发送给Broker。

补偿消息消费工人 RetryMessageWorker

1. 将消费失败的消息保存到重试消费内存队列。每300毫秒重试一次。

2. 高可靠操作。应用启动时将事务消息表未发送成功的消息,再次发送。

重试策略

每个业务自行决定最大重试次数。若达到最大重试次数是报警还是放弃。

统一配置消息发送失败重试时间间隔和每种业务的topic和重试策略

distribution-message-transaction:
  failMessageFailedSend: 3
  transaction-map:
    order:
      businessCode: order
      topicName: orderTopic
      maxRetires: 3 


TransactionMessage

/**
 * 事务消息
 * @author jiguansheng
 * @date 2023/7/15
 **/
@Getter
@Setter
public class TransactionMessage {
    /**
     * 业务编码
      */ 
    private String businessCode;
    
    /**
     * 本地消息事务id
     */
    private String transactionId;
    /**
     * 消息内容
     */
    private String body;
    /**
     * 0 初始化 1 已发送  2发送失败  3 抛弃
     */
    private Integer sendStatus;
    /**
     * 重试次数
     */
    private Integer retriesTime;
    
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
}

TransactionMessage

@Getter
@Setter
public class TransactionMessageProperties {
    /**
     * 业务编码
     */
    private String businessCode;

    /**
     * 消息主题
     */
    private String topic;

    /**
     * 消息主题key
     */
    private String key;

    /**
     * 最大重试次数
     */
    private Integer maxTries;

    /**
     * 报警策略
     */
    private Integer warningStrategy;
}
/**
 * 事务消息配置
 * @author jiguansheng
 * @date 2023/7/15
 **/
@ConfigurationProperties("distribution-message-transaction")
@Getter
@Setter
public class DistributionTransactionMessageConfig {
    /**
     * 补偿消息发送间隔
     */
    private int failMessageSendInterval;
    private Map<String,TransactionMessageProperties> transactionMessage;
}

UML图

消息正常消费时序图

消息异常消费时序图

类图

TransactionMessageAnnotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionMessageAnnotation {

    /**
     * 业务编码
     * @return
     */
    String transactionCode();
}

TransactionMessageAop


/**
 * 事务消息处理
 *
 * @author jiguansheng
 * @date 2023/7/15
 **/
@Aspect
@Component
public class TransactionMessageAop {

    private final Map<String, TransactionMessageProperties> transactionMessageMap;
    private final MessageWorker worker;

    private final TransactionMessageService transactionMessageService;
    private final TransactionTemplate transactionTemplate;


    public TransactionMessageAop(Map<String, TransactionMessageProperties> transactionMessageMap, MessageWorker worker, TransactionMessageService transactionMessageService, TransactionTemplate transactionTemplate) {
        this.transactionMessageMap = transactionMessageMap;
        this.worker = worker;
        this.transactionMessageService = transactionMessageService;
        this.transactionTemplate = transactionTemplate;
    }


    @Pointcut("@annotation(com.example.transaction.config.TransactionMessageAnnotation)")
    public void scope() {

    }

    @Around("scope()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {

        //事务模版执行事务
        ImmutablePair<TransactionMessageDTO, Object> pair = transactionTemplate.execute(s -> {
            Object result;
            try {
                result = proceedingJoinPoint.proceed();
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
            //要求返回值必须ImmutablePair对象。 
            if (result instanceof ImmutablePair) {
                ImmutablePair<String, String> obj = (ImmutablePair<String, String>) result;
                MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
                TransactionMessageAnnotation annotation = AnnotatedElementUtils.findMergedAnnotation(signature.getMethod(), TransactionMessageAnnotation.class);
                TransactionMessageProperties transactionMessageProperties = transactionMessageMap.get(annotation.transactionCode());
                if (transactionMessageProperties == null) {
                    throw new IllegalArgumentException("业务编码不存在!");
                }
                TransactionMessageDTO transactionMessageDTO = saveTransactionMessage(obj, transactionMessageProperties);
                return ImmutablePair.of(transactionMessageDTO, result);
            } else {
                throw new IllegalArgumentException("返回值类型必须是ImmutablePair");
            }
        });
        worker.addTask(pair.getLeft());
        return pair.getRight();

    }

    private TransactionMessageDTO saveTransactionMessage(ImmutablePair<String, String> obj, TransactionMessageProperties transactionMessageProperties) {

        TransactionMessageDTO transactionMessageDTO = new TransactionMessageDTO();
        transactionMessageDTO.setTransactionId(obj.getLeft());
        transactionMessageDTO.setBody(obj.getRight());
        transactionMessageDTO.setTopic(transactionMessageProperties.getTopic());
        transactionMessageDTO.setRetriesTime(0);
        transactionMessageDTO.setBusinessCode(transactionMessageProperties.getBusinessCode());
        transactionMessageService.saveTransactionMessage(transactionMessageDTO);
        return transactionMessageDTO;
    }
}

要求调用方法返回值必须是ImmutablePair ,方便获取事务提交的唯一值(比如说订单号编码、主键id)和要发送的消息体。

消息发送工人

/**
 * 事务消息工人
 * @author jiguansheng
 * @date 2023/7/15
 **/
@Service
public class MessageWorker {
    private LinkedBlockingQueue<TransactionMessageDTO> messageQueue = new LinkedBlockingQueue<>();

    private final TransactionMessageService transactionMessageService;

    public MessageWorker(TransactionMessageService transactionMessageService) {
        this.transactionMessageService = transactionMessageService;
    }
    /**
     * 分派任务给工人
     * @param transactionMessageDTO
     */
    public void addTask(TransactionMessageDTO transactionMessageDTO){
        messageQueue.add(transactionMessageDTO);
    }

    /**
     * 工人工作
     */
    public void work(){
        new Thread(()->{
          while (true){
              try {
                  TransactionMessageDTO message = messageQueue.take();
                  //发送消息
                  transactionMessageService.setSuccess(message);
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }

          }
        }).start();
    }
}

补偿消息发送工厂

/**
 * 事务消息工人
 * @author jiguansheng
 * @date 2023/7/15
 **/

@Service
public class RetryMessageWorker implements InitializingBean {
    /**
     * 事务消息管理
     */
    private final TransactionMessageService messageService;

    /**
     * 业务配置
     */
    private DistributionTransactionMessageConfig config;

    private LinkedBlockingQueue<TransactionMessageDTO> messageQueue = new LinkedBlockingQueue<>();



    public RetryMessageWorker(TransactionMessageService messageService,DistributionTransactionMessageConfig config) {
        this.messageService = messageService;
        this.config = config;
    }


    public void addTask(TransactionMessageDTO transactionMessageDTO){
        messageQueue.add(transactionMessageDTO);
    }

    public void work(){
        new Thread(()->{

          while (true){
              try {
                  //休息3秒
                  TimeUnit.SECONDS.sleep(config.getFailMessageSendInterval());
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
              TransactionMessageDTO message;
              try {
                  message = messageQueue.take();
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
              //发送成功消息
              messageService.setSuccess(message);
              //发送失败
              if(Objects.equals(config.getTransactionMessage().get(message.getBusinessCode()).getMaxTries(), message.getRetriesTime())){
                  messageService.setAbandon(message);
              }else{
                  messageService.setFailed(message);
                  message.setRetriesTime(message.getRetriesTime()+1);
                  messageQueue.add(message);
              }
          }
        }).start();
    }

    /**
     * 检查失败的任务,当应用启动时。
     */
   public void checkFailTask(){

   }

    /**
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        work();
        checkFailTask();
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiguansheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值