crossoverJie/JCSprout · warning · RuntimeException
库存不足 Redis currentCount=
Error message
库存不足 Redis currentCount=
What it means
Thrown by checkStockByRedis() when the cached sale count in Redis equals the cached total count — meaning inventory is exhausted according to the Redis cache layer. This is the Redis-optimised variant of the stock check: instead of querying the DB every time, it reads count and sale from Redis keys (STOCK_COUNT+sid and STOCK_SALE+sid). The message includes the current sale value for diagnostics.
Source
Thrown at MD/third-party-component/seconds-kill.md:611
@Override
public int createOptimisticOrderUseRedis(int sid) throws Exception {
//检验库存,从 Redis 获取
Stock stock = checkStockByRedis(sid);
//乐观锁更新库存 以及更新 Redis
saleStockOptimisticByRedis(stock);
//创建订单
int id = createOrder(stock);
return id ;
}
private Stock checkStockByRedis(int sid) throws Exception {
Integer count = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_COUNT + sid));
Integer sale = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_SALE + sid));
if (count.equals(sale)){
throw new RuntimeException("库存不足 Redis currentCount=" + sale);
}
Integer version = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_VERSION + sid));
Stock stock = new Stock() ;
stock.setId(sid);
stock.setCount(count);
stock.setSale(sale);
stock.setVersion(version);
return stock;
}
/**
* 乐观锁更新数据库 还要更新 Redis
* @param stock
*/
private void saleStockOptimisticByRedis(Stock stock) {
int count = stockService.updateStockByOptimistic(stock);View on GitHub (pinned to fc4c6e5f6d)
Solutions
- Ensure Redis stock keys (STOCK_COUNT, STOCK_SALE, STOCK_VERSION) are initialised before the sale begins — via a startup script or an admin endpoint.
- Add a null/missing-key guard before Integer.parseInt so you get a clear error instead of a NumberFormatException masking the real issue.
- If this fires immediately at sale start, check whether a previous sale drained the Redis keys without resetting them.
- Reconcile Redis with the DB periodically or on startup to prevent drift.
Example fix
// before — assumes Redis keys always exist and are numeric
Integer count = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_COUNT + sid));
Integer sale = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_SALE + sid));
if (count.equals(sale)) {
throw new RuntimeException("库存不足 Redis currentCount=" + sale);
}
// after — guard missing keys and use >=
String countStr = redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_COUNT + sid);
String saleStr = redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_SALE + sid);
if (countStr == null || saleStr == null) {
throw new IllegalStateException("Stock keys not initialised in Redis for sid=" + sid);
}
int count = Integer.parseInt(countStr);
int sale = Integer.parseInt(saleStr);
if (sale >= count) {
throw new RuntimeException("库存不足 Redis currentCount=" + sale);
} Defensive patterns
Strategy: validation
Validate before calling
// Validate Redis keys exist and are numeric before the order flow.
String countStr = redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_COUNT + sid);
String saleStr = redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_SALE + sid);
if (countStr == null || saleStr == null) {
throw new IllegalStateException("Redis stock keys not initialised for sid=" + sid);
}
int count = Integer.parseInt(countStr);
int sale = Integer.parseInt(saleStr);
if (sale >= count) {
// stock exhausted — reject early
throw new RuntimeException("库存不足 Redis currentCount=" + sale);
} Try / catch
try {
int orderId = orderService.createOptimisticOrderUseRedis(sid);
return ResponseEntity.ok(orderId);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().startsWith("库存不足")) {
return ResponseEntity.status(409).body("Sold out: " + e.getMessage());
}
throw e;
} Prevention
- Initialise Redis stock keys (STOCK_COUNT, STOCK_SALE, STOCK_VERSION) before the sale starts.
- Guard against null Redis returns before Integer.parseInt to avoid masking this with a NumberFormatException.
- Periodically reconcile Redis stock values with the DB to prevent drift.
- Use >= instead of == in the exhaustion check to catch already-oversold state.
When it happens
Trigger: Calling createOptimisticOrderUseRedis(sid) after the Redis STOCK_SALE key has caught up to the STOCK_COUNT key for that sid. Because the Redis check and the DB update are not atomic, under extreme concurrency multiple threads can read count > sale from Redis before any of them increments the Redis sale key, so this check alone does not prevent overselling — the DB optimistic lock in saleStockOptimisticByRedis is the real guard.
Common situations: Stock genuinely sold out during a flash sale. Redis stock keys were not initialised (returning null, which would cause a NumberFormatException before reaching this check). Redis and DB are out of sync — e.g. a manual DB update was not reflected in Redis, or a Redis increment failed after a successful DB update.
Related errors
AI-assisted analysis of crossoverJie/JCSprout@fc4c6e5f6d (2026-08-14).
Data as JSON: /api/errors/7368c4d1e1f33d14.
Report an issue: GitHub.