Heim  >  Artikel  >  Datenbank  >  Beispielanalyse für die Verwendung von Redis-Befehlen

Beispielanalyse für die Verwendung von Redis-Befehlen

WBOY
WBOYnach vorne
2023-05-30 15:46:52654Durchsuche

Ursache des Problems

Die Anwendung, für die der Editor verantwortlich ist, ist eine Verwaltungshintergrundanwendung. Da mehrere Knoten vorhanden sind, muss eine verteilte Sitzung verwendet werden, sodass Redis zum Speichern von Sitzungsinformationen verwendet wird .

Da Shiro die Redis-Speichersitzungskomponente nicht direkt bereitstellt, musste Afan shiro-redis verwenden, eine Open-Source-Komponente von Github.

Da das Shiro-Framework regelmäßig überprüfen muss, ob die Sitzung gültig ist, ruft die unterste Schicht von Shiro SessionDAO#getActiveSessions auf, um alle Sitzungsinformationen abzurufen. SessionDAO#getActiveSessions 获取所有的 Session 信息。

shiro-redis 正好继承 SessionDAO 这个接口,底层使用用keys 命令查找 Redis 所有存储的 Session key。

public Set<byte[]> keys(byte[] pattern){
    checkAndInit();
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();
    try{
        keys = jedis.keys(pattern);
    }finally{
        jedis.close();
    }
    return keys;
}

找到问题原因,解决办法就比较简单了,github 上查找到解决方案,升级一下 shiro-redis 到最新版本。

在这个版本,shiro-redis 采用 scan命令代替 keys,从而修复这个问题。

public Set<byte[]> keys(byte[] pattern) {
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();


    try{
        keys = new HashSet<byte[]>();
        ScanParams params = new ScanParams();
        params.count(count);
        params.match(pattern);
        byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;
        ScanResult<byte[]> scanResult;
        do{
            scanResult = jedis.scan(cursor,params);
            keys.addAll(scanResult.getResult());
            cursor = scanResult.getCursorAsBytes();
        }while(scanResult.getStringCursor().compareTo(ScanParams.SCAN_POINTER_START) > 0);
    }finally{
        jedis.close();
    }
    return keys;


}

虽然问题成功解决了,但是阿粉心里还是有点不解。

为什么keys 指令会导致其他命令执行变慢?

为什么Keys 指令查询会这么慢?

为什么Scan 指令就没有问题?

Redis 执行命令的原理

首先我们来看第一个问题,为什么keys 指令会导致其他命令执行变慢?

站在客户端的视角,执行一条命令分为三步:

  1. 发送命令

  2. 执行命令

  3. 返回结果

但是这仅仅客户端自己以为的过程,但是实际上同一时刻,可能存在很多客户端发送命令给 Redis ,而 Redis 我们都知道它采用的是单线程模型。

为了处理同一时刻所有的客户端的请求命令,Redis 内部采用了队列的方式,排队执行。

于是客户端执行一条命令实际需要四步:

  1. 发送命令

  2. 命令排队

  3. 执行命令

  4. 返回结果

由于 Redis 单线程执行命令,只能顺序从队列取出任务开始执行。

只要 3 这个过程执行命令速度过慢,队列其他任务不得不进行等待,这对外部客户端看来,Redis 好像就被阻塞一样,一直得不到响应。

所以使用 Redis 过程切勿执行需要长时间运行的指令,这样可能导致 Redis 阻塞,影响执行其他指令。

KEYS 原理

接下来开始回答第二个问题,为什么Keys 指令查询会这么慢?

回答这个问题之前,请大家回想一下 Redis 底层存储结构。

不太清楚朋友的也没关系,大家可以回看一下之前的文章「阿里面试官:HashMap 熟悉吧?好的,那就来聊聊 Redis 字典吧!」。

keys命令需要返回所有的符合给定模式 pattern 的  Redis 中键,为了实现这个目的,Redis 不得不遍历字典中 ht[0]哈希表底层数组,这个时间复杂度为 「O(N)」(N 为 Redis 中 key 所有的数量)。

即使 Redis 中的键数量很少,它仍然会有很快的执行速度。当Redis键的数量逐渐增多,达到百万、千万,甚至上亿级别时,它的执行速度会变得非常缓慢。

下面是阿粉本地做的一次实验,使用 lua 脚本往 Redis 中增加 10W 个 key,然后使用 keys 查询所有键,这个查询大概会阻塞十几秒的时间。

eval "for i=1,100000  do redis.call('set',i,i+1) end" 0

