クラスター環境でローカル キャッシュを同期する方法について説明します。

序文

以前、 Redis を使用してマルチレベルのキャッシュ同期を実現する方法について説明する記事を投稿しましたある読者が、プロジェクトの Redis バージョンがバージョン 6.0 以降ではないため、私の記事を使用してローカル キャッシュ同期を実現するために MQ を紹介したというメッセージを私に残しました。彼の同期プロセスは大まかに次のとおりです。


彼の当初のビジネス プロセスは、毎朝タイマーを開始してサードパーティ データをクロールし、それを Redis に永続化することでしたが、その後、Redis でダウンタイムが発生したため、偶然私の記事を読んで、マルチレベル キャッシュが使用できると考えました。 、ボトムラインを作成するために使用されます。彼のビジネス プロセスは上の図に示されているとおりです。つまり、毎朝タイマーを開始してサードパーティ データをクロールし、それを Redis といずれかのサービスのローカル キャッシュに永続化し、クロールされたビジネス データを Kafka に送信します。 Kafka を購読し、ビジネス データをローカル キャッシュに保存することで、その他のビジネス サービスを利用できます。

変換後、彼はある日突然、クラスター環境ではサービスの 1 つが Kafka データを消費する限り、他のサービスは消費できないことに気づきました。今日はこのトピックを取り上げて、クラスター環境でローカル キャッシュを同期する方法について話しましょう。

予備知識

Kafka 消費トピック パーティション モードは、サブスクライブ モードと割り当てモードに分かれています。サブスクライブ モードでは、group.id を指定する必要があります。これにより、パーティションがコンシューマに自動的に割り当てられ、同じ group.id にある異なるコンシューマが同じパーティションを使用することはありません。割り当てモードでは、コンシューマが使用するトピック パーティションを手動で明示的に指定する必要があります。これは、指定された group.id が無効であることと同等の group.id によって制限されません。平たく言えば、割り当てモードでは、すべてのコンシューマーが指定されたパーティションをサブスクライブできます。

メッセージ キューを介してローカル キャッシュ同期を実現したいと考えています。本質的に、ブロードキャスト機能を提供するにはメッセージ キューを使用する必要がありますが、kafka にはデフォルトでそれがありません。ただし、kafka が提供する消費モードに従ってカスタマイズできるため、kafka にはブロードキャスト機能も備わります。

クラスターのローカルキャッシュ同期スキーム

解決策 1: MQ ブロードキャスト機能を使用する

リーダープロジェクトはkafkaを使用し、プロジェクトはspring-kafkaを使用するため、これを例として取り上げます

1. 購読モード

事前知識を通じて、サブスクライブ モードでは、同じ group.id に属する異なるコンシューマが同じパーティションを消費しないことを学びました。つまり、ブロードキャストの効果を達成するには、異なる group.id を指定することで同じパーティションを消費できるということです。

同じクラスター サービスに異なる group.id を実装するにはどうすればよいですか?

この時点で、Spring EL 式が役に立ちます。Spring EL 式を使用して、各コンシューマ グループの名前に UUID を含むサフィックスを生成します。このようにして、放送消費の目的を達成するために、各プロジェクトによって開始される消費者グループが異なることを保証できます。

  @KafkaListener(topics = "${userCache.topic}",groupId =  "${userCache.topic}_group_" + "#{T(java.util.UUID).randomUUID()})")
    public void receive(Acknowledgment ack, String data){
    
    
        System.out.println(String.format("serverPort:【%s】,接收到数据:【%s】",serverPort,data));
        ack.acknowledge();
    }

UUID が直感的ではないと判断した場合は、同じクラスター サービスの group.id が一意であることが保証できる限り、IP を識別子として使用することもできます。

しかし、それを ip に変更したい場合は、何らかの変換を行う必要があります。変換手順は次のとおりです

a. IP アドレス情報を取得し、環境に配置します。

public class ServerAddrEnvironmentPostProcessor implements EnvironmentPostProcessor{
    
    



    private String SERVER_ADDRESS = "server.addr";

