Technologieaustausch

Lokaler Java-Cache (Hochleistungseinstellung)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

In der Java-Welt wird Caching-Technologie häufig zur Verbesserung der Anwendungsleistung eingesetzt und wird hauptsächlich in zwei Kategorien unterteilt: Remote-Caching und lokales Caching. Remote-Caching mit seiner hervorragenden Leistung und Flexibilität wird oft durch beliebte Lösungen wie Redis und Memcached implementiert. Der lokale Cache mit seinen leichten und schnellen Zugriffseigenschaften wird durch Technologien wie HashMap, Guava Cache, Caffeine und Ehcache repräsentiert.

Wir werden uns in zukünftigen Blogbeiträgen mit den Geheimnissen des Remote-Caching befassen, aber heute konzentrieren wir uns auf das lokale Caching. Dieser Artikel führt Sie zunächst durch die lokale Caching-Technologie, um Ihnen einen umfassenden Überblick zu geben. Als Nächstes befassen wir uns mit der Caching-Technologie, die als König der Leistung bekannt ist, und erkunden die Prinzipien und Implementierungsmethoden dahinter. Abschließend zeigen wir anhand einer Reihe praktischer Fälle, wie Sie diese leistungsstarken lokalen Caching-Technologien in der täglichen Arbeit effektiv nutzen können, um Ihre Entwicklungseffizienz und Anwendungsleistung zu verbessern.

1. Lokaler Java-Cache

1.1 HashMap

Mit der zugrunde liegenden Implementierung von Map können wir die zwischenzuspeichernden Objekte direkt im Speicher speichern, was eine direkte und effiziente Methode ist.

Vorteil: Diese Methode ist einfach und direkt, ohne auf externe Bibliotheken angewiesen zu sein und eignet sich sehr gut für Anwendungen mit unkomplizierten Caching-Anforderungen und einfachen Szenarien.

Nachteile: Dieser Methode fehlt jedoch ein automatischer Cache-Eliminierungsmechanismus. Wenn erweiterte Caching-Strategien implementiert werden müssen, sind möglicherweise höhere kundenspezifische Entwicklungskosten erforderlich.

  1. public class LRUCache extends LinkedHashMap {
  2. /**
  3. * 可重入读写锁,保证并发读写安全性
  4. */
  5. private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  6. private Lock readLock = readWriteLock.readLock();
  7. private Lock writeLock = readWriteLock.writeLock();
  8. /**
  9. * 缓存大小限制
  10. */
  11. private int maxSize;
  12. public LRUCache(int maxSize) {
  13. super(maxSize + 1, 1.0f, true);
  14. this.maxSize = maxSize;
  15. }
  16. @Override
  17. public Object get(Object key) {
  18. readLock.lock();
  19. try {
  20. return super.get(key);
  21. } finally {
  22. readLock.unlock();
  23. }
  24. }
  25. @Override
  26. public Object put(Object key, Object value) {
  27. writeLock.lock();
  28. try {
  29. return super.put(key, value);
  30. } finally {
  31. writeLock.unlock();
  32. }
  33. }
  34. @Override
  35. protected boolean removeEldestEntry(Map.Entry eldest) {
  36. return this.size() > maxSize;
  37. }
  38. }

1.2 Guaven-Cache

Guava Cache ist eine von Google entwickelte Caching-Technologie, die auf dem LRU-Ersetzungsalgorithmus (Least Latest Used) basiert. Mit dem Aufkommen von Koffein verschwand Guava Cache jedoch allmählich aus der Sicht der Menschen. Koffein erbt nicht nur die Vorteile von Guava Cache, sondern übertrifft es in vielerlei Hinsicht. Obwohl hier kein spezifischer Beispielcode bereitgestellt wird, können Leser, die sich für Guava Cache interessieren, die offizielle Website besuchen, um weitere Informationen zu erhalten.

Vorteil: Guava Cache unterstützt das Festlegen maximaler Kapazitätsgrenzen und bietet zwei Ablaufstrategien: basierend auf Einfügezeit und Zugriffszeit, und unterstützt auch einige grundlegende statistische Funktionen.

Nachteile: Mit der Veröffentlichung von Spring Boot 2 und Spring 5 wird Guava Cache für beide nicht mehr empfohlen.

1.3 Koffein

