[Java Advanced] Redis-Cache-Optimierung: die beste Wahl zur Verbesserung der Anwendungsleistung

Fügen Sie hier eine Bildbeschreibung ein

In der modernen Softwareentwicklung ist Leistung seit jeher eines der von Entwicklern verfolgten Ziele. In Szenarien, in denen häufig auf die Datenbank zugegriffen wird und das Lesen von Daten langsam ist, ist die Verwendung des Caches eine der effektivsten Möglichkeiten zur Leistungsverbesserung. Als leistungsstarke In-Memory-Datenbank wird Redis häufig als Caching-Tool verwendet. Dieser Artikel bietet eine detaillierte Erklärung der Redis-Cache-Optimierung und enthüllt Ihnen die Geheimnisse, wie Sie die Anwendungsleistung durch Cache-Optimierung verbessern können.

Der Charme des Cachings

Der Cache kann als rücksichtsvoller Assistent viele Vorgänge der Anwendung beschleunigen. Es speichert einige Berechnungsergebnisse oder Datenbankabfrageergebnisse an einem schnell zugänglichen Ort, sodass nachfolgende gleiche Anforderungen Daten schneller abrufen und den Druck auf die Datenbank verringern können. In diesem Prozess ist Redis, die „Zauberkiste“, zu einem Stern im Herzen vieler Entwickler geworden.

Redis-Cache-Grundlagen

Bevor wir den Redis-Cache verwenden, müssen wir die Grundkonzepte und Grundoperationen von Redis verstehen. Redis ist ein speicherbasiertes Schlüsselwertspeichersystem, das eine Vielzahl von Datenstrukturen wie Zeichenfolgen, Hashes, Listen, Mengen, geordnete Mengen usw. bereitstellt. Diese Datenstrukturen bieten uns flexible Caching-Optionen.

String-Cache

Schauen wir uns zunächst ein einfaches Beispiel für einen String-Cache an:

import redis.clients.jedis.Jedis;

public class RedisStringCacheExample {
    
    

    public static void main(String[] args) {
    
    
        // 连接到本地的 Redis 服务器
        Jedis jedis = new Jedis("localhost", 6379);
        System.out.println("连接成功");

        // 缓存数据
        jedis.set("username:1001", "Alice");
        jedis.set("username:1002", "Bob");

        // 从缓存中获取数据
        String user1 = jedis.get("username:1001");
        String user2 = jedis.get("username:1002");

        // 打印结果
        System.out.println("用户1001:" + user1);
        System.out.println("用户1002:" + user2);

        // 关闭连接
        jedis.close();
    }
}

In diesem Beispiel verwenden wir die String-Datenstruktur von Redis. setDie Benutzernamen der beiden Benutzer werden über die Methode zwischengespeichert und anschließend getwerden die Daten über die Methode aus dem Cache abgerufen. Dies ist ein einfaches und intuitives Caching-Beispiel.

Hash-Cache

Wenn wir komplexere Daten wie Benutzerdetails zwischenspeichern müssen, können wir die Hash-Datenstruktur von Redis verwenden:

import redis.clients.jedis.Jedis;
import java.util.Map;

public class RedisHashCacheExample {
    
    

    public static void main(String[] args) {
    
    
        // 连接到本地的 Redis 服务器
        Jedis jedis = new Jedis("localhost", 6379);
        System.out.println("连接成功");

        // 缓存用户详细信息
        String userId = "1001";
        jedis.hset("user:" + userId, "name", "Alice");
        jedis.hset("user:" + userId, "age", "25");
        jedis.hset("user:" + userId, "city", "New York");

        // 从缓存中获取用户详细信息
        Map<String, String> userInfo = jedis.hgetAll("user:" + userId);

        // 打印结果
        System.out.println("用户详细信息:" + userInfo);

        // 关闭连接
        jedis.close();
    }
}

In diesem Beispiel verwenden wir die Redis-Hash-Datenstruktur (Hash). Über die Methode hsetwerden mehrere Felder der Benutzerdetails festgelegt und anschließend hgetAllwird über die Methode die gesamte Hash-Tabelle abgerufen. Der Hash-Cache eignet sich für Szenarien, in denen strukturierte Daten gespeichert werden müssen.

Listencache

Wenn wir einige Listendaten zwischenspeichern müssen, z. B. den aktuellen Browserverlauf des Benutzers, können wir die Listendatenstruktur von Redis verwenden:

import redis.clients.jedis.Jedis;
import java.util.List;

public class RedisListCacheExample {
    
    

    public static void main(String[] args) {
    
    
        // 连接到本地的 Redis 服务器
        Jedis jedis = new Jedis("localhost", 6379);
        System.out.println("连接成功");

        // 缓存用户最近浏览记录
        String userId = "1001";
        jedis.lpush("history:" + userId, "product1", "product2", "product3");

        // 从缓存中获取用户最近浏览记录
        List<String> history = jedis.lrange("history:" + userId, 0, -1);

        // 打印结果
        System.out.println("用户最近浏览记录:" + history);

        // 关闭连接
        jedis.close();
    }
}

