I.はじめに
Web システムでは、http の 3 ウェイ ハンドシェイクに加えて、システムとシステム間の対話的な仲介としてよく使用される重要なコンポーネントがあり、それがメッセージ キューです。メッセージ キューは、リアルタイムのパフォーマンスを実現するために多少の犠牲は伴いますが、大きな問題は解決されます。
メッセージキュー
2. メッセージキューの利用シーン
システムのデカップリング
オープンクローズ原則は設計パターンの原則であり、システムの独立性を維持するために、ソフトウェア エンティティは拡張に対してオープンであり、変更に対してクローズである必要があることを意味します。この原則は分離の考え方を強調しており、メッセージ キューの使用を通じて、外部の拡張インターフェイスが暗黙的にシステムに導入されていることがわかります。このようにして、ビジネス ロジックを簡単に分離することができ、呼び出し元は下流のロジックがどのように実行されるかを気にせずにメッセージを送信するだけで済みます。メッセージ キューの存在により、システムのさまざまなコンポーネントがパブリッシュ/サブスクライブ モデルを通じて通信できるようになり、システムのスケーラビリティが向上します。
システム間の分離は、RPC サービス呼び出しを使用して実現することもできます。メッセージ キューを使用する利点はありますか?リモート サービス呼び出しを使用する場合は、呼び出し元の 1 つでビジネス ロジックを明示的にコーディングする必要があります。メッセージ キューを使用すると、この問題は発生しません。依存関係の逆転は、システム間でより適切に実現できます。これは、システム間の重要な部分でもあります。デザインパターンを原則としております。
非同期処理
非同期処理は、高い同時実行性と高可用性のシステム設計を扱う上で重要なメカニズムの 1 つです。システムがメッセージをすぐに処理できない場合、またはシステムの処理能力によって制限されている場合、メッセージ キューを使用してリクエストを非同期に処理できます。
典型的な非同期処理シナリオは、トラフィックのピークカットです。 e コマースのフラッシュ セールを例に挙げると、フラッシュ セールはトラフィックのピークを引き起こすことがよくあります。ただし、サービスは多くの場合、このような高いアクセス圧力に瞬時に耐えることができません。この問題を解決するには、メッセージ キューを導入し、トラフィック制御ツールと組み合わせて、システムのしきい値を超えるリクエストをメッセージ キューに保存し、トラフィックのピークが過ぎた後に処理します。
非同期処理により、システムは高度な同時リクエストに適切に対処し、過剰なトラフィックによるシステム クラッシュやパフォーマンスの低下を回避できます。メッセージ キューはトラフィックをバッファリングして調整する役割を果たし、システムの負荷を効果的に分散します。
リクエストのバッファリング
プロデューサおよびコンシューマ モデルでは、メッセージ キューをバッファリング層として使用できます。メッセージキューは、異なる業務システム間の処理性能の違いにもスムーズに対応できます。初期のエンタープライズ アプリケーション システムには、さまざまな内部システムを統合するために使用されるエンタープライズ データ バス (ESB) の概念がありました。
データ配信
メッセージ キューはさまざまなサブスクリプション モードを提供します。その 1 つとして 1 対多のブロードキャスト メカニズムがあり、データ配信を実現するために使用できます。典型的な例は、リレーショナル データベースによる binlog のサブスクリプション処理です。
リレーショナル データベースでは、binlog はデータベースの変更を記録するログを指します。メイン ライブラリによって生成されるバイナリログは 1 つだけですが、ダウンストリーム コンシューマにはさまざまなファイル インデックス、オフライン データベースなどが含まれる場合があります。現時点では、メッセージ キューを使用してデータを分散できます。
これらの典型的なアプリケーションに加えて、メッセージ キューを使用して分散トランザクションを実装することもできます。前に「分散トランザクションのソリューション」で説明したように、データベース + ローカル メッセージ テーブルを使用した分散整合性は、非常に古典的な分散方法です。
3. rocketMQ の概要
Tencent Cloud rocketMQ を例として示します。
Maven pom ファイルに依存関係を導入する
<!--cos -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>${cos_api.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
<version>${rocketmq.version}</version>
</dependency>
import java.io.UnsupportedEncodingException;
import java.util.List;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class RocketTXMqService {
@Value("${rocketmq.namespace:-1}")
private String namespace;
@Value("${rocketmq.producer.group:-1}")
private String groupName;
@Value("${rocketmq.producer.access-key:-1}")
private String accessKey;
@Value("${rocketmq.producer.secret-key:-1}")
private String secretKey;
@Value("${rocketmq.name-server:-1}")
private String nameserver;
// MQ生产者
private DefaultMQProducer producer;
// MQ实例化消费者push
private DefaultMQPushConsumer pushConsumer;
// MQ实例化消费者pull
private DefaultLitePullConsumer pullConsumer;
/**
* 创建生产者
*
* @return
*/
public DefaultMQProducer getProducer() {
if (null == producer) {
// 实例化消息生产者Producer
producer = new DefaultMQProducer(namespace, groupName,
new AclClientRPCHook(new SessionCredentials(accessKey, secretKey)) // ACL权限
);
// 设置NameServer的地址
producer.setNamesrvAddr(nameserver);
try {
// 启动Producer实例
producer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
return producer;
}
/**
* 同步发送 发送消息
*/
public void syncSend(String topic, String tag, String data) {
producer = getProducer();
// 发送消息
SendResult sendResult = null;
try {
// 创建消息实例,设置topic和消息内容
Message msg = new Message(topic, tag, data.getBytes(RemotingHelper.DEFAULT_CHARSET));
sendResult = producer.send(msg);
log.info("埋点信息发送腾讯云MQ:" + data);
log.info("发送腾讯云MQ接口返回状态sendResult:" + sendResult);
} catch (UnsupportedEncodingException e) {
log.error("UnsupportedEncodingException:" + e.getMessage());
} catch (MQClientException e) {
log.error("MQClientException:" + e.getMessage());
} catch (RemotingException e) {
log.error("RemotingException:" + e.getMessage());
} catch (MQBrokerException e) {
log.error("MQBrokerException:" + e.getMessage());
} catch (InterruptedException e) {
log.error("InterruptedException:" + e.getMessage());
}
}
/**
* 创建push消费者
*
* @return
*/
public DefaultMQPushConsumer getPushConsumer() {
if (null == pushConsumer) {// 实例化消费者
pushConsumer = new DefaultMQPushConsumer(namespace, groupName,
new AclClientRPCHook(new SessionCredentials(accessKey, secretKey))); // ACL权限
// 设置NameServer的地址
pushConsumer.setNamesrvAddr(nameserver);
}
return pushConsumer;
}
/**
* 创建pull 消费者
*
* @return
*/
public DefaultLitePullConsumer getPullConsumer() {
if (null == pullConsumer) {// 实例化消费者
// 实例化消费者
pullConsumer = new DefaultLitePullConsumer(namespace, groupName,
new AclClientRPCHook(new SessionCredentials(accessKey, secretKey)));
// 设置NameServer的地址
pullConsumer.setNamesrvAddr(nameserver);
// 设置从第一个偏移量开始消费
pullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
}
return pullConsumer;
}
/**
* push方式订阅消费
*
* @param topicName
*/
public void pushConsumer(String topicName) {
pushConsumer = this.getPushConsumer();
if (null != pushConsumer) {
try {
pushConsumer.subscribe(topicName, "*");
// 注册回调实现类来处理从broker拉取回来的消息
pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
// 消息处理逻辑
log.info("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
// 标记该消息已经被成功消费, 根据消费情况,返回处理状态
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
// 启动消费者实例
pushConsumer.start();
} catch (MQClientException e) {
log.error("push MQClientException:" + e.getMessage());
}
}
}
/**
* pull方式订阅消费
*
* @param topicName
*/
public void pullConsumer(String topicName) {
pullConsumer = this.getPullConsumer();
if (null != pullConsumer) {
try {
// 订阅topic
pullConsumer.subscribe(topicName, "*");
// 启动消费者实例
pullConsumer.start();
} catch (MQClientException e) {
log.error(" pull MQClientException:" + e.getMessage());
}
try {
log.info("Consumer Started.%n");
while (true) {
// 拉取消息
List<MessageExt> messageExts = pullConsumer.poll();
log.info("%s%n", messageExts);
}
} finally {
pullConsumer.shutdown();
}
}
}
}
mq にメッセージを送信する
@Autowired
private RocketTXMqService rocketTXMqService;
文字列入力 = JSONObject.toJSONString(dataParam);
rocketTXMqService.syncSend("埋没点トピック", mAccessLog.getEventAliasName(), input);