当前位置:网站首页>Jd.com: how does redis implement inventory deduction? How to prevent oversold?
Jd.com: how does redis implement inventory deduction? How to prevent oversold?
2022-06-24 20:17:00 【Java technology stack】
Click on the official account ,Java Timely delivery of dry goods
source :my.oschina.net/xiaolyuh/blog/1615639
In daily development, there are many similar operations of inventory deduction , For example, commodity inventory in e-commerce system , Prize inventory in the lottery system .
Solution
- Use mysql database , Use a field to store inventory , Update this field every time you deduct inventory .
- Or use a database , But the inventory is layered and stored in multiple records , When deducting inventory, route , This increases the amount of concurrency , But still can not avoid a large number of access to the database to update the inventory .
- Put stock in redis Use redis Of incrby Features to reduce inventory .
analysis
The first and second methods above are based on data to deduct inventory .
Based on database single inventory
The first way is to wait for the lock here for all requests , Acquire lock to deduct inventory . It can be used when the concurrency is not high , But once the concurrency is large, there will be a lot of requests blocking here , Causes the request to time out , And then the whole system avalanches ; And will visit the database frequently , Take up a lot of database resources , So in the case of high concurrency, this method is not applicable .
Multi inventory based on Database
The second way is actually the optimized version of the first way , To some extent, it improves the concurrency , However, there will still be a large number of database update operations taking up a large number of database resources .
There are still some problems in reducing inventory based on database :
How to deduct inventory with database , The inventory deduction must be performed in one statement , Not first selec stay update, In this way, there will be a situation of over deduction under the concurrency . Such as :
update number set x=x-1 where x > 0MySQL There will be problems with its own processing performance for high concurrency , Generally speaking ,MySQL The processing performance of will follow the concurrency thread Rise and rise , But after a certain degree of concurrency, there will be obvious inflection points , Then all the way down , In the end, it's even better than Dan thread The performance is even worse .
When inventory reduction and high concurrency come together , Because the number of stocks in operation is on the same line , There will be a fight InnoDB The problem of line lock , Lead to waiting for each other or even deadlock , Thus greatly reducing MySQL Processing performance of , Finally, a timeout exception occurs on the front-end page . in addition , newest MySQL The interview questions are sorted out , You can Java Interview database Small program online brush questions .
be based on redis
To solve the above problems, we have a third plan , Put inventory in cache , utilize redis Of incrby Features to reduce inventory , Solve the problem of over buckle and performance .
But once the cache is lost, we need to consider the recovery plan . For example, when the lottery system buckles the prize stock , Initial stock = Total stock - The number of awards that have been awarded , But if it's asynchronous Awards , Need to wait until MQ Message consumption is over before restart redis Initialize inventory , Otherwise, there is the problem of inconsistent inventory .
be based on redis Realize the specific realization of inventory deduction
- We use redis Of lua Script to reduce inventory
- In the distributed environment, a distributed lock is needed to control that only one service can initialize the inventory
- You need to provide a callback function , When initializing the inventory, call this function to get the initialization inventory
Initialize inventory callback function (IStockCallback )
/**
* Get inventory callback
* @author yuhao.wang
*/
public interface IStockCallback {
/**
* Get inventory
* @return
*/
int getStock();
}Recommend a Spring Boot Basic tutorials and practical examples :
https://github.com/javastacks/spring-boot-best-practice
Deduct inventory service (StockService)
/**
* Inventory deduction
*
* @author yuhao.wang
*/
@Service
public class StockService {
Logger logger = LoggerFactory.getLogger(StockService.class);
/**
* No stock limit
*/
public static final long UNINITIALIZED_STOCK = -3L;
/**
* Redis client
*/
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* Execute stock deduction script
*/
public static final String STOCK_LUA;
static {
/**
*
* @desc Deducting the inventory Lua Script
* stock (stock)-1: Means unlimited stock
* stock (stock)0: Means there is no stock
* stock (stock) Greater than 0: Represents the remaining stock
*
* @params stock key
* @return
* -3: Inventory not initialized
* -2: Insufficient inventory
* -1: No stock limit
* Greater than or equal to 0: Surplus stock ( The remaining stock after deduction )
* redis Cached inventory (value) yes -1 Means unlimited stock , Go straight back to 1
*/
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();
}
/**
* @param key stock key
* @param expire Stock effective time , Unit second
* @param num Quantity deducted
* @param stockCallback Initialize inventory callback function
* @return -2: Insufficient inventory ; -1: No stock limit ; Greater than or equal to 0: Remaining stock after deducting stock
*/
public long stock(String key, long expire, int num, IStockCallback stockCallback) {
long stock = stock(key, num);
// Initialize inventory
if (stock == UNINITIALIZED_STOCK) {
RedisLock redisLock = new RedisLock(redisTemplate, key);
try {
// Get the lock
if (redisLock.tryLock()) {
// Double verification , Avoid returning to the source to the database repeatedly in case of concurrency
stock = stock(key, num);
if (stock == UNINITIALIZED_STOCK) {
// Get initialization stock
final int initStock = stockCallback.getStock();
// Set inventory to redis
redisTemplate.opsForValue().set(key, initStock, expire, TimeUnit.SECONDS);
// Adjust the operation of stock deduction once
stock = stock(key, num);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
redisLock.unlock();
}
}
return stock;
}
/**
* Add inventory ( Restore inventory )
*
* @param key stock key
* @param num Inventory quantity
* @return
*/
public long addStock(String key, int num) {
return addStock(key, null, num);
}
/**
* Add inventory
*
* @param key stock key
* @param expire Expiration time ( second )
* @param num Inventory quantity
* @return
*/
public long addStock(String key, Long expire, int num) {
boolean hasKey = redisTemplate.hasKey(key);
// Judge key Whether there is , Live and update
if (hasKey) {
return redisTemplate.opsForValue().increment(key, num);
}
Assert.notNull(expire," Failed to initialize inventory , Stock expiration time cannot be null");
RedisLock redisLock = new RedisLock(redisTemplate, key);
try {
if (redisLock.tryLock()) {
// After obtaining the lock, judge whether there is key
hasKey = redisTemplate.hasKey(key);
if (!hasKey) {
// Initialize inventory
redisTemplate.opsForValue().set(key, num, expire, TimeUnit.SECONDS);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
redisLock.unlock();
}
return num;
}
/**
* Get inventory
*
* @param key stock key
* @return -1: No stock limit ; Greater than or equal to 0: Surplus stock
*/
public int getStock(String key) {
Integer stock = (Integer) redisTemplate.opsForValue().get(key);
return stock == null ? -1 : stock;
}
/**
* Inventory deduction
*
* @param key stock key
* @param num Deduct stock quantity
* @return The remaining stock after deduction 【-3: Inventory not initialized ; -2: Insufficient inventory ; -1: No stock limit ; Greater than or equal to 0: Remaining stock after deducting stock 】
*/
private Long stock(String key, int num) {
// In the script KEYS Parameters
List<String> keys = new ArrayList<>();
keys.add(key);
// In the script ARGV Parameters
List<String> args = new ArrayList<>();
args.add(Integer.toString(num));
long result = redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// The cluster mode and the stand-alone mode execute scripts in the same way , But there is no common interface , So it can only be carried out separately
// Cluster pattern
if (nativeConnection instanceof JedisCluster) {
return (Long) ((JedisCluster) nativeConnection).eval(STOCK_LUA, keys, args);
}
// standalone mode
else if (nativeConnection instanceof Jedis) {
return (Long) ((Jedis) nativeConnection).eval(STOCK_LUA, keys, args);
}
return UNINITIALIZED_STOCK;
}
});
return result;
}
}in addition , newest Redis The interview questions are sorted out , You can Java Interview database Small program online brush questions .
call
/**
* @author yuhao.wang
*/
@RestController
public class StockController {
@Autowired
private StockService stockService;
@RequestMapping(value = "stock", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object stock() {
// goods ID
long commodityId = 1;
// stock ID
String redisKey = "redis_key:stock:" + commodityId;
long stock = stockService.stock(redisKey, 60 * 60, 2, () -> initStock(commodityId));
return stock >= 0;
}
/**
* Get the initial stock
*
* @return
*/
private int initStock(long commodityId) {
// TODO Here are some operations to initialize the inventory
return 1000;
}
@RequestMapping(value = "getStock", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object getStock() {
// goods ID
long commodityId = 1;
// stock ID
String redisKey = "redis_key:stock:" + commodityId;
return stockService.getStock(redisKey);
}
@RequestMapping(value = "addStock", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object addStock() {
// goods ID
long commodityId = 2;
// stock ID
String redisKey = "redis_key:stock:" + commodityId;
return stockService.addStock(redisKey, 2);
}
}Spring Boot After the scheduled task is started , How to stop automatically ?
Java 8 Sort of 10 A pose , What a show !
23 Design mode and Practice ( Very comprehensive )
Spring Boot Protect sensitive configurations 4 Methods !
Face a 5 year Java, Neither thread can exchange data !
Why does Ali recommend LongAdder?
A new technical director : No code writing with headphones ..
blockbuster !Spring Boot 2.7 Official release
Java 18 Official release ,finalize deprecated ..
Spring Boot Admin Born in the sky !
Spring Boot Learning notes , This is so complete !
Focus on Java Technology stack, see more dry goods
obtain Spring Boot Practical notes !
边栏推荐
- 2022年最新四川建筑八大员(电气施工员)模拟题库及答案
- Predicate
- RF_ DC system clock setting gen1/gen2
- Fundamentals of performance testing -- definitions of common terms
- You can capture fingerprints with a mobile camera?! Accuracy comparable to signature and monogram, expert: you are aggravating discrimination
- Test drive citus 11.0 beta (official blog)
- 托管服务与SASE,纵享网络与安全融合 | 一期一会回顾
- Apache+php+mysql environment construction is super detailed!!!
- Comparative analysis of arrayblockingqueue and linkedblockingqueue
- Bytebase 加入阿里云 PolarDB 开源数据库社区
猜你喜欢

网络安全审查办公室对知网启动网络安全审查,称其“掌握大量重要数据及敏感信息”

Drawing DEM with GEE gracefully

UART communication (STM32F103 library function)

应用实践 | 海量数据,秒级分析!Flink+Doris 构建实时数仓方案

An accident caused by a MySQL misoperation cannot be withstood by High Availability!

年轻人捧红的做饭生意经:博主忙卖课带货,机构月入百万

Apache+php+mysql environment construction is super detailed!!!

对“宁王”边卖边买,高瓴资本“高抛低吸”已套现数十亿

Technology implementation | Apache Doris cold and hot data storage (I)

Win7 10 tips for installing Office2010 five solutions for installing MSXML components
随机推荐
IP address to integer
gateway
Power efficiency test
Vs2017 setting function Chinese Notes
《梦华录》“超点”,鹅被骂冤吗?
Obstacle avoidance sensor module (stm32f103c8t6)
建立自己的网站(14)
Power supply noise analysis
[go language questions] go from 0 to entry 4: advanced usage of slice, elementary review and introduction to map
Hutool reads large excel (over 10m) files
Docker installing MySQL
Bytebase rejoint la communauté de base de données open source d'alicloud polardb
Accurate calculation of task progress bar of lol mobile game
Nodered has no return value after successfully inserting into the database (the request cannot be ended)
What is showcase? What should showcase pay attention to?
The difference between the lazy man mode and the hungry man mode
16个优秀业务流程管理工具
Unity mobile game performance optimization spectrum CPU time-consuming optimization divided by engine modules
试驾 Citus 11.0 beta(官方博客)
Ribbon source code analysis @loadbalanced and loadbalancerclient