Compartir tecnología

Caché local de Java (configuración de alto rendimiento)

2024-07-12

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

En el mundo Java, la tecnología de almacenamiento en caché se usa ampliamente para mejorar el rendimiento de las aplicaciones y se divide principalmente en dos categorías: almacenamiento en caché remoto y almacenamiento en caché local. El almacenamiento en caché remoto, con su excelente rendimiento y flexibilidad, a menudo se implementa mediante soluciones populares como Redis y Memcached. El caché local, con sus características livianas y de rápido acceso, está representado por tecnologías como HashMap, Guava Cache, Caffeine y Ehcache.

Profundizaremos en los misterios del almacenamiento en caché remoto en futuras publicaciones del blog, pero hoy centrémonos en el almacenamiento en caché local. Este artículo primero lo guiará a través de la tecnología de almacenamiento en caché local para brindarle una descripción general completa. A continuación, profundizaremos en la tecnología de almacenamiento en caché, conocida como la reina del rendimiento, y exploraremos los principios y métodos de implementación detrás de ella. Finalmente, a través de una serie de casos prácticos, demostraremos cómo utilizar eficazmente estas tecnologías de almacenamiento en caché local de alto rendimiento en el trabajo diario para mejorar la eficiencia del desarrollo y el rendimiento de las aplicaciones.

1. Caché local de Java

1.1 Mapa hash

Utilizando la implementación subyacente de Map, podemos almacenar los objetos que se almacenarán en caché directamente en la memoria, lo cual es un método directo y eficiente.

Ventaja: Este método es simple y directo, no depende de bibliotecas externas y es muy adecuado para aplicaciones con requisitos de almacenamiento en caché sencillos y escenarios simples.

Desventajas: Sin embargo, este método carece de un mecanismo de eliminación automática de caché. Si es necesario implementar estrategias de almacenamiento en caché más avanzadas, es posible que se requieran mayores costos de desarrollo personalizado.

  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 Caché de guayaba

Guava Cache es una tecnología de almacenamiento en caché desarrollada por Google que se basa en el algoritmo de reemplazo LRU (menos utilizado recientemente). Sin embargo, con el aumento de la cafeína, Guava Cache desapareció gradualmente de la vista de la gente. La cafeína no sólo hereda las ventajas de Guava Cache, sino que también la supera en muchos aspectos. Aunque aquí no se proporciona un código de muestra específico, los lectores interesados ​​en Guava Cache pueden visitar su sitio web oficial para obtener más información.

Ventaja: Guava Cache admite el establecimiento de límites de capacidad máxima y proporciona dos estrategias de caducidad: basada en el tiempo de inserción y el tiempo de acceso, y también admite algunas funciones estadísticas básicas.

Desventajas: Con el lanzamiento de Spring Boot 2 y Spring 5, ya no se recomienda Guava Cache para ninguno de ellos.

1.3 Cafeína

Caffeine es una tecnología de almacenamiento en caché de código abierto que utiliza el algoritmo W-TinyLFU, que es una estrategia de eliminación de caché que combina las ventajas de LRU y LFU (menor frecuencia utilizada). El rendimiento de la caché de Caffeine está cerca de la solución óptima teórica y puede considerarse como una versión mejorada de 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 caché

Ehcache es un marco de almacenamiento en caché en proceso de Java puro conocido por su rendimiento rápido y eficiente. Es ampliamente reconocido y sirve como proveedor de caché predeterminado para Hibernate.

Ventaja : Ehcache proporciona una amplia gama de algoritmos de desalojo de caché, incluidos LFU (el menos utilizado), LRU (el menos utilizado recientemente) y FIFO (primero en entrar, primero en salir). Admite diferentes tipos de almacenamiento en caché, como caché en el montón, caché fuera del montón y caché en disco, para adaptarse a las diferentes necesidades de almacenamiento. Además, Ehcache también admite una variedad de soluciones de clúster, resolviendo eficazmente el problema del intercambio de datos.

Desventajas: Aunque Ehcache sobresale en muchos aspectos, está ligeramente por debajo de Caffeine en términos de rendimiento.

  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. Caché de alto rendimiento Cafeína

2.1 tipo de caché

2.1.1 Caché

 

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

La interfaz de caché brinda la capacidad de buscar, actualizar y eliminar elementos almacenados en caché mediante búsquedas explícitas. Cuando el elemento almacenado en caché no se puede generar o se lanza una excepción durante el proceso de generación y la generación del elemento falla, cache.get puede devolver null.

2.1.2 Carga de caché

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

 

Un LoadingCache es una implementación de caché de un Cache con la capacidad CacheLoader agregada.
Si el caché no está presente, el elemento de caché correspondiente se generará a través de CacheLoader.load.

2.1.3 Caché asincrónica

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 es la forma asincrónica de Cache, que brinda a Executor la capacidad de generar elementos de caché y devolver CompletableFuture. La implementación predeterminada del grupo de subprocesos es ForkJoinPool.commonPool(), pero también puede personalizar la selección de su grupo de subprocesos anulando e implementando el método Caffeine.executor(Executor).

2.1.4 Caché de carga asincrónica

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 es la forma asincrónica de LoadingCache, que proporciona la función de carga asincrónica para generar elementos de caché.

2.2 Estrategia de desalojo

  • Basado en capacidad

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

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

//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 Mecanismo de actualización

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

La estrategia de actualización solo se puede utilizar en LoadingCache. A diferencia del desalojo, si se consulta el elemento de caché durante la actualización, se seguirá devolviendo su valor anterior y el nuevo valor actualizado no se devolverá hasta que se complete la actualización del elemento.

2.4 Estadísticas

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

La recopilación de datos se puede activar utilizando el método Caffeine.recordStats(). El método Cache.stats() devolverá un objeto CacheStats, que contendrá algunos indicadores estadísticos, como por ejemplo:

  • hitRate(): tasa de aciertos de la caché de consultas

  • evictionCount(): Número de cachés que se están desalojando

  • AverageLoadPenalty(): el tiempo promedio que tarda en cargarse un nuevo valor.

Con el controlador RESTful proporcionado por SpringBoot, puede consultar fácilmente el uso de la caché.