記事のディレクトリ
1.冪等とは何ですか?冪等メッセージキュー1.1 1.2アナログ再試行メカニズム1.2.1プロデューサーコード1.2.2コンシューマコード1.2.3消費者application.ymlの設定 2.消費を繰り返すことがないように、メッセージ等べきことを確実にするためにどのように?ソリューション
1.冪等とは何ですか?
プログラミング動作中に同じの最初のパフォーマンスの影響と影響されている任意の回数実行されるべき等の特性です。
冪等HTTPメソッドは、複数回からリソース要求を参照し、同じ副作用を有するべきです。コンパイラは、唯一のメッセージフォーマット構文によってそれを定義する方法はありません構文エラー、HTTPの仕様を確認するために助けることができるよう冪等は、意味カテゴリに属しています。
ジェーン:要求、どんなに何回変更されません結果を繰り返します。
の1.1冪等メッセージキュー
冪等HTTPメソッド、同じメッセージキューの冪等の問題など。
であり、消費者へのMQ Yibaメッセージ、消費者のMQネットワークの停止時に、MQは確認メッセージが表示されないので、ACKを返すために、メッセージが消費者の他の物品に再配布される消費者のMQメッセージの消費者、または消費者へのネットワーク再接続後に再度送るが、消費者が実際に成功し、ニュース記事を消費し、消費者の支出が重複したメッセージを引き起こした;注意し、RabbitMQのこのメッセージの再試行(補償)は、デフォルトのメカニズムですA。
だから、機構MQを再試行する主な要因MQ消費者冪等の問題、支出のネットワークやクライアント遅延の理由は、消費が繰り返されたため。
だから、どのように右の再試行メカニズムを選択するには?のは2例を見てみましょう。
ケース1:消費者は、サードパーティのインターフェースを呼び出し、メッセージが表示されますが、インタフェースは、再試行する必要が一時的に利用できないのですか?
これは、再試行する必要があります
ケース2:消費者はメッセージを取得し、データ変換が例外をスローし、再試行する必要がありますか?
あなたは、再試行する必要はありません。
总结:对于情况2,如果消费者代码抛出异常是需要发布新版本才能解决的问题,那么不需要重试,重试也无济于事。应该采用日志记录+定时任务 job 健康检查+人工进行补偿
1.2 模拟重试机制
我们采用一种短信消费者客户端异常的情况来模拟 RabbitMQ 的重试机制。
@RabbitListener(queues = "fanout_sms_queue")
public void process(String msg) {
System.out.println("短信消费者获取生产者消息msg:" + msg);
int i = 1/0;
}
如上代码,很显然会报错,一担报错生产者的消息时不会被消费的?
@RabbitListener 底层使用 AOP 进行异常通知拦截,如果程序没有抛出异常信息,那么就会自动提交事务;如果 AOP 异常通知拦截有捕获到异常信息的话,就会自动实现重试(补偿)机制,同时,这个补偿机制的消息会缓存到 RabbitMQ 服务器端进行存放,一直重试到不抛出异常为止。
1.2.1 生产者代码
@Component
public class FanoutProducer {
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送消息
*
* @param queueName 队列名称
*/
public void send(String queueName) {
String msg = "my_fanout_msg:" + System.currentTimeMillis();
Message message = MessageBuilder
.withBody(msg.getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_JSON)
.setContentEncoding("utf-8")
.setMessageId(UUID.randomUUID() + "")
.build();
System.out.println(msg + ":" + msg);
amqpTemplate.convertAndSend(queueName, message);
}
}
1.2.2 消费者代码
@Component
public class FanoutEamilConsumer {
@RabbitListener(queues = "fanout_eamil_queue")
public void process(Message message) throws Exception {
String revMessage = Thread.currentThread().getName()
+ ",邮件消费者获取生产者消息msg:"
+ new String(message.getBody(), "UTF-8")
+ ",messageId:" + message.getMessageProperties().getMessageId();
System.out.println(revMessage);
}
}
1.2.3 消费者 application.yml 配置
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: guest
####密码
password: guest
### 地址
virtual-host: /admin_host
listener:
simple:
retry:
####开启消费者重试
enabled: true
####最大重试次数
max-attempts: 5
####重试间隔次数
initial-interval: 3000
server:
port: 8081
我们通过 RabbitMQ 配置,增加了 RabbitMQ 重试时间以及重试次数限制,在一定程度上解决了重复消费的问题,接下来看一道常问的面试题。
2. 如何保证消息幂等性,不被重复消费?
其实,这个问题也算是 MQ 面试当中经常考察的一点,因为无论是什么 MQ 都会有这个问题。
首先通过上边我们了解了什么是“幂等性”,以及 MQ 幂等性问题的产生,所以我们要清楚为什么会出现重复性消费?在什么场景会出现重复消费?
解决方法
使用全局 MessageID 判断消费方使用同一个,解决幂等性问题。
或者使用业务逻辑保证唯一(比如订单号码)
生产者关键代码:
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 发送消息
*
* @param queueName 队列名称
*/
public void send(String queueName) {
String msg = "my_fanout_msg:" + System.currentTimeMillis();
Message message = MessageBuilder
.withBody(msg.getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_JSON)
.setContentEncoding("utf-8")
.setMessageId(UUID.randomUUID() + "")
.build();
System.out.println(msg + ":" + msg);
amqpTemplate.convertAndSend(queueName, message);
}
如上,生产者在发送消息时(convertAndSend),给消息对象设置了唯一的 MessageID,只有该 MessageID 没有被消费者标记方能在重试机制中再次被消费。
消费者关键代码:
@RabbitListener(queues = "fanout_eamil_queue")
public void process(Message message) throws Exception {
String revMessage = Thread.currentThread().getName()
+ ",邮件消费者获取生产者消息msg:"
+ new String(message.getBody(), "UTF-8")
+ ",messageId:" + message.getMessageProperties().getMessageId();
System.out.println(revMessage);
发送邮件的逻辑XXX
}
如上,通过 message.getMessageProperties().getMessageId() 获取 MessageID,获取的 MessageID 可以用来判断是否已经被消费者消费过了,如果已经消费则取消再次消费。
通常怎么判断呢?
比如上方是一个邮件发送的消费者,在做补偿时,假如上一步邮件发送成功了,我们会把该 ID 存至 redis中,下次再调用时,先去 redis 判断是否存在该 ID 了,如果存在表明已经消费过了则直接返回,不再消费,否则消费,然后将记录存至 redis。
我创建了一个java相关的公众号,用来记录自己的学习之路,感兴趣的小伙伴可以关注一下微信公众号哈:niceyoo