前几节我们了解了可靠消息的基本架构:
但是现成的MQ中间件产品并不支持消息发送一致性(先进存储,再被确认后才能发送的2步式流程),直接改造MQ的难度又太大,所以我们进行了自己的改进。
可靠消息最终一致性解决方案1----本地消息服务
- 将主动方应用系统中的业务操作和消息存储与发送都放到主动方应用系统中,消息存储服务不再是放到消息中间件中
- 将主动方应用系统的业务操作和消息存储与发送都放在同一个本地事务当中,保证了业务处理操作和消息存储与发送功能要么同时成功,要么同时失败,这样子就保证了消息发送一致性的正向流程
- 如果业务操作和消息存储等功能都是成功的,就会将要发送给被动方应用系统的消息发送给实时消息服务(消息中间件,MQ/MQ集群)
- 当消息中间件有数据的时候,由于消息消费端应用一直监听消息中间件,所以有数据的时候,他会有感知,消息消费端应用就会消费消息,消费的时候会调用被动方应用系统的业务接口。
- 被动方进行业务处理完成后,会有一个结果反馈(被动方业务处理成功/失败)给消息消费端应用
- 接收到的消息可能是成功/失败
(1)成功:实时消息服务接收到消息消费端应用发送来的成功的消息,清除掉实时消息服务中的消息队列的对应消息。消息消费端应用告知主动方应用系统操作成功,主动方应用删除本地数据库中存储的消息数据。
(2)失败:利用消息恢复系统进行处理。 - 消息恢复系统提供了重试机制,若主动方应用长时间没有接收到消息的确认,则我们消息恢复系统重新进行查询,将消息重新放到实时消息服务中,重复过程。
- 消息的持久化因为MQ一般没有持久化的功能,所以我们放在了主动方应用的本地数据库中存储,保证了消息的可靠性。
优点:
(1)消息的时效性比较高
(2)从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了MQ中间件特性的依赖
(3)方案轻量,容易实现。
缺点:
(1)与具体的业务场景绑定,耦合性强,不可公用
(2)消息数据与业务数据同库,占用业务系统资源
(3)业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。
可靠消息最终一致性解决方案2----独立消息服务的设计
(1)主动方应用的业务处理在一个本地的事务域当中
(2)消息服务子系统所有的消息处理在一个本地事务域当中
(3)被动方应用系统的业务处理在本地事务域当中
(4)主动方应用先预发送消息,消息服务子系统存储主动方应用发送来的预发送消息,此时该消息不能被消费,如果消息服务子系统存储失败,则返回失败信息,主动方业务处理不执行;如果消息服务子系统预发送消息存储成功,则此时返回给主动方应用一个成功的标志位,主动方应用开始执行业务操作,并将结果发送给消息服务子系统,消息服务子系统确认并发送消息(将存储的预发送消息状态改为可发送),消息服务子系统更细完消息状态后,将消息发送到实时消息服务中(传到MQ中),此时实时消息服务中有可发送的消息,消息业务消费端监听到要发送的消息,将其消费,与消息业务消费端进行调用,完成被动方的业务处理。
(5)如果主动方应用和消息服务中间件之间通讯失败:
- 消息服务子系统存储消息成功,但是返回给主动方应用系统的时候由于网络等原因失败
- 主动方接收到消息服务子系统成功,业务处理成功,但是返回给消息服务的子系统时候失败,此时消息服务子系统就没有收到业务处理的结果。
类似这种异常流程,我们可以依赖消息状态确认的子系统来进行处理,消息状态确认子系统就是一个定时任务,会定期按规则查询状态确认超时消息,查询主动方应用业务处理成功但是没有返回给消息服务子系统的业务数据,将这些数据重新给到消息服务子系统中。如果主动方业务执行的消息是失败的,则消息状态确认子系统会通知消息服务对消息删除。
(6)当实时消息服务有消息的时候,消息业务消费端会监听其是否有消息,若有消息,将消息给被动方应用,被动方应用利用该消息进行处理,若处理成功,消息业务消费端就会像实时消息服务发送ack表示操作成功,该服务就会删除MQ中的消息。同时消息业务消费端也会通过消息恢复子系统调用消息服务子系统中确认消息已经被成功消费的接口,将消息状态改为已经消费,或者直接从消息服务库中删除
(7)消息恢复子系统也会定期查询消息服务子系统中消息已经发送给被动方应用,但是没有被确认的消息,将这些消息重新投递。
优点:
- 消息服务独立部署,独立维护,独立伸缩
- 消息存储可以按照需求选择不同的数据库来实现
- 消息服务可以被相同的使用场景公用,降低重复建设消息服务的成本
- 降低了耦合,有利于系统的维护
代码实现:
首先我们看下基本架构:
可以看到我们将消息服务子系统作为接口(roncoo-pay-service-message-api)和具体的实现(roncoo-pay-service-message)
- 其api接口定义的方法:
public interface RpTransactionMessageService {
/**
* 预存储消息.
*/
public int saveMessageWaitingConfirm(RpTransactionMessage rpTransactionMessage) throws MessageBizException;
/**
* 确认并发送消息.
*/
public void confirmAndSendMessage(String messageId) throws MessageBizException;
/**
* 存储并发送消息.
*/
public int saveAndSendMessage(RpTransactionMessage rpTransactionMessage) throws MessageBizException;
/**
* 直接发送消息.
*/
public void directSendMessage(RpTransactionMessage rpTransactionMessage) throws MessageBizException;
/**
* 重发消息.
*/
public void reSendMessage(RpTransactionMessage rpTransactionMessage) throws MessageBizException;
/**
* 根据messageId重发某条消息.
*/
public void reSendMessageByMessageId(String messageId) throws MessageBizException;
/**
* 将消息标记为死亡消息.
*/
public void setMessageToAreadlyDead(String messageId) throws MessageBizException;
/**
* 根据消息ID获取消息
*/
public RpTransactionMessage getMessageByMessageId(String messageId) throws MessageBizException;
/**
* 根据消息ID删除消息
*/
public void deleteMessageByMessageId(String messageId) throws MessageBizException;
/**
* 重发某个消息队列中的全部已死亡的消息.
*/
public void reSendAllDeadMessageByQueueName(String queueName, int batchSize) throws MessageBizException;
/**
* 获取分页数据
*/
PageBean listPage(PageParam pageParam, Map<String, Object> paramMap) throws MessageBizException;
}
我们来依次分析上面的方法,看其具体的代码实现
- 保存预存储消息
/**
* 预存储消息.
*/
public int saveMessageWaitingConfirm(RpTransactionMessage rpTransactionMessage) throws MessageBizException;
上面的这个方法的作用是保存预发送的消息,就是主动方应用进行的第一步操作,将预发送消息发送给消息服务子系统
具体实现:
public int saveMessageWaitingConfirm(RpTransactionMessage message) {
if (message == null) {
throw new MessageBizException(MessageBizException.SAVA_MESSAGE_IS_NULL, "保存的消息为空");
}
if (StringUtil.isEmpty(message.getConsumerQueue())) {
throw new MessageBizException(MessageBizException.MESSAGE_CONSUMER_QUEUE_IS_NULL, "消息的消费队列不能为空 ");
}
message.setEditTime(new Date());
message.setStatus(MessageStatusEnum.WAITING_CONFIRM.name());
message.setAreadlyDead(PublicEnum.NO.name());
message.setMessageSendTimes(0);
return rpTransactionMessageDao.insert(message);
}
- 确认并发送消息
/**
* 确认并发送消息.
*/
public void confirmAndSendMessage(String messageId) throws MessageBizException;
该方法是用来在主动方业务执行完成后,将消息发送给消息服务子系统,消息服务子系统接受消息,更改消息状态,并将消息发送到实时消息服务。
public void confirmAndSendMessage(String messageId) {
final RpTransactionMessage message = getMessageByMessageId(messageId);
if (message == null) {
throw new MessageBizException(MessageBizException.SAVA_MESSAGE_IS_NULL, "根据消息id查找的消息为空");
}
// 消息状态改变为发送中
message.setStatus(MessageStatusEnum.SENDING.name());
message.setEditTime(new Date());
rpTransactionMessageDao.update(message);
notifyJmsTemplate.setDefaultDestinationName(message.getConsumerQueue());
notifyJmsTemplate.send(new MessageCreator() {
public Message createMessage(Session session)</