当前位置:   article > 正文

Redis分段锁实现超高并发秒杀

redis分段锁

参考尼恩著:《Java高并发核心编程》
技术自由圈

业务情景

还是秒杀场景,假设抖音直播间小杨哥上架6000单螺蛳粉,价格9.9买10包,限时1分钟秒杀,此时准备秒杀的人有1万人。我们首先从技术上来看看如果不进行优化是否可以通过redis分布式锁完成。

我们假设假设加锁之后,释放锁之前,查库存 -> 创建订单 -> 扣减库存,每个IO操作100ms,大概300毫秒。如下图所示:在这里插入图片描述
我们计算一下,一分钟秒杀6000单,那么一秒钟就应该成功下单100单,可是技术上我们一单需要300豪秒,一秒卖出不超过4单。就算扣减库存和创建订单是并行的那也需要200毫秒,一秒不超过5单能够秒杀成功。这就是技术瓶颈,因此,有没有一种技术大大提高秒杀性能?答案就是分段锁秒杀!

分段加锁的思想来源

先前的博客Java高并发核心编程—CAS与JUC原子类中介绍了JUC中的原子类—LongAdder。在争用激烈的场景下,会导致大量的CAS空自旋。比如,在大量的线程同时并发修改一个AtomicInteger时,可能有很多线程会不停地自旋,甚至有的线程会进入一个无限重复的循环中。大量的CAS空自旋会浪费大量的CPU资源,大大降低了程序的性能。大量的CAS操作还可能导致“总线风暴” 。在高并发场景下如何提升CAS操作性能呢?可以使用LongAdder替代AtomicInteger。
在这里插入图片描述
Java 8提供一个新的类LongAdder,以空间换时间的方式提升高并发场景下CAS操作性能。LongAdder核心思想就是热点分离,与ConcurrentHashMap的设计思想类似:将value值分离成一个数组,当多线程访问时,通过Hash算法将线程映射到数组的一个元素进行操作;而获取最终的value结果时,则将数组的元素求和。LongAdder的内部成员包含一个base值和一个cells数组。在最初无竞争时,只操作base的值;当线程执行CAS失败后, 才初始cells数组,并为线程分配所对应的元素。 相当于分段乐观锁!

使用LongAdder思想实现分段锁

我们的目标是60秒能够秒杀6000单,那么最低要做的1秒秒杀100单,我们技术是有前瞻性的,要做就做一秒秒杀500单!理论上一个线程一把锁下单最少需要200毫秒,因此,一秒最多下单5单!距离我们的500单差100倍,于是上锁吧!将1把锁扩展成100把分段锁,每个锁负责60单,因此在redis中我们需要将一个stock0=6000单分成stock1…stock100,每个stcok=60,这样就能避免并发修改!根据用户的userID进行Hash映射,再模100得到的是对应的锁,这里也可以使用随机访问策略,算法如下:
在这里插入图片描述

package com.crazymaker.springcloud.standard.lock;
import com.crazymaker.springcloud.common.util.RandomUtil;
import com.crazymaker.springcloud.common.util.ThreadUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Slf4j
@Data
@AllArgsConstructor
public class JedisMultiSegmentLock implements Lock {
    public static final int NO_SEG = -1;
    //拿到锁的线程
    private Thread thread;
    //拿到锁的状态
    private volatile boolean isLocked = false;
    //段数
    private final int segAmount;
    public static final int DEFAULT_TIMEOUT = 2000;
    public static final Long WAIT_GAT = Long.valueOf(100);

    //内部的锁
    InnerLock[] innerLocks = null;
    //被锁住的分段
    int segmentIndexLocked = NO_SEG;
    /**
     * 默认为2000ms
     */
    long expire = 2000L;
    int segmentIndex = 0;
    public JedisMultiSegmentLock(String lockKey, String requestId, int segAmount) {
        this.segAmount = segAmount;
        innerLocks = new InnerLock[segAmount];
        for (int i = 0; i < this.segAmount; i++) {
            //每一个分段,加上一个编号
            String innerLockKey = lockKey + ":" + i;
            innerLocks[i] = new InnerLock(innerLockKey, requestId);
        }
        segmentIndex = RandomUtil.randInModLower(this.segAmount);
    }
    /**
     * 获取一个分布式锁 , 超时则返回失败
     *
     * @return 获锁成功 - true | 获锁失败 - false
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        //本地可重入
        if (isLocked && thread == Thread.currentThread()) {
            return true;
        }
        expire = unit != null ? unit.toMillis(time) : DEFAULT_TIMEOUT;
        long startMillis = System.currentTimeMillis();
        Long millisToWait = expire;

        boolean localLocked = false;
        int turn = 1;
        InnerLock innerLock = innerLocks[segmentIndex];

        while (!localLocked) {
            localLocked = innerLock.lock(expire);
            if (!localLocked) {
                millisToWait = millisToWait - (System.currentTimeMillis() - startMillis);
                startMillis = System.currentTimeMillis();
                if (millisToWait > 0L) {
                    /**
                     * 还没有超时
                     */
                    ThreadUtil.sleepMilliSeconds(WAIT_GAT);
                    log.info("睡眠一下,重新开始,turn:{},剩余时间:{}", turn++, millisToWait);

                    segmentIndex++;
                    if (segmentIndex >= this.segAmount) {
                        segmentIndex = 0;
                    }
                    innerLock = innerLocks[segmentIndex];
                } else {
                    log.info("抢锁超时");
                    return false;
                }
            } else {
                segmentIndexLocked = segmentIndex;
                isLocked = true;
                localLocked = true;
                thread = Thread.currentThread();
            }
        }
        return isLocked;
    }

    /**
     * 抢夺锁
     */
    @Override
    public void lock() {
        throw new IllegalStateException(
                "方法 'lock' 尚未实现!");
    }
    //释放锁
    @Override
    public void unlock() {
        if (segmentIndexLocked == NO_SEG) {
            return;
        }
        this.innerLocks[segmentIndexLocked].unlock();
        segmentIndexLocked = NO_SEG;
        thread = null;
        isLocked = false;
    }

    @Override
    public Condition newCondition() {
        throw new IllegalStateException(
                "方法 'newCondition' 尚未实现!");
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new IllegalStateException(
                "方法 'lockInterruptibly' 尚未实现!");

    }

    @Override
    public boolean tryLock() {
        throw new IllegalStateException(
                "方法 'tryLock' 尚未实现!");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

注意:使用分段锁共享变量也需要分段,即一段一锁!不然并发修改导致超卖!以上如有错误,望指出!
参考:https://www.cnblogs.com/crazymakercircle/p/14731826.html#autoid-h2-12-6-0
!!!!!尼恩永远的神!!!!!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/424366
推荐阅读
相关标签
  

闽ICP备14008679号