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

news2025/1/10 16:18:39

参考尼恩著:《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' 尚未实现!");
    }
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/613215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

控制四旋翼飞行器以进行多目标航点导航的MPC算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

信不信,3招就能测出你的网工水平

大家好&#xff0c;我的网工朋友 老话常谈一下&#xff0c;在网工这行发展&#xff0c;技术肯定是第一位的。 从你入行的第一步起&#xff0c;就很看重你的技术水平了&#xff0c;越往后&#xff0c;就更注重技术水平和项目经验的融合度。 再往后&#xff0c;技术要有&#…

网络安全实验室|网络信息安全攻防学习平台(脚本关1-6)

传送门&#xff1a; http://hackinglab.cn/ 1. key又又不见了 点击此处开启抓包&#xff0c;send ti repeater 模块 yougotit_script_now2. 快速口算 脚本来源&#xff1a; https://blog.csdn.net/hzxtjx/article/details/125692349 import requests,re# 自动处理cookies&a…

【PCB专题】案例:PCB封装中哪些禁布区我们最常容易忘记增加

USB等直插器件焊接面禁布区 USB器件面那大家都知道有座子有自己的Place_Bound。不同的器件如果Place_Bound重叠的话会报DRC。 但是因为器件是只在一个面上,另一个面是焊接脚,或是另一面不是焊接脚,但PCB封装上为通孔。比如有一些器件从PCB上看是通孔,但PCB Layout人员没见…

c#如何将数据缓存至Redis

环境准备 首先肯定是需要安装redis啦&#xff01;这是我跑在docker的redis&#xff0c;和安装在本地的是 一样的效果 可视化工具Redis Desktop Manager。 使用方法 第一步&#xff0c;安装nuget包 Microsoft.Extensions.Caching.StackExchangRedis 创建一个asp.net.core项目…

Ubuntu20.04 终端打开不了的问题排查

Ubuntu20.04 终端打开不了的问题排查 今天用virtualbox安装了ubuntu20.04 问题&#xff1a;右键打开终端&#xff0c;怎么也打开不了&#xff01; 点了也没反应&#xff0c;或者鼠标转小圈圈&#xff0c;然后也没有反应… 解决方法&#xff1a; 1、Ctrl Alt F6 先切换到终…

ChatGPT:求求你憋再问我关于C++多态的任何问题了...

文章目录 &#x1f490;专栏导读&#x1f490;文章导读&#x1f337;多态在继承中的表现&#x1f33a;虚函数的重写&#x1f33a;虚函数重写的两个例外&#x1f3f5;️1.协变&#x1f3f5;️2.析构函数的重写 &#x1f33a;C11 override 和 final关键字&#x1f3f5;️final&am…

msvcr110.dll丢失怎么修复

msvcr110.dll是Microsoft Visual C 2012 Redistributable的一部分&#xff0c;它是一种动态链接库&#xff08;DLL&#xff09;&#xff0c;旨在存储许多Microsoft Visual C应用程序共享的功能。这些功能包括数学运算、字符串处理、内存分配和释放等。它在Windows操作系统中起着…

精通Java数组的艺术:从初学者到高手的进阶之路(一)

⭐ 数组⭐ 数组的定义⭐ 创建数组和初始化⭐ 数组常见操作⭐ 数组的遍历⭐ for-each 循环⭐ 数组的拷贝⭐ java.util.Arrays 类 ⭐ 数组 数组的概念 ⭐ 数组的定义 数组是相同类型数据的有序集合。其中&#xff0c;每一个数据称作一个元素&#xff0c;每个元素可以通过一个索引…

Python+Selenium 网页自动化 exe 程序编程实现(最全避坑指南)

前言 在我的日常工作中&#xff0c;经常需要在内网&#xff08;不连接互联网&#xff09;的网页版办公系统中进行抓取网页数据、修改表单等大量重复性的操作。我就想是否可以编写出自动化的工具&#xff0c;将这些日常琐碎的操作变得轻松而高效。虽然本人非计算机相关专业&…

