Heim > Datenbank > Redis > Hauptteil

Gemeinsame Nutzung von Techniken mit hoher Parallelität mithilfe von Redis und lokalem Cache

WBOY
Freigeben: 2022-11-02 20:19:12
nach vorne
2405 Leute haben es durchsucht

Dieser Artikel vermittelt Ihnen relevantes Wissen über Redis. Er stellt hauptsächlich die Verwendungsfähigkeiten von verteiltem Cache und lokalem Cache vor, einschließlich einer Einführung in Cache-Typen, verschiedene Verwendungsszenarien und deren Verwendung Schauen Sie sich unten einen praktischen Fall an. Ich hoffe, er wird für alle hilfreich sein.

Empfohlenes Lernen: Redis-Video-Tutorial

Wie wir alle wissen, besteht der Hauptzweck des Cachings darin, den Zugriff zu beschleunigen und den Datenbankdruck zu verringern. Der am häufigsten verwendete Cache ist der verteilte Cache, z. B. Redis. In den meisten Parallelitätsszenarien oder in Situationen, in denen der Datenverkehr einiger kleiner und mittlerer Unternehmen nicht so hoch ist, kann die Verwendung von Redis das Problem grundsätzlich lösen. Bei hohem Datenverkehr müssen Sie jedoch möglicherweise einen lokalen Cache verwenden, z. B. LoadingCache von guava und Open-Source-ReloadableCache von Kuaishou.

Nutzungsszenarien von drei Caches

In diesem Teil werden die Verwendungsszenarien und Einschränkungen von Redis vorgestellt, z. B. der LoadingCache von Guava und der Open-Source-ReloadableCache von Kuaishous. Durch die Einführung dieses Teils können Sie wissen, welcher Cache in welchen Geschäftsszenarien verwendet werden sollte . und warum.

Nutzungsszenarien und Einschränkungen von Redis

Wenn wir allgemein darüber sprechen, wann Redis verwendet werden soll, dann wird es natürlich an Orten verwendet, an denen die Anzahl der Benutzerbesuche zu hoch ist, wodurch der Zugriff beschleunigt und der Datenbankdruck verringert wird. Wenn es aufgeschlüsselt wird, kann es in Einzelknotenprobleme und Nicht-Einzelknotenprobleme unterteilt werden.

Wenn eine Seite relativ viele Benutzerbesuche hat, diese jedoch nicht auf dieselbe Ressource zugreifen. Beispielsweise weist die Benutzerdetailseite eine relativ hohe Anzahl von Besuchen auf, aber die Daten jedes Benutzers sind unterschiedlich. In diesem Fall ist es offensichtlich, dass nur der verteilte Cache verwendet werden kann, der Schlüssel ist der eindeutige des Benutzers Schlüssel, und der Wert sind die Benutzerinformationen.

Cache-Ausfall durch Redis verursacht.

Zu beachten ist jedoch, dass die Ablaufzeit festgelegt werden muss und nicht gleichzeitig ablaufen kann. Wenn ein Benutzer beispielsweise über eine Aktivitätsseite verfügt und die Aktivitätsseite die preisgekrönten Daten während der Aktivität des Benutzers sehen kann, kann eine unvorsichtige Person den Ablaufzeitpunkt der Benutzerdaten auf das Ende der Aktivität festlegen, was dazu führt ein einzelnes (heißes) Problem

Das Einzelknotenproblem bezieht sich auf das Parallelitätsproblem eines einzelnen Redis-Knotens, da derselbe Schlüssel auf denselben Knoten des Redis-Clusters fällt, wenn also der Zugriff auf diesen Schlüssel zu hoch ist , dann gibt es auf diesem Redis-Knoten Parallelität. Es besteht eine versteckte Gefahr, dieser Schlüssel wird als Hotkey bezeichnet.

