java redis订单减库存6_Redis+RocketMQ实现并发条件下库存的扣减/增加(秒杀库存控制)...

前言

前面我的博客介绍了有关分布式锁,分布式事务相关的问题以及解决方案,但是还是不能解决并发下单,扣减的问题,并发的时候由于数据库的隔离级别/乐观锁/悲观锁...总是会出现一些问题。最近集成了一套方案解决此类型问题,并可以适用于一般情况的秒杀方案。欢迎拍砖...

情景分析

前提条件:

商品 P 库存数量为 100

用户A,用户B 同时分别想买P,并且A,B分别买一个

数据库有版本号控制乐观锁

期望结果:

用户A,B都买到商品P,并且商品P的库存变为98

分析:

1.以前碰到这类问题的时候,我们说,可以添加分布式锁(也就是悲观锁),将商品P的id锁住,然后A,B提交订

单的时候分别预扣商品P的库存就好了(如方案一)。

2.是的,1分析的有道理,我们的核心思路确实要将并行控制为串行,但是事与愿违。我们仔细想一想:

如果,A,B想买的不仅仅P一个商品,而是P,Q两个商品呢?我们该怎么锁?一次性将P,Q都锁住?显然不是

很现实,并且实际上提交订单的时候都是从购物车下单,一个购物车里包括多个商品去下单也是很常见的,

并且还有如下逻辑大家仔细思考:

用户A下单->用户A读到库存为100

用户A预扣库存,判断剩余库存是否>=0,100-1=99>0,则A预扣

用户A的下单流程....

此时A的事务没有提交,B下单了:

用户B下单->用户B读到库存为100(这里读到100,是因为A还有其他逻辑在执行,还未提交事务,B读未提交了!)

用户B预扣库存,判断剩余库存是否>=0,100-1=99>0,则B预扣

用户B的下单流程....

最后不论A/B谁先提交事务,后面提交事务的就不会扣减库存成功。因为版本号不一致(就算没有乐观锁,

修改的结果也会错,而且错的更离谱)。最终的结局就是库存是99

3.解决方案

目前控制库存的方案有很多种,我这边介绍通过redis预减库存,通过mq发送消息同步的扣减数据库库存的

方案。

方案一

@Transactional

public void createOrder(...){

//1.校验,扣减库存

check(Item item);

//2.创建订单

//3.创建支付单

}

@RedisLock("pId")

public void check(Item item){

}

解决方案伪代码

//当然,我们管理平台新建商品时需要初始化到redis库存里,

//这里暂时就不介绍了

//下单部分

@Transactional

public void createOrder(...){

//1.校验,扣减库存

check(Item item);

//2.创建订单

//3.创建支付单

//4.redis扣减库存

}

//支付回调部分

@Transactional

public void wxCall(...){

//1.校验订单状态

//2.修改订单,支付单状态

//3.mq发送全局顺序消息 扣减库存

}

//取消支付部分

@Transactional

public void cancelOrder(...){

//1.校验订单状态

//2.修改订单,支付单状态

//3.redis回退库存

}

//退货/退款部分

@Transactional

public void returnOrder(...){

//1.校验订单状态

//2.修改订单,支付单状态

//3.redis回退库存

//4.mq发送全局顺序消息

}

代码部分

实现思路

我们使用redis的lua脚本来实现扣减库存

由于是分布式环境下所以还需要一个分布式锁来控制只能有一个服务去初始化库存

需要提供一个回调函数,在初始化库存的时候去调用这个函数获取初始化库存

初始化库存回调函数(IStockCallback )

/**

* 获取库存回调

* create by liuliang

* on 2019-11-13 10:45

*/

public interface IStockCallback {

/**

* 获取库存

* @return

*/

String getStock();

}

分布式锁控制初始化库存

/**

*

* Redis分布式锁

* 使用 SET resource-name anystring NX EX max-lock-time 实现

*

* 该方案在 Redis 官方 SET 命令页有详细介绍。

* http://doc.redisfans.com/string/set.html

*

* 在介绍该分布式锁设计之前,我们先来看一下在从 Redis 2.6.12 开始 SET 提供的新特性,

* 命令 SET key value [EX seconds] [PX milliseconds] [NX|XX],其中:

*

* EX seconds — 以秒为单位设置 key 的过期时间;

* PX milliseconds — 以毫秒为单位设置 key 的过期时间;

* NX — 将key 的值设为value ,当且仅当key 不存在,等效于 SETNX。

* XX — 将key 的值设为value ,当且仅当key 存在,等效于 SETEX。

*

* 命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。

*

* 客户端执行以上的命令:

*

* 如果服务器返回 OK ,那么这个客户端获得锁。

* 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。

*

*

* create by liuliang

* on 2019-11-13 10:49

*/

public class RedisStockLock {

private static Logger logger = LoggerFactory.getLogger(RedisStockLock.class);

private RedisTemplate redisTemplate;

/**

* 将key 的值设为value ,当且仅当key 不存在,等效于 SETNX。

*/

public static final String NX = "NX";

/**

* seconds — 以秒为单位设置 key 的过期时间,等效于EXPIRE key seconds

*/

public static final String EX = "EX";

/**

* 调用set后的返回值

*/

public static final String OK = "OK";

/**

* 默认请求锁的超时时间(ms 毫秒)

*/

private static final long TIME_OUT = 100;

/**

* 默认锁的有效时间(s)<

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis可以通过使用事务或Lua脚本来实现并发下的抢购/秒杀功能。 1. 使用事务:抢购/秒杀的过程可以看做是一个先检查库存是否充足,再扣减库存的过程。使用Redis的事务可以保证这个过程是原子性的,即要么全部成功,要么全部失败。 具体实现方法如下: - 使用MULTI命令开启一个事务。 - 使用WATCH命令对库存进行监视(即设置监视器)。 - 使用GET命令获取当前库存。 - 判断库存是否充足,如果充足,则使用DECRBY命令扣减库存。 - 使用EXEC命令提交事务,如果提交成功,则说明扣减成功,否则说明被其他线程抢先扣减库存。 示例代码如下: ```python def decrease_stock(redis_conn, stock_key): with redis_conn.pipeline() as pipeline: while True: try: pipeline.watch(stock_key) stock = int(pipeline.get(stock_key)) if stock > 0: pipeline.multi() pipeline.decr(stock_key) pipeline.execute() return True else: return False except WatchError: continue ``` 2. 使用Lua脚本:Lua脚本可以在Redis端原子性地执行多个命令,可以少网络开销和锁竞争的问题。 具体实现方法如下: - 编一个Lua脚本,该脚本首先使用GET命令获取当前库存,如果库存充足,则使用DECRBY命令扣减库存,否则返回0。 - 在Python中使用Redis的EVAL命令执行该Lua脚本。 示例代码如下: ```python def decrease_stock(redis_conn, stock_key): script = """ local stock = tonumber(redis.call('GET', KEYS[1])) if stock > 0 then redis.call('DECRBY', KEYS[1], 1) return 1 else return 0 end """ result = redis_conn.eval(script, 1, stock_key) return bool(result) ``` 以上两种方法都可以实现并发下的抢购/秒杀功能,但是使用Lua脚本的方法效率更高,因为在Redis端执行命令可以少网络开销和锁竞争的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值