需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。
包括数据模型和核心逻辑
定义业务的消息配置如topic 、key、tag
定义业务的重试策略(最大重试次数、报警方式 短信 钉钉)
消息内存队列 LinkedBlockedQueue
重试消息内存队列 RetryMessageBlockedQueue
持久层 transaction_message
使用TransactionTemplate 保证业务业务操作和TransactionMessage一并提交事务。
并且分派一个任务给消息消费工人。
对外暴露注解,使用AOP+注解调用本地事务。
1. 负责将本地事务生成的消息保存到消息内存队列。
2. 消费者线程读取消息并发送给Broker。
1. 将消费失败的消息保存到重试消费内存队列。每300毫秒重试一次。
2. 高可靠操作。应用启动时将事务消息表未发送成功的消息,再次发送。
每个业务自行决定最大重试次数。若达到最大重试次数是报警还是放弃。
distribution-message-transaction:
failMessageFailedSend: 3
transaction-map:
order:
businessCode: order
topicName: orderTopic
maxRetires: 3
/**
* 事务消息
* @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;
}
@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 transactionMessage;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionMessageAnnotation {
/**
* 业务编码
* @return
*/
String transactionCode();
}
/**
* 事务消息处理
*
* @author jiguansheng
* @date 2023/7/15
**/
@Aspect
@Component
public class TransactionMessageAop {
private final Map transactionMessageMap;
private final MessageWorker worker;
private final TransactionMessageService transactionMessageService;
private final TransactionTemplate transactionTemplate;
public TransactionMessageAop(Map 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 pair = transactionTemplate.execute(s -> {
Object result;
try {
result = proceedingJoinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
//要求返回值必须ImmutablePair对象。
if (result instanceof ImmutablePair) {
ImmutablePair obj = (ImmutablePair) 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 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 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 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();
}
}