Berbagi teknologi

Cache lokal Java (pengaturan kinerja tinggi)

2024-07-12

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

Di dunia Java, teknologi caching banyak digunakan untuk meningkatkan kinerja aplikasi dan dibagi menjadi dua kategori: caching jarak jauh dan caching lokal. Caching jarak jauh, dengan kinerja dan fleksibilitasnya yang luar biasa, sering kali diterapkan melalui solusi populer seperti Redis dan Memcached. Cache lokal, dengan karakteristik aksesnya yang ringan dan cepat, diwakili oleh teknologi seperti HashMap, Guava Cache, Caffeine, dan Ehcache.

Kita akan menyelidiki misteri cache jarak jauh di postingan blog mendatang, namun hari ini, mari kita fokus pada cache lokal. Artikel ini pertama-tama akan membawa Anda mempelajari teknologi caching lokal untuk memberi Anda gambaran umum yang komprehensif. Selanjutnya, kita akan mempelajari teknologi caching, yang dikenal sebagai raja kinerja, dan mengeksplorasi prinsip serta metode implementasi di baliknya. Terakhir, melalui serangkaian kasus praktis, kami akan mendemonstrasikan cara memanfaatkan teknologi caching lokal berkinerja tinggi ini secara efektif dalam pekerjaan sehari-hari untuk meningkatkan efisiensi pengembangan dan kinerja aplikasi Anda.

1. Cache lokal Java

1.1 Peta Hash

Dengan menggunakan implementasi Map yang mendasarinya, kita dapat menyimpan objek yang akan di-cache langsung di memori, yang merupakan metode langsung dan efisien.

Keuntungan: Metode ini sederhana dan langsung, tanpa bergantung pada perpustakaan eksternal, dan sangat cocok untuk aplikasi dengan persyaratan caching yang tidak rumit dan skenario yang sederhana.

Kekurangan: Namun, metode ini tidak memiliki mekanisme penghapusan cache otomatis. Jika strategi caching yang lebih canggih perlu diterapkan, mungkin diperlukan biaya pengembangan khusus yang lebih tinggi.

  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 Cache Jambu Biji

Guava Cache adalah teknologi caching yang dikembangkan oleh Google yang didasarkan pada algoritma penggantian LRU (paling baru digunakan). Namun, dengan maraknya Kafein, Jambu Cache secara bertahap menghilang dari pandangan orang. Kafein tidak hanya mewarisi keunggulan Guava Cache, tetapi juga mengunggulinya dalam banyak aspek. Meskipun kode contoh spesifik tidak disediakan di sini, pembaca yang tertarik dengan Guava Cache dapat mengunjungi situs resminya untuk informasi lebih lanjut.

Keuntungan: Guava Cache mendukung pengaturan batas kapasitas maksimum dan menyediakan dua strategi kedaluwarsa: berdasarkan waktu penyisipan dan waktu akses, dan juga mendukung beberapa fungsi statistik dasar.

Kekurangan: Dengan dirilisnya Spring Boot 2 dan Spring 5, Guava Cache tidak lagi direkomendasikan untuk keduanya.

1.3 Kafein

Caffeine merupakan teknologi caching open source yang menggunakan algoritma W-TinyLFU, yaitu strategi penghapusan cache yang menggabungkan keunggulan LRU dan LFU (least frekuensi yang digunakan). Performa cache Caffeine mendekati solusi optimal teoretis dan dapat dianggap sebagai versi terbaru dari Guava Cache.

  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 Penyimpanan data

Ehcache adalah kerangka caching dalam proses Java murni yang dikenal karena kinerjanya yang cepat dan efisien. Ini dikenal luas dan berfungsi sebagai penyedia cache default untuk Hibernate.

Keuntungan : Ehcache menyediakan berbagai algoritma penggusuran cache, termasuk LFU (paling jarang digunakan), LRU (paling jarang digunakan) dan FIFO (masuk pertama, keluar pertama). Ini mendukung berbagai jenis penyimpanan cache, seperti cache in-heap, cache off-heap, dan cache disk, untuk beradaptasi dengan kebutuhan penyimpanan yang berbeda. Selain itu, Ehcache juga mendukung berbagai solusi cluster, yang secara efektif memecahkan masalah berbagi data.

Kekurangan: Meskipun Ehcache unggul dalam banyak aspek, ia sedikit kalah dengan Kafein dalam hal kinerja.

  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. Cache Kafein berkinerja tinggi

2.1 Jenis cache

2.1.1 Tembolok

 

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

Antarmuka Cache menyediakan kemampuan untuk menemukan, memperbarui, dan menghapus elemen cache menggunakan pencarian eksplisit. Ketika elemen yang di-cache tidak dapat dibuat atau pengecualian dilemparkan selama proses pembuatan dan pembuatan elemen gagal, cache.get dapat mengembalikan null .

2.1.2 Memuat Cache

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

 

LoadingCache adalah implementasi cache dari Cache dengan kemampuan CacheLoader yang ditambahkan.
Jika cache tidak ada, elemen cache yang sesuai akan dihasilkan melalui CacheLoader.load.

2.1.3 Cache Asinkron

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 adalah bentuk Cache asinkron, yang memberikan kemampuan bagi Executor untuk menghasilkan elemen cache dan mengembalikan CompletableFuture. Implementasi kumpulan thread default adalah ForkJoinPool.commonPool(), namun Anda juga dapat menyesuaikan pilihan kumpulan thread dengan mengganti dan mengimplementasikan metode Caffeine.executor(Executor).

2.1.4 Pemuatan Cache Asinkron

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 adalah bentuk LoadingCache asinkron, yang menyediakan fungsi pemuatan asinkron untuk menghasilkan elemen cache.

2.2 Strategi penggusuran

  • Berbasis kapasitas

// 基于缓存内的元素个数进行驱逐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));
  • berdasarkan waktu

// 基于固定的过期时间驱逐策略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));
  • berdasarkan kutipan

//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 Mekanisme penyegaran

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

Strategi penyegaran hanya dapat digunakan di LoadingCache. Berbeda dengan eviction, jika elemen cache ditanyakan selama penyegaran, nilai lamanya akan tetap dikembalikan, dan nilai baru yang disegarkan tidak akan dikembalikan hingga penyegaran elemen selesai.

2.4 Statistik

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

Pengumpulan data dapat diaktifkan menggunakan metode Caffeine.recordStats(). Metode Cache.stats() akan mengembalikan objek CacheStats, yang berisi beberapa indikator statistik, seperti:

  • hitRate(): Tingkat hit cache kueri

  • evictionCount(): Jumlah cache yang dikeluarkan

  • averageLoadPenalty(): Waktu rata-rata yang diperlukan untuk memuat nilai baru

Dengan RESTful Controller yang disediakan oleh SpringBoot, Anda dapat dengan mudah menanyakan penggunaan Cache.