Wenn beispielsweise alle Benutzer auf dieselbe Ressource zugreifen, die Homepage der Xiao Ai App allen Benutzern denselben Inhalt anzeigt (Anfangsphase) und der Server denselben großen JSON an h5 zurückgibt, muss offensichtlich ein Cache verwendet werden. Zunächst prüfen wir, ob die Verwendung von Redis möglich ist. Da Redis ein Einzelpunktproblem aufweist, erreichen alle Benutzeranforderungen denselben Redis-Knoten, und es muss bewertet werden, ob der Knoten diesem standhalten kann ein großer Fluss. Unsere Regel lautet: Wenn die QPS eines einzelnen Knotens tausend Ebenen erreicht, muss ein Einzelpunktproblem gelöst werden (auch wenn Redis behauptet, dass es QPS von hunderttausend Ebenen standhalten kann). Die häufigste Methode ist die Verwendung des lokalen Caches . Offensichtlich beträgt der Verkehr auf der Homepage der Xiaoai-App weniger als 100, sodass die Verwendung von Redis kein Problem darstellt.

Verwendungsszenarien und Einschränkungen von LoadingCache

Für das oben erwähnte Hotkey-Problem besteht unser direktster Ansatz darin, einen lokalen Cache zu verwenden, z. B. den LoadingCache von Guave, mit dem Sie am besten vertraut sind. Die Verwendung des lokalen Caches erfordert jedoch die Fähigkeit, dies zu akzeptieren Eine bestimmte Menge schmutziger Daten, denn wenn Sie die Homepage aktualisieren, wird der Cache nur gemäß einer bestimmten Ablaufrichtlinie neu geladen. In unserem Szenario ist dies jedoch völlig in Ordnung, da die Homepage nicht aktualisiert wird Wird im Hintergrund gepusht, wird es nicht erneut aktualisiert. Selbst wenn es sich ändert, gibt es kein Problem. Sie können den Schreibablauf auf eine halbe Stunde einstellen und den Cache nach einer halben Stunde neu laden. Wir können schmutzige Daten in einem so kurzen Zeitraum akzeptieren.

Cache-Zusammenbruch durch LoadingCache

Obwohl der lokale Cache stark mit der Maschine zusammenhängt, obwohl die Codeebene so geschrieben ist, dass sie in einer halben Stunde abläuft, aufgrund der unterschiedlichen Startzeit jeder Maschine, ist die Ladezeit der Der Cache ist ebenfalls unterschiedlich, sodass nach Ablauf des Caches nicht alle Anforderungen auf dem Computer gleichzeitig die Datenbank anfordern. Allerdings kommt es auch bei einer einzelnen Maschine zu einer Cache-Penetration. Wenn es 10 Maschinen mit jeweils 1.000 qps gibt, können diese 1.000 Anfragen gleichzeitig in der Datenbank eintreffen, solange ein Cache abläuft. Diese Art von Problem ist eigentlich einfacher zu lösen, kann aber leicht ignoriert werden. Das heißt, wenn Sie LoadingCache einrichten, verwenden Sie die Load-Miss-Methode von LoadingCache, anstatt direkt zu beurteilen, ob „cache.getIfPresent()== null“ ist db; Ersteres fügt eine virtuelle Maschine hinzu. Die Layer-Sperre stellt sicher, dass nur eine Anfrage an die Datenbank geht, wodurch dieses Problem perfekt gelöst wird.

Wenn jedoch hohe Echtzeitanforderungen bestehen, wie z. B. häufige Aktivitäten über einen bestimmten Zeitraum, möchte ich sicherstellen, dass die Aktivitätsseite nahezu in Echtzeit aktualisiert werden kann, d. h. nachdem der Betreiber die Aktivitätsinformationen konfiguriert hat Im Hintergrund muss es auf der C-Seite in Echtzeit aktualisiert werden. Um die Aktivitätsinformationen dieser Konfiguration in Echtzeit anzuzeigen, reicht die Verwendung von LoadingCache derzeit definitiv nicht aus.

Nutzungsszenarien und Einschränkungen von ReloadableCache

