This article distills practical insights for using Redis and Kafka in production systems, framed as common interview questions. It moves beyond theoretical answers to explain real-world patterns for caching, event-driven architectures, and distributed system challenges, drawing examples from a point-of-sale (POS) and inventory microsystem. Key discussions include Redis's single-threaded performance, mitigation of caching issues like cache penetration and breakdown, and strategic decisions for when not to use Redis.
Read original on Dev.to #systemdesignThe article uses a PSI (POS + inventory microsystem), comprising Spring Boot and Go services, as a practical example to demonstrate robust uses of Redis for caching and Kafka for asynchronous events. The author emphasizes a 'boring architecture' approach, prioritizing reliability and timely delivery over novel complexities.
Redis's speed, despite being single-threaded, is attributed to fundamental optimizations that eliminate common bottlenecks in multi-threaded environments. The single-threaded model inherently avoids:
When Redis's Single-Thread Breaks
While efficient, the single-threaded model means long-running commands can block the entire server. Examples include `KEYS *`, `SMEMBERS` on large collections, large key `DEL` operations, and extended Lua scripts. Understanding these limitations is crucial for robust system design.
The article details practical solutions for three common caching issues:
// 1. Penetration
Sku sku = redis.get("sku:" + code);
if (sku == null) {
sku = db.query(code);
if (sku == null) {
redis.setex("sku:" + code, 300, "NULL_TOKEN"); // 5-min null cache
return null;
}
redis.setex("sku:" + code, 3600, JsonUtil.toJson(sku));
}
// 2. Breakdown
Sku cached = redis.get("sku:" + code);
if (cached != null) return cached;
String lockKey = "lock:sku:" + code;
if (redis.set(lockKey, "1", "NX", "EX", 5) != null) { // got the lock
try {
Sku fresh = db.query(code);
redis.setex("sku:" + code, 3600, JsonUtil.toJson(fresh));
return fresh;
} finally {
redis.del(lockKey);
}
}
Thread.sleep(50); // wait + retry
// 3. Avalanche
long baseTtl = 3600;
long jitter = ThreadLocalRandom.current().nextLong(0, 600);
redis.setex("sku:" + code, baseTtl + jitter, JsonUtil.toJson(sku));While Redis is powerful, it's not a universal solution. The article highlights scenarios where local caching combined with a publish-subscribe invalidation mechanism might be more appropriate. For example, storing a permission tree that changes infrequently and requires local access across many nodes can benefit from a local cache (like Caffeine) with Redis Pub/Sub used only for invalidation, reducing network overhead for reads.