关于网站建设的图片素材,做奥数题网站,阿里巴巴登录入口,网站开发客户阿里云案例本文已经收录到Github仓库#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点#xff0c;欢迎star~
Github地址#xff1a;https://github.com/…本文已经收录到Github仓库该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点欢迎star~
Github地址https://github.com/Tyson0314/Java-learning 电商当项目经验已经非常普遍了不管你是包装的还是真实的起码要能讲清楚电商中常见的问题比如库存的操作怎么防止商品被超卖
解决方案
基于数据库单库存基于数据库多库存基于redis
基于redis实现扣减库存的具体实现
初始化库存回调函数IStockCallback扣减库存服务StockService调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-STapA2Ti-1679409278382)(http://img.topjavaer.cn/img/库存扣减1.png)] 在日常开发中有很多地方都有类似扣减库存的操作比如电商系统中的商品库存抽奖系统中的奖品库存等。
解决方案
使用mysql数据库使用一个字段来存储库存每次扣减库存去更新这个字段。还是使用数据库但是将库存分层多份存到多条记录里面扣减库存的时候路由一下这样子增大了并发量但是还是避免不了大量的去访问数据库来更新库存。将库存放到redis使用redis的incrby特性来扣减库存。
分析
在上面的第一种和第二种方式都是基于数据来扣减库存。
基于数据库单库存
第一种方式在所有请求都会在这里等待锁获取锁有去扣减库存。在并发量不高的情况下可以使用但是一旦并发量大了就会有大量请求阻塞在这里导致请求超时进而整个系统雪崩而且会频繁的去访问数据库大量占用数据库资源所以在并发高的情况下这种方式不适用。
基于数据库多库存
第二种方式其实是第一种方式的优化版本在一定程度上提高了并发量但是在还是会大量的对数据库做更新操作大量占用数据库资源。
基于数据库来实现扣减库存还存在的一些问题
用数据库扣减库存的方式扣减库存的操作必须在一条语句中执行不能先selec在update这样在并发下会出现超扣的情况。如
update number set xx-1 where x 0MySQL自身对于高并发的处理性能就会出现问题一般来说MySQL的处理性能会随着并发thread上升而上升但是到了一定的并发度之后会出现明显的拐点之后一路下降最终甚至会比单thread的性能还要差。当减库存和高并发碰到一起的时候由于操作的库存数目在同一行就会出现争抢InnoDB行锁的问题导致出现互相等待甚至死锁从而大大降低MySQL的处理性能最终导致前端页面出现超时异常。
基于redis
针对上述问题的问题我们就有了第三种方案将库存放到缓存利用redis的incrby特性来扣减库存解决了超扣和性能问题。但是一旦缓存丢失需要考虑恢复方案。比如抽奖系统扣奖品库存的时候初始库存总的库存数-已经发放的奖励数但是如果是异步发奖需要等到MQ消息消费完了才能重启redis初始化库存否则也存在库存不一致的问题。
基于redis实现扣减库存的具体实现
我们使用redis的lua脚本来实现扣减库存由于是分布式环境下所以还需要一个分布式锁来控制只能有一个服务去初始化库存需要提供一个回调函数在初始化库存的时候去调用这个函数获取初始化库存
初始化库存回调函数(IStockCallback )
/*** 获取库存回调*/
public interface IStockCallback {/*** 获取库存* return*/int getStock();
}扣减库存服务StockService
/*** 扣库存**/
Service
public class StockService {Logger logger LoggerFactory.getLogger(StockService.class);/*** 不限库存*/public static final long UNINITIALIZED_STOCK -3L;/*** Redis 客户端*/Autowiredprivate RedisTemplateString, Object redisTemplate;/*** 执行扣库存的脚本*/public static final String STOCK_LUA;static {/**** desc 扣减库存Lua脚本* 库存stock-1表示不限库存* 库存stock0表示没有库存* 库存stock大于0表示剩余库存** params 库存key* return* -3:库存未初始化* -2:库存不足* -1:不限库存* 大于等于0:剩余库存扣减之后剩余的库存* redis缓存的库存(value)是-1表示不限库存直接返回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 库存key* param expire 库存有效时间,单位秒* param num 扣减数量* param stockCallback 初始化库存回调函数* return -2:库存不足; -1:不限库存; 大于等于0:扣减库存之后的剩余库存*/public long stock(String key, long expire, int num, IStockCallback stockCallback) {long stock stock(key, num);// 初始化库存if (stock UNINITIALIZED_STOCK) {RedisLock redisLock new RedisLock(redisTemplate, key);try {// 获取锁if (redisLock.tryLock()) {// 双重验证避免并发时重复回源到数据库stock stock(key, num);if (stock UNINITIALIZED_STOCK) {// 获取初始化库存final int initStock stockCallback.getStock();// 将库存设置到redisredisTemplate.opsForValue().set(key, initStock, expire, TimeUnit.SECONDS);// 调一次扣库存的操作stock stock(key, num);}}} catch (Exception e) {logger.error(e.getMessage(), e);} finally {redisLock.unlock();}}return stock;}/*** 加库存(还原库存)** param key 库存key* param num 库存数量* return*/public long addStock(String key, int num) {return addStock(key, null, num);}/*** 加库存** param key 库存key* param expire 过期时间秒* param num 库存数量* return*/public long addStock(String key, Long expire, int num) {boolean hasKey redisTemplate.hasKey(key);// 判断key是否存在存在就直接更新if (hasKey) {return redisTemplate.opsForValue().increment(key, num);}Assert.notNull(expire,初始化库存失败库存过期时间不能为null);RedisLock redisLock new RedisLock(redisTemplate, key);try {if (redisLock.tryLock()) {// 获取到锁后再次判断一下是否有keyhasKey redisTemplate.hasKey(key);if (!hasKey) {// 初始化库存redisTemplate.opsForValue().set(key, num, expire, TimeUnit.SECONDS);}}} catch (Exception e) {logger.error(e.getMessage(), e);} finally {redisLock.unlock();}return num;}/*** 获取库存** param key 库存key* return -1:不限库存; 大于等于0:剩余库存*/public int getStock(String key) {Integer stock (Integer) redisTemplate.opsForValue().get(key);return stock null ? -1 : stock;}/*** 扣库存** param key 库存key* param num 扣减库存数量* return 扣减之后剩余的库存【-3:库存未初始化; -2:库存不足; -1:不限库存; 大于等于0:扣减库存之后的剩余库存】*/private Long stock(String key, int num) {// 脚本里的KEYS参数ListString keys new ArrayList();keys.add(key);// 脚本里的ARGV参数ListString args new ArrayList();args.add(Integer.toString(num));long result redisTemplate.execute(new RedisCallbackLong() {Overridepublic Long doInRedis(RedisConnection connection) throws DataAccessException {Object nativeConnection connection.getNativeConnection();// 集群模式和单机模式虽然执行脚本的方法一样但是没有共同的接口所以只能分开执行// 集群模式if (nativeConnection instanceof JedisCluster) {return (Long) ((JedisCluster) nativeConnection).eval(STOCK_LUA, keys, args);}// 单机模式else if (nativeConnection instanceof Jedis) {return (Long) ((Jedis) nativeConnection).eval(STOCK_LUA, keys, args);}return UNINITIALIZED_STOCK;}});return result;}}调用
RestController
public class StockController {Autowiredprivate StockService stockService;RequestMapping(value stock, produces MediaType.APPLICATION_JSON_UTF8_VALUE)public Object stock() {// 商品IDlong commodityId 1;// 库存IDString redisKey redis_key:stock: commodityId;long stock stockService.stock(redisKey, 60 * 60, 2, () - initStock(commodityId));return stock 0;}/*** 获取初始的库存** return*/private int initStock(long commodityId) {// TODO 这里做一些初始化库存的操作return 1000;}RequestMapping(value getStock, produces MediaType.APPLICATION_JSON_UTF8_VALUE)public Object getStock() {// 商品IDlong commodityId 1;// 库存IDString redisKey redis_key:stock: commodityId;return stockService.getStock(redisKey);}RequestMapping(value addStock, produces MediaType.APPLICATION_JSON_UTF8_VALUE)public Object addStock() {// 商品IDlong commodityId 2;// 库存IDString redisKey redis_key:stock: commodityId;return stockService.addStock(redisKey, 2);}
}最后给大家分享一个Github仓库上面有大彬整理的300多本经典的计算机书籍PDF包括C语言、C、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等可以star一下下次找书直接在上面搜索仓库持续更新中~ Github地址https://github.com/Tyson0314/java-books