当前位置:网站首页>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 !
边栏推荐
- 别再用 System.currentTimeMillis() 统计耗时了,太 Low,StopWatch 好用到爆!
- 两位湖南老乡,联手干出一个百亿IPO
- SQL export CSV data, unlimited number of entries
- Zadig + cave Iast: let safety dissolve in continuous delivery
- Camera module and hardware interface of Camera1 camera
- Docker installing Oracle
- Vs2017 add header file path method
- Showcase是什么?Showcase需要注意什么?
- 16个优秀业务流程管理工具
- [cann document express issue 06] first knowledge of tbe DSL operator development
猜你喜欢

苹果不差钱,但做内容“没底气”
![[go language questions] go from 0 to entry 4: advanced usage of slice, elementary review and introduction to map](/img/3a/db240deb4c66b219ef86f40d4c7b7d.png)
[go language questions] go from 0 to entry 4: advanced usage of slice, elementary review and introduction to map

Full link service tracking implementation scheme

STM32 uses time delay to realize breathing lamp register version

实现基于Socket自定义的redis简单客户端

Two solutions to the problem of 0xv0000225 unable to start the computer

Why is the executor thread pool framework introduced

R for Data Science (notes) -- data transformation (used by filter)

Docker installing Oracle

Capacitive inching touch switch module control (stm32f103c8t6)
随机推荐
苹果不差钱,但做内容“没底气”
What other data besides SHP data
视频平台如何将旧数据库导入到新数据库?
Teach you how to view the number of connected people on WiFi in detail how to view the number of connected people on WiFi
IBPS开源表单设计器有什么功能?
Working for 6 years with a monthly salary of 3W and a history of striving for one PM
Win7 10 tips for installing Office2010 five solutions for installing MSXML components
Capacitive inching touch switch module control (stm32f103c8t6)
UART communication (STM32F103 library function)
[cann document express issue 04] unveiling the development of shengteng cann operator
16 excellent business process management tools
JMeter environment deployment
Maps are grouped according to the values of the passed in parameters (similar to database groupby)
Uninstall tool v3.5.10.5670 single file portable official version
Openstack actual installation and deployment tutorial, openstack installation tutorial
16个优秀业务流程管理工具
redis数据结构之压缩列表
用手机摄像头就能捕捉指纹?!准确度堪比签字画押,专家:你们在加剧歧视
“拯救”直播带货,一个董宇辉还不够
Steering gear control (stm32f103c8t6)