redis配合lua脚本完成原子性操作

redis配合lua脚本

redis是可以使用 外部的lua脚本 从而完成一系列操作的原子性

  1. 在做秒杀的时候,需要把 商品 的id 和 库存余量存入redis ,并且还需要建立一张表,表里面存入有谁已经买过了这个商品(一人一单需要用到) 实际上需要的最重要的参数就是:用户id 和 商品 id
  2. 基本的流程如下:
  • 首先判断库存是否充足
  • 不充足就直接返回失败,充足就去看这个人是不是买过了
  • 买过了则返回失败,没买过就扣减库存,并且将这个用户id存入redis的表中,代表这个人已经买过了
  1. 至于最终的存入mysql这样的数据库,完全可以有一定的延时性,比如使用 mq 来做。
local goodsId = ARG[1]
local userId = ARG[2]

local goodsIdMatchStock='seckill:stock:'..goodsId
local goodsIdMatchUserId='seckill:order:'..userId

--在redis中有两个key 一个是      goodsIdMatchStock   value 是 库存,假如是100
--                  一个是     goodsIdMatchUserId  value 是 set集合,里面存储的是用户的id


--首先是看库存是否充足
if (tonumber(redis.call('get',goodsIdMatchStock))<=0)  then
    return 1
end

--到这里代表 库存充足 接下来判断这个用户是不是已经买过了
if redis.call('sismember',goodsIdMatchUserId,userId)==1 then
    return 2
end

--到这里代表 库存充足 并且 用户没买过 所以要 扣除库存
redis.call('incrby',goodsIdMatchStock,-1)
redis.call('sadd',goodsIdMatchUserId,userId)
return 0

调用lua脚本

   @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;

    /*阻塞队列*/
    private BlockingQueue<VoucherOrder> orderTask = new ArrayBlockingQueue<>(1024);

    /*线程池*/
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = new ThreadPoolExecutor(
            4,
            8,
            10,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1024)
    );
    
    private class VoucherOrderHandler implements Runnable{

        @Override
        public void run() {
            try {
                VoucherOrder voucherOrder = orderTask.take();
                /*接下来就是处理订单,往数据库插入数据*/

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @PostConstruct //类初始化完成后,就执行这个方法
    private void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }

	//这里是加载资源文件
    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

	//这里是redis调用 lua 文件
    public Integer seckillByLuaScript(Long goodsId, Long userId) throws Exception {

        Long execute = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                goodsId, userId
        );

        int i = execute.intValue();

        if (i != 0) {
            /*代表没有购买资格*/
            throw new Exception("没有购买资格");
        }

        /*有购买资格,这个时候其实库存已经减了,下面只需要将订单放入mysql中就行,而且这个步骤也不着急
         * 对于实时性没有那么的高,所以这里只需要把一些信息放入阻塞队列中,并由一个线程去处理这个队列中的信息
         * ,后期可以使用mq优化*/
        VoucherOrder order=new VoucherOrder();
        order.setVoucherId(goodsId);
        order.setUserId(userId);
        orderTask.add(order);
        
        return null;
    }
整体的流程为: 类加载的时候, lua脚本也被加载,并且线程池由于 @PostConstruct 注解的原因,也会开始执行 run方法(方法体的内容是,从阻塞队列中取出数据,插入数据库)。每当用户开始下单的时候,相关参数就会被穿入redis中并执行lua脚本,保证原子性,redis 执行完成后,将相关参数放入阻塞队列,而由于run方法一直在监视着阻塞队列,所以一旦阻塞队列中有元素,就会被取出并执行插入数据库的操作。

但是这样做有一个问题:阻塞队列用的是 JVM 的内存。如果阻塞队列满了怎么办,或者是,由于用户下单量过大,内存溢出了怎么办。

所以最好还是使用第三方来做mq。redis也能做,但是不那么专业

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值