【Redis】-Redis实现高并发下秒杀系统


**

前言

Redis事务
  Redis事务是一种将多个命令打包执行的机制,确保这些命令要么全部执行成功,要么全部执行失败。Redis事务通过MULTI、EXEC、DISCARD和WATCH这四个命令来实现。

MULTI:该命令标志着事务的开始,之后的命令会被缓存起来而不会立即执行。
EXEC:该命令用于执行之前缓存的所有命令,如果执行成功,则事务中的所有命令都会被执行。如果其中有任何一条命令执行失败(例如语法错误),那么事务将中止,但不会影响其他客户端的事务。
DISCARD:该命令用于取消事务,清除之前缓存的所有命令。
WATCH:该命令用于在事务开始之前监视一个或多个键。如果在执行EXEC之前,被监视的键被其他客户端修改,那么这个事务会中止。
使用事务可以确保原子性操作,但是不支持回滚,即使其中某些命令执行失败,其他命令仍然会被执行。因此,在使用事务时,需要确保所有命令都能正确执行,或者在失败时进行适当的处理。

Lua脚本
  基本原理为使脚本相当于一个 redis 命令,可以结合 redis 原有命令,自定义脚本逻辑。

相同点
  很好的实现了一致性、隔离性和持久性,但没有实现原子性,无论是 redis 事务,还是 lua 脚本,如果执行期间出现运行错误,之前的执行过的命令是不会回滚的。

不同点
(1)redis 事务是基于乐观锁,lua 脚本是基于 redis 的单线程执行命令。
(2)redis 事务的执行原理就是一次命令的批量执行,而 lua 脚本可以加入自定义逻辑。
**

一、场景

  秒杀系统存在高并发的场景,在对商品进行秒杀时,由于并发过高可能会导致库存超卖的情况,那么可以通过Redis提供的事务机制超卖问题;Redis事务实际就是将所有命令都按顺序地执行。事务在执行时不会被其他的命令所打断。

二、商品超卖的场景

代码复现

@RestController
@RequestMapping("/second-kill")
@Slf4j
public class SecondKillController {
    @Resource
    private RedisTemplate redisTemplate;




    //记录实际卖出的商品数量
    private AtomicInteger successNum = new AtomicInteger(0);

    @GetMapping(value = "/init")
    public String init() {
        // 初始化库存数量,模拟库存只要5个商品,写入到redis中
        redisTemplate.opsForValue().set("stock", 5);
        successNum.set(0);
        log.info("===>>>库存初始化成功,库存数为" + 5);
        return "初始化库存成功";
    }

    @GetMapping(value = "/reduce")
    public String reduce() {
        int stock = (Integer) redisTemplate.opsForValue().get("stock");
        log.info("===>>>当前数量" + stock);
        // 模拟只减少一个库存
        stock = stock - 1;
        if (stock < 0) {
            log.info("===>>>库存不足");
            return "库存不足";
        }
        // 将剩余数量回写到redis
        redisTemplate.opsForValue().set("stock", stock);
        // 记录实际卖出的商品数量(线程安全每个请求都会记录)
        log.info("===>>>减少库存成功,共出售" + successNum.incrementAndGet());
        return "减少库存成功";
    }
    }

执行结果:

这里我们使用JMeter并发进行测试
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们的库存总共只有5,而这里就共出售了12件商品,出现了超卖的情况。

三、使用分布式锁解决超卖

代码实现:

  @GetMapping(value = "/distributedLocksReduce")
    public String distributedLocksReduce() {
        int stock = (Integer) redisTemplate.opsForValue().get("stock");
        if (stock <= 0) {
            log.info("===>>>库存不足");
            return "库存不足";
        }
        String LOCK_KEY = "lockKey";
        String value = UUID.randomUUID().toString();
        // value值任意即可,秒杀设置锁的时间为1秒(根据实际情况更多)
        boolean absent = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, value, 1, TimeUnit.SECONDS);
        if (absent) {
            // 当前key没有锁,加锁成功
            log.info("===>>>加锁成功,获取并扣减库存");

            Integer sku = (Integer) redisTemplate.opsForValue().get("stock");
            //模拟只减少一个库存
            sku = sku - 1;
            if (sku < 0) {
                log.info("===>>>库存不足");
                // 执行脚本 删除锁
                redisTemplate.delete(LOCK_KEY);

                return "库存不足";
            }
            // 将扣减后的数量写入redis
            redisTemplate.opsForValue().set("stock", sku);

            log.info("===>>>减少库存成功,共出售" + successNum.incrementAndGet());

            // 执行脚本 删除锁
            List<String> lockKeys = Collections.singletonList(LOCK_KEY);
            String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) return 1 else return 0 end";
            RedisScript<Long> luaScript = RedisScript.of(lua, Long.class);
            // 删除锁
            Object execute = redisTemplate.execute(luaScript, lockKeys, value);

            log.info("===>>>抢购成功");
            return "抢购成功";
        } else {
            log.info("===>>>抢购失败");
            return "抢购失败";
        }
    }

执行结果:
这里我们使用JMeter并发进行测试
在这里插入图片描述

我们可以看到这里总共只出售了五件商品

四、使用Redis事务乐观锁解决超卖

代码实现:

  @GetMapping(value = "/optimisticReduce")
    public String optimisticReduce() {
        // 开启事务
        redisTemplate.setEnableTransactionSupport(true);
        List<Object> results = (List<Object>) redisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                // 监视key
                operations.watch("stock");
                Integer stock = (Integer) operations.opsForValue().get("stock");
                operations.multi();
                stock = stock - 1;
                if (stock < 0) {
                    log.info("===>>>库存不足");
                    return null;
                }
                operations.opsForValue().set("stock", stock);
                return operations.exec();
            }
        });
        if (results != null && results.size() > 0) {
            log.info("===>>>减少库存成功,共出售" + successNum.incrementAndGet());
            return "减少库存成功";
        }
        log.info("库存不足");
        return "库存不足";
    }

执行结果:
在这里插入图片描述

  • 20
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值