Redis Stduy秒杀情况处理

全局唯一ID

在使用数据库自增id时,常存在一些问题:
1、id的规律性太明显,易被盗取信息
2、受单表数据量的限制
在这里插入图片描述
满足以上性质提供id生成器
在这里插入图片描述显然我们满足其所需要的的性质

Redis实现全局唯一id

基于上述的ID实现封装类

public class RedisIdWorker {
    /**
     * 开始时间戳
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;
    /**
     * 序列号的位数
     */
    private static final int COUNT_BITS = 32;

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix) {
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;

        // 2.生成序列号
        // 2.1.获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2.自增长
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

        // 3.拼接并返回
        return timestamp << COUNT_BITS | count;
    }
}

其中的count的生成方式值得思考
对于自增长的值,我们不可以使其随意的自增长,在后续加一个data
data可以方便我们统计当天(月 / 年)的生产量(订单量)

秒杀超卖

分析

在这里插入图片描述
在访问量比较大时,多线程中由于数据库的访问时间比较长,会出现库存为负数的情况
在这里插入图片描述
在多线程中,通常使用加锁的方式解决,但是由于悲观锁会导致性能大幅度的降低,相当于只能一个线程访问数据库,会大大影响用户体验,所以常常用乐观锁解决,关于悲观锁和乐观锁,可以去看看os

下面使用乐观锁的解决方式
在这里插入图片描述
很显然,这样我们可以在多线程中,既不影响用户体验,也基本解决了超卖问题,可以发现,version的作用完全可以用stock代替,所以就出现了cas法(Compare And Swap)
在这里插入图片描述
但是基于上述的cas法时,实践操作之后,失败率却大大提高了,原因时乐观锁的的设置需要stock要求过高,所以我们可以将stock的判断设置成大于0即可,实践操作之后发现成功率大幅度提升
下面提供代码实现

public Result seckillVoucher(Long voucherId) {
        // 1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            // 尚未开始
            return Result.fail("秒杀尚未开始!");
        }
        // 3.判断秒杀是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            // 尚未开始
            return Result.fail("秒杀已经结束!");
        }
        // 4.判断库存是否充足
        if (voucher.getStock() < 1) {
            // 库存不足
            return Result.fail("库存不足!");
        }
        // 5.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1") // set stock = stock - 1
                .eq("voucher_id", voucherId()).gt("stock", voucher.getStock()) // where id = ? and stock > 0
                .update();
        if (!success) {
            // 扣减失败
            return Result.fail("库存不足");
        }
        //6.创建订单
        ...

        return createVoucherOrder(voucherId);
    }

总结

在这里插入图片描述

实现一人一单

在这里插入图片描述
== 实际业务中我们要注意以下几点情况 ==
1.首先我们想到的肯定是判断该用户是否已经下单过 -----在高并发下,一个用户如果同时发送多个请求,乐观锁的失误率也会大大增加
2.所以想到使用悲观锁控制,注意加锁的位置,如果直接加载数据库处理位置,整个程序将成为串行,会大大影响体验。
3.这里的悲观锁的位置要考虑spring的事务处理 == !!! ==

但是在集群模式下,同一个用户多次发送请求时
在这里插入图片描述
就需要一个锁来管理所有的jvm 对一个用户判断时,考虑所有的jvm中这个用户是否可以操作

就需要分布式锁。。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值