Für die oben genannten Echtzeitprobleme, die von LoadingCache nicht gelöst werden können, können Sie die Verwendung von ReloadableCache in Betracht ziehen, einem von Kuaishou bereitgestellten lokalen Caching-Framework. Das größte Merkmal ist, dass es mehrere Maschinen unterstützt Angenommen, wir ändern die Homepage-Informationen und treffen dann die Anforderung auf Maschine A. Zu diesem Zeitpunkt wird der ReloadableCache neu geladen und sendet dann eine Benachrichtigung an andere Maschinen Der ZK-Knoten aktualisiert den Cache nach Erhalt der Benachrichtigung erneut. Die allgemeine Anforderung für die Verwendung dieses Caches besteht darin, die gesamte Datenmenge in den lokalen Cache zu laden. Wenn die Datenmenge also zu groß ist, wird der GC definitiv unter Druck gesetzt und kann in diesem Fall nicht verwendet werden. Da die Homepage von Xiao Ai einen Status hat und es im Allgemeinen nur zwei Online-Status gibt, können Sie ReloadableCache verwenden, um nur die Homepages mit dem Online-Status zu laden.

Zusammenfassung

Hier wurden grundsätzlich drei Arten von Caches eingeführt:

  • Für den Zugriff auf Nicht-Hotspot-Daten, z. B. benutzerdimensionale Daten, verwenden Sie einfach Redis direkt.
  • Für den Zugriff auf Hotspot-Daten Der Datenverkehr ist nicht sehr hoch, verwenden Sie Redis einfach ohne nachzudenken.
  • Wenn schmutzige Daten innerhalb eines bestimmten Zeitraums zulässig sind, verwenden Sie LoadingCache.
  • Wenn die Konsistenzanforderungen hoch sind und die Menge an Die Datenmenge ist nicht groß. Verwenden Sie in diesem Fall einfach ReloadableCache.

Tipps

Unabhängig davon, welche Art von lokalem Cache über eine Sperre auf virtueller Maschinenebene verfügt, können Unfälle immer auf unerwartete Weise passieren Sie können einen zweistufigen Cache verwenden, nämlich lokalen Cache + Redis + DB.

Eine kurze Einführung in die Verwendung von Cache

Ich werde hier nicht mehr über die Verwendung von Redis sagen. Ich glaube, dass viele Leute mit der Verwendung von API besser vertraut sind als ich

Die Verwendung von LoadingCache

Dies ist ein Allheilmittel online, das von guava bereitgestellt wird. Hier sind jedoch zwei Dinge zu beachten.

  • Wenn Sie Load-Miss verwenden möchten, verwenden Sie entweder V get(K key, Callable<? extends V> loader);要么使用build的时候使用的是build(CacheLoader<? super K1, V1> loader)Sie können get() zu diesem Zeitpunkt direkt verwenden. Darüber hinaus wird empfohlen, Load-Miss zu verwenden, anstatt die Datenbank zu überprüfen, wenn getIfPresent==null ist, was zu einem Cache-Ausfall führen kann.
  • Verwenden Sie Load-Miss, da es Thread-sicher ist. Wenn der Cache ausfällt, werden mehrere Threads aufgerufen Beim Abrufen fragt nur ein Thread die Datenbank ab, und andere Threads müssen warten, was bedeutet, dass es threadsicher ist.
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(1000L)
                .expireAfterAccess(Duration.ofHours(1L)) // 多久不访问就过期
                .expireAfterWrite(Duration.ofHours(1L))  // 多久这个key没修改就过期
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        // 数据装载方式,一般就是loadDB
                        return key + " world";
                    }
                });
String value = cache.get("hello"); // 返回hello world
Nach dem Login kopieren

ReloadableCache-Nutzung

Um Abhängigkeiten von Drittanbietern zu importieren

<dependency>
  <groupId>com.github.phantomthief</groupId>
  <artifactId>zknotify-cache</artifactId>
  <version>0.1.22</version>
</dependency>
Nach dem Login kopieren

müssen Sie die Dokumentation lesen, sonst funktioniert es nicht. Wenn Sie interessiert sind, können Sie selbst eine schreiben.

public interface ReloadableCache<T> extends Supplier<T> {

    /**
     * 获取缓存数据
     */
    @Override
    T get();

    /**
     * 通知全局缓存更新
     * 注意:如果本地缓存没有初始化,本方法并不会初始化本地缓存并重新加载
     *
     * 如果需要初始化本地缓存,请先调用 {@link ReloadableCache#get()}
     */
    void reload();

