Redis は遅延キューを実装します

メッセージ キューに関しては、Rabbitmq と Kafka をメッセージ キュー ミドルウェアとして使用することに慣れています。しかし、消費キューにコンシューマーのグループが 1 つしかない場合、それほど高い信頼性は必要なく、ミドルウェアを使用するのは非常に面倒です. このとき、redis の特性を利用して単純なメッセージ キューを実装できます。

非同期メッセージ キュー

Redisのリストデータ構造は非同期メッセージキューとしてよく使われます. キューなどの操作には rpush と lpush を使います. デキューには lpop と rpop を使います. 複数のプロデューサと複数のコンシューマが同時にメッセージを送受信することをサポートしています
ここに画像の説明を挿入
.以下に示すように、メッセージはすべて個別のリスト要素です。
ここに画像の説明を挿入

> rpush notify-queue apple banba pear
> lpop notify-queue
“apple”
> lpop notify-queue
"banana" 
> lpop notify-queue
"pear"
> lpop notify-queue
(nil)

上記は rpush と lpop の組み合わせですが、lpush と rpop を組み合わせて使用​​することもでき、効果は同じです。

キューが空だった場合の処理

クライアントは、キューの pop 操作によってメッセージを取得し、それを処理します。処理後、メッセージを取得して処理します。このようなサイクルは、キュー コンシューマーとしてのクライアントのライフ サイクルです。
しかし、キューが空の場合、クライアントはポップの無限ループに陥り、ポップし続けます. この種の空のポーリングは、クライアントの CPU 消費を増加させるだけでなく、Redis の QPS も増加させます. client ノードが数十個ある場合、Redis の遅いクエリが大幅に増加する可能性があります。

通常、この問題を解決するには sleep を使用します. キューが空の場合、スレッドをしばらくスリープさせます. これにより、クライアントの CPU 消費が削減されるだけでなく、Redis の QPS も削減されます.

読み取りのブロック

上記のスリープ方法で問題を解決できます。しかし、別の小さな問題があります。つまり、睡眠によってメッセージの遅延が増加します。コンシューマーが 1 つしかない場合、この遅延は 1 秒です。複数のコンシューマがある場合、コンシューマのスリープ時間が発散されるため、この遅延は減少します。
消費の問題と遅延の問題の両方を解決する方法はありますか? もちろんありますが、それは blpop/brpop です.
これら 2 つの命令の接頭文字 b は、ブロッキング、つまりブロッキング読み取りを表します。
ブロッキング読み取りに列のデータがない場合、すぐにスリープ状態になり、データが到着するとすぐに復帰します。メッセージのレイテンシはほぼゼロです。

アイドル接続は自動的に切断されます

上記の解決策は空の列の問題を解決しますが、完全ではなく、接続がアイドル状態になるという問題がまだ残っています。
スレッドが常にブロックされている場合、Redis クライアント接続はアイドル状態の接続になります. アイドル状態が長すぎる場合、サーバーは通常、アイドル状態のリソースの占有を減らすために積極的に接続を切断します. この時点で、blpop/brpop は例外をスローします。そのため、クライアント側のコンシューマーを作成するときは注意して、例外がキャッチされた場合は再試行してください。

遅延キューの実装

遅延キューは、Redis の zset を介して実装できます。メッセージを zset の値として文字列にシリアル化し、メッセージの有効期限の処理時間をスコアとして、複数のスレッドを使用して zset をポーリングし、期限切れのタスクを取得して処理します。可用性を確保するために複数のスレッドが使用され、1 つのスレッドがハングアップしても、処理を続行できる他のスレッドが存在します。複数のスレッドがあるため、タスクが複数回実行されないように、同時競合タスクを考慮する必要があります。

Redis の zrem メソッドは、タスクのマルチスレッドおよびマルチプロセス競合の鍵です. その戻り値は、現在のインスタンスがタスクをつかんだかどうかを決定します.複数のプロセスが呼び出され、複数のスレッドがそれを取得します。
同時に、個々の文字が問題を処理し、ループが失敗して異常終了するのを防ぐために、handle_msg をキャプチャする必要があります。

public class RedisDelayingQueue<T> {
    
    
    static class  TaskItem<T> {
    
    
        public String id;
        public T msg;
    }

    private Type TaskType = new TypeReference<TaskItem<T>>(){
    
    }.getType() ;

    private Jedis jedis;

    private String queueKey;

    public RedisDelayingQueue(Jedis jedis,String queueKey) {
    
    
        this.jedis = jedis;
        this.queueKey = queueKey;
    }

    public void deplay(T msg) {
    
    
        TaskItem<T> task = new TaskItem<>();
        task.id = UUID.randomUUID().toString();
        task.msg = msg;
        String s = JSON.toJSONString(task);
        jedis.zadd(queueKey,System.currentTimeMillis() + 5000, s);
    }

    public void loop() {
    
    
        while (!Thread.interrupted()) {
    
    
            Set<String>  values = jedis.zrangeByScore(queueKey,0,System.currentTimeMillis(),0 , 1);
            if (values.isEmpty()) {
    
    
                try {
    
    
                    Thread.sleep(50);
                }catch (InterruptedException e) {
    
    
                    break;
                }
                continue;
            }
            // 取出
            String s = values.iterator().next();
            if (jedis.zrem(queueKey,s) > 0) {
    
    
                TaskItem<T> task = JSON.parseObject(s,TaskType);
                this.handleMsg(task.msg);
            }
        }
    }

    public void handleMsg(T msg) {
    
    
        System.out.println(msg);
    }

    public static void main(String[] args) {
    
    
        Jedis jedis = new Jedis("124.221.52.57",6379);
        jedis.auth("li12345...");
        jedis.select(2);
        final RedisDelayingQueue<String> queue = new RedisDelayingQueue<>(jedis,"q-demo");
        Thread producer = new Thread() {
    
    
            public void run() {
    
    
                for (int i = 0; i < 10; i++) {
    
    
                    queue.deplay("codehole"+i);
                }
            }
        };
        Thread consumer = new Thread() {
    
    
            public void run() {
    
    
                queue.loop();
            }
        };
        producer.start();
        consumer.start();
        try {
    
    
//            producer.join();
            Thread.sleep(6000);
            consumer.interrupt();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

おすすめ

転載: blog.csdn.net/qq_45473439/article/details/126340345