RocketMQ(4)- 发送事务消息

1. 事务消息原理

RocketMQ 事务消息的实现原理基于两阶段提交和定时事务状态回查来决定消息最终是提交还是回滚。

2. 事务消息启动

TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer("trans_producer");
ExecutorService executorService = new ThreadPoolExecutor(2, 5,
		100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
	@Override
	public Thread newThread(Runnable r) {
		Thread thread = new Thread(r);
		thread.setName("client-transaction-msg-check-thread");
		return thread;
	}
});
// 执行事务监听任务的线程池
producer.setExecutorService(executorService);
producer.setTransactionListener(transactionListener);
producer.start();

2.1 事务生产者启动

调用 org.apache.rocketmq.client.producer.TransactionMQProducer#start :首先初始化事务生产者的基础配置,然后调用 org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#start:完成生产者的启动。这里的逻辑和默认的生产者启动逻辑一样。

public void start() throws MQClientException {
    // 初始化事务消息环境,设置执行事务消息监听的线程池
    this.defaultMQProducerImpl.initTransactionEnv();
    // 启动默认的 producer 流程
    super.start();
}

public void initTransactionEnv() {
	// 事务状态执行的线程池初始化
	TransactionMQProducer producer = (TransactionMQProducer) this.defaultMQProducer;
	if (producer.getExecutorService() != null) {
		this.checkExecutor = producer.getExecutorService();
	} else {
		this.checkRequestQueue = new LinkedBlockingQueue<Runnable>(2000);
		this.checkExecutor = new ThreadPoolExecutor(
			1,
			1,
			1000 * 60,
			TimeUnit.MILLISECONDS,
			this.checkRequestQueue);
	}
}

3 发送事务消息

org.apache.rocketmq.client.producer.TransactionMQProducer#sendMessageInTransaction:是事务生产者的发送消息的接口,首先检查事件监听器是否注册,如果为空,则直接抛出遗产给,最终调用 org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendMessageInTransaction :默认同步发送

public TransactionSendResult sendMessageInTransaction(final Message msg, final Object arg) throws MQClientException {
	if (null == this.transactionListener) {
		throw new MQClientException("TransactionListener is null", null);
	}
	return this.defaultMQProducerImpl.sendMessageInTransaction(msg, transactionListener, arg);
}

