Ensuring Accurate Inventory Deduction in High‑Concurrency Sales with Redis
This article explains why simple GET‑modify‑SET inventory updates cause overselling in flash‑sale spikes and presents several Redis‑based solutions—including Lua scripts, WATCH‑based optimistic locks, distributed SETNX locks, and asynchronous queue processing—detailing their implementation, advantages, and trade‑offs.
1. Inventory Deduction Problem
In flash‑sale or抢购 scenarios, many users try to buy the same item simultaneously. Using a simple GET‑modify‑SET sequence in Redis leads to lost updates and overselling because the commands are not atomic.
2. Atomic Operations with Lua Scripts
Redis can execute a Lua script as a single atomic operation. The script reads the stock, checks the requested decrement, and uses DECRBY to reduce the stock safely.
-- Get stock, default 0
local product_stock = tonumber(redis.call('GET', KEYS[1]) or 0)
-- Get decrement amount
local count = tonumber(ARGV[1])
if not count or count <= 0 then
return -2 -- illegal decrement
end
if product_stock >= count then
return redis.call('DECRBY', KEYS[1], count)
else
return -1 -- insufficient stock
end3. Optimistic Lock with WATCH and Transactions
When Lua scripts are not desired, Redis WATCH can monitor a key and MULTI/EXEC provide an optimistic lock. If the key changes before EXEC, the transaction aborts.
WATCH product_stock
product_stock = GET product_stock
if product_stock >= count then
MULTI
DECRBY product_stock count
if EXEC then
return 0 -- success
else
return -2 -- transaction failed
end
else
UNWATCH
return -1 -- insufficient stock
end4. Distributed Lock with SETNX (RedLock)
In a distributed environment, a lock can be created with SET key value NX PX 5000. After acquiring the lock, the stock is checked and decremented, then the lock is released.
# Try to acquire lock
SET product_lock_123 "token" NX PX 5000
if lock_success then
product_stock = GET product_stock
if product_stock >= count then
DECRBY product_stock count
DEL product_lock_123
return 0
else
DEL product_lock_123
return -1
end
else
return -2 # lock failed
end5. Asynchronous Processing with Message Queues
For extreme spikes, the workflow can pre‑deduct stock in Redis, push the order to a message queue (RabbitMQ, RocketMQ), and later update the database asynchronously. Additional measures such as JVM‑level rate limiting, stock pre‑warming, queue length monitoring, scheduled tasks for order timeout rollback, and Redis persistence (AOF/RDB) are recommended.
These techniques allow a trade‑off between performance and strong consistency, and the appropriate solution should be chosen based on business volume and tolerance.
Ma Wei Says
Follow me! Discussing software architecture and development, AIGC and AI Agents... Sometimes sharing insights on IT professionals' life experiences.
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.
