How to Implement High‑Performance Stock Deduction with Redis Lua and Spring Boot
This article compares three stock‑deduction strategies—single‑field MySQL, sharded MySQL rows, and Redis incrby with Lua scripts—analyzes their concurrency issues, and provides a complete Spring Boot implementation using a distributed lock, Lua script, and callback for initializing stock.
In daily development, stock deduction appears in e‑commerce, lottery systems, and similar scenarios.
Solution
Use MySQL with a single field to store stock and update it on each deduction.
Store stock in multiple rows (sharding) to increase concurrency, but still heavy DB load.
Put stock in Redis and use Redis incrby to deduct.
Analysis
Both database‑based methods rely on data updates. The single‑field approach blocks all requests under high concurrency, causing timeouts and heavy DB load. The sharded approach improves concurrency slightly but still generates many DB updates.
Database deduction must be performed in a single statement to avoid over‑deduction, e.g.: update number set x=x-1 where x > 0 MySQL performance degrades sharply under high thread counts, and row‑level locking can cause deadlocks.
Redis based solution
Using Redis with Lua scripts avoids over‑deduction and improves performance. However, cache loss requires a recovery strategy, especially when initializing stock after asynchronous reward distribution.
Redis implementation details
Use a Redis Lua script to deduct stock.
Employ a distributed lock for initializing stock in a distributed environment.
Provide a callback to obtain initial stock.
Stock initialization callback (IStockCallback)
/**
* Get stock callback
* @author yuhao.wang
*/
public interface IStockCallback {
/**
* Get stock
* @return
*/
int getStock();
}StockService
/**
* Stock deduction service
* @author yuhao.wang
*/
@Service
public class StockService {
Logger logger = LoggerFactory.getLogger(StockService.class);
public static final long UNINITIALIZED_STOCK = -3L;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public static final String STOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if (redis.call('exists', KEYS[1]) == 1) then");
sb.append(" local stock = tonumber(redis.call('get', KEYS[1]));");
sb.append(" local num = tonumber(ARGV[1]);");
sb.append(" if (stock == -1) then");
sb.append(" return -1;");
sb.append(" end;");
sb.append(" if (stock >= num) then");
sb.append(" return redis.call('incrby', KEYS[1], 0 - num);");
sb.append(" end;");
sb.append(" return -2;");
sb.append("end;");
sb.append("return -3;");
STOCK_LUA = sb.toString();
}
// other methods omitted for brevity
}Controller usage
@RestController
public class StockController {
@Autowired
private StockService stockService;
@RequestMapping(value = "stock", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object stock() {
long commodityId = 1;
String redisKey = "redis_key:stock:" + commodityId;
long stock = stockService.stock(redisKey, 60 * 60, 2, () -> initStock(commodityId));
return stock >= 0;
}
private int initStock(long commodityId) {
return 1000; // initialize stock
}
// other endpoints omitted
}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 High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