3.1 事务消息发送流程

  1. org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendMessageInTransaction:发送事务消息,首先检查事务执行器是否存在、然后检查消息是否合法,然后给属性添加消息:TRAN_MSG:truePGROUP:groupName

    // 如果事务执行器为空,则抛出异常
    if (null == tranExecuter) {
    	throw new MQClientException("tranExecutor is null", null);
    }
    // 检查消息
    Validators.checkMessage(msg, this.defaultMQProducer);
    SendResult sendResult = null;
    // 设置 msg 属性,TRAN_MSG:true
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
    // PGROUP:groupName
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
    try {
    
  2. 然后调用org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send):默认 producer 的发送消息逻辑,上文分析过,这里针对事务消息,主要就是设置消息的类型为 Trans_Msg_Half 消息,然后调用 org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#prepareMessage: broker 接收到消息后,处理 prepare 消息。其他逻辑与一般消息一样。

    // 事务消息处理
    final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
    // 事务提交或者不操作
    if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE
    	|| tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
    	// 延迟投递, 保证延迟级别最大为 maxDelayLevel
    	if (msg.getDelayTimeLevel() > 0) {
    		if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
    			msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
    		}
    		// 存储消息时,延迟消息进入 Topic 为 SCHEDULE_TOPIC_XXXX
    		topic = ScheduleMessageService.SCHEDULE_TOPIC;
    		// 延迟级别与消息队列编号做固定映射:queueId = delayLevel - 1
    		queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());
    
    		// 备份真实的主题和消息队列id到属性中
    		MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
    		MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
    		msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
    		// 设置修改后的 topic 和 queueId,这里保证了同一消息级别的消息都会放到同一个 queue
    		msg.setTopic(topic);
    		msg.setQueueId(queueId);
    	}
    }
    
  3. 得到上一步的消息发送结果后,执行对应的操作。

    1. 发送成功,设置消息的 transactionId,然后执行 org.apache.rocketmq.client.producer.TransactionListener#executeLocalTransaction :事务监听器中的本地事务的逻辑。得到事务消息的本地事务状态,这里是为了保证本地事务与业务代码处于同一个事务,也就是发送方这边的事务一致性。
    2. 发送失败:则设置本次事务状态为 ROLLBACK_MESSAGE
    // 初始化本地事务状态为 UNKNOW
    LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
    Throwable localException = null;
    switch (sendResult.getSendStatus()) {
    	case SEND_OK: {
    		try {
    			// 设置事务id
    			if (sendResult.getTransactionId() != null) {
    				msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
    			}
    			// 获取消息客户端的事务id
    			String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
    			if (null != transactionId && !"".equals(transactionId)) {
    				msg.setTransactionId(transactionId);
    			}
    			// 2.如果消息发送成功,处理与消息关联的本地事务单元,客户端自己实现的事务消息监听
    			localTransactionState = tranExecuter.executeLocalTransaction(msg, arg);
    			// 返回的本地事务状态如果不为 commit,那么都会进行回滚
    			if (null == localTransactionState) {
    				localTransactionState = LocalTransactionState.UNKNOW;
    			}
    			if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
    				log.info("executeLocalTransactionBranch return {}", localTransactionState);
    				log.info(msg.toString());
    			}
    		} catch (Throwable e) {
    			log.info("executeLocalTransactionBranch exception", e);
    			log.info(msg.toString());
    			localException = e;
    		}
    	}
    	break;
    	case FLUSH_DISK_TIMEOUT:
    	case FLUSH_SLAVE_TIMEOUT:
    	case SLAVE_NOT_AVAILABLE:
    		localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
    		break;
    	default:
    		break;
    }
    
  4. 调用 org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#endTransaction:结束事务,根据上一步得到的事务状态执行提交、回滚或者暂时不处理操作。最后向 broker 发送结束事务请求,根据事务状态,提交或者回滚 commitlog 文件进度和是否删除 prepare 消息。

    public void endTransaction(
    	final SendResult sendResult,
    	final LocalTransactionState localTransactionState,
    	final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
    	//1.根据sendResult找到Prepared消息 ,sendResult包含事务消息的ID
    	//2.根据localTransaction更新消息的最终状态
    	final MessageId id;
    	if (sendResult.getOffsetMsgId() != null) {
    		id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId());
    	} else {
    		id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
    	}
    	String transactionId = sendResult.getTransactionId();
    	// 找到 broker 地址,用于发送消息请求
    	final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName());
    	EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
    	requestHeader.setTransactionId(transactionId);
    	// commilog 文件偏移量
    	requestHeader.setCommitLogOffset(id.getOffset());
    	switch (localTransactionState) {
    		case COMMIT_MESSAGE:
    			requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE);
    			break;
    		case ROLLBACK_MESSAGE:
    			requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE);
    			break;
    		case UNKNOW:
    			requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE);
    			break;
    		default:
    			break;
    	}
    	requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
    	requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
    	requestHeader.setMsgId(sendResult.getMsgId());
    	String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
    	// 发送请求,单向发送
    	this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
    		this.defaultMQProducer.getSendMsgTimeout());
    }
    

3 提交或回滚事务

事务的提交或者回滚,都是依赖于本地事务逻辑执行后返回的结果,当结果为 COMMIT_MESSAGEROLLBACK_MESSAGE 或者是 UNKNOW 时,都会进行不同的处理。

3.1 事务处理

