記事ディレクトリ
序文
多くの学生がWEBサービスを開発したことがあると思いますが、WEBサービスの開発では、サービスのパフォーマンスを向上させ、正常に利用できるようにするために、キャッシュ、キュー、読み書き分離、ピークシェービングとバレーフィリング、電流制限とダウングレードが一般的に使用されます。サービス。ピークカットとバレーフィリングには、ビッグデータに適したkafka、高性能でトランザクションアクティビティの高いrabbitmqなどのMQメッセージミドルウェアを使用する必要があります。MQの選択と統合は、JAVA WEB開発においてすでに不可欠です。欠落している部分。もちろん、JAVA マイクロサービス フレームワークのファミリー バケットとして知られる Spring Cloud も、ミドルウェアを適応させる優れた機能を提供します。今日は、マイクロサービスのファミリー バケットである Spring Cloud によって提供されるメッセージ駆動型の Spring Cloud Stream を統合します。
Spring Cloud Stream の簡単な分析
Spring Cloud Stream は、メッセージ駆動型の機能を備えたマイクロサービスを構築するためのフレームワークであり、アプリケーションは入出力チャネルを通じてバインダーと対話し、バインダーはメッセージ ミドルウェアと通信します。
バインダーの役割は、メッセージ ミドルウェアを結合することです。これは、開発者が基盤となるメッセージ ミドルウェアがどのように動作するかを気にする必要がないように、サードパーティのミドルウェアをカプセル化して統合することに相当します。
inputs はメッセージ ミドルウェアのコンシューマに似たメッセージ入力チャネルであり、outputs はメッセージ ミドルウェアのプロデューサに似たメッセージ出力チャネルです。アプリケーションはメッセージを送受信する際に、メッセージ ミドルウェアのインターフェイスやロジック コードを直接呼び出すのではなく、Spring Cloud Stream の OUTPUT チャネルと INPUT チャネルを直接使用して処理します。
バインダー バインディングを通じてさまざまなメッセージ ミドルウェアを選択でき、バインディングを通じてミドルウェアの関連パラメータを設定できるため、アプリケーションはメッセージ ミドルウェアを柔軟に設定および切り替えることができます。
Spring Cloud Stream と Rabbitmq の統合
この統合は、rabbitmq と直接統合されており、kafka を使用している場合は、設定を直接移植して、対応するボンディング mq を変更できます。
この統合により、消費再試行メカニズム、デッドレターキューが追加され、運用環境に直接移植できるデッドレターキュー消費監視メソッドが提供されました。
1. pom 依存関係を追加する
spring-cloud-starter-stream-rabbit の導入は Spring Cloud から導入する必要があるため、dependencyManagement の設定に注意してください。
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. Application.yml は mq 設定を追加します
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin
virtual-host: /
cloud:
stream:
binders: #stream框架粘接的mq
myRabbit: #自定义个人mq名称
type: rabbit
environment:
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin
virtual-host: /
bindings: #stream绑定信道
output_channel: #自定义发送信道名称
destination: assExchange #目的地 交换机/主题
content-type: application/json
binder: myRabbit #粘接到的mq
group: assGroup
input_channel: #自定义接收信道
destination: assExchange #目的地 交换机/主题
content-type: application/json
binder: myRabbit #粘接到的mq
group: assGroup
consumer:
maxAttempts: 3 # 尝试消费该消息的最大次数(消息消费失败后,发布者会重新投递)。默认3
backOffInitialInterval: 1000 # 重试消费消息的初始化间隔时间。默认1s,即第一次重试消费会在1s后进行
backOffMultiplier: 2 # 相邻两次重试之间的间隔时间的倍数。默认2
backOffMaxInterval: 10000 # 下一次尝试重试的最大时间间隔,默认为10000ms,即10s
rabbit: #stream mq配置
bindings:
input_channel:
consumer:
concurrency: 1 #消费者数量
max-concurrency: 5 #最大消费者数量
durable-subscription: true #持久化队列
recovery-interval: 3000 #3s 重连
acknowledge-mode: MANUAL #手动
requeue-rejected: false #是否重新放入队列
auto-bind-dlq: true #开启死信队列
requeueRejected: true #异常放入死信
3. 入力チャンネルと出力チャンネルを定義します。
/**
* MqChannel
* @author senfel
* @version 1.0
* @date 2023/6/2 15:46
*/
public interface MqChannel {
/**
* 消息目的地 RabbitMQ中为交换机名称
*/
String destination = "assExchange";
/**
* 输出信道
*/
String OUTPUT_CHANNEL = "output_channel";
/**
* 输入信道
*/
String INPUT_CHANNEL = "input_channel";
/**
* 死信队列
*/
String INPUT_CHANNEL_DLQ = "assExchange.assGroup.dlq";
@Output(MqChannel.OUTPUT_CHANNEL)
MessageChannel output();
@Input(MqChannel.INPUT_CHANNEL)
SubscribableChannel input();
}
4. 入出力チャネルを使用してメッセージを送受信する
テストMQサービス
/**
* TestMQService
* @author senfel
* @version 1.0
* @date 2023/6/2 15:47
*/
public interface TestMQService {
/**
* 发送消息
*/
void send(String str);
}
TestMQServiceImpl
/**
* TestMQServiceImpl
* @author senfel
* @version 1.0
* @date 2023/6/2 15:49
*/
@Service
@Slf4j
@EnableBinding(MqChannel.class)
public class TestMQServiceImpl implements TestMQService {
@Resource
private MqChannel mqChannel;
@Override
public void send(String str) {
mqChannel.output().send(MessageBuilder.withPayload("测试=========="+str).build());
}
/**
* 接收消息监听
* @param message 消息体
* @param channel 信道
* @param tag 标签
* @param death
* @author senfel
* @date 2023/6/5 9:25
* @return void
*/
@StreamListener(MqChannel.INPUT_CHANNEL)
public void process(String message,
@Header(AmqpHeaders.CHANNEL) Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
log.info("message : "+message);
if(message.contains("9")){
// 参数1为消息的tag 参数2为是否多条处理 参数3为是否重发
//channel.basicNack(tag,false,false);
System.err.println("--------------消费者消费异常--------------------------------------");
System.err.println(message);
throw new RuntimeException("抛出异常");
}else{
System.err.println("--------------消费者--------------------------------------");
System.err.println(message);
channel.basicAck(tag,false);
}
}
/**
* 死信监听
* @param message 消息体
* @param channel 信道
* @param tag 标签
* @param death
* @author senfel
* @date 2023/6/5 14:30
* @return void
*/
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(MqChannel.INPUT_CHANNEL_DLQ)
, exchange = @Exchange(MqChannel.destination)
),
concurrency = "1-5"
)
public void processByDlq(String message,
@Header(AmqpHeaders.CHANNEL) Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
log.info("message : "+message);
System.err.println("---------------死信消费者------------------------------------");
System.err.println(message);
}
}
コントローラ
/**
* @author senfel
* @version 1.0
* @date 2023/6/2 17:27
*/
@RestController
public class TestController{
@Resource
private TestMQService testMQService;
@GetMapping("/test")
public String testMq(String str){
testMQService.send(str);
return str;
}
}
5. 通常のメッセージ消費をシミュレートする
6. 例外メッセージをシミュレートする
3 回の配信を再試行した後、異常なメッセージがデッドレター消費に入る