Caffeine ist eine Open-Source-Caching-Technologie, die den W-TinyLFU-Algorithmus verwendet, eine Strategie zur Cache-Eliminierung, die die Vorteile von LRU und LFU (geringste verwendete Frequenz) kombiniert. Die Cache-Leistung von Caffeine liegt nahe an der theoretisch optimalen Lösung und kann als aktualisierte Version von Guava Cache betrachtet werden.

  1. public class CaffeineCacheTest {
  2. public static void main(String[] args) throws Exception {
  3. //创建guava cache
  4. Cache<String, String> loadingCache = Caffeine.newBuilder()
  5. //cache的初始容量
  6. .initialCapacity(5)
  7. //cache最大缓存数
  8. .maximumSize(10)
  9. //设置写缓存后n秒钟过期
  10. .expireAfterWrite(17, TimeUnit.SECONDS)
  11. //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
  12. //.expireAfterAccess(17, TimeUnit.SECONDS)
  13. .build();
  14. String key = "key";
  15. // 往缓存写数据
  16. loadingCache.put(key, "v");
  17. // 获取value的值,如果key不存在,获取value后再返回
  18. String value = loadingCache.get(key, CaffeineCacheTest::getValueFromDB);
  19. // 删除key
  20. loadingCache.invalidate(key);
  21. }
  22. private static String getValueFromDB(String key) {
  23. return "v";
  24. }
  25. }

1.4 Ehcache

Ehcache ist ein reines Java-In-Process-Caching-Framework, das für seine schnelle und effiziente Leistung bekannt ist. Es ist weithin anerkannt und dient als Standard-Cache-Anbieter für Hibernate.

Vorteil : Ehcache bietet eine breite Palette von Cache-Räumungsalgorithmen, einschließlich LFU (am wenigsten häufig verwendet), LRU (am wenigsten kürzlich verwendet) und FIFO (First In, First Out). Es unterstützt verschiedene Arten von Cache-Speichern, z. B. In-Heap-Cache, Off-Heap-Cache und Festplatten-Cache, um sich an unterschiedliche Speicheranforderungen anzupassen. Darüber hinaus unterstützt Ehcache auch verschiedene Clusterlösungen und löst so effektiv das Problem des Datenaustauschs.

Nachteile: Obwohl Ehcache in vielen Aspekten überragend ist, bleibt es in Bezug auf die Leistung etwas hinter Caffeine zurück.

  1. public class EncacheTest {
  2. public static void main(String[] args) throws Exception {
  3. // 声明一个cacheBuilder
  4. CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
  5. .withCache("encacheInstance", CacheConfigurationBuilder
  6. //声明一个容量为20的堆内缓存
  7. .newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.heap(20)))
  8. .build(true);
  9. // 获取Cache实例
  10. Cache<String,String> myCache = cacheManager.getCache("encacheInstance", String.class, String.class);
  11. // 写缓存
  12. myCache.put("key","v");
  13. // 读缓存
  14. String value = myCache.get("key");
  15. // 移除换粗
  16. cacheManager.removeCache("myCache");
  17. cacheManager.close();
  18. }
  19. }

 

2. Hochleistungs-Cache-Koffein

2.1 Cache-Typ

2.1.1 Zwischenspeicher

 

Cache<Key, Graph> cache = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .maximumSize(10_000)    .build();
// 查找一个缓存元素, 没有查找到的时候返回nullGraph graph = cache.getIfPresent(key);// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回nullgraph = cache.get(key, k -> createExpensiveGraph(key));// 添加或者更新一个缓存元素cache.put(key, graph);// 移除一个缓存元素cache.invalidate(key);

Die Cache-Schnittstelle bietet die Möglichkeit, zwischengespeicherte Elemente mithilfe expliziter Suchvorgänge zu finden, zu aktualisieren und zu entfernen. Wenn das zwischengespeicherte Element nicht generiert werden kann oder während des Generierungsprozesses eine Ausnahme ausgelöst wird und die Generierung des Elements fehlschlägt, gibt Cache.get möglicherweise null zurück.

2.1.2 Cache laden

LoadingCache<Key, Graph> cache = Caffeine.newBuilder()    .maximumSize(10_000)    .expireAfterWrite(10, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));
// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回nullGraph graph = cache.get(key);// 批量查找缓存,如果缓存不存在则生成缓存元素Map<Key, Graph> graphs = cache.getAll(keys);

 

Ein LoadingCache ist eine Cache-Implementierung eines Caches mit hinzugefügter CacheLoader-Funktion.
Wenn der Cache nicht vorhanden ist, wird das entsprechende Cache-Element über CacheLoader.load generiert.

