背景:我们的订单处理系统中,对库存的更改在这之前一直是使用的数据库update,但是近几年直播带货盛行,经常会出现短时间内同一个商品的销售量高达几十万甚至上百万,但因为高频率的更新同一条库存数据,受到数据库行锁限制,每秒钟的qps只能达到 500 ~ 1000 左右,虽然升级硬件可以提高qps,但基于成本考虑,我们选择了利用Redis,根据官方提供的数据,Redis单节点的QPS可以达到约100000+。
最终我们做了一个利用Redis处理库存的模板工具,需要使用直接写一个服务类继承 DdzStockTemplate,并定义自己的redis key 规则即可实现基本的库存加减:
核心代码如下:
接口:DdzStockCallback
package com.rddz.util.stock.template;
/**
* 回调接口
*
* @author ddz
*/
public interface DdzStockCallback {
/**
* 查数据库的库存数量
*
* @param stockId 库存id
* @return 库存数量,未查到返回 null
*/
Long getStockByDb(String stockId);
/**
* 缓存 KEY
*
* @param stockId 库存 ID
* @return
*/
String getCacheKey(String stockId);
/**
* 锁 KEY
*
* @param stockId 库存 ID
* @return
*/
String getLockKey(String stockId);
/**
* 重置所有的库存缓存
*/
void resetStockCache();
}
模板抽象类:DdzStockTemplate
package com.rddz.util.stock.template;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* 抽象类,主要提供以下方法:
*
* getStock(String stockId) :查库存,先从缓存查,没有则初始化
* increment(String stockId, long num) :直接加库存
* decrement(String stockId, long num) :直接减库存(不校验够不够,允许负库存)
* reduce(String stockId, long num) :lua脚本减库存,return -1 找不到库存 -2 库存不足 >=0 扣减之后的剩余库存
* reducePart(String stockId, long num) :lua脚本部分扣减库存,return -1 找不到库存 >=0 本次扣减了多少
* reduceAll(String stockId) :lua脚本清零库存,return -1 找不到库存 >=0 本次扣减了多少
*
* @author ddz
*/
public abstract class DdzStockTemplate implements DdzStockCallback {
@Autowired
protected RedisTemplate<String, Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
/** 缓存过期时间(秒) 默认3天【60*60*24*3】 */
public final static long STOCK_CACHE_TIMEOUT = 259200L;
/**