In diesem Beispiel verwenden wir die Redis-Listendatenstruktur. Fügen Sie über die Methode lpushmehrere Produkte zum Browserverlauf des Benutzers hinzu und lrangerufen Sie dann über die Methode die gesamte Liste ab. Das Listen-Caching eignet sich für Szenarien, in denen mehrere Elemente der Reihe nach gespeichert werden müssen.

Cache-Optimierungsstrategie

Lösungen zur Cache-Aufschlüsselung

Unter Cache-Aufschlüsselung versteht man eine große Anzahl gleichzeitiger Zugriffe auf Daten, die nicht im Cache, aber in der Datenbank vorhanden sind. Dies führt dazu, dass eine große Anzahl von Anforderungen in den Cache eindringt, um direkt auf die Datenbank zuzugreifen, was die Belastung der Datenbank erhöht. Um dieses Problem zu lösen, können wir eine Mutex-Sperre verwenden oder Nullwerte zwischenspeichern.

Mutex-Sperre
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;

public class CacheBreakdownSolution {
    
    

    public static void main(String[] args) {
    
    
        Jedis jedis = null;
        try {
    
    
            // 获取 Jedis 实例
            jedis = new Jedis("localhost", 6379);

            String key = "product:123";
            String value = jedis.get(key);

            if (value == null) {
    
    
                // 设置互斥锁
                String lockKey = "lock:" + key;
                String lock

Value = "1";
                String result = jedis.set(lockKey, lockValue, "NX", "EX", 10);

                if ("OK".equals(result)) {
    
    
                    // 查询数据库并设置缓存
                    value = "queryFromDatabase";
                    jedis.setex(key, 3600, value);
                    
                    // 释放锁
                    jedis.del(lockKey);
                } else {
    
    
                    // 其他线程持有锁,等待片刻后重试
                    Thread.sleep(100);
                    main(args); // 重新执行
                }
            }

            // 打印结果
            System.out.println("获取到的值: " + value);

        } catch (JedisConnectionException | InterruptedException e) {
    
    
            // 处理连接异常
            System.err.println("连接异常:" + e.getMessage());
        } finally {
    
    
            if (jedis != null) {
    
    
                jedis.close();
            }
        }
    }
}

In diesem Beispiel verwenden wir die Optionen (set when not present) und (expiration time) SETdes Redis-Befehls , um eine Mutex-Sperre zu implementieren. Wenn ein Thread die Sperre erhält, fragt er die Datenbank ab, legt den Cache fest und gibt dann die Sperre frei. Andere Threads müssen warten, bis die Sperre aufgehoben wird, um zu vermeiden, dass mehrere Threads gleichzeitig die Datenbank abfragen.NXEX

Cache-Nullwert
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;

public class CacheBreakdownSolution {
    
    

    public static void main(String[] args) {
    
    
        Jedis jedis = null;
        try {
    
    
            // 获取 Jedis 实例
            jedis = new Jedis("localhost", 6379);

            String key = "product:123";
            String value = jedis.get(key);

            if (value == null) {
    
    
                // 查询数据库
                value = "queryFromDatabase";

                // 如果数据库中没有值,则设置缓存空值,防止缓存穿透
                if (value != null) {
    
    
                    jedis.setex(key, 3600, value);
                } else {
    
    
                    // 设置缓存空值,并设置较短的过期时间
                    jedis.setex(key, 60, "");
                }
            }

            // 打印结果
            System.out.println("获取到的值: " + value);

        } catch (JedisConnectionException e) {
    
    
            // 处理连接异常
            System.err.println("连接异常:" + e.getMessage());
        } finally {
    
    
            if (jedis != null) {
    
    
                jedis.close();
            }
        }
    }
}

Wenn wir in diesem Beispiel die Datenbank abfragen und feststellen, dass in der Datenbank kein Wert vorhanden ist, setexlegen wir über die Methode einen Cache-Nullwert mit einer kürzeren Ablaufzeit fest. Selbst wenn die nächste Anforderung weiterhin die Datenbank abfragt, erhalten auf diese Weise innerhalb dieser kurzen Zeitspanne andere Anforderungen direkt den Cache-Nullwert aus dem Cache, wodurch Probleme mit der Cache-Penetration vermieden werden.

Cache-Lawinenlösung

Cache-Lawine bedeutet, dass zu einem bestimmten Zeitpunkt eine große Datenmenge im Cache gleichzeitig abläuft, was dazu führt, dass die Datenbank direkt von einer großen Anzahl von Anforderungen getroffen wird, was zu einer übermäßigen Belastung der Datenbank führt. Um dieses Problem zu lösen, können wir verschiedene Methoden verwenden, z. B. die Ablaufzeit angemessen festlegen, unterschiedliche Ablaufzeiten verwenden, den Ablauf des Schiebefensters verwenden usw.

Stellen Sie die Ablaufzeit entsprechend ein
import redis.clients.jedis.Jedis;

public class CacheAvalancheSolution {
    
    

