Boost Spring Boot Performance with Multi‑Level Caffeine‑Redis Cache
This article explains why a multi‑level cache combining JVM‑level Caffeine and distributed Redis is essential for modern Spring Boot applications, outlines design challenges, provides step‑by‑step integration instructions, and demonstrates performance gains with benchmark results and core implementation code.
Why Multi‑Level Cache
Introducing caching is now a must for most systems. While Redis is a common middleware, large data sizes and complex structures can degrade performance, and network I/O becomes a significant bottleneck, especially in micro‑service architectures where a single request may trigger many downstream calls.
Caffeine, a high‑performance local in‑memory cache, offers considerably better speed than typical memory cache implementations.
Conclusion: we need to build an L1 Caffeine JVM‑level cache and an L2 Redis cache.
Design Challenges
Most application caches are based on Spring Cache with annotation support, which has the following limitations:
Spring Cache supports only a single cache provider, so Redis and Caffeine cannot be used simultaneously.
Data consistency issues between cache layers (e.g., application‑level cache vs. distributed cache).
Spring Cache does not support proactive expiration policies.
How to Use
Add the dependency:
<code><dependency>
<groupId>com.pig4cloud.plugin</groupId>
<artifactId>multilevel-cache-spring-boot-starter</artifactId>
<version>0.0.1</version>
</dependency></code>Enable cache support:
<code>@EnableCaching
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}</code>Declare target interface with Spring Cache annotations:
<code>@Cacheable(value = "get", key = "#key")
@GetMapping("/get")
public String get(String key) {
return "success";
}</code>Performance Comparison
Benchmark environment: macOS Mojave, 2.3 GHz Intel Core i5, 8 GB RAM, Corretto 11 JDK, Redis running locally.
Results (operations per second):
Multi‑level implementation: 2716 ops/s
Default Redis only: 1373 ops/s
Code Principles
Custom CacheManager for Multi‑Level Cache
<code>public class RedisCaffeineCacheManager implements CacheManager {
@Override
public Cache getCache(String name) {
Cache cache = cacheMap.get(name);
if (cache != null) {
return cache;
}
cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(), cacheConfigProperties);
Cache oldCache = cacheMap.putIfAbsent(name, cache);
log.debug("create cache instance, the cache name is : {}", name);
return oldCache == null ? cache : oldCache;
}
}</code>Multi‑Level Read and Expiration Strategy
<code>public class RedisCaffeineCache extends AbstractValueAdaptingCache {
@Override
protected Object lookup(Object key) {
Object cacheKey = getKey(key);
// 1. Try Caffeine first
Object value = caffeineCache.getIfPresent(key);
if (value != null) {
log.debug("get cache from caffeine, the key is : {}", cacheKey);
return value;
}
// 2. Fallback to Redis
value = stringKeyRedisTemplate.opsForValue().get(cacheKey);
if (value != null) {
log.debug("get cache from redis and put in caffeine, the key is : {}", cacheKey);
caffeineCache.put(key, value);
}
return value;
}
@Override
public void put(Object key, Object value) {
push(new CacheMessage(this.name, key));
}
@Override
public void evict(Object key) {
push(new CacheMessage(this.name, key));
}
@Override
public void clear() {
push(new CacheMessage(this.name, null));
}
private void push(CacheMessage message) {
stringKeyRedisTemplate.convertAndSend(topic, message);
}
}</code>Message Listener for Cache Invalidation
<code>public class CacheMessageListener implements MessageListener {
private final RedisTemplate<Object, Object> redisTemplate;
private final RedisCaffeineCacheManager redisCaffeineCacheManager;
@Override
public void onMessage(Message message, byte[] pattern) {
CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody());
redisCaffeineCacheManager.clearLocal(cacheMessage.getCacheName(), cacheMessage.getKey());
}
}</code>Source Code
https://github.com/pig-mesh/multilevel-cache-spring-boot-starter
References
pig oauth2.0 client authentication: https://gitee.com/log4j/pig
Caffeine benchmark details: https://github.com/ben-manes/caffeine/wiki/Benchmarks
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.