这里阿粉使用 Docker 部署 Redis,性能可能会稍差。

SCAN 原理

最后我们来看下第三个问题,为什么scan 指令就没有问题?

这是因为 scan命令采用一种黑科技-「基于游标的迭代器」

每次调用 scan 命令,Redis 都会向用户返回一个新的游标以及一定数量的 key。下次再想继续获取剩余的 key,需要将这个游标传入 scan 命令, 以此来延续之前的迭代过程。

简单来讲,scan 命令使用分页查询 redis 。

下面是一个 scan 命令的迭代过程示例:

scan 命令使用游标这种方式,巧妙将一次全量查询拆分成多次,降低查询复杂度。

虽然  scan 命令时间复杂度与 keys一样,都是 「O(N)」,但是由于 scan 命令只需要返回少量的 key,所以执行速度会很快。

最后,虽然scan 命令解决 keys

Und shiro-redis erbt zufällig die SessionDAO-Schnittstelle. Die unterste Ebene verwendet den Befehl keys, um alle Session in Redis gespeichert >key. <ul class=" list-paddingleft-2">rrreee<li> Sobald Sie die Ursache des Problems gefunden haben, ist die Lösung relativ einfach. Finden Sie die Lösung auf Github und aktualisieren Sie <code>shiro-redis auf die neueste Version.

In dieser Version verwendet shiro-redis den Befehl scan anstelle von keys, um dieses Problem zu beheben. 🎜rrreee🎜Obwohl das Problem erfolgreich gelöst wurde, war Ah Fen immer noch etwas verwirrt. 🎜🎜Warum führt die keys-Direktive dazu, dass andere Befehle langsamer ausgeführt werden? 🎜🎜Warum ist die Befehlsabfrage Keys so langsam? 🎜🎜Warum gibt es kein Problem mit dem Befehl Scan? 🎜🎜Das Prinzip der Redis-Befehlsausführung🎜🎜Schauen wir uns zunächst die erste Frage an: Warum führt die Anweisung keys dazu, dass die Ausführung anderer Befehle verlangsamt wird? 🎜🎜Aus Sicht des Clients ist die Ausführung eines Befehls in drei Schritte unterteilt: 🎜
    🎜🎜Senden Sie den Befehl🎜
🎜🎜Führen Sie den Befehl aus🎜🎜🎜Ergebnis zurückgeben🎜🎜Aber das ist nur der Prozess, den der Client denkt, aber tatsächlich kann es gleichzeitig viele Clients geben, die Befehle an Redis senden, und Redis kennen wir alle dass es ein Single-Threaded-Modell verwendet. 🎜🎜Um alle Client-Anfragebefehle gleichzeitig zu verarbeiten, verwendet Redis intern eine Warteschlange, um die Ausführung in die Warteschlange zu stellen. 🎜🎜Der Client benötigt also tatsächlich vier Schritte, um einen Befehl auszuführen: 🎜
    🎜🎜Senden Sie den Befehl🎜🎜🎜Befehlswarteschlange🎜🎜🎜Führen Sie den aus Befehl🎜🎜🎜Ergebnisse zurückgeben🎜