    public static void main(String[] args) {
    
    
        Jedis jedis = null;
        try {
    
    
            // 获取 Jedis 实例
            jedis = new Jedis("localhost", 6379);

            String key = "product:123";
            String value = jedis.get(key);

            if (value == null) {
    
    
                // 查询数据库
                value = "queryFromDatabase";

                // 设置合理的过期时间,避免缓存雪崩
                jedis.setex(key, 3600 + (int) (Math.random() * 600), value);
            }

            // 打印结果
            System.out.println("获取到的值: " + value);

        } finally {
    
    
            if (jedis != null) {
    
    
                jedis.close();
            }
        }
    }
}

In diesem Beispiel generieren wir Math.random()eine Zufallszahl und legen die Ablaufzeit zwischen 1 Stunde und 1 Stunde und 10 Minuten fest. Dadurch wird verhindert, dass eine große Datenmenge gleichzeitig abläuft, wodurch Anfragen an die Datenbank verteilt werden und Cache-Lawinen vermieden werden.

Verwenden Sie unterschiedliche Ablaufzeiten
import redis.clients.jedis.Jedis;

public class CacheAvalancheSolution {
    
    

    public static void main(String[] args) {
    
    
        Jedis jedis = null;
        try {
    
    
            // 获取 Jedis 实例
            jedis = new Jedis("localhost", 6379);

            String key = "product:123";
            String value = jedis.get(key);

            if (value == null) {
    
    
                // 查询数据库
                value = "queryFromDatabase";

                // 使用不同的过期时间,避免缓存雪崩
                int randomExpiry = (int) (Math.random() * 600); // 0到600秒之间的随机数
                jedis.setex(key, 3600 + randomExpiry, value);
            }

            // 打印结果
            System.out.println("获取到的值: " + value);

        } finally {
    
    
            if (jedis != null) {
    
    
                jedis.close();
            }
        }
    }
}

In diesem Beispiel legen wir die Ablaufzeit zwischen 1 Stunde und 1 Stunde und 10 Minuten fest, indem wir eine Zufallszahl zwischen 0 und 600 Sekunden generieren. Dadurch können unterschiedliche zwischengespeicherte Daten unterschiedliche Ablaufzeiten haben, wodurch die Wahrscheinlichkeit eines gleichzeitigen Cache-Ausfalls verringert und so eine Cache-Lawine vermieden wird.

Verwenden Sie den Ablauf des gleitenden Fensters
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;

public class CacheAvalancheSolution {
    
    

    public static void main(String[] args) {
    
    
        Jedis jedis = null;
        try {
    
    
            // 获取 Jedis 实例
            jedis = new Jedis("localhost", 6379);

            String key = "product:123";
            String value = jedis.get(key);

            if (value == null) {
    
    
                // 查询数据库
                value = "queryFromDatabase";

                // 采用滑动窗口过期,避免缓存雪崩
                int window = 600; // 窗口大小为600秒
                int randomExpiry = (int) (Math.random() * window); // 0到600秒之间的随机数
                int expireTime = window - randomExpiry; // 设置过期时间

                jedis.setex(key, expireTime, value);
            }

            // 打印结果
            System.out.println("获取到的值: " + value);

        } catch (JedisConnectionException e) {
    
    
            // 处理连接异常
            System.err.println("连接异常:" + e.getMessage());
        } finally {
    
    
            if (jedis != null) {
    
    
                jedis.close();
            }
        }
    }
}

In diesem Beispiel definieren wir ein Schiebefenster mit einer Fenstergröße von 600 Sekunden und berechnen die eingestellte Ablaufzeit, indem wir eine Zufallszahl zwischen 0 und 600 Sekunden generieren. Dadurch kann die Ablaufzeit der zwischengespeicherten Daten innerhalb eines Fensters verkürzt, eine gleichzeitige Ungültigmachung vermieden und die Wahrscheinlichkeit einer Cache-Lawine effektiv verringert werden.

Abschluss

Ich glaube, dass Sie durch die Einleitung dieses Artikels ein tieferes Verständnis der Redis-Cache-Optimierung erhalten. Caching ist ein leistungsstarkes Tool zur Verbesserung der Anwendungsleistung, muss jedoch auch mit Vorsicht eingesetzt und auf der Grundlage tatsächlicher Geschäftsszenarien angemessen optimiert werden. Durch die Lösung häufiger Probleme wie Cache-Ausfälle und Cache-Lawine können wir die Leistung des Redis-Cache besser nutzen, die Reaktionsgeschwindigkeit der Anwendung verbessern und die Benutzererfahrung verbessern. In tatsächlichen Anwendungen werden geeignete Caching-Strategien basierend auf Geschäftsszenarien und -anforderungen ausgewählt und Caching wird in die Systemarchitektur integriert, um die effiziente Ausführung von Anwendungen zu unterstützen. Ich hoffe, dieser Artikel kann Ihnen dabei helfen, Probleme bei der Cache-Optimierung in der tatsächlichen Entwicklung besser zu bewältigen und Ihre Anwendung auf ein höheres Leistungsniveau zu bringen.

Informationen zum Autor

Autor: Fanyi
CSDN: https://techfanyi.blog.csdn.net
Nuggets: https://juejin.cn/user/4154386571867191

Guess you like

Origin blog.csdn.net/qq_21484461/article/details/135300036