OpenGL 纹理

1.简介 纹理是一个2D图片&#xff08;甚至也有1D和3D的纹理&#xff09;&#xff0c;它可以用来添加物体的细节&#xff1b;你可以想象纹理是一张绘有砖块的纸&#xff0c;无缝折叠贴合到你的3D的房子上&#xff0c;这样你的房子看起来就像有砖墙外表了。 为了能够把纹理映射(M…

Day973.授权码许可类型中,为什么一定要有授权码? -OAuth 2.0

授权码许可类型中&#xff0c;为什么一定要有授权码&#xff1f; Hi&#xff0c;我是阿昌&#xff0c;今天学习的是auth2中为什么一定要有授权码的内容。 OAuth 2.0 的授权码许可类型&#xff0c;在小兔打单软件的例子里面&#xff0c;小兔最终是通过访问令牌请求到小明的店铺…

使用raspberry pi pico 制作红绿灯

需要的东西&#xff1a;一块面包版、一块raspberry pi pico、红绿黄led灯各一颗、220欧电阻3只、若干线 编程软件&#xff1a;thonny 操作系统&#xff1a;deepin 23 结果展示&#xff1a; 使用raspberry pi pico 制作红绿灯 from machine import Pin import utime yellowled…

4种整流电路和5种滤波电路

4种整流电路和5种滤波电路 基本电路&#xff1a;一般直流稳压电源都使用220伏市电作为电源&#xff0c;经过变压、整流、滤波后输送给稳压电路进行稳压&#xff0c;最终成为稳定的直流电源。这个过程中的变压、整流、滤波等电路可以看作直流稳压电源的基础电路&#xff0c;没有…

前端人必须知道的三种移动跨平台方案

跨平台技术是前端人必备技能&#xff0c;今天就来为大家解读一下近几年业界主流的三大移动端跨平台方案&#xff1a; Web 天然跨平台&#xff1a; Web App、PWA&#xff08;Progressive Web Apps&#xff09;、Hybrid App、PHA&#xff08;Progress Hybrid App&#xff09;都可…

Springboot自定义starter

文章目录 前言1.引入依赖1.1 json的转换1.2 xml转换依赖 2.定义Formate核心转化接口3.实现核心接口json和xml的转换3.1 json转换的实现3.2 xml转换的实现 4. FormatProperties类5.FormatAutoConfiguration 类配置6.提供一个MyFormatTemplate 模板类7.注册到springboot8.创建spr…

cavity开盖制作的辅助层别

cavity开盖工艺制作的辅助层别 数量&#xff1a;6个

suricata的flow流会话管理分析1

在《suricata中的线程管理分析》一文中&#xff0c;我们看到suricata中有FlowWorker和FlowManager两个线程来处理流表&#xff0c;说明流表的实现应该不简单&#xff0c;果然&#xff0c;看了流相关的这块代码后&#xff0c;发现确实有点复杂&#xff0c;代码估计得慢慢坑&…

[SpringMVC]Controller控制器、Interceptor拦截器、RestFul风格、异常处理、JSON数据格式与AJAX请求

文章目录 MVC理论基础配置环境并搭建项目Controller控制器配置视图解析器和控制器RequestMapping详解RequestParam和RequestHeader详解CookieValue和SessionAttrbutie重定向和请求转发Bean的Web作用域 RestFul风格Interceptor拦截器创建拦截器多级拦截器 异常处理JSON数据格式与…

C# Socket入门编程winform案例(附下载链接)

C# socket编程实现信息的接收&#xff08;winform&#xff09; 点我下载项目资源 服务器端&#xff1a; 第一步&#xff1a;建立一个用于通信的Socket对象 第二步&#xff1a;使用bind绑定IP地址和端口号 第三步&#xff1a;使用listen监听客户端 第四步&#xff1a;使用accep…