🎜Da Redis Befehle in einem einzelnen Thread ausführt, kann es Aufgaben nur nacheinander aus der Warteschlange entfernen und mit der Ausführung beginnen. 🎜🎜Solange 3, ist die Befehlsausführungsgeschwindigkeit in diesem Prozess zu langsam und andere Aufgaben in der Warteschlange müssen warten. Für den externen Client scheint Redis blockiert zu sein und antwortet nie. 🎜🎜Führen Sie daher bei Verwendung des Redis-Prozesses keine Anweisungen aus, die über einen längeren Zeitraum ausgeführt werden müssen. Dies kann dazu führen, dass Redis die Ausführung anderer Anweisungen blockiert und beeinträchtigt. 🎜🎜KEYS-Prinzip🎜🎜 Beantworten wir als nächstes die zweite Frage: Warum ist die Befehlsabfrage Keys so langsam? 🎜🎜Bevor Sie diese Frage beantworten, denken Sie bitte an die zugrunde liegende Speicherstruktur von Redis. 🎜🎜Es spielt keine Rolle, ob Sie das Ihres Freundes nicht kennen. Sie können auf den vorherigen Artikel zurückblicken: „Alibaba-Interviewer: Sind Sie mit HashMap vertraut? Okay, lass uns über das Redis-Wörterbuch sprechen!“ 🎜🎜Der Befehl keys muss alle Redis-Mittelschlüssel zurückgeben, die dem angegebenen Muster pattern entsprechen. Um diesen Zweck zu erreichen, muss Redis ht[0 ] im Wörterbuch Das zugrunde liegende Array der Hash-Tabelle, diesmal ist die Komplexität "O(N)" (N ist die Anzahl der Schlüssel in Redis). 🎜🎜 Auch wenn die Anzahl der Schlüssel in Redis gering ist, ist die Ausführungsgeschwindigkeit dennoch hoch. Wenn die Anzahl der Redis-Schlüssel allmählich zunimmt und Millionen, Dutzende Millionen oder sogar Hunderte Millionen erreicht, wird die Ausführungsgeschwindigkeit sehr langsam. 🎜🎜Das Folgende ist ein von Ah Fen lokal durchgeführtes Experiment. Er hat ein Lua-Skript verwendet, um 10-W-Schlüssel zu Redis hinzuzufügen, und dann keys verwendet, um alle Schlüssel abzufragen. 🎜rrreee
🎜Dieser Fan verwendet Docker, um Redis bereitzustellen, und die Leistung ist möglicherweise etwas schlechter. 🎜
🎜SCAN-Prinzip🎜🎜Schauen wir uns abschließend die dritte Frage an: Warum gibt es kein Problem mit dem Befehl scan? 🎜🎜Das liegt daran, dass der Befehl scan eine schwarze Technologie verwendet – „cursorbasierter Iterator“. 🎜🎜Jedes Mal, wenn der Befehl scan aufgerufen wird, gibt Redis dem Benutzer einen neuen Cursor und eine bestimmte Anzahl von Tasten zurück. Wenn Sie beim nächsten Mal weiterhin die verbleibenden Schlüssel abrufen möchten, müssen Sie diesen Cursor an den Scan-Befehl übergeben, um den vorherigen Iterationsprozess fortzusetzen. 🎜🎜Um es einfach auszudrücken: Der Befehl scan verwendet Paging, um Redis abzufragen. 🎜🎜Das Folgende ist ein Beispiel für den Iterationsprozess des Scan-Befehls: 🎜🎜Der scan-Befehl verwendet Cursor, um eine vollständige Abfrage geschickt in mehrere Male aufzuteilen, um die Abfragekomplexität zu reduzieren. 🎜🎜Obwohl die zeitliche Komplexität des scan-Befehls die gleiche ist wie die von keys, sind beide "O(N)", aber fällig Zum scan Der Code>-Befehl muss nur eine kleine Anzahl von Schlüsseln zurückgeben, sodass die Ausführungsgeschwindigkeit sehr hoch ist. 🎜🎜Abschließend: Obwohl der Befehl scan die Mängel von keys behebt, führt er auch zu einigen anderen Mängeln: 🎜🎜🎜🎜Dasselbe Element kann mehrmals zurückgegeben werden, was erforderlich ist Unsere Anwendung fügt Funktionen zum Umgang mit doppelten Elementen hinzu. 🎜
  • Während des Iterationsprozesses können die zu Redis hinzugefügten oder gelöschten Elemente zurückgegeben werden oder nicht.

  • Die oben genannten Mängel müssen bei unserer Entwicklung berücksichtigt werden.

    Zusätzlich zu scan verfügt Redis über mehrere andere Befehle für die inkrementelle Iteration: scan以外,redis 还有其他几个用于增量迭代命令:

    • sscan:用于迭代当前数据库中的数据库键,用于解决 smembers可能产生阻塞问题

    • hscan命令用于迭代哈希键中的键值对,用于解决 hgetall 可能产生阻塞问题。

    • zscan:命令用于迭代有序集合中的元素(包括元素成员和元素分值),用于产生 zrange

      • sscan: Wird zum Iterieren verwendet Die Datenbankschlüssel in der aktuellen Datenbank werden verwendet, um das mögliche Blockierungsproblem von smembers zu lösen.
      🎜🎜🎜 Der Befehl hscan wird verwendet, um die Schlüssel-Wert-Paare im Hash-Schlüssel zu iterieren , Wird verwendet, um das mögliche Blockierungsproblem von hgetall zu lösen. 🎜🎜🎜🎜zscan: Der Befehl wird verwendet, um Elemente in einer geordneten Menge zu iterieren (einschließlich Elementmitgliedern und Elementbewertungen) und wird verwendet, um zrange zu generieren, was zu Blockierungen führen kann Probleme. 🎜🎜🎜

      Das obige ist der detaillierte Inhalt vonBeispielanalyse für die Verwendung von Redis-Befehlen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

      Stellungnahme:
      Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen