我有以下RabbitMq使用者:
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, MQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
sendNotificationIntoTopic(message);
saveIntoDatabase(message);
}
};
可能发生以下情况:
结果是我们的数据不一致。
预期结果或者两个操作都成功执行了,或者根本没有执行。
任何解决方案如何实现?
目前我有以下想法(请评论)
我们可以假设经纪人不会丢失任何消息。
我们必须订阅要发送的主题。
status
设置为值'pending'status
我们必须完成一项任务,即必须检查状态为待处理的行。目前可能有2种情况:
3.1通知根本没有发送
3.2已发送通知,但保存到数据库失败(概率很低,但可能)
因此,我们必须以某种方式区分这两种情况:我们可以将主题中的消息存储在集合中,作业可以检查消息是否被接受。因此,如果作业找到一条与数据库行相对应的消息,我们必须将状态更新为“成功”。否则,我们必须从数据库中删除条目。
我认为我的想法有一些弱点(例如,如果我们有多节点应用程序,则必须将消息存储在hazelcast(或类似物)中,但这是假设失败的另一点)
答案 0 :(得分:1)
以下是尝试取消确认模式https://servicecomb.apache.org/docs/distributed_saga_3/的示例,该模式应能够解决您的问题。您应该容忍通过队列两次提交数据的机会。这是一个示例:
6A 。如果您需要100%完成操作,则需要第二个队列,收件人方将在该队列中发布消息ID-DONE。如果不需要这种一致性,请跳过此步骤。或者,它可以发布ID-失败的失败原因。
6B 。提交方通过将状态DONE写入数据库来等待6A的消息来完成操作。
请注意,所有这些步骤均不涉及技术交易。您可以使用非事务性数据库来做到这一点。
我写的是“尝试取消确认模式”的一种变体,其中每个邮件接收者都应该知道如何管理自己的数据。
答案 1 :(得分:0)
如果我们在步骤1上失败-一切正常-数据处于一致状态,因为作业对该数据一无所知
如果我们在步骤2.1上失败-没问题,下一个作业调用将尝试处理
如果我们在步骤2.2上失败-如果我们在此处失败-这意味着下一个作业调用将再次处理相同的数据。乍一看,您可以认为这是一个问题。但是您的使用者必须是幂等的-这意味着它必须了解消息已被处理并跳过处理。此要求是所有消息代理都保证消息将至少一次传递的结果。因此,我们的消费者无论如何都要准备好重复的消息。没问题了。
答案 2 :(得分:0)
如果有足够的时间修改设计,建议使用类似JTA的API来管理2phase提交。甚至weblogic和WebSphere也支持XA资源进行两阶段提交。
如果时间轴较短,建议执行以下操作以减少故障间隔。
仅当第4步失败时,这里才会发生失败。这将导致相同的消息再次发送。因此接收系统将收到重复的消息。每个消息在JMS2.0结构中都有唯一的messageID和CorrelationID。因此,找到重复项有点直截了当(但这将在接收系统中处理)
这两种情况也适用于集群环境。
针对您的情况严格,认为以下步骤可能有助于解决您的问题
为您的主题订阅一个listener listener-1。
进程1
Listener-1
如果侦听器本身处于关闭状态,则主题将具有消息,直到侦听器读取消息为止。在此之前,SENT消息将处于“待发送”状态。
答案 3 :(得分:0)
这是我的操作方式的伪代码:(假设dao层具有事务处理能力,而您的消息传递层则没有)
//Start a transaction
try {
String message = new String(body, "UTF-8");
// Ordering is important here as I'm assuming the database has commit and rollback capabilities, but the messaging system doesnt.
saveIntoDatabase(message);
sendNotificationIntoTopic(message);
} catch (MessageDeliveryException) {
// rollback the transaction
// Throw a domain specific exception
}
//commit the transaction
场景:
1.如果数据库失败,则不会发送该消息,因为异常会破坏代码流。
2.如果数据库调用成功并且消息传递系统无法传递,请捕获异常并回滚数据库更改
记录和重播故障所需的所有操作都可以在此方法之外