    /**
     * 更新本地缓存的本地副本
     * 注意:如果本地缓存没有初始化,本方法并不会初始化并刷新本地的缓存
     *
     * 如果需要初始化本地缓存,请先调用 {@link ReloadableCache#get()}
     */
    void reloadLocal();
}
Nach dem Login kopieren

Das alltägliche Cache-Ausfall-/Penetrations-/Lawinenproblem

Diese drei sind wirklich ewige Probleme, und Sie müssen sie wirklich berücksichtigen, wenn der Datenverkehr groß ist.

Cache-Ausfall

Einfach ausgedrückt: Der Cache fällt aus, was dazu führt, dass eine große Anzahl von Anfragen gleichzeitig in der Datenbank eintrifft. Für das Cache-Aufschlüsselungsproblem wurden oben viele Lösungen angegeben.

  • Verwenden Sie beispielsweise einen lokalen Cache.
  • Der lokale Cache verwendet die Load-Miss-Methode.
  • Verwenden Sie einen Drittanbieterdienst, um den Cache zu laden.

Sowohl 1.2 als auch 1.2 haben es gesagt, schauen Sie sich hauptsächlich 3 an. Wenn das Unternehmen bereit ist, Redis zu verwenden, aber beispielsweise keinen lokalen Cache verwenden kann, ist die Datenmenge zu groß und die Echtzeitanforderungen sind relativ hoch. Wenn dann der Cache ausfällt, müssen Sie einen Weg finden, um sicherzustellen, dass nur eine kleine Anzahl von Anforderungen die Datenbank erreicht. Es liegt nahe, über die Verwendung verteilter Sperren nachzudenken, was theoretisch machbar ist, aber tatsächlich lauern versteckte Gefahren. Wir glauben, dass viele Leute Redis + Lua verwenden, um unsere verteilte Sperre zu implementieren und ein Rotationstraining durchzuführen. Wenn das Anforderungsvolumen und die Datenmenge groß sind, wird Redis zu einer versteckten Gefahr und nimmt tatsächlich zu viel Platz ein Der Geschäftsthread erhöht nur die Komplexität durch die Einführung verteilter Sperren. Unser Prinzip besteht darin, ihn nicht zu verwenden, wenn er verwendet werden kann.

Können wir also einen RPC-Dienst entwerfen, der verteilten Sperren ähnelt, aber zuverlässiger ist? Beim Aufrufen der Get-Methode stellt dieser RPC-Dienst sicher, dass derselbe Schlüssel auf denselben Knoten trifft, synchronisiert zum Sperren verwendet wird und dann das Laden der Daten abschließt. Kuaishou stellt ein Framework namens CacheSetter bereit. Im Folgenden finden Sie eine vereinfachte Version, die sich leicht umsetzen lässt, indem Sie sie selbst schreiben.

import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;

/**
 * @Description 分布式加载缓存的rpc服务,如果部署了多台机器那么调用端最好使用id做一致性hash保证相同id的请求打到同一台机器。
 **/
public abstract class AbstractCacheSetterService implements CacheSetterService {

    private final ConcurrentMap<String, CountDownLatch> loadCache = new ConcurrentHashMap<>();

    private final Object lock = new Object();

    @Override
    public void load(Collection<String> needLoadIds) {
        if (CollectionUtils.isEmpty(needLoadIds)) {
            return;
        }
        CountDownLatch latch;
        Collection<CountDownLatch> loadingLatchList;
        synchronized (lock) {
            loadingLatchList = excludeLoadingIds(needLoadIds);

            needLoadIds = Collections.unmodifiableCollection(needLoadIds);

            latch = saveLatch(needLoadIds);
        }
        System.out.println("needLoadIds:" + needLoadIds);
        try {
            if (CollectionUtils.isNotEmpty(needLoadIds)) {
                loadCache(needLoadIds);
            }
        } finally {
            release(needLoadIds, latch);
            block(loadingLatchList);
        }

    }

    /**
     * 加锁
     * @param loadingLatchList 需要加锁的id对应的CountDownLatch
     */
    protected void block(Collection<CountDownLatch> loadingLatchList) {
        if (CollectionUtils.isEmpty(loadingLatchList)) {
            return;
        }
        System.out.println("block:" + loadingLatchList);
        loadingLatchList.forEach(l -> {
            try {
                l.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 释放锁
     * @param needLoadIds 需要释放锁的id集合
     * @param latch 通过该CountDownLatch来释放锁
     */
    private void release(Collection<String> needLoadIds, CountDownLatch latch) {
        if (CollectionUtils.isEmpty(needLoadIds)) {
            return;
        }
        synchronized (lock) {
            needLoadIds.forEach(id -> loadCache.remove(id));
        }
        if (latch != null) {
            latch.countDown();
        }
    }

    /**
     * 加载缓存,比如根据id从db查询数据,然后设置到redis中
     * @param needLoadIds 加载缓存的id集合
     */
    protected abstract void loadCache(Collection<String> needLoadIds);

    /**
     * 对需要加载缓存的id绑定CountDownLatch,后续相同的id请求来了从map中找到CountDownLatch,并且await,直到该线程加载完了缓存
     * @param needLoadIds 能够正在去加载缓存的id集合
     * @return 公用的CountDownLatch
     */
    protected CountDownLatch saveLatch(Collection<String> needLoadIds) {
        if (CollectionUtils.isEmpty(needLoadIds)) {
            return null;
        }
        CountDownLatch latch = new CountDownLatch(1);
        needLoadIds.forEach(loadId -> loadCache.put(loadId, latch));
        System.out.println("loadCache:" + loadCache);
        return latch;
    }

    /**
     * 哪些id正在加载数据,此时持有相同id的线程需要等待
     * @param ids 需要加载缓存的id集合
     * @return 正在加载的id所对应的CountDownLatch集合
     */
    private Collection<CountDownLatch> excludeLoadingIds(Collection<String> ids) {
        List<CountDownLatch> loadingLatchList = Lists.newArrayList();
        Iterator<String> iterator = ids.iterator();
        while (iterator.hasNext()) {
            String id = iterator.next();
            CountDownLatch latch = loadCache.get(id);
            if (latch != null) {
                loadingLatchList.add(latch);
                iterator.remove();
            }
        }
        System.out.println("loadingLatchList:" + loadingLatchList);
        return loadingLatchList;
    }
}
Nach dem Login kopieren

Geschäftsimplementierung

import java.util.Collection;
public class BizCacheSetterRpcService extends AbstractCacheSetterService {
    @Override
    protected void loadCache(Collection<String> needLoadIds) {
        // 读取db进行处理
   	// 设置缓存
    }
}
Nach dem Login kopieren

Cache-Penetration

Einfach ausgedrückt sind die angeforderten Daten nicht in der Datenbank vorhanden, was dazu führt, dass ungültige Anforderungen in die Datenbank eindringen.

Die Lösung ist auch sehr einfach. Die Methode zum Abrufen von Daten aus der Datenbank (getByKey (K-Schlüssel)) muss einen Standardwert angeben.

Zum Beispiel habe ich einen Preispool mit einer Obergrenze von 1 W. Wenn der Benutzer die Aufgabe erledigt, sende ich ihm Geld, erfasse es mit Redis und protokolliere es in der Tabelle. Der Benutzer kann den verbleibenden Betrag sehen Der Preispool wird in Echtzeit auf der Aufgabenseite angezeigt. Es ist offensichtlich, dass die Höhe des Preispools in redis und db unverändert bleibt, was eine Überprüfung erforderlich macht Wenn in diesem Fall die Daten nicht aus der Datenbank gefunden werden, sollte der Wert 0 im Cache zwischengespeichert werden.

Cache-Lawine

Das bedeutet, dass eine große Anzahl zentraler Cache-Fehler die Datenbank treffen. Natürlich muss es sich bei allen um Business-Caches handeln. Letztendlich liegt ein Problem beim Schreiben des Codes vor. Sie können die Ablaufzeit der Cache-Ungültigmachung unterbrechen und verhindern, dass sie zentral fehlschlägt.

Empfohlenes Lernen: Redis-Video-Tutorial

Das obige ist der detaillierte Inhalt vonGemeinsame Nutzung von Techniken mit hoher Parallelität mithilfe von Redis und lokalem Cache. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:jb51.net
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!