不管本地事务的结果是什么,客户端都会发送一个单向的请求给 master 节点的 broker。broker 接收到请求后,根据请求码,会转发给 EndTransactionProcessor 处理。调用 org.apache.rocketmq.broker.processor.EndTransactionProcessor#processRequest 处理事务结果。

  1. 检查 store 的角色是否为 SALVE 模式,如果是直接返回。

    // 如果 store 为 slave 模式,那么直接返回
    if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) {
    	response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE);
    	LOGGER.warn("Message store is slave mode, so end transaction is forbidden. ");
    	return response;
    }
    
  2. 如果事务状态为 TRANSACTION_NOT_TYPE,则直接返回 null,不做处理.

    switch (requestHeader.getCommitOrRollback()) {
    	// 不做操作,直接返回 null
    	case MessageSysFlag.TRANSACTION_NOT_TYPE: {
    		LOGGER.warn("The producer[{}] end transaction in sending message,  and it's pending status."
    				+ "RequestHeader: {} Remark: {}",
    			RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
    			requestHeader.toString(),
    			request.getRemark());
    		return null;
    	}
            .........
            .........
    }
    
  3. 如果事务状态为 TRANSACTION_COMMIT_TYPE:表示本地事务执行成功,首先从 commitlog 文件中根据偏移量获取 prepare 消息,如果获取 prepare 消息成功,检查 prepare 消息是否合法。最后利用 prepare 消息,还原原始的 topicqueueId 等信息构建消息,发送最终消息并且将 prepare 消息删除。

    if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
    	// 从 commitlog 文件中根据偏移量获取 prepare 消息
    	result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
    	// 获取 prepare  消息成功
    	if (result.getResponseCode() == ResponseCode.SUCCESS) {
    		// 检查 prepare 消息是否合法
    		RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
    		if (res.getCode() == ResponseCode.SUCCESS) {
    			// 为什么用 prepare 消息,因为 prepare 消息不会被消费,所以会一直存在,直到要发送最终消息时,
    			// 利用 prepare 消息,重新使用原始的 topic、queueId 构建消息,发送消息。最后将 prepare 消息删除,
    			MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
    			msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
    			msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
    			msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
    			msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());
    			// 发送最终消息,其实就是将之前修改后的消息还原后的消息
    			RemotingCommand sendResult = sendFinalMessage(msgInner);
    			// 删除 prepare 消息,然后
    			if (sendResult.getCode() == ResponseCode.SUCCESS) {
    				this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
    			}
    			return sendResult;
    		}
    		return res;
    	}
    }
    
  4. 如果事务状态为 TRANSACTION_ROLLBACK_TYPE:表示本地事务执行失败,首先从 commitlog 文件中根据偏移量获取 prepare 消息,如果获取 prepare 消息成功,检查 prepare 消息是否合法。最后直接将 prepare 消息删除。这里是保证第一阶段是原子性的。

    if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {
    	// 从 commitlog 文件中根据偏移量获取 prepare 消息
    	result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader);
    	// 获取 prepare  消息成功
    	if (result.getResponseCode() == ResponseCode.SUCCESS) {
    		// 检查 prepare 消息是否合法
    		RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
    		if (res.getCode() == ResponseCode.SUCCESS) {
    			// 回滚操作则直接删除 prepare 消息。
    			this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
    		}
    		return res;
    	}
    }
    
  5. 上面两种事务状态提到的删除,并不是真正的删除,调用 org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#deletePrepareMessage:将 prepare 消息存储到 RMQ_SYS_TRANS_OP_HALF_TOPIC 主题中,表示该事务消息 (prepare状态的消息)已经处理过了,为未处理的事务进行事务回查提供查找依据。

    public boolean deletePrepareMessage(MessageExt msgExt) {
    	if (this.transactionalMessageBridge.putOpMessage(msgExt, TransactionalMessageUtil.REMOVETAG)) {
    		log.info("Transaction op message write successfully. messageId={}, queueId={} msgExt:{}", msgExt.getMsgId(), msgExt.getQueueId(), msgExt);
    		return true;
    	} else {
    		log.error("Transaction op message write failed. messageId is {}, queueId is {}", msgExt.getMsgId(), msgExt.getQueueId());
    		return false;
    	}
    }
    

    3.2 事务回查

    RocketMQ 通过 TransactionalMessageCheckService 线程 时去检测 RMQ_SYS_ TRANS_ HALF TOPIC 主题中的消息,回查消息 事务状态 TransactionalMessageCheckService 的检测频率默认为 1 分钟,可通过在 broker.conf 文件中设置 transactionChecklnterval 来改变默认值,单位为毫秒。

    1. 这里事务状态为 TRANSACTION_COMMIT_TYPETRANSACTION_ROLLBACK_TYPE 的逻辑都已经处理完毕,当状态为 TRANSACTION_NOT_TYPE,prepare 消息还没有删除,那么就会交给事务回查服务 TransactionalMessageCheckService 去回查事务状态,也就是自定义的事务回查接口。这个服务是 org.apache.rocketmq.broker.BrokerController#initialTransaction broker控制器初始化时,启动的事务回查服务。

      public void start() {
      	// started 默认为 false
      	if (started.compareAndSet(false, true)) {
      		// 运行 run 方法
      		super.start();
      		this.brokerController.getTransactionalMessageService().open();
      	}
      }
      public void run() {
      	log.info("Start transaction check service thread!");
      	// 默认检查间隔为 60 * 1000 = 1s
      	long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval();
      	while (!this.isStopped()) {
      		// 执行
      		this.waitForRunning(checkInterval);
      	}
      	log.info("End transaction check service thread!");
      }
      
    2. org.apache.rocketmq.broker.transaction.TransactionalMessageCheckService#onWaitEnd: 回查消息的事务状态。

    protected void onWaitEnd() {
    // 事务过期时间,默认为 3s
    long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();
    // 事务回查最大检查次数,默认为 5次
    int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax();
    long begin = System.currentTimeMillis();
    log.info(“Begin to check prepare message, begin time:{}”, begin);
    this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());
    log.info(“End to check prepare message, consumed time:{}”, System.currentTimeMillis() - begin);
    }

    
    3. `org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#check`:核心方法,检查消息的事务状态,通过主题 `RMQ_SYS_TRANS_HALF_TOPIC` 获取消息队列,即 prepare 消息所在队列。
    
    ```java
    // 处理 prepare 消息的主题
    String topic = MixAll.RMQ_SYS_TRANS_HALF_TOPIC;
    // 从 broker 中获取 topic 的路由信息和消息队列
    Set<MessageQueue> msgQueues = transactionalMessageBridge.fetchMessageQueues(topic);
    if (msgQueues == null || msgQueues.size() == 0) {
    	log.warn("The queue of topic is empty :" + topic);
    	return;
    }
    
    1. 遍历获取到的消息队列,获取已经处理过的 prepare 消息,主题为 SYS_TRANS_OP_HALF_TOPIC,即一阶段是提交的事务状态为提交、回滚状态的消息。然后分别获取同一消息队列上 prepare 消息进度、已经处理过的 prepare 消息进度。

      for (MessageQueue messageQueue : msgQueues) {
      	long startTime = System.currentTimeMillis();
      	// 获取处理过的 prepare 消息的消息主题 RMQ SYS_TRANS_OP_HALF_TOPIC
      	MessageQueue opQueue = getOpQueue(messageQueue);
      	// 获取 prepare 消息的消费进度
      	long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue);
      	// 获取处理过的 prepare 消息的消息进度
      	long opOffset = transactionalMessageBridge.fetchConsumeOffset(opQueue);
      	log.info("Before check, the queue={} msgOffset={} opOffset={}", messageQueue, halfOffset, opOffset);
      	if (halfOffset < 0 || opOffset < 0) {
      		log.error("MessageQueue: {} illegal offset read: {}, op offset: {},skip this queue", messageQueue,
      			halfOffset, opOffset);
      		continue;
      	}
      	.......
      	.......
      }
      
    2. 调用 org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#fillOpRemoveMap: 从 opQueue 中拉取 32 条消息,方便判断消息是否已经处理过,如果处理过,就不用再次发送事务状态回查请求,避免重复的事务回查。

      List<Long> doneOpOffset = new ArrayList<>();
      HashMap<Long, Long> removeMap = new HashMap<>();
      // 根据当前的处理进度依次从已处理队列拉取 32 条消息,方便判断消息是否已经处理过,
      // 如果处理过,就不用再次发送事务状态回查请求,避免重复的事务回查
      PullResult pullResult = fillOpRemoveMap(removeMap, opQueue, opOffset, halfOffset, doneOpOffset);
      // 拉取消息为空,继续尝试拉取下一个消费队列
      if (null == pullResult) {
      	log.error("The queue={} check msgOffset={} with opOffset={} failed, pullResult is null",
      		messageQueue, halfOffset, opOffset);
      	continue;
      }
      
      private PullResult fillOpRemoveMap(HashMap<Long, Long> removeMap,
      	MessageQueue opQueue, long pullOffsetOfOp, long miniOffset, List<Long> doneOpOffset) {
      	// 从 opQueue 中拉取 32条 消息
      	PullResult pullResult = pullOpMsg(opQueue, pullOffsetOfOp, 32);
      	if (null == pullResult) {
      		return null;
      	}
      	// 拉取消息状态
      	if (pullResult.getPullStatus() == PullStatus.OFFSET_ILLEGAL
      		|| pullResult.getPullStatus() == PullStatus.NO_MATCHED_MSG) {
      		log.warn("The miss op offset={} in queue={} is illegal, pullResult={}", pullOffsetOfOp, opQueue,
      			pullResult);
      		transactionalMessageBridge.updateConsumeOffset(opQueue, pullResult.getNextBeginOffset());
      		return pullResult;
      	} else if (pullResult.getPullStatus() == PullStatus.NO_NEW_MSG) {
      		log.warn("The miss op offset={} in queue={} is NO_NEW_MSG, pullResult={}", pullOffsetOfOp, opQueue,
      			pullResult);
      		return pullResult;
      	}
      	List<MessageExt> opMsg = pullResult.getMsgFoundList();
      	if (opMsg == null) {
      		log.warn("The miss op offset={} in queue={} is empty, pullResult={}", pullOffsetOfOp, opQueue, pullResult);
      		return pullResult;
      	}
      	// 拉取已经处理的 prepare 消息成功
      	for (MessageExt opMessageExt : opMsg) {
      		Long queueOffset = getLong(new String(opMessageExt.getBody(), TransactionalMessageUtil.charset));
      		log.info("Topic: {} tags: {}, OpOffset: {}, HalfOffset: {}", opMessageExt.getTopic(),
      			opMessageExt.getTags(), opMessageExt.getQueueOffset(), queueOffset);
      		if (TransactionalMessageUtil.REMOVETAG.equals(opMessageExt.getTags())) {
      			if (queueOffset < miniOffset) {
      				// 已经处理过的消息集合
      				doneOpOffset.add(opMessageExt.getQueueOffset());
      			} else {
      				// 将要移除的消息集合 key:halfOffset, value: opOffset.
      				removeMap.put(queueOffset, opMessageExt.getQueueOffset());
      			}
      		} else {
      			log.error("Found a illegal tag in opMessageExt= {} ", opMessageExt);
      		}
      	}
      	// 需要移除的 op 消息进度
      	log.debug("Remove map: {}", removeMap);
      	// 已经完成的消息进度
      	log.debug("Done op list: {}", doneOpOffset);
      	return pullResult;
      }
      
    3. 接下来处理 pullResult 结果如果 prepare 消息的偏移量已经处理过了,那么则从集合中移除,执行下一次操作。

      // 获取空消息的次数
      int getMessageNullCount = 1;
      // 当前处理 RMQ_SYS_TRANS_HALF_TOPIC#queueld 的最新进度
      long newOffset = halfOffset;
      // 当前处理消息的队列偏移量,主题依然为 RMQ_SYS_TRANS_HALF_TOPIC
      long i = halfOffset;
      while (true) {
      	// 处理时间大于 1min 则停止
      	if (System.currentTimeMillis() - startTime > MAX_PROCESS_TIME_LIMIT) {
      		log.info("Queue={} process time reach max={}", messageQueue, MAX_PROCESS_TIME_LIMIT);
      		break;
      	}
      	// 当前消息已经处理过了,从集合中移除,并继续处理下一次消息
      	if (removeMap.containsKey(i)) {
      		log.info("Half offset {} has been committed/rolled back", i);
      		removeMap.remove(i);
      	} else {
      		.....
      		.....
      	}
      	newOffset = i + 1;
          i++;
      }
      
    4. 当 prepare 消息的偏移量没有处理过,那么根据偏移量获取 prepare 消息,检查消息是否合法。

      // 根据消息队列偏移量 从消费队列中获取消息
      GetResult getResult = getHalfMsg(messageQueue, i);
      MessageExt msgExt = getResult.getMsg();
      // 消息为空
      if (msgExt == null) {
      	// 当拉取消息为空时,默认重试 1次,超过则结束该消息队列的事务状态回查
      	if (getMessageNullCount++ > MAX_RETRY_COUNT_WHEN_HALF_NULL) {
      		break;
      	}
      	// 没有新消息,直接 break
      	if (getResult.getPullResult().getPullStatus() == PullStatus.NO_NEW_MSG) {
      		log.info("No new msg, the miss offset={} in={}, continue check={}, pull result={}", i,
      			messageQueue, getMessageNullCount, getResult.getPullResult());
      		break;
      	} else {
      		// i 设置为下一个偏移量,重新拉取
      		log.info("Illegal offset, the miss offset={} in={}, continue check={}, pull result={}",
      			i, messageQueue, getMessageNullCount, getResult.getPullResult());
      		i = getResult.getPullResult().getNextBeginOffset();
      		newOffset = i;
      		continue;
      	}
      }
      
    5. 走到这里表示消息是经过检查了的,判断消息是否需要丢弃、跳过。丢弃标准为:消息回查的次数大于允许的最大回查次数,则丢弃,消息的属性TRANSACTION_CHECK_TIMES 加1,默认为 5,跳过标准:事务消息超过文件的过期时间,默认为 72 小时,如果确定丢弃或者跳过,则执行下一次获取的消息。继续判断消息的存储时间是否大于当前时间,如果大于,表示该消息是在回查之后添加的消息,直接 break。

      // 消息不为空时,判断消息是否需要丢弃(吞没、丢弃、不处理)或者跳过
      // 判断丢弃:消息回查的次数大于允许的最大回查次数,则丢弃,消息的属性TRANSACTION_CHECK_TIMES 加1,默认为 5
      // 判断跳过:事务消息超过文件的过期时间,默认为 72 小时
      if (needDiscard(msgExt, transactionCheckMax) || needSkip(msgExt)) {
      	listener.resolveDiscardMsg(msgExt);
      	newOffset = i + 1;
      	i++;
      	continue;
      }
      // 存储时间 >= 开始时间,表示之后添加进来的消息,这里不做处理,break
      if (msgExt.getStoreTimestamp() >= startTime) {
      	log.info("Fresh stored. the miss offset={}, check it later, store={}", i,
      		new Date(msgExt.getStoreTimestamp()));
      	break;
      }
      
    6. 检查当前消息是否能够进行执行回查请求,判断消息的存储时间和最开始 check 方法时间、存储时间和立即检测消息等。

      // 消息已存储的时间,为系统当前时间减去消息存储的时间戳
      long valueOfCurrentMinusBorn = System.currentTimeMillis() - msgExt.getBornTimestamp();
      // checkImmunityTime:立即检测事务消息的时间,transactionTimeout:事务消息的超时时间,默认为 3s
      long checkImmunityTime = transactionTimeout;
      // 消息事务消息回查请求的最晚时间,只有在这个时间内收到回查消息才有效,默认为 null
      String checkImmunityTimeStr = msgExt.getUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS);
      if (null != checkImmunityTimeStr) {
      	checkImmunityTime = getImmunityTime(checkImmunityTimeStr, transactionTimeout);
      	//消息已存储的时间 < 立即检查的时间,等待下一次再试
      	if (valueOfCurrentMinusBorn < checkImmunityTime) {
      		// 如果返回 true 则跳过该消息
      		if (checkPrepareQueueOffset(removeMap, doneOpOffset, msgExt, checkImmunityTime)) {
      			newOffset = i + 1;
      			i++;
      			continue;
      		}
      	}
      } else {
      	// checkImmunityTime=transactionTimeout,新到达的消息不在本次范围,直接break
      	if ((0 <= valueOfCurrentMinusBorn) && (valueOfCurrentMinusBorn < checkImmunityTime)) {
      		log.info("New arrived, the miss offset={}, check it later checkImmunity={}, born={}", i,
      			checkImmunityTime, new Date(msgExt.getBornTimestamp()));
      		break;
      	}
      }
      
    7. 走到这里,表示最后的检查,是否需要发送事务回查消息,确定执行回查请求后,调用异步线程池发送事务回查请求。

      • 如果已处理消息队列中没有已处理消息并且已经超过应用程序事务结束时间,即 transactionTimeout
      • 已处理消息队列不为空,并且最后一条消息的存储时间大于 transactionTimeout。
      // 判断是否需要发送事务回查消息:
      // 1. 如果已处理消息队列中没有已处理消息并且已经超过应用程序事务结束时间,即 transactionTimeout
      // 2. 已处理消息队列不为空,并且最后一条消息的存储时间大于 transactionTimeout。
      boolean isNeedCheck = (opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime)
      	|| (opMsg != null && (opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout))
      	|| (valueOfCurrentMinusBorn <= -1);
      
      if (isNeedCheck) {
      	// 先将消息放入 RMQ_SYS_TRANS_HALF_TOPIC 主题,发送成功为 true
      	if (!putBackHalfMsgQueue(msgExt, i)) {
      		continue;
      	}
      	// 调用异步线程池发送事务回查命令
      	listener.resolveHalfMsg(msgExt);
      } else {
      	// 再次获取已处理消息进度,用于后续的过滤重复处理数据
      	pullResult = fillOpRemoveMap(removeMap, opQueue, pullResult.getNextBeginOffset(), halfOffset, doneOpOffset);
      	log.info("The miss offset:{} in messageQueue:{} need to get more opMsg, result is:{}", i,
      		messageQueue, pullResult);
      	continue;
      }
      
    8. 最后更新 prepare 消息消费队列、已处理过 prepare 消息队列。

      // 表示存在多条消息,更新消费队列
      if (newOffset != halfOffset) {
      	transactionalMessageBridge.updateConsumeOffset(messageQueue, newOffset);
      }
      // 重新计算已经处理过的消息的消费进度
      long newOpOffset = calculateOpOffset(doneOpOffset, opOffset);
      // 表示增加了已处理的消息进度,更新消费队列
      if (newOpOffset != opOffset) {
      	transactionalMessageBridge.updateConsumeOffset(opQueue, newOpOffset);
      }
      

3.3 发送事务回查

org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener#resolveHalfMsg:异步线程池发送回查请求, org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener#sendCheckMessage:发送回查请求。

public void sendCheckMessage(MessageExt msgExt) throws Exception {
	CheckTransactionStateRequestHeader checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader();
	// 封装回查请求头
	checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset());
	checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId());
	checkTransactionStateRequestHeader.setMsgId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
	checkTransactionStateRequestHeader.setTransactionId(checkTransactionStateRequestHeader.getMsgId());
	checkTransactionStateRequestHeader.setTranStateTableOffset(msgExt.getQueueOffset());
	msgExt.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));
	msgExt.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID)));
	msgExt.setStoreSize(0);
	// 获取生产组id
	String groupId = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);
	// 根据生产者组id,获取可用的生产者
	Channel channel = brokerController.getProducerManager().getAvaliableChannel(groupId);
	if (channel != null) {
		// 处理事务回查,最终会调用生产者自己定义的方法 org.apache.rocketmq.client.producer.TransactionListener.checkLocalTransaction
		// 根据提交的事务结果,再次进行一阶段的事务处理,提交、回滚或者不做处理。
		brokerController.getBroker2Client().checkProducerTransactionState(groupId, channel, checkTransactionStateRequestHeader, msgExt);
	} else {
		LOGGER.warn("Check transaction failed, channel is null. groupId={}", groupId);
	}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在SpringBoot中整合RocketMQ发送事务消息的步骤如下: 首先,定义一个生产者类,使用@Component注解将其标记为一个组件,并使用@Autowired注解注入RocketMQTemplate实例。在该类中,可以编写一个sendMsg方法来发送消息。该方法接受两个参数,分别是topic和msg,使用MessageBuilder构建一个消息对象,并通过rocketMQTemplate的sendMessageInTransaction方法发送消息。需要注意的是,该方法的第一个参数要与@RocketMQTransactionListener注解中的txProducerGroup属性保持一致。\[1\] 其次,定义一个消费者类,使用@Component和@RocketMQMessageListener注解将其标记为一个组件,并指定topic和consumerGroup。该类需要实现RocketMQListener接口,并实现其中的onMessage方法,用于处理接收到的消息。\[2\] 最后,在引导类中使用@SpringBootApplication注解标记该类为Spring Boot应用程序的入口,并在main方法中调用SpringApplication的run方法启动应用程序。\[3\] 通过以上步骤,就可以在SpringBoot中整合RocketMQ发送事务消息了。 #### 引用[.reference_title] - *1* *2* *3* [springboot 整合 rocketmq 发送事务消息](https://blog.csdn.net/weixin_42494845/article/details/109362030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值