使用Redis事务实现秒杀

public Integer redisSeckill() {
    byte[] cacheKey = REDIS_SECKILL_KEY.getBytes(StandardCharsets.UTF_8);
    return RedisUtil.getRedisTemplate().execute((connection) -> {
        byte[] bytes = connection.get(cacheKey);
        if (bytes == null) {
            throw new RuntimeException("指定缓存key不存在!");
        }
        while (true) {
            connection.watch(cacheKey);
            bytes = connection.get(cacheKey);
            int surplusCount = Integer.parseInt(new String(bytes));
            if (surplusCount <= 0) {
                connection.unwatch();
                return surplusCount;
            }
            connection.multi();
            connection.decr(cacheKey);
            List<Object> exec = connection.exec();
            if (exec != null && exec.get(0) != null) {
                return ((Long) exec.get(0)).intValue();
            }
        }
    }, true);
}

这里使用了Redis流水线操作,在扣除库存和判断库存数量并不是一个合并的原子操作,所以在判断库存的过程中使用了watch,监控在判断库存的过程中库存数量的缓存是否被修改,如果被修改那么当前事务就会失败,exec返回null值。下面是redis官网对watch的解释:

WATCH explained

So what is WATCH really about? It is a command that will make the EXEC conditional: we are asking Redis to perform the transaction only if none of the WATCHed keys were modified. This includes modifications made by the client, like write commands, and by Redis itself, like expiration or eviction. If keys were modified between when they were WATCHed and when the EXEC was received, the entire transaction will be aborted instead.

下面设置100个线程,设置10个库存并发抢夺

public void setRedisSeckillTest() {
    int actionCount = 1000;
    RedisUtil.valueOps().set(REDIS_SECKILL_KEY, 10);
    CountDownLatch countDownLatch = new CountDownLatch(actionCount);
    for (int i = 1; i <= actionCount; i++) {
        executor.execute(() -> {
            Integer surplus = redisSeckill();
            countDownLatch.countDown();
            log.debug("当前线程:【{}】,秒杀后剩余数量为:{}", Thread.currentThread().getName(), surplus);
        });
    }
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

当前线程:【ALone-Thread-7】,秒杀后剩余数量为:9
当前线程:【ALone-Thread-1】,秒杀后剩余数量为:7
当前线程:【ALone-Thread-8】,秒杀后剩余数量为:6
当前线程:【ALone-Thread-17】,秒杀后剩余数量为:8
当前线程:【ALone-Thread-7】,秒杀后剩余数量为:5
当前线程:【ALone-Thread-19】,秒杀后剩余数量为:4
当前线程:【ALone-Thread-7】,秒杀后剩余数量为:3
当前线程:【ALone-Thread-3】,秒杀后剩余数量为:2
当前线程:【ALone-Thread-20】,秒杀后剩余数量为:1
当前线程:【ALone-Thread-8】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-20】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-3】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-10】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-1】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-2】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-9】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-4】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-19】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-12】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-16】,秒杀后剩余数量为:0
当前线程:【ALone-Thread-8】,秒杀后剩余数量为:0

 

posted @ 2022-08-07 11:17  ALonely  阅读(176)  评论(0编辑  收藏  举报