Redis implementiert eine Verzögerungswarteschlange

In Bezug auf Nachrichtenwarteschlangen sind wir es gewohnt, Rabbitmq und Kafka als Nachrichtenwarteschlangen-Middleware zu verwenden. Wenn sich jedoch nur eine Gruppe von Verbrauchern in der Verbrauchswarteschlange befindet, ist keine sehr hohe Zuverlässigkeit erforderlich, und die Verwendung von Middleware ist sehr umständlich.Zu diesem Zeitpunkt können wir die Eigenschaften von Redis verwenden, um eine einfache Nachrichtenwarteschlange zu implementieren.

asynchrone Nachrichtenwarteschlange

Die Listendatenstruktur von Redis wird häufig als asynchrone Nachrichtenwarteschlange verwendet. Verwenden Sie rpush und lpush, um wie eine Warteschlange zu arbeiten, und verwenden Sie lpop und rpop, um sie aus der Warteschlange zu entfernen. Es unterstützt mehrere Produzenten und mehrere Verbraucher, um Nachrichten gleichzeitig ein- und
Bildbeschreibung hier einfügen
auszusenden Consumer bekommt Die Nachrichten sind alle unterschiedliche Listenelemente, wie gezeigt:
Bildbeschreibung hier einfügen

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

Das Obige ist die Kombination aus rpush und lpop, Sie können auch die Kombination aus lpush und rpop verwenden, der Effekt ist derselbe.

Behandlung, wenn die Warteschlange leer ist

Der Client erhält die Nachricht durch die Pop-Operation der Warteschlange und verarbeitet sie dann. Rufen Sie nach der Verarbeitung die Nachricht ab und verarbeiten Sie sie. Ein solcher Zyklus ist der Lebenszyklus des Clients als Warteschlangenverbraucher.
Aber wenn die Warteschlange leer ist, wird der Client in eine Endlosschleife von Pops geraten und weiter poppen. Diese Art von leerem Polling erhöht nicht nur den CPU-Verbrauch des Clients, sondern erhöht auch die QPS von Redis. Wenn solch ein leeres Polling Client Wenn es Dutzende von Knoten gibt, kann die langsame Abfrage von Redis erheblich zunehmen.

Normalerweise lösen wir dieses Problem mit sleep: Wenn die Warteschlange leer ist, lassen Sie den Thread eine Weile schlafen, was nicht nur den CPU-Verbrauch des Clients reduziert, sondern auch die QPS von Redis.

Lesen blockieren

Die obige Schlafmethode kann das Problem lösen. Aber es gibt noch ein weiteres kleines Problem, nämlich, dass der Schlaf dazu führt, dass die Verzögerung der Nachricht zunimmt. Wenn nur 1 Verbraucher vorhanden ist, beträgt diese Verzögerung 1 s. Wenn mehrere Verbraucher vorhanden sind, wird diese Verzögerung reduziert, da die Ruhezeit der Verbraucher auseinanderläuft.
Gibt es eine Möglichkeit, sowohl das Verbrauchsproblem als auch das Verzögerungsproblem zu lösen? Natürlich gibt es sie, nämlich blpop/brpop.Das
vorangestellte Zeichen b dieser beiden Befehle steht für Blockieren, also Blockierendes Lesens.
Wenn der blockierende Lesevorgang keine Daten für die Spalte enthält, wird er sofort in den Ruhezustand versetzt, und sobald die Daten eintreffen, wird er sofort wieder aktiviert. Nachrichten haben nahezu keine Latenz.

Eine inaktive Verbindung wird automatisch getrennt

Obwohl die obige Lösung das Problem leerer Spalten löst, ist sie nicht perfekt und hat immer noch das Problem leerer Verbindungen.
Wenn der Thread die ganze Zeit blockiert wird, wird die Redis-Client-Verbindung zu einer Leerlaufverbindung.Wenn sie zu lange im Leerlauf ist, wird der Server normalerweise die Verbindung aktiv trennen, um die Belegung von Leerlaufressourcen zu reduzieren. Zu diesem Zeitpunkt löst blpop/brpop eine Ausnahme aus. Seien Sie also vorsichtig, wenn Sie clientseitige Verbraucher schreiben, und versuchen Sie es erneut, wenn eine Ausnahme abgefangen wird.

Implementierung einer verzögerten Warteschlange

Verzögerte Warteschlangen können über das zset von Redis implementiert werden. Wir serialisieren die Nachricht in eine Zeichenfolge als Wert von zset und die Ablaufverarbeitungszeit der Nachricht als Punktzahl und verwenden dann mehrere Threads, um zset abzufragen, um die abgelaufenen Aufgaben zur Verarbeitung zu erhalten. Mehrere Threads werden verwendet, um die Verfügbarkeit zu gewährleisten. Wenn ein Thread aufgehängt wird, gibt es andere Threads, die die Verarbeitung fortsetzen können. Da mehrere Threads vorhanden sind, müssen gleichzeitige Konfliktaufgaben berücksichtigt werden, um sicherzustellen, dass Aufgaben nicht mehrmals ausgeführt werden.

Die zrem-Methode von Redis stellt den Schlüssel zum Multi-Thread- und Multi-Prozess-Konkurrenz um Aufgaben dar. Ihr Rückgabewert bestimmt, ob die aktuelle Instanz die Aufgabe geschnappt hat, da die Schleifenmethode von mehreren Threads und Prozessen und derselben Aufgabe aufgerufen werden kann kann als mehrere Prozesse und mehrere Threads bezeichnet werden.
Gleichzeitig müssen wir handle_msg erfassen, um zu verhindern, dass einzelne Zeichen Probleme behandeln und bewirken, dass die Schleife fehlschlägt und abnormal beendet wird.

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();
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_45473439/article/details/126340345
Recomendado
Clasificación