一般的な問題へのRabbitMQソリューション

前のビルドとシンプルなエントリーへのサービスの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時間、ロールバック・メッセージは、キューをバックアップしてみましょうメッセージを
  • デッドレターキューは、元のスイッチを切り替えるように設定されています
公開された100元の記事 ウォン称賛23 ビュー90000 +

おすすめ

転載: blog.csdn.net/qq_32099833/article/details/103940471