当前位置:网站首页>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 !
边栏推荐
- You can capture fingerprints with a mobile camera?! Accuracy comparable to signature and monogram, expert: you are aggravating discrimination
- [suggested collection] time series prediction application and paper summary
- Clustered index (clustered index), nonclustered index (nonclustered index)
- RF_ DC system clock setting gen1/gen2
- Todesk remote control, detailed introduction and tutorial
- What is showcase? What should showcase pay attention to?
- LCD1602 string display (STM32F103)
- 京东一面:Redis 如何实现库存扣减操作?如何防止商品被超卖?
- [go language questions] go from 0 to entry 4: advanced usage of slice, elementary review and introduction to map
- Understanding openstack network
猜你喜欢

《梦华录》“超点”,鹅被骂冤吗?

Apache+PHP+MySQL环境搭建超详细!!!

Methods for comparing float types in the kernel

Confirm whether the host is a large terminal or a small terminal

Vs2017 setting function Chinese Notes

Using dynamic time warping (DTW) to solve the similarity measurement of time series and the similarity identification analysis of pollution concentration in upstream and downstream rivers

Unity mobile game performance optimization spectrum CPU time-consuming optimization divided by engine modules

Capacitive inching touch switch module control (stm32f103c8t6)

Five day summary of software testing

建立自己的网站(14)
随机推荐
Full link service tracking implementation scheme
[cann document express issue 04] unveiling the development of shengteng cann operator
With its own cells as raw materials, the first 3D printing ear transplantation was successful! More complex organs can be printed in the future
[cann document express issue 06] first knowledge of tbe DSL operator development
京东一面:Redis 如何实现库存扣减操作?如何防止商品被超卖?
If the programmer tells the truth during the interview
Error in Android connection database query statement
对国产数据库厂商提几个关于SQL引擎的小需求
年轻人捧红的做饭生意经:博主忙卖课带货,机构月入百万
Digital twin industry case: Digital Smart port
【Go語言刷題篇】Go從0到入門4:切片的高級用法、初級複習與Map入門學習
天天鉴宝暴雷背后:拖欠数千万、APP停摆,创始人预谋跑路?
科技抗疫: 运营商网络洞察和实践白皮书 | 云享书库NO.20推荐
建立自己的网站(14)
宅男救不了元宇宙
What is showcase? What should showcase pay attention to?
Docker installing Oracle
Install the custom module into the system and use find in the independent project_ Package found
Uninstall tool v3.5.10.5670 single file portable official version
【建议收藏】时间序列预测应用、paper汇总