    @Override
    @SneakyThrows
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    
    
        MutablePropertySources propertySources = environment.getPropertySources();
        Map<String, Object> source = new HashMap<>();
        String serverAddr = InetAddress.getLocalHost().getHostAddress();
        source.put(SERVER_ADDRESS,serverAddr);
        MapPropertySource mapPropertySource = new MapPropertySource("serverAddrProperties",source);
        propertySources.addFirst(mapPropertySource);

    }



}

b. spi を構成する

src/main/resourceディレクトリにMETA-INF/spring.factoriesを設定します。設定内容は以下のとおりです。

org.springframework.boot.env.EnvironmentPostProcessor=\
com.github.lybgeek.comsumer.ip.ServerAddrEnvironmentPostProcessor

c. @KafkaListener は次のように構成されています

 @KafkaListener(topics = "${userCache.topic}",groupId =  "${userCache.topic}_group_" + "${server.addr}" + "_${server.port}")

まとめ

この方法の利点は、比較的単純であることですが、サービスの O&M 監視と統計を実行する必要がある場合にはあまりフレンドリーではありません。IP を指定する方がランダムな UUID よりも優れていますが、コンテナ化された展開の場合、それぞれのIP の展開も変更されるため、マシンに従って UUID を指定しますが、違いは大きくありません。次に、クラウド製品を使用する場合、例えば Alibaba Cloud ではコンシューマ グループの数に上限があり、コンシューマ グループを事前に作成する必要があるため、このソリューションの使用にはあまり適していません。

割り当てモード

割り当てモードを通じて、対応するパーティションを手動で使用します

   @KafkaListener(topicPartitions =
            {
    
    @TopicPartition(topic = "${userCache.topic}", partitions = "0")})
    public void receive(Acknowledgment ack, ConsumerRecord record){
    
    
        System.out.println(String.format("serverPort:【%s】,接收到数据:【%s】",serverPort,record));
        ack.acknowledge();
    }

まとめ

この方法は実装も非常に簡単なので、新しいパーティションを動的に作成する必要がない場合は、このソリューションを使用してブロードキャストを実装するのが良い選択になります。ただし、この方法の欠点は明らかです。パーティションを手動で指定するため、パーティションに問題がある場合は非常に面倒です。

解決策 2: タイマーによってトリガーされる

このスキームは、変換後の以下の図に示すように、主にリーダーの現在の同期変換に基づいています。

コアは読者のビジネスの特性に基づいており、読者は毎晩一定の間隔で同期的にクロールするため、データは少なくとも同日には基本的に変更されず、クラスター内のサービスを定期的に実行できます。今回は xxl のみ - ジョブのスケジュール戦略をシャード ブロードキャストに変更するだけで、ジョブを Redis に永続化し、ローカル キャッシュにも永続化できるようになります。

まとめ

このソリューションの変更は比較的小さく、クラスター内のすべてのサービスがスケジュールされているため、redis が永続化を繰り返すことになるという小さな欠点がありますが、それが良い限り、問題はそれほど大きくありません。最後に読者は選択肢を選択します

要約する

この記事では主にクラスター環境でローカル キャッシュを同期する方法について説明します。以前、読者から「マルチレベル キャッシュを使用するときにデータの整合性を確保するにはどうすればよいですか?」という質問がありました。以前だったら二重削除を遅らせるとか、mysqlならcanal+mqとか、さらには分散ロックを使って保証するとか、技術的な観点からお答えするかもしれません。しかし今では、この問題をもっとビジネスの観点から考えています。キャッシュの使用を検討しているということは、ビジネスにおける特定の矛盾を許容できるということですか?許容できるので、最終的には何らかの報酬プランを通じてこの問題を解決できるでしょうか? 矛盾

完璧なソリューションはありません。現時点であなたが感じている完璧なソリューションは、そのビジネス シナリオに適合するトレードオフである可能性があります。

デモリンク

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-kafka-broadcast

おすすめ

転載: blog.csdn.net/kingwinstar/article/details/129319035