Redis Lazy Loading Cache with Guava Local Cache: Design, Implementation, and Evaluation
This article introduces a Redis lazy‑loading caching strategy combined with Guava local cache, presents design diagrams and Java code examples, analyzes its advantages and disadvantages, and discusses suitable scenarios such as precise query workloads and micro‑service architectures.
In many backend systems Redis is used as a high‑frequency cache to improve performance and reduce pressure on relational databases, but heavy read/write traffic can still cause I/O bottlenecks. To alleviate this, the article proposes using a local Guava cache as a supplementary layer, enabling lazy loading of data from Redis only when needed.
Design Example
The "Redis Lazy Loading Cache" pattern stores data in Redis only after a precise query; new data is not cached on insertion. A flow diagram illustrates the process of checking the cache first, falling back to the database, and then populating Redis for future queries.
// Pseudo‑code example Xx represents your business object (e.g., User, Goods)
public class XxLazyCache {
@Autowired
private RedisTemplate<String, Xx> redisTemplate;
@Autowired
private XxService xxService; // your business service
/**
* Query: check cache first, load from DB if missing, then cache the result.
*/
public Xx getXx(int id) {
// 1. Check cache
Xx xxCache = getXxFromCache(id);
if (xxCache != null) {
return xxCache; // fast return
}
// 2. Query DB (assume id always exists)
Xx xx = xxService.getXxById(id);
// 3. Populate cache (lazy load)
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) {
// query DB (implementation omitted)
return xx;
}
public void updateXx(Xx xx) {
// update DB (omitted)
xxLazyCache.deleteXxFromCache(xx.getId());
}
public void deleteXx(long id) {
// delete DB (omitted)
xxLazyCache.deleteXxFromCache(id);
}
}
@Data
public class Xx {
private Long id; // primary key
// ... other fields
}Advantages
Minimizes cache size while satisfying precise‑query workloads, avoiding cold data occupying memory.
Low intrusion on CRUD operations; cache invalidation occurs automatically on delete.
Plug‑in design allows upgrading legacy systems without pre‑loading all data.
Disadvantages
Requires controlled data volume; not suitable for unbounded growth scenarios.
Less appropriate for global caching in micro‑service environments where a shared cache is needed.
Redis + Local Cache in Micro‑services
In high‑frequency streaming scenarios, multiple micro‑services share a large Redis cache, causing heavy load. By adding a Guava local cache, each service reduces Redis reads, achieving lower latency without network overhead.
/**
* Demonstrates how to combine Redis auto‑increment, hash, and Guava local cache
* for generating and caching per‑device incremental IDs.
*/
public class DeviceIncCache {
// Guava 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"; // Redis key for global counter
private static final String DEVICE_INC_VALUE = "device_inc_value"; // Redis hash key for device → inc mapping
/**
* Get the incremental number for a device.
*/
public int getInc(String deviceCode) {
// 1. Try local cache
Integer inc = localCache.getIfPresent(deviceCode);
if (inc != null) {
return inc;
}
// 2. Try Redis hash
inc = (Integer) redisTemplate.opsForHash().get(DEVICE_INC_VALUE, deviceCode);
// 3. If not present, generate a new global 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;
}
}Pros of This Combined Approach
Redis guarantees persistence while the local cache provides ultra‑fast reads, reducing overall Redis pressure.
Guava offers rich APIs for expiration policies and size limits, keeping memory usage under control.
Cache loss on service restart does not affect correctness because data remains in Redis.
In distributed micro‑service setups, each instance caches only its own device IDs, optimizing memory usage.
The incremental IDs support uniform partitioning of Kafka topics and device‑count statistics.
Cons
Adds implementation complexity compared to a pure Redis solution.
Only suitable for data that is append‑only (no updates) because cache invalidation logic assumes immutability.
Conclusion
Redis’s versatile data structures (strings, hashes, bitmaps, lists) make it ideal for many business scenarios, while Guava’s local cache complements it by delivering high‑speed reads and fine‑grained control over memory usage. Together they form a powerful, controllable caching layer for precise‑query workloads and micro‑service architectures.
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.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.
