做网站文字编辑累吗南宁市千界网络科技有限公司
2026/3/7 14:45:28 网站建设 项目流程
做网站文字编辑累吗,南宁市千界网络科技有限公司,网站搭建 商城 seo,产品seo优化电商当项目经验已经非常普遍了#xff0c;不管你是包装的还是真实的#xff0c;起码要能讲清楚电商中常见的问题#xff0c;比如库存的操作怎么防止商品被超卖 解决方案#xff1a; 基于数据库单库存基于数据库多库存基于redis 基于redis实现扣减库存的具体实现 初始化…电商当项目经验已经非常普遍了不管你是包装的还是真实的起码要能讲清楚电商中常见的问题比如库存的操作怎么防止商品被超卖解决方案基于数据库单库存基于数据库多库存基于redis基于redis实现扣减库存的具体实现初始化库存回调函数IStockCallback扣减库存服务StockService调用在日常开发中有很多地方都有类似扣减库存的操作比如电商系统中的商品库存抽奖系统中的奖品库存等。解决方案使用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 客户端 */ Autowired private 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(); // 将库存设置到redis redisTemplate.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()) { // 获取到锁后再次判断一下是否有key hasKey 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() { Override public 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 { Autowired private StockService stockService; RequestMapping(value stock, produces MediaType.APPLICATION_JSON_UTF8_VALUE) public Object stock() { // 商品ID long commodityId 1; // 库存ID String 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() { // 商品ID long commodityId 1; // 库存ID String redisKey redis_key:stock: commodityId; return stockService.getStock(redisKey); } RequestMapping(value addStock, produces MediaType.APPLICATION_JSON_UTF8_VALUE) public Object addStock() { // 商品ID long commodityId 2; // 库存ID String redisKey redis_key:stock: commodityId; return stockService.addStock(redisKey, 2); } }获取更多干货内容记得关注我哦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询