当前位置:网站首页>Know two things: how does redis realize inventory deduction and prevent oversold?
Know two things: how does redis realize inventory deduction and prevent oversold?
2022-07-23 18:53:00 【Technical Trivia】
E-commerce project experience has been very common , Whether you are packaged or real , At least be able to explain the common problems in e-commerce , For example, how to prevent the inventory operation from being oversold
- Solution
- analysis
- Based on database single inventory
- Multi inventory based on Database
- be based on redis
- be based on redis Realize the specific realization of inventory deduction
- Initialize inventory callback function (IStockCallback)
- Deduct inventory service (StockService)
- call

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 :
- MySQL 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 .
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 . Project practice ( Click to download ):SpringBoot+SpringCloud+Mybatis+Vue E-commerce project actual battle
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();
}
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;
}
}
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);
}
}
边栏推荐
- LeetCode 0131. 分割回文串
- 【2018】【论文笔记】石墨烯场效应管及【1】——GFETs的种类和原理,GFETs特性,GFETs在太赫兹中的应用和原理
- Paddlenlp's UIE classification model [taking emotional propensity analysis news classification as an example] including intelligent annotation scheme)
- 【ONNX】动态输入尺寸的问题(多输出/多输入)
- JUC concurrent programming [detailed explanation and demonstration]
- Redis [2022 latest interview question]
- 【2018】【论文笔记】石墨烯场效应管及【2】——石墨烯的制备、转移
- How to become a modeler? Which is more popular, industrial modeling or game modeling?
- [2013] [paper notes] terahertz band nano particle surface enhanced Raman——
- 到底适不适合学习3D建模?这5点少1个都不行
猜你喜欢

学次世代建模是场景好还是角色好?选对职业薪资多一半

Flink Exactly-Once 投递实现浅析
![Multithreading [comprehensive study of graphics and text]](/img/70/8a1227b2159349cf25a85dff8f9d1c.png)
Multithreading [comprehensive study of graphics and text]

1259. 不相交的握手 动态规划

Great God "magic change" airpods, equipped with usb-c interface, 3D printing shell makes maintenance easier

Modeling just learning is very confused. How to learn the next generation role modeling process?
![MQ [messagequeue graphic explanation and four MQ comparisons]](/img/e0/a8fd8cf3420426c7b1595da79d1b4b.png)
MQ [messagequeue graphic explanation and four MQ comparisons]

opencv(13):cv2.findContours、cv::findContours简要介绍及opencv各版本cv2.findContours函数说明

Alliance DAO创始人:100+Web3基础设施及Dapp创业清单

BOM系列之BOM介绍
随机推荐
BOM introduction of BOM series
?前台传参的问题待确认
Error reporting caused by the use of responsebodyadvice interface and its solution
ZigBee集成开发环境IAR安装
【重磅】聚焦券商终端业务,博睿数据发布新一代券商终端核心业务体验可观测平台
次世代行业现状如何?90%转行建模师都在学习这套流程
【游戏建模模型制作全流程】用ZBrush制作游戏士兵角色
入门学习3D建模一般会遇到哪些问题?实在是太多了
Huawei fat thin AP switching method
398. Random number index hash table method
Modeling just learning is very confused. How to learn the next generation role modeling process?
What problems do you usually encounter when learning 3D modeling? It's too much
jumpserver管理员账号被锁定
1259. 不相交的握手 动态规划
ResponseBodyAdvice接口使用导致的报错及解决
Opencv (13): brief introduction to cv2.findcontours, cv:: findcontours and description of cv2.findcontours function in various versions of opencv
[whole process of game modeling model production] 3ds Max and ZBrush produce radio receivers
[2013] [paper notes] terahertz band nano particle surface enhanced Raman——
As a senior 3D modeler, I give some suggestions to novice learning partners to use
一文详解:TMP1750芯片 三通道线性LED驱动器