当前位置:网站首页>According to the e-commerce written on the resume, how does redis realize inventory deduction and prevent oversold?
According to the e-commerce written on the resume, how does redis realize inventory deduction and prevent oversold?
2022-07-23 19:36:00 【Ah size】
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);
}
}
边栏推荐
- R语言使用dwilcox函数生成Wilcoxon秩和统计分布密度函数数据、使用plot函数可视化Wilcoxon秩和统计分布密度函数数据
- Weights & Biases (一)
- Lendingclub loan status business details current, charge off, issued, full paid, grace period
- 【leetcode天梯】链表 · 203 移除链表元素
- .net core implements background tasks (scheduled tasks) longbow Tasks component (III)
- not all arguments converted during string formatting
- Weights & biases (I)
- Codeforces Round #809 (Div. 2)【VP记录】
- 虹科干货 | 教您如何解析MODBUS中的浮点型数据
- LeetCode刷题:回文数
猜你喜欢

There is great competition pressure for software testing jobs, and the "migrant workers" who graduated from 985 also suffer

三维点云课程(七)——特征点描述

PowerCLi 导入 LicenseKey 到esxi

【C语言】程序环境和预处理

虹科干货 | 教您如何解析MODBUS中的浮点型数据

USB3.0:VL817Q7-C0的LAYOUT指南
![[shutter -- layout] flexible layout (flex and expanded)](/img/03/0f07a56487f8e91045f9e893e9f96c.png)
[shutter -- layout] flexible layout (flex and expanded)

Ros2 self study notes: rviz visualization tool

Understand chisel language. 21. Chisel sequential circuit (I) -- detailed explanation of chisel register

使用 frp 实现内网穿透
随机推荐
H7-TOOL的CANFD/CAN接口脱机烧写操作说明, 已经更新(2022-07-12)
BoundsChecker用法「建议收藏」
Latex(katex)csdn 希腊字母表示,数学符号,集合符号,特殊标记
Labyrinth DP integration
R语言作图:坐标轴设置
Fragment
LeetCode刷题:回文数
Lendingclub loan status business details current, charge off, issued, full paid, grace period
界面设计四大原则
【leetcode天梯】链表 · 203 移除链表元素
虹科干货 | 教您如何解析MODBUS中的浮点型数据
Mysql database [Database Foundation -- introduction]
MySQL中 8 种常见的 SQL 错误用法
《自适应机器人交互白皮书》
当代励志“女”录
公链之Sui(前脸书/Meta员工成立的新公链项目)
Educational Codeforces Round 132 (Rated for Div. 2)【比赛记录】
There is great competition pressure for software testing jobs, and the "migrant workers" who graduated from 985 also suffer
有向图之求两点之间所有路径
R语言检验样本是否符合正态性(检验样本是否来自一个正态分布总体):使用nortest包的sf.test函数检验样本是否符合正态分布(normality test)