Why Redis Distributed Locks Fail and How to Stop Overselling in Flash Sales
This article analyzes a real‑world flash‑sale incident where a Redis distributed lock expired under high load, causing a 100‑bottle product to be oversold, and presents safer lock implementations, atomic stock checks, and performance‑oriented redesigns to prevent such failures.
Incident Overview
Using Redis for distributed locks is common, but in our project a flash‑sale of 100 bottles of a scarce product resulted in overselling due to lock expiration and high latency in user‑service verification.
Root Causes
No fault‑tolerance handling for the user‑service gateway under heavy load.
Distributed lock implementation was unsafe: the lock could expire while a thread was still processing, allowing another thread to acquire it and later the original thread would release the wrong lock.
Stock verification was non‑atomic, leading to race conditions.
Solution Overview
We introduced a safer lock that ties the lock value to a unique identifier and releases it only when the stored value matches, using a Lua script.
public void safedUnLock(String key, String val) {
String luaScript = "local in = ARGV[1] local curr=redis.call('get', KEYS[1]) if in==curr then redis.call('del', KEYS[1]) end return 'OK'";
RedisScript<String> redisScript = RedisScript.of(luaScript);
redisTemplate.execute(redisScript, Collections.singletonList(key), Collections.singleton(val));
}For stock deduction we rely on Redis's atomic increment operation:
Long currStock = redisTemplate.opsForHash().increment("key", "stock", -1);We refactored the seckill handler to use a DistributedLocker class and the atomic stock decrement, removing the previous non‑atomic checks.
public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {
String key = "key:" + request.getSeckillId();
String val = UUID.randomUUID().toString();
try {
boolean lockFlag = distributedLocker.lock(key, val, 10, TimeUnit.SECONDS);
if (!lockFlag) {
// handle lock failure
}
Long currStock = stringRedisTemplate.opsForHash().increment(key + ":info", "stock", -1);
if (currStock < 0) {
log.error("[Seckill] No stock");
// handle out‑of‑stock
} else {
// generate order, publish event, build response
}
} finally {
distributedLocker.safedUnLock(key, val);
}
return response;
}Further Optimizations
We explored eliminating Redis entirely by pre‑loading stock information into a ConcurrentHashMap and using a hash‑based routing at the gateway level, achieving higher throughput while still preventing oversell.
private static ConcurrentHashMap<Long, Boolean> SECKILL_FLAG_MAP = new ConcurrentHashMap<>();
private static Map<Long, AtomicInteger> SECKILL_STOCK_MAP = new HashMap<>();
public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {
Long seckillId = request.getSeckillId();
if (!SECKILL_FLAG_MAP.get(seckillId)) {
// handle flag false
}
if (SECKILL_STOCK_MAP.get(seckillId).decrementAndGet() < 0) {
SECKILL_FLAG_MAP.put(seckillId, false);
// handle out‑of‑stock
}
// generate order, publish event, build response
return response;
}Deep Reflections
While Redis’s atomic operations can replace the lock for simple stock decrement, a lock still helps throttle traffic to downstream services, reducing overall system pressure. Choosing a lock implementation (e.g., RedLock) depends on the required reliability versus performance trade‑off.
Conclusion
Overselling a scarce product is a critical failure that can damage a platform’s reputation. By adopting a value‑based distributed lock, leveraging Redis’s atomic commands, and considering in‑memory stock management, we can achieve both safety and performance in high‑concurrency seckill scenarios.
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.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
