Mi información de contacto
Correo[email protected]
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.
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.
- public class LRUCache extends LinkedHashMap {
-
- /**
- * 可重入读写锁,保证并发读写安全性
- */
- private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
- private Lock readLock = readWriteLock.readLock();
- private Lock writeLock = readWriteLock.writeLock();
-
- /**
- * 缓存大小限制
- */
- private int maxSize;
-
- public LRUCache(int maxSize) {
- super(maxSize + 1, 1.0f, true);
- this.maxSize = maxSize;
- }
-
- @Override
- public Object get(Object key) {
- readLock.lock();
- try {
- return super.get(key);
- } finally {
- readLock.unlock();
- }
- }
-
- @Override
- public Object put(Object key, Object value) {
- writeLock.lock();
- try {
- return super.put(key, value);
- } finally {
- writeLock.unlock();
- }
- }
-
- @Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
- return this.size() > maxSize;
- }
- }
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.
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.
- public class CaffeineCacheTest {
-
- public static void main(String[] args) throws Exception {
- //创建guava cache
- Cache<String, String> loadingCache = Caffeine.newBuilder()
- //cache的初始容量
- .initialCapacity(5)
- //cache最大缓存数
- .maximumSize(10)
- //设置写缓存后n秒钟过期
- .expireAfterWrite(17, TimeUnit.SECONDS)
- //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
- //.expireAfterAccess(17, TimeUnit.SECONDS)
- .build();
- String key = "key";
- // 往缓存写数据
- loadingCache.put(key, "v");
-
- // 获取value的值,如果key不存在,获取value后再返回
- String value = loadingCache.get(key, CaffeineCacheTest::getValueFromDB);
-
- // 删除key
- loadingCache.invalidate(key);
- }
-
- private static String getValueFromDB(String key) {
- return "v";
- }
- }
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.
-
- public class EncacheTest {
-
- public static void main(String[] args) throws Exception {
- // 声明一个cacheBuilder
- CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
- .withCache("encacheInstance", CacheConfigurationBuilder
- //声明一个容量为20的堆内缓存
- .newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.heap(20)))
- .build(true);
- // 获取Cache实例
- Cache<String,String> myCache = cacheManager.getCache("encacheInstance", String.class, String.class);
- // 写缓存
- myCache.put("key","v");
- // 读缓存
- String value = myCache.get("key");
- // 移除换粗
- cacheManager.removeCache("myCache");
- cacheManager.close();
- }
- }
Cache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
// 查找一个缓存元素, 没有查找到的时候返回null
Graph graph = cache.getIfPresent(key);
// 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null
graph = 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.
LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
// 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null
Graph 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.
AsyncCache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.buildAsync();
// 查找一个缓存元素, 没有查找到的时候返回null
CompletableFuture<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).
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é.
// 基于缓存内的元素个数进行驱逐
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));
// 基于固定的过期时间驱逐策略
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));
// 当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));
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.
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é.