Boost Application Performance with Multi‑Level Caffeine & Redis Caching
This article explains why multi‑level caching is needed, outlines design challenges of combining Redis and Caffeine, provides step‑by‑step integration instructions for Spring Boot, and presents performance benchmarks demonstrating the speed advantage of the multi‑level approach.
Why Multi‑Level Cache
Cache is essential for modern systems; as data size and structure grow, Redis alone can suffer performance degradation and network I/O becomes a significant bottleneck, especially in micro‑service architectures where a single request may trigger multiple downstream calls.
Design Challenges
Spring Cache supports only a single cache provider, so it cannot simultaneously use Redis and Caffeine.
Data consistency between cache layers (e.g., JVM‑level Caffeine and distributed Redis) is difficult to guarantee.
Spring Cache lacks active expiration policies, making cache eviction management cumbersome.
Business Flow
How to Use
Add the starter dependency:
<code><dependency>
<groupId>com.pig4cloud.plugin</groupId>
<artifactId>multilevel-cache-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency></code>Enable caching in the Spring Boot application:
<code>@EnableCaching
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}</code>Declare cacheable methods 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 on localhost.
Benchmark Mode Cnt Score Units
Multi‑level thrpt 2 2716.074 ops/s
Default Redis thrpt 2 1373.476 ops/sCode Principles
Custom
CacheManagerthat creates a combined Redis‑Caffeine cache instance.
<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>Cache read logic checks Caffeine first, then falls back to Redis, and populates Caffeine on a Redis hit.
<code>protected Object lookup(Object key) {
Object cacheKey = getKey(key);
Object value = caffeineCache.getIfPresent(key);
if (value != null) {
log.debug("get cache from caffeine, the key is : {}", cacheKey);
return value;
}
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;
}</code>All write and eviction operations publish a Redis Pub/Sub message so that other nodes can invalidate their local Caffeine entries.
<code>public void put(Object key, Object value) {
push(new CacheMessage(this.name, key));
}
public void evict(Object key) {
push(new CacheMessage(this.name, key));
}
private void push(CacheMessage message) {
stringKeyRedisTemplate.convertAndSend(topic, message);
}</code>Message listener removes the corresponding entry from the local Caffeine cache when a Pub/Sub notification is received.
<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.