前のビルドとシンプルなエントリーへのサービスのRabbitMQのレコードに関するブログが、光が十分ではないかもしれません。
RabbitMQのに生産に使用されますが、多くの問題を検討し、解決する必要があります。
ディレクトリ
メッセージコンバータ
メッセージの送受信の春には、いくつかの処理を行っているためRabbitMQのネイティブのみバイト配列、統合とSpringBootを送って、春には、開発者がオブジェクトを送信することができます。
コンバータのデフォルトメッセージ:SimpleMessageConverter
変換ロジックは、おおよそ次の通り:
- リクエストがのcontentTypeに基づいている場合
text
を開始すると、メッセージが文字列に変換されます。デフォルトの文字セットに与えられていない場合は、転送する前に、指定された文字セットかどうかが決定されるUTF-8
変換。 - contentTypeが同じ場合
application/x-java-serialized-object
、その後のJavaのメッセージ伝送シーケンス。 - 上記条件は、変速機として、何の変換に満足でない場合。
カスタムメッセージコンバータ
必要であれば、あなたも自分のメッセージコンバータを実装することを選択することができます。
実装するクラスを作成MessageConverter
インターフェースを、生産者は実現toMessage
方法を、消費者が実感fromMessage
する方法を。
public class MyMessageConverter implements MessageConverter {
//生产者发送转换
@Override
public Message toMessage(Object o, MessageProperties messageProperties) throws MessageConversionException {
//使用FastJson
Message message = new Message(JSONObject.toJSONBytes(o), messageProperties);
return message;
}
//消费者接收转换
@Override
public Object fromMessage(Message message) throws MessageConversionException {
return JSONObject.parse(message.getBody());
}
}
テンプレートセットのカスタムコンバータ
//设置自定义消息转换器
template.setMessageConverter(new MyMessageConverter());
生産者のメッセージが失われないことを確実にするためにどのように?
メッセージは、次の2つの方法で正常に送信されました:
- ブローカーメッセージが到着します
- メッセージが正常にキューにルーティングされます
ビジネスの観点から、一般的な、唯一のメッセージがキューにルーティングされ、真に正常に送信されました。
ConfirmCallbackとReturnCallback:どちらの場合も、RabbitMQのは、2つのコールバックを提供します。
ConfirmCallback
用ConfirmCallbackメッセージは関係なく、キューのそれにルーティングされるかどうかに関して、「メッセージブローカに達します」。
ブローカーにメッセージを送信するときに、RabbitMQの応答を発行します、彼は、彼らがメッセージを受信したことを生産者に語りました。
コードの実装
オープン差出人確認
//开启 发送者确认
connectionFactory.setPublisherConfirms(true);
メッセージ確認書のコールバッククラスを送信
/**
* @Author: pch
* @Date: 2020/1/11 11:06
* @Description: email消息发送方确认回调
*/
public class EmailConfirmCallback implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//发送消息时没传递correlationData,这里就为null
System.err.println("消息附带标识:" + correlationData);
//消息是否到达broker
System.err.println("消息是否到达broker:" + ack);
//消息发送失败的原因(RabbitMQ服务宕机等)
System.err.println("失败原因:"+cause);
if (!ack) {
//消息发送失败,写入到数据库,等待后续处理
}
}
}
テンプレート送達確認コールバック
//开启 发送者确认
template.setConfirmCallback(new EmailConfirmCallback());
ユーザー登録インタフェースは、成功裏に登録後メッセージサービスコードを送信します
@PostMapping("register")
public Object register(String name, String email) {
//保存注册信息逻辑...
System.out.println(name + "-注册成功,开始发送email消息...");
//发送消息到队列
Map<String, Object> map = new HashMap<>(2);
map.put("name", name);
map.put("email", email);
//发送消息时,可以携带一个CorrelationData保存业务主键
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().toUpperCase());
rabbitTemplate.convertAndSend("emailExchange", "email_error", JSONObject.toJSONString(map),correlationData);
return name + "-注册成功";
}
要求、下に示すように、コンソール:
メッセージは、ブローカーが到着したが、メッセージがキューにルーティングされているかどうかを、ここであなたはReturnCallbackを使用する必要性を判断することはできません。
ReturnCallback
ReturnCallbackコールバックは、成功のためには、メッセージが通常ConfirmCallbackと組み合わせて使用されるキューにルーティングされているかどうかです。
メッセージは、ブローカーの到着が、キューにルーティングされていない場合、それはReturnCallbackがトリガされます。例えば:交換は、キューをバインドされていません。
それどころか、それはReturnCallbackをトリガしません。
必須の
RabbitMQのルーティングメッセージを受信した場合、異なる必須によれば、必須のデフォルトは偽で動作します。
必須がfalseの場合、メッセージをルーティングすることができないとき、RabbitMQの直接メッセージを廃棄。
真必須、メッセージをルーティングすることができない場合は、RabbitMQのはBasic.Returnを呼び出すコマンドメッセージをプロデューサーに返されます。
コードの実装
オープン差出人確認
//开启 发送者确认
connectionFactory.setPublisherConfirms(true);
メッセージを作成するには、コールバッククラスを失敗しました
/**
* @Author: pch
* @Date: 2020/1/11 12:46
* @Description: email消息失败回调
*/
public class EmailReturnCallback implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.err.println("消息内容:" + new String(message.getBody()));
System.err.println("响应状态码:" + replyCode);
System.err.println("响应内容:" + replyText);
System.err.println("exchange:" + exchange);
System.err.println("rou![在这里插入图片描述](https://img-blog.csdnimg.cn/20200111130632936.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyMDk5ODMz,size_16,color_FFFFFF,t_70)tingKey:" + routingKey);
//消息没有被路由到Queue,写入数据库,待后续处理...
}
}
強制モードがオンになり、コールバックの例を設定されています
//开启mandatory模式(开启失败回调)
template.setMandatory(true);
template.setReturnCallback(new EmailReturnCallback());
メッセージを送信するとき、間違ったセットroutingKeyは、コンソールには次のように表示されます。
正しいroutingKeyを設定し、トリガーのコールバックしません。
バックアップスイッチ
switch文は、プロパティが提供されることがありますalternate-exchange
ヘルプをバックアップスイッチを設定します。
メッセージがキューにルーティングすることができない場合、RabbitMQのメッセージは、代替経路に切り替わり、スタンバイスイッチは、典型的に宣言されFANOUT
たメッセージがキューにルーティングされることを確実にするタイプ。
ことを注意:メッセージがスタンバイスイッチ内のルーティング成功限り、メインスイッチをルーティングに成功しなかった場合でも、それはReturnCallbackコールバックをトリガしません。
待機時switch文ReturnCallbackは、メイン・スイッチとスタンバイスイッチが正常にルーティングすることができないときにのみ、コールバックをトリガします。
概要
ConfirmCallback针对消息没有到达Broker的回调处理,ReturnCallback针对消息到达Broker但是没路由到Queue的回调处理。
一般将两者配合使用,可以保证消息发送100%不丢失。
RabbitMQ也支持事务,也可以做到消息发送不丢失,但是开启事务后性能严重下降,不建议使用。
消费者如何防止消息丢失?
除了生产者要保证消息100%发送外,消费者也必须确保消息不丢失,这样才能最终确保消息不丢失。
RabbitMQ的消息确认模式:
- AcknowledgeMode.NONE
- AcknowledgeMode.MANUAL
- AcknowledgeMode.AUTO
默认为自动确认,即消息被消费者取走后Queue就会将其删除,不管其是否消费成功。
如果对数据的要求不高,如:日志记录,哪怕丢失一部分日志也无所谓,则可以使用自动确认,这样可以保证最好的性能。
但是如果对数据要求很高,则必须改为:手动确认。
消息手动确认
在手动确认模式下,消息被消费者取走之后,Queue不会将其删除,而是将消息的状态改为Unacked
待确认,消费者获取到消息进行消费时,可能成功也可能失败。
消费成功时,则通知RabbitMQ,将消息从Queue中删除。
消费失败时,有两种选择,一是通知RabbitMQ将消息放回队列,交给其他消费者消费、二是认为消息是废数据,让RabbitMQ直接丢弃即可。
不管是否消费成功,都必须进行消息确认,否则消息会一直处于Unacked
状态,堆积在Queue中。
消费者获取消息后宕机,消息会丢失吗?
不会,手动确认模式下,即使消费者获取到消息,Queue也不会将其删除,只是将消息的状态改为Unacked
待确认。
消费者宕机后,连接就会断开,RabbitMQ检测到连接断开后,会将消息状态改为Ready
分发给其他消费者。
虽然不会丢失消息,但是会带来另外一个问题:消息重复消费。
例如:消费者获取消息成功消费后,在消息确认之前服务突然宕机,RabbitMQ会认为消息没有被成功消费,会分发给其他消费者,导致消息被重复消费,可以通过“消息幂等性”解决,后面会介绍。
开启消息手动确认,也可通过yml方式
@Bean("simpleContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//消息手动确认
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return factory;
}
消费者:消息的确认和退回
@Component
public class EmailConsumer {
//监听的队列名称
@RabbitListener(queues = "emailQueue", containerFactory = "simpleContainerFactory")
public void consumer(Map<String, String> map, Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("消费者,消息ID:" + deliveryTag);
if (sendEmail(map.get("name"), map.get("email"))) {
//邮件发送成功,确认消息 消息ID 是否批量确认
channel.basicAck(deliveryTag, false);
}else {
//消息退回 消息ID 是否批量确认 是否重回队列
channel.basicNack(deliveryTag, false, true);
//消息退回,只能单条 建议用basicNack
//channel.basicReject(deliveryTag, true);
}
}
//发送邮件
private boolean sendEmail(String name, String email){
System.out.println("发送邮件:" + email);
//编写业务逻辑....
return true;
}
}
消息预取
聊消息预取之前,先说一说RabbitMQ的消息分发机制。
默认情况下,RabbitMQ会以最快的速度,将消息以轮询的方式全部分发给消费者,尽管消费者还来不及处理。
这样可以保证RabbitMQ本身不会因为消息堆积而影响性能,但是对消费者而言却不太友好。
举个例子:Queue中有100个消息,同时启动两个消费者,RabbitMQ会立即给每个消费者分发50个消息,假设消费者A性能很强,1S就能消费完50个消息,而消费者B性能弱,需要10S才能消费完。这时消费者A就会很空闲,消费者B就会很忙碌,没有充分利用A的性能。
而消息预取则可以根据消费者的实际情况来进行设置,开启消息预取后,RabbitMQ不会直接分发所有的消息,而是根据给定的预取数量来分发,当消费者全部处理完毕后,RabbitMQ才会进行下一轮的消息分发。
例如:Queue中有100个消息,消费者消费1个消息耗时1S,消息预取设为1,则RabbitMQ每秒分发一个消息,如下图所示:
开启消息预取后,还可以对消息进行批量确认,从而进一步提升性能。
设置消息预取的数量
@Bean("simpleContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//消息手动确认
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
//消息预取数量
factory.setPrefetchCount(100);
return factory;
}
消息批量确认
//deliveryTag当前消息的标识,RabbitMQ会自动将一组消息确认
channel.basicAck(deliveryTag, true);
消息预取的数量需要设置一个合适的值,太小性能差但是数据可靠性高,太大性能高但是数据可靠性差。
消息重复消费
开启消息预取后,可能存在“消息重复消费”的问题。
批量获取100条消息,成功消费了99条,在消费最后一条时系统宕机,RabbitMQ会将100条消息重新分发给其他消费者。
RabbitMQのソリューションを提供していない、我々は彼らの自身のビジネス、共通の解決策を解決する必要があります。
- プラスデータベーステーブルのフィールドには、区別の消費をマーク。
- 使用Redisのは成功した識別するために消費者を保存します。
デッドレター・スイッチ
とき声明「デッドレター・スイッチ」(に設定することができ、キュー、dead-letter-exchange
メッセージは、メッセージがスイッチを再ルーティング不能でのRabbitMQにリダイレクトされます)、ケース。
- メッセージが返されます(channel.basicNack)
- メッセージの有効期限(TTL)
- メッセージ番号(X-MAX-長さ)が上限を超えてキューが除去されます
- メッセージの合計サイズは、(X-MAX-長バイト)が最大限界を超えてキューが除去されます
メッセージは状況の上に表示された場合は、メッセージが破棄された場合のRabbitMQは望んでいない、あなたが死んで手紙スイッチを提供することで、データを保存することができます。
ある不正メール効果スイッチ:メッセージが正しく消費者支出することができない場合は、再試行または手動による介入に待っている、別のキューにルーティングされました。
文のキュー、配信不能スイッチ、deadExchangeがキューに再ルーティング満了に送信されるメッセージ。
@Bean
public Queue queue(){
Map<String, Object> map = new HashMap<>();
//设置过期时间
map.put("x-message-ttl", 1000);
//设置死信交换机
map.put("x-dead-letter-exchange", "deadExchange");
//死信交换机路由时新的routingKey
map.put("x-dead-letter-routing-key", "dead.key");
return new Queue("emailQueue", true, false, false, map);
}
メッセージは、同様に、返されました。
//消息退回 如果设置了死信交换机,则会被发送到死信交换机重新路由
channel.basicNack(deliveryTag,false,false);
無限ループを避けます
RabbitMQのは、無限ループを避けるため使用するときに特別な注意が必要、メッセージは、消費者が正しいことはできません常にあるが、RabbitMQのは、たとえば、無限ループで、その結果、消費者にメッセージを送信し続けます。
- 消費者だけで1時間、ロールバック・メッセージは、キューをバックアップしてみましょうメッセージを
- デッドレターキューは、元のスイッチを切り替えるように設定されています