ロケットMQ
1. MQの概要
Message Queue は、メッセージ キュー サービスを提供するミドルウェアです。メッセージの作成、保存、消費のプロセス全体に API を提供するソフトウェア システム。
MQの役割
- 電流制限とピーククリッピング: ユーザーが過剰なリクエストを送信すると、リクエストは後の処理のために一時的に保存されます。MQ 一時ストレージを使用せずに業務システムに直接リクエストすると、システムがクラッシュする可能性があります。
- 非同期デカップリング: 上流システムと下流システムが同期的に呼び出される場合、システムのスループットと同時実行性が大幅に低下します。MQ 層は 2 つのシステム間の非同期呼び出しを実装します。
- データ収集: 分散システムは、ビジネス ログ、監視データ、ユーザーの行動などの大量のデータ ストリームを生成します。ビッグ データ分析のためにこれらのデータ ストリームを収集して要約します。
主流アプリケーション向けの MQ 製品
- Kafka: Scala/Java 言語開発。スループットが高いのが特徴ですが、データが失われるため、ビッグデータ分野のリアルタイムコンピューティングやログ収集によく使われています。MQ プロトコルには従わず、独自に開発したプロトコルを使用してください。
- RocketMQ: Java 言語開発。アリババ ダブルイレブンの数年間のテストを経て、パフォーマンスと安定性は非常に高く、機能は包括的です。MQ プロトコルには従わず、独自に開発したプロトコルを使用してください。オープンソース版はクラウド版(Ali商用版)ほど優れていません
MQ 共通プロトコル
-
JMS : Java メッセージング サービス。Java プラットフォーム上の MOM (メッセージ指向ミドルウェア) の技術仕様。Java アプリケーションのメッセージ交換を容易にし、開発を簡素化する標準インターフェイスを提供します。ActiveMQ の典型的な実装
-
STOMP : ストリーミングテキスト指向メッセージプロトコル。は、MOM 用の単純なテキスト プロトコルです。STOMP は、クライアントが任意の STOMP メッセージ ブローカーと対話できるようにする相互運用可能な接続形式を提供します。ActiveMQ の典型的な実装
-
AMQP : 高度なメッセージ キュー プロトコル。ユニファイド メッセージング サービスを提供するアプリケーション層標準であり、アプリケーション層プロトコルのオープン標準です。RabbitMQ は典型的な実装です
-
MQTT : メッセージ キュー テレメトリ トランスポート。IBM が開発したインスタント メッセージング プロトコル (バイナリ プロトコル)。主にサーバーと低電力 IoT デバイス間の通信に使用されます。
2. 基本的な考え方
トピック: メッセージのクラスのコレクションを示します (メッセージのタイプとして理解できます)。各メッセージは、メッセージ サブスクリプションの RocketMQ の基本単位である 1 つのトピックにのみ属することができます。プロデューサは複数のトピック メッセージを同時に送信できますが、コンシューマは 1 つのトピック メッセージしか受信できません
タグ (タグ): メッセージを素早くフィルタリングするために使用されます。
3. Linux は RocketMQ サービスをデプロイします
1.公式 Web サイトでコンパイル済みのバイナリ圧縮パッケージ (バージョン 5.0.0) をダウンロードし、Linux にアップロードします。
2.解凍する
3. 環境変数 ROCKETMQ_HOME および NAMESRV_ADDR を構成します。
4. bin ディレクトリに runserver.sh を設定し、実際の状況に応じて JVM のメモリ パラメータを変更します。
5. bin ディレクトリに runbroker.sh を設定し、実際の状況に応じて JVM のメモリ パラメータを変更します。
6. nohup コマンドを実行して、RocketMQ サービスをバックグラウンドで実行します (ネームサーバーを最初に起動し、ブローカーをネームサーバーに登録する必要があります)。
# 启动nameserver
nohup bin/mqnamesrv &
# 启动broker
nohup bin/mqbroker -c [confFile] & # -c可指定加载的配置文件,默认为conf/broker.conf
# 查看日志rocketmq是否成功启动
tail nohup.out
# 查看进程
jps
# 停止broker
sh bin/mqshutdown broker
# 停止namesrv
sh bin/mqshutdown namesrv
7. コマンド テストを実行します (テスト サンプルは rocketmq によって提供され、プロデューサーは 1,000 個のメッセージを送信します)
bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
8. コマンド テストを実行します (rocketmq によって提供されるテスト サンプル、コンシューマーは 1,000 個のメッセージを受け入れます)
bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
4.RocketMQ API
プロデューサーはメッセージを同期的に送信します
public void test_SyncProducer() throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer("producer_group_name");
//设置注册服务的ip地址的端口
producer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
//启动生产者
producer.start();
for(int i=0; i<3; i++){
try {
// 封装消息,设置topic,tag(用于消息快速过滤),消息数据
Message message = new Message(
"TopicTest",
"TagA",
"ID04287777",
("Hello, RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
//同步发送消息并获取发送结果,producer从broker获取发送结果
SendResult sendResult = producer.send(message);
System.out.println(sendResult);
Thread.sleep(1500);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
producer.shutdown();
}
プロデューサーはメッセージを非同期に送信します
public void test_AsyncProducer() throws Exception{
DefaultMQProducer producer = new DefaultMQProducer(RocketMQConstant.PRODUCER_GROUP_NAME);
producer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
producer.start();
producer.setRetryTimesWhenSendAsyncFailed(0);
int messageCount = 10;
final CountDownLatch countDownLatch = new CountDownLatch(messageCount);
for(int i=0; i<messageCount; i++){
final int index = i;
// 封装消息,设置topic,tag(用于消息快速过滤),消息数据
Message message = new Message(
"TopicTest",
"TagA",
"ID04287777",
("Hello, RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 异步发送消息,若broker有响应会调用SendCallback中的方法
producer.send(message, new SendCallback() {
public void onSuccess(SendResult sendResult) {
countDownLatch.countDown();
System.out.println(" Send Message "+ index +" OK: "+sendResult);
}
public void onException(Throwable throwable) {
countDownLatch.countDown();
System.out.println(" Send Message "+ index +" Exception: "+throwable);
}
});
//单向发送
producer.sendOneway(message);
System.out.println("Message "+index+" send done");
}
//在100条消息发送完后关闭
countDownLatch.await(5, TimeUnit.SECONDS);
producer.shutdown();
}
プロデューサーは一方向にメッセージを送信します
public void test_OneWayProducer() throws Exception{
DefaultMQProducer producer = new DefaultMQProducer(RocketMQConstant.PRODUCER_GROUP_NAME);
producer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
producer.start();
producer.setRetryTimesWhenSendAsyncFailed(0);
int messageCount = 10;
final CountDownLatch countDownLatch = new CountDownLatch(messageCount);
for(int i=0; i<messageCount; i++){
final int index = i;
// 封装消息,设置topic,tag(用于消息快速过滤),消息数据
Message message = new Message(
"TopicTest",
"TagA",
"ID04287777",
("Hello, RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
//单向发送
producer.sendOneway(message);
System.out.println("Message "+index+" send done");
}
//在100条消息发送完后关闭
countDownLatch.await(5, TimeUnit.SECONDS);
producer.shutdown();
}
消費者プッシュモデル
public static void test_PushConsumer() throws Exception{
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group_name");
consumer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
//消费者订阅的消息topic和tag(subExpression,*表示任意)
consumer.subscribe("TopicTest", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
System.out.println("Receive New Message : "+list);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Start...");
}
消費者プルモデル
プッシュ モードのコンシューマーとは異なり、プル モードでは、MessageQueue とオフセットの間のマッピング関係を手動で管理する必要があります。ただし、LitePullConsumer の最新の基盤ソース コードでは、mq と offset の管理が実現され、より便利になりました。
//拉模式消费者
public static void test_LitePullConsumer() throws Exception{
DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(RocketMQConstant.CONSUMER_GROUP_NAME);
litePullConsumer.setNamesrvAddr(RocketMQConstant.NAME_SRV_ADDR);
litePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
litePullConsumer.subscribe("TopicTest", "*");
litePullConsumer.start();
try {
while(true){
List<MessageExt> messageExts = litePullConsumer.poll();
System.out.printf("%s%n", messageExts);
}
}finally {
litePullConsumer.shutdown();
}
}
RocketMQ はオブジェクトを転送します。オブジェクトが属するクラスはシリアル化インターフェイスを実装し、オブジェクトをバイト配列に変換してメッセージ本文に格納する必要があります。
連続メッセージ
ネットワーク送信による影響を防ぐために、メッセージのローカル順序付け (必ずしもすべてのメッセージの順序付けではなく、複数のメッセージの順序付け) を保証します。
実現原理
プロデューサは、順序付けされたメッセージのグループを一度に同じ MessageQueue に送信します (ローカル順序を確保するためのキューの特性に応じて)。コンシューマは、MessageQueue メッセージを消費した後、次の MessageQueue メッセージを消費します。
public class OrderProducer {
public static void main(String[] args) {
DefaultMQProducer producer = new DefaultMQProducer(WanfengConstant.PRODUCER_GROUP_NAME);
try {
producer.setNamesrvAddr(WanfengConstant.NAMESRV_ADDR);
producer.start();
for(int i=0; i<5; i++){
//用于指定顺序的id
int orderId = i;
for(int j=0; j<5; j++){
Message message = new Message(
WanfengConstant.ORDER_TOPIC,
"order_"+orderId,
"KEY"+orderId,
("order_"+orderId+" step "+j).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
//实现消息队列选择器对象,使同一个orderId的消息发送到同一个消息队列
SendResult sendResult = producer.send(
message,
new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
},
orderId
);
System.out.printf("%s%n", sendResult);
}
}
}catch(Exception e){
e.printStackTrace();
producer.shutdown();
}
}
}
public class OrderConsumer {
public static void main(String[] args) {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(WanfengConstant.CONSUMER_GROUP_NAME);
consumer.setNamesrvAddr(WanfengConstant.NAMESRV_ADDR);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
try {
consumer.subscribe(WanfengConstant.ORDER_TOPIC, "*");
//实现顺序消息监听者接口
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
context.setAutoCommit(true);
for(MessageExt messageExt : msgs){
System.out.println("Receive Message: " + new String(messageExt.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Start...");
} catch (Exception e) {
e.printStackTrace();
consumer.shutdown();
}
}
}
同報メッセージ
プロデューサによって送信されたメッセージは、グループのすべてのコンシューマにプッシュされます
実装原則: ブロードキャスト モードでコンシューマを MessageModel に設定します。
public class BroadcastConsumer {
public static void main(String[] args) {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(WanfengConstant.CONSUMER_GROUP_NAME);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
//设定消息模式为广播
consumer.setMessageModel(MessageModel.BROADCASTING);
try {
consumer.subscribe(WanfengConstant.ARCHIVE_TOPIC, "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
msgs.forEach(messageExt -> {
Archive archive = (Archive) WanfengObjectUtil.bytesToObject(messageExt.getBody());
System.out.println("Receive Message : "+archive.getId());
});
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Broadcast Consumer Start...");
}catch (Exception e){
e.printStackTrace();
consumer.shutdown();
}
}
}
指定された MessageModel が CLUSTERING の場合、プロデューサーによって送信されるメッセージは、消費するコンシューマーをランダムに指定します。
遅延メッセージ
名前が示すように、メッセージがブローカーに送信されると、指定された時間だけ遅延してからコンシューマーに送信されます。タイミング送信によく使用されます
メッセージをフィルタリングする
メッセージのフィルタリングはタグによって実装され、フィルタタグはコンシューマ側で指定できます。
//消费者订阅tag1或tag2的消息
consumer.subscribe("TopicTest", "tag1 || tag2");
RocketMQ では、コンシューマーがフィルター条件を指定した後、それがブローカーにプッシュされ、ブローカー内でタグ フィルターが実行されてネットワーク IO が削減されますが、ブローカーのビジー状態も増加します。
トランザクションメッセージ
public class TransactionProducer {
public static void main(String[] args) {
TransactionMQProducer producer = new TransactionMQProducer(WanfengConstant.PRODUCER_GROUP_NAME);
TransactionListener transactionListener = new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
System.out.println("[WANFENG-INFO] TransactionProducer.executeLocalTransaction(): 执行成功...");
String tags = msg.getTags();
if (StringUtils.contains(tags, "TagA")) {
//消息提交(发送出去)
return LocalTransactionState.COMMIT_MESSAGE;
} else if (StringUtils.contains(tags, "TagB")) {
//消息回滚(丢掉消息)
return LocalTransactionState.ROLLBACK_MESSAGE;
} else {
return LocalTransactionState.UNKNOW;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
System.out.println("[WANFENG-INFO] TransactionProducer.checkLocalTransaction(): 执行成功...");
String tags = msg.getTags();
if (StringUtils.contains(tags, "TagC")) {
return LocalTransactionState.COMMIT_MESSAGE;
} else {
return LocalTransactionState.UNKNOW;
}
}
};
ExecutorService executorService = new ThreadPoolExecutor(
2,
5,
100, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3)
);
producer.setExecutorService(executorService);
producer.setTransactionListener(transactionListener);
try {
producer.start();
} catch (Exception e) {
e.printStackTrace();
}
String[] tags = new String[]{
"TagA", "TagB", "TagC"};
CountDownLatch countDownLatch = new CountDownLatch(9);
for (int i = 0; i < 9; i++) {
try {
Message message = new Message("TopicTest", tags[i % tags.length], "Key" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.sendMessageInTransaction(message, null);
System.out.println(sendResult);
Thread.sleep(1000);
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
producer.shutdown();
}
}
}
ACL権限制御
ACL はトピック リソースへのユーザーのアクセスを制御します
pom 依存関係に acl の依存関係パッケージを導入します。
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
<version>5.0.0</version>
</dependency>
サーバー側の conf/broker.conf ファイルに構成を追加し、ACL を有効にします。
aclEnable=true
サーバー側の conf/plain_acl.yml ファイルで、特定の許可ルールを構成します (ホットロード、mq を再起動する必要はありません)。
accounts:
- accessKey: RocketMQ #用户名
secretKey: 12345678 #密码
whiteRemoteAddress: #访问地址白名单
admin: false #是否为管理员(管理员可以访问所有Topic)
defaultTopicPerm: DENY #默认Topic访问权限
defaultGroupPerm: SUB #默认组权限
topicPerms: #Topic对应的权限,若这里找不到则采用defaultTopicPerm
- topicA=DENY
- topicB=PUB|SUB
- topicC=SUB
groupPerms:
# the group should convert to retry topic
- groupA=DENY
- groupB=PUB|SUB
- groupC=SUB
プロデューサーオブジェクトの作成時にRPCHook(ACLユーザー情報)を追加する必要があります
public class AclProducer {
private static final String ACL_ACCESS_KEY = "RocketMQ";
private static final String ACL_SECRET_KEY = "12345678";
/**
* 通过用户名和密码获取RPCHook
* @return
*/
public static RPCHook getAclRPCHook(){
return new AclClientRPCHook(new SessionCredentials(ACL_ACCESS_KEY, ACL_SECRET_KEY));
}
public static void main(String[] args) throws MQClientException, InterruptedException {
//创建生产者时加入用户信息,即RPCHook
DefaultMQProducer producer = new DefaultMQProducer(WanfengConstant.PRODUCER_GROUP_NAME, getAclRPCHook());
producer.setNamesrvAddr(WanfengConstant.NAMESRV_ADDR);
producer.start();
for (int i = 0; i < 20; i++) {
try {
Message message = new Message(
"TopicTest",
WanfengConstant.TAGS_NAME,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /*消息体转换成二进制数组*/
);
SendResult sendResult = producer.send(message);
System.out.printf("%s%n", sendResult);
} catch (Exception e) {
e.printStackTrace();
Thread.sleep(1000);
}
}
}
}
メッセージトラック
プロデューサー、コンシューマー、およびブローカーのメッセージに関する情報をプロセスします。
メッセージ トラックの実現原理は、MQ がメッセージ トラックを RMQ_SYS_TRACE_TOPIC のトピックに配置することです。
ブローカー構成ファイル内のメッセージ トラックを開きます。
traceTopicEnable=true
プロデューサを作成するときに、enableMsgTrace パラメータを true に指定してメッセージ トレースを有効にします。また、customizedTraceTopic パラメーターを指定して、メッセージ トレースのトピックをカスタマイズすることもできます。
public class TraceProducer {
public static void main(String[] args) throws MQClientException {
//指定enableMsgTrace参数为true,开启消息轨迹
DefaultMQProducer producer = new DefaultMQProducer(WanfengConstant.PRODUCER_GROUP_NAME, true);
producer.setNamesrvAddr(WanfengConstant.NAMESRV_ADDR);
producer.start();
for (int i = 0; i < 20; i++) {
try {
Message message = new Message(
"TopicTest",
WanfengConstant.TAGS_NAME,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /*消息体转换成二进制数组*/
);
SendResult sendResult = producer.send(message);
System.out.printf("%s%n", sendResult);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}