2.1.3 Asynchroner Cache

AsyncCache<Key, Graph> cache = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .maximumSize(10_000)    .buildAsync();
// 查找一个缓存元素, 没有查找到的时候返回nullCompletableFuture<Graph> graph = cache.getIfPresent(key);// 查找缓存元素,如果不存在,则异步生成graph = cache.get(key, k -> createExpensiveGraph(key));// 添加或者更新一个缓存元素cache.put(key, graph);// 移除一个缓存元素cache.synchronous().invalidate(key);

AsyncCache ist die asynchrone Form von Cache und bietet Executor die Möglichkeit, Cache-Elemente zu generieren und CompletableFuture zurückzugeben. Die Standard-Thread-Pool-Implementierung ist ForkJoinPool.commonPool(), aber Sie können Ihre Thread-Pool-Auswahl auch anpassen, indem Sie die Methode Caffeine.executor(Executor) überschreiben und implementieren.

2.1.4 Asynchroner Ladecache

AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()    .maximumSize(10_000)    .expireAfterWrite(10, TimeUnit.MINUTES)    // 你可以选择: 去异步的封装一段同步操作来生成缓存元素    .buildAsync(key -> createExpensiveGraph(key));    // 你也可以选择: 构建一个异步缓存元素操作并返回一个future    .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
// 查找缓存元素,如果其不存在,将会异步进行生成CompletableFuture<Graph> graph = cache.get(key);// 批量查找缓存元素,如果其不存在,将会异步进行生成CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);

AsyncLoadingCache ist die asynchrone Form von LoadingCache, die die Funktion des asynchronen Ladens zum Generieren von Cache-Elementen bereitstellt.

2.2 Räumungsstrategie

  • Kapazitätsbasiert

// 基于缓存内的元素个数进行驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumSize(10_000)    .build(key -> createExpensiveGraph(key));
// 基于缓存内元素权重进行驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumWeight(10_000)    .weigher((Key key, Graph graph) -> graph.vertices().size())    .build(key -> createExpensiveGraph(key));
  • zeitbasiert

// 基于固定的过期时间驱逐策略LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .expireAfterAccess(5, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));
// 基于不同的过期驱逐策略LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .expireAfter(new Expiry<Key, Graph>() {      public long expireAfterCreate(Key key, Graph graph, long currentTime) {        // Use wall clock time, rather than nanotime, if from an external resource        long seconds = graph.creationDate().plusHours(5)            .minus(System.currentTimeMillis(), MILLIS)            .toEpochSecond();        return TimeUnit.SECONDS.toNanos(seconds);      }      public long expireAfterUpdate(Key key, Graph graph,           long currentTime, long currentDuration) {        return currentDuration;      }      public long expireAfterRead(Key key, Graph graph,          long currentTime, long currentDuration) {        return currentDuration;      }    })    .build(key -> createExpensiveGraph(key));
  • basierend auf Zitat

//key和缓存元素都不再存在其他强引用的时候驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .weakKeys()    .weakValues()    .build(key -> createExpensiveGraph(key));
// 当进行GC的时候进行驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .softValues()    .build(key -> createExpensiveGraph(key));

2.3 Aktualisierungsmechanismus

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumSize(10_000)    .refreshAfterWrite(1, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));

Die Aktualisierungsstrategie kann nur in LoadingCache verwendet werden. Wenn das Cache-Element während der Aktualisierung abgefragt wird, wird weiterhin sein alter Wert zurückgegeben, und der aktualisierte neue Wert wird erst zurückgegeben, wenn die Aktualisierung des Elements abgeschlossen ist.

2.4 Statistiken

Cache<Key, Graph> graphs = Caffeine.newBuilder() .maximumSize(10_000) .recordStats() .build();

Die Datenerfassung kann mit der Methode Caffeine.recordStats() aktiviert werden. Die Methode Cache.stats() gibt ein CacheStats-Objekt zurück, das einige statistische Indikatoren enthält, wie zum Beispiel:

  • hitRate(): Cache-Trefferrate abfragen

  • evictionCount(): Anzahl der Caches, die geräumt werden

  • AverageLoadPenalty(): Die durchschnittliche Zeit, die zum Laden eines neuen Werts benötigt wird

Mit dem von SpringBoot bereitgestellten RESTful Controller können Sie die Cache-Nutzung einfach abfragen.