Redis入门到实战(虎哥版)个人笔记-实战篇第三章“优惠券秒杀+分布式锁”

一、全局唯一id

1.为什么要建立全局唯一id?

(1)全局唯一id可以增加id复杂性,不让不怀好意的人轻易获取到一些敏感信息。

(2)数据库单个表的订单记录不能太大,太大影响性能;需要分表,这就要保证不同订单表中的id全局唯一。

2.创建全局唯一id的方式

(1)UUID(2)redis自增(3)雪花算法(4)数据库自增

这里采用redis自增方式:每天一个key,记录每天的订单量;ID的构造是“时间戳”+“计数器”

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.拼接并返回
        // 总共64位:最高位是符号位,为0;后面32位是当天的订单量;剩下31位是时间戳
        return timestamp << COUNT_BITS | count;
    }

二、秒杀业务(基础版)

(1)先来描述一下业务:获取到前端发来的请求,查询优惠券信息,先判断该秒杀券是否在活动时间内;在活动时间内就判断库存是否充足;充足则下单成功。

三、超卖问题

在高并发情况下,会存在超卖问题。原因在于当库存只剩下1的时候,这时候有一个线程判断库存还剩1,是充足的,准备操作数据库扣减库存;但在扣减库存之前,又有其他线程判断库存是否充足,也是充足的,也会操作数据库扣减库存。这就出现了超卖现象。

解决方法:毫无疑问,直接加锁。锁分为悲观锁和乐观锁两种,选哪种比较好呢?

(1)悲观锁:如果使用悲观锁,就要把查询库存和扣减库存这两个步骤加上锁,同一时刻只有一个线程能执行业务,其他线程只能阻塞等待,性能不是很好。常见的悲观锁有SynchronizedLock等。

(2)乐观锁:不用时时刻刻提防着别的线程来修改数据,你在修改数据的时候加个判断看看是否是正常的你才做修改。乐观锁的关键是判断之前查询得到的数据是否有被修改过。常见的方法有版本号法和CAS法。这里使用CAS法,先比较再交换。示例代码如下(主要体会乐观锁的思想,完整代码就不粘了):

        在修改的时候,再判断一下库存是否和之前查的一样,一样才修改成功。

boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1") //set stock = stock -1
            .eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update(); //where id = ? and stock = ?

        但是以上这种方式通过测试发现会有很多失败的情况,失败的原因在于:在使用乐观锁过程中假设100个线程同时都拿到了100的库存,然后大家一起去进行扣减,但是100个人中只有1个人能扣减成功,其他的人在处理时,他们在扣减时,库存已经被修改过了,所以此时其他线程都会失败。改进方法:判断条件改成库存大于0就可以扣减库存。

boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).update().gt("stock",0); //where id = ? and stock > 0

四、一人一单问题

秒杀的时候,一般一个用户只能下一单。

(1)解决方法:根据用户id和优惠券id查询订单,判断订单是否存在再执行业务。

(2)但是这样又会造成像超卖问题那样的线程安全问题,但也挺好解决的,加个互斥锁就行。

(3)但是这样问题又来了,这样只能解决单机情况下的一人一单,当你用多个机子部署服务器时,不同的机子锁监视器不同,每个机子的锁监视器只能管理当前机子的锁,所以在多机情况下,还是没能锁上该业务,一人一单问题仍未解决。

五、分布式锁

虎哥笔记原话:由于现在我们部署了多个tomcat,每个tomcat都有一个属于自己的jvm,那么假设在服务器A的tomcat内部,有两个线程,这两个线程由于使用的是同一份代码,那么他们的锁对象是同一个,是可以实现互斥的,但是如果现在是服务器B的tomcat内部,又有两个线程,但是他们的锁对象写的虽然和服务器A一样,但是锁对象却不是同一个,所以线程3和线程4可以实现互斥,但是却无法和线程1和线程2实现互斥,这就是 集群环境下,syn锁失效的原因,在这种情况下,我们就需要使用分布式锁来解决这个问题。

基于Redis的分布式锁实现思路:

  • 利用set nx ex获取锁,并设置过期时间,保存线程标示

  • 释放锁时先判断线程标示是否与自己一致,一致则通过lua脚本删除锁,保证删除锁的原子性

    • 特性:

      • 利用set nx满足互斥性

      • 利用set ex保证故障时锁依然能释放,避免死锁,提高安全性

      • 利用Redis集群保证高可用和高并发特性

Redisson分布式锁

1.可重入原理:利用hash结构记录线程id和重入次数,当有同一线程拿到锁时就加1,释放就减1。直到重入次数为0的时候,锁才释放。

2.可重试和超时续约:利用信号量和PubSub发布订阅功能实现等待、唤醒,获取锁失败的重试机制;利用看门狗机制来定时更新锁释放时间,使得锁不会超时释放。

3.mutilLock原理:首先,redis就不用主从集群了。加锁的时候,给每个reids主节点都加上锁,所有redis加上锁才算加锁成功;释放锁也是每个redis同时释放。

六、秒杀优化

        已经不想写了,要炸了,思路如下:

以上图片均来自虎哥给的资料

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值