SpringBootはRabbitMQを統合します
Springには3つの構成方法があります
- XMLに基づく
- JavaConfigに基づく
- 注釈ベース
もちろん、現在XMLが構成に使用されることはめったにありません。JavaConfigとアノテーションを使用した構成方法を紹介するだけです。
RabbitMQはSpringBootを統合しているため、対応するスターターを追加するだけで済みます。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
注釈ベース
application.yamlの設定は次のとおりです
spring:
rabbitmq:
host: myhost
port: 5672
username: guest
password: guest
virtual-host: /
log:
exchange: log.exchange
info:
queue: info.log.queue
binding-key: info.log.key
error:
queue: error.log.queue
binding-key: error.log.key
all:
queue: all.log.queue
binding-key: '*.log.key'
コンシューマーコードは次のとおりです
@Slf4j
@Component
public class LogReceiverListener {
/**
* 接收info级别的日志
*/
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${log.info.queue}", durable = "true"),
exchange = @Exchange(value = "${log.exchange}", type = ExchangeTypes.TOPIC),
key = "${log.info.binding-key}"
)
)
public void infoLog(Message message) {
String msg = new String(message.getBody());
log.info("infoLogQueue 收到的消息为: {}", msg);
}
/**
* 接收所有的日志
*/
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${log.all.queue}", durable = "true"),
exchange = @Exchange(value = "${log.exchange}", type = ExchangeTypes.TOPIC),
key = "${log.all.binding-key}"
)
)
public void allLog(Message message) {
String msg = new String(message.getBody());
log.info("allLogQueue 收到的消息为: {}", msg);
}
}
生産者は以下の通りです
@RunWith(SpringRunner.class)
@SpringBootTest
public class MsgProducerTest {
@Autowired
private AmqpTemplate amqpTemplate;
@Value("${log.exchange}")
private String exchange;
@Value("${log.info.binding-key}")
private String routingKey;
@SneakyThrows
@Test
public void sendMsg() {
for (int i = 0; i < 5; i++) {
String message = "this is info message " + i;
amqpTemplate.convertAndSend(exchange, routingKey, message);
}
System.in.read();
}
}
メッセージackに対するSpringBootのアプローチは、ネイティブAPIがメッセージackをターゲットにする方法とは少し異なります。
ネイティブAPIメッセージのACKメソッド
メッセージを確認する方法は2つあります
自動確認(autoAck = true)
手動確認(autoAck = false)
コンシューマーがメッセージを消費するとき、autoAckパラメーターを指定できます
String basicConsume(String queue、boolean autoAck、Consumer callback)
autoAck = false:RabbitMQは、メモリ(またはディスク)からメッセージを削除する前に、コンシューマーが応答確認メッセージを表示するのを待ちます
autoAck = true:RabbitMQは、送信されたメッセージを確認として自動的に設定し、コンシューマーが実際にこれらのメッセージを消費するかどうかに関係なく、メモリ(またはディスク)からメッセージを削除します。
手動確認の方法は以下の通りで、2つのパラメータがあります
basicAck(long deliveryTag、ブール値の倍数)
deliveryTag:チャネルで配信されたメッセージを識別するために使用されます。RabbitMQがメッセージをコンシューマーにプッシュすると、deliveryTagが添付され、メッセージが確認されたときにコンシューマーがどのメッセージが確認されたかをRabbitMQに通知できます。
RabbitMQは、各チャネルで、各メッセージのdeliveryTagが1から増加することを保証します
multiple = true:メッセージID <= deliveryTagのメッセージはすべて確認されます
myltiple = false:メッセージid = deliveryTagのメッセージが確認されます
メッセージが確認されない場合はどうなりますか?
キュー内のメッセージがコンシューマーに送信され、コンシューマーがメッセージを確認しない場合、メッセージは確認されるまでキューに残ります。
コンシューマーAに送信されたメッセージが確認されていない場合、rabbitmqは、コンシューマーAとrabbitmqの間の接続が中断された場合にのみ、コンシューマーAの未確認メッセージを別のコンシューマーに再配信することを検討します。
SpringBootでのメッセージ確認の方法
AcknowledgeMode列挙クラスで定義されている3つの方法があります
メッセージのSpringBootのデフォルトのackモードはAUTOです。
実際のシナリオでは、通常、手動で確認します。
application.yamlの設定が次のように変更されます
spring:
rabbitmq:
host: myhost
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
acknowledge-mode: manual # 手动ack,默认为auto
対応する消費者コードはに変更されます
@Slf4j
@Component
public class LogListenerManual {
/**
* 接收info级别的日志
*/
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${log.info.queue}", durable = "true"),
exchange = @Exchange(value = "${log.exchange}", type = ExchangeTypes.TOPIC),
key = "${log.info.binding-key}"
)
)
public void infoLog(Message message, Channel channel) throws Exception {
String msg = new String(message.getBody());
log.info("infoLogQueue 收到的消息为: {}", msg);
try {
// 这里写各种业务逻辑
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
}
上記で使用した注釈は次のとおりです
JavaConfigに基づく
アノテーションの使用はとても便利なので、なぜJavaConfigが必要なのですか?
JavaConfigは、複数の仮想ホストを同時に構成するなど、さまざまな属性をカスタマイズするのに便利です。
特定のコードについてはGitHubを参照してください
RabbitMQはどのようにしてメッセージの信頼できる配信を保証しますか
メッセージは多くの場合、次の段階を経ます
[画像のアップロードに失敗しました...(image-555f54-1603419542750)]
ここに画像の説明を挿入
したがって、メッセージの信頼性の高い配信を保証するには、これら3つの段階の信頼性の高い配信を保証するだけで済みます。
生産段階
この段階での信頼性の高い配信は、主にConfirmListener(パブリッシャー確認)とReturnListener(失敗通知)に依存します。
前述のように、RabbitMQでのメッセージのフローは、
プロデューサー-> rabbitmqブローカークラスター->交換->キュー->コンシューマーです。
ConfirmListenerは、メッセージがプロデューサーからブローカーに送信されたかどうかを取得できます。ReturnListenerは
、交換からキューにルーティングされていないメッセージを取得できます。
Spring BootStarterのAPIを使用して効果を実証します
application.yaml
spring:
rabbitmq:
host: myhost
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
acknowledge-mode: manual # 手动ack,默认为auto
log:
exchange: log.exchange
info:
queue: info.log.queue
binding-key: info.log.key
出版社はコールバックを確認します
@Component
public class ConfirmCallback implements RabbitTemplate.ConfirmCallback {
@Autowired
private MessageSender messageSender;
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String msgId = correlationData.getId();
String msg = messageSender.dequeueUnAckMsg(msgId);
if (ack) {
System.out.println(String.format("消息 {%s} 成功发送给mq", msg));
} else {
// 可以加一些重试的逻辑
System.out.println(String.format("消息 {%s} 发送mq失败", msg));
}
}
}
失敗通知コールバック
@Component
public class ReturnCallback implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
String msg = new String(message.getBody());
System.out.println(String.format("消息 {%s} 不能被正确路由,routingKey为 {%s}", msg, routingKey));
}
}
@Configuration
public class RabbitMqConfig {
@Bean
public ConnectionFactory connectionFactory(
@Value("${spring.rabbitmq.host}") String host,
@Value("${spring.rabbitmq.port}") int port,
@Value("${spring.rabbitmq.username}") String username,
@Value("${spring.rabbitmq.password}") String password,
@Value("${spring.rabbitmq.virtual-host}") String vhost) {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host);
connectionFactory.setPort(port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(vhost);
connectionFactory.setPublisherConfirms(true);
connectionFactory.setPublisherReturns(true);
return connectionFactory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,
ReturnCallback returnCallback, ConfirmCallback confirmCallback) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setReturnCallback(returnCallback);
rabbitTemplate.setConfirmCallback(confirmCallback);
// 要想使 returnCallback 生效,必须设置为true
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
}
ここでRabbitTemplateのパッケージを作成しました。主なことは、送信時にメッセージIDを追加し、メッセージIDとメッセージの対応を保存することです。RabbitTemplate.ConfirmCallbackはメッセージIDのみを取得でき、メッセージの内容は取得できないためです。したがって、独自のSave thismappingリレーションシップが必要です。信頼性の要件が高い一部のシステムでは、このマッピング関係をデータベースに保存し、正常に送信してマッピング関係を削除し、失敗した場合は常に送信できます。
@Component
public class MessageSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public final Map<String, String> unAckMsgQueue = new ConcurrentHashMap<>();
public void convertAndSend(String exchange, String routingKey, String message) {
String msgId = UUID.randomUUID().toString();
CorrelationData correlationData = new CorrelationData();
correlationData.setId(msgId);
rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);
unAckMsgQueue.put(msgId, message);
}
public String dequeueUnAckMsg(String msgId) {
return unAckMsgQueue.remove(msgId);
}
}
テストコードは
@RunWith(SpringRunner.class)
@SpringBootTest
public class MsgProducerTest {
@Autowired
private MessageSender messageSender;
@Value("${log.exchange}")
private String exchange;
@Value("${log.info.binding-key}")
private String routingKey;
/**
* 测试失败通知
*/
@SneakyThrows
@Test
public void sendErrorMsg() {
for (int i = 0; i < 3; i++) {
String message = "this is error message " + i;
messageSender.convertAndSend(exchange, "test", message);
}
System.in.read();
}
/**
* 测试发布者确认
*/
@SneakyThrows
@Test
public void sendInfoMsg() {
for (int i = 0; i < 3; i++) {
String message = "this is info message " + i;
messageSender.convertAndSend(exchange, routingKey, message);
}
System.in.read();
}
}
最初にテスト失敗の通知に来てください
出力は
消息 {this is error message 0} 不能被正确路由,routingKey为 {test}
消息 {this is error message 0} 成功发送给mq
消息 {this is error message 2} 不能被正确路由,routingKey为 {test}
消息 {this is error message 2} 成功发送给mq
消息 {this is error message 1} 不能被正确路由,routingKey为 {test}
消息 {this is error message 1} 成功发送给mq
メッセージはブローカーに正常に送信されますが、キューにルーティングされません
出版社の確認をテストしてみましょう
出力は
消息 {this is info message 0} 成功发送给mq
infoLogQueue 收到的消息为: {this is info message 0}
infoLogQueue 收到的消息为: {this is info message 1}
消息 {this is info message 1} 成功发送给mq
infoLogQueue 收到的消息为: {this is info message 2}
消息 {this is info message 2} 成功发送给mq
メッセージはブローカーに正常に送信され、キューにも正常にルーティングされます
保管段階
この段階では高可用性については検討していませんが、結局のところ、クラスターは運用と保守によって構築されています。時間があれば、この簡単なコンテンツを追加します。
消費段階
消費段階での信頼性の高い配信は、主にackによって保証されます。
前回の記事では、ネイティブapiackメソッドとSpringBootフレームワークackメソッドを紹介しました
全体として、実稼働環境では、通常、単一の手動ackを使用します。消費が失敗した後、キューに再入することはありません(再び失敗する可能性が高いため)が、メッセージを再配信します。将来のトラブルシューティングを容易にするために、デッドレターキューに移動します。
さまざまな状況を要約する
- メッセージは確認後にブローカーから削除されます
- nackまたはrejectの後、次の2つの状況に分けられます
(1)reque = true、メッセージはキューに再入力されます(2)reque = fasle、メッセージは直接破棄されます。指定すると、デッドレターキューに配信されます