Redis Lazy Loading Cache and Local Cache Integration: Design, Implementation, and Trade‑offs
This article explains how to combine Redis lazy‑loading cache with a Guava local cache to minimize memory usage while ensuring precise query performance, provides Java code examples for both lazy loading and device‑increment caching, and discusses the advantages, disadvantages, and suitable scenarios of the approach.
In many projects Redis is used as a cache to improve performance and reduce pressure on relational databases, but high‑frequency data streams can cause significant I/O overhead on Redis.
To alleviate this, a lazy‑loading cache pattern is introduced where data is cached in Redis only after a precise query, and a local Guava cache is used to further reduce Redis read pressure.
Redis Lazy Loading Cache
Data is added to MySQL without immediate caching; only when a precise lookup occurs is the data cached, achieving "cache on read, no cache on miss".
Diagram:
Code Example – Lazy Loading Cache
// Pseudo‑code example Xx represents your business object such as User, Goods, etc.
public class XxLazyCache {
@Autowired
private RedisTemplate<String, Xx> redisTemplate;
@Autowired
private XxService xxService; // your business service
/**
* Query: check cache first, load from DB if miss, then cache.
*/
public Xx getXx(int id) {
Xx xxCache = getXxFromCache(id);
if (xxCache != null) {
return xxCache; // guard clause for readability
}
Xx xx = xxService.getXxById(id);
setXxFromCache(xx);
return xx;
}
/**
* Delete or update: remove cache after DB operation.
*/
public void deleteXxFromCache(long id) {
String key = "Xx:" + xx.getId();
redisTemplate.delete(key);
}
private void setXxFromCache(Xx xx) {
String key = "Xx:" + xx.getId();
redisTemplate.opsForValue().set(key, xx);
}
private Xx getXxFromCache(int id) {
String key = "Xx:" + id;
return redisTemplate.opsForValue().get(key);
}
}
// Business class
public class XxServie {
@Autowired
private XxLazyCache xxLazyCache;
public Xx getXxById(long id) { /* omitted */ }
public void updateXx(Xx xx) { /* update DB */ xxLazyCache.deleteXxFromCache(xx.getId()); }
public void deleteXx(long id) { /* delete DB */ xxLazyCache.deleteXxFromCache(id); }
}
@Data
public class Xx {
private Long id; // ... other fields
}Advantages
Minimizes cache size while satisfying precise query workloads, avoiding cold data occupying valuable memory.
Low intrusion on CRUD operations; cache invalidation is immediate.
Pluggable for legacy system upgrades; no need to pre‑populate cache at startup.
Disadvantages
Requires controlled data volume; not suitable for unbounded growth scenarios.
Less effective in microservice environments where a global cache is needed.
Redis + Local Cache (Guava) for Microservices
In microservice scenarios, combining a shared Redis cache with a per‑instance Guava local cache reduces Redis pressure while providing ultra‑fast reads without network latency.
Diagram:
Code Example – Device Increment Cache
/**
* Demonstrates how to combine Redis auto‑increment, hash, and Guava local cache
* to generate and cache device increment numbers.
*/
public class DeviceIncCache {
/** Local cache */
private Cache<String, Integer> localCache = CacheBuilder.newBuilder()
.concurrencyLevel(16)
.initialCapacity(1000)
.maximumSize(10000)
.expireAfterAccess(1, TimeUnit.HOURS)
.build();
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
private static final String DEVICE_INC_COUNT = "device_inc_count";
private static final String DEVICE_INC_VALUE = "device_inc_value";
/** Get device increment number */
public int getInc(String deviceCode) {
// 1. Try local cache
Integer inc = localCache.get(deviceCode);
if (inc != null) {
return inc;
}
// 2. Try Redis hash
inc = (Integer) redisTemplate.opsForHash().get(DEVICE_INC_VALUE, deviceCode);
// 3. If not present, generate via Redis auto‑increment
if (inc == null) {
inc = redisTemplate.opsForValue().increment(DEVICE_INC_COUNT).intValue();
redisTemplate.opsForHash().put(DEVICE_INC_VALUE, deviceCode, inc);
}
// 4. Populate local cache
localCache.put(deviceCode, inc);
return inc;
}
}Advantages of This Combination
Redis guarantees data persistence; local cache provides ultra‑fast reads, reducing Redis load in shared‑cache scenarios.
Guava offers rich APIs for expiration policies and size limits, keeping memory usage under control.
Cache loss on service restart does not affect business correctness.
Each microservice instance caches only its own device IDs, optimizing memory usage.
Supports both partition‑based message routing and device‑count statistics.
Disadvantages
Increases implementation complexity.
Applicable only to data that is append‑only (no updates).
Summary
Local cache space is controllable with good expiration strategies.
Suitable for microservice and distributed scenarios.
Cache content must be immutable (no updates).
Provides superior performance by offloading frequent reads from Redis.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
