当前位置: 首页 > news >正文

【仿12306项目】通过加“锁”,解决高并发抢票的超卖问题

文章目录

  • 一. 测试工具
  • 二. 超卖现象演示
  • 三. 原因分析
  • 四. 解决办法
    • 方法一:加synchronized锁
      • 1. 单个服务节点情况
      • 2. 增加服务器节点,分布式环境synchronized失效演示
    • 方法二:使用Redis分布式锁锁解决超卖问题
      • 1. 添加Redis分布式锁
      • 2. 结果
    • 方法三:使用Redisson看门狗解决锁超时的问题
      • 1. 添加Redisson依赖
      • 2. 使用Redisson看门狗
      • 3. 结果
    • 方法四:Redis红锁解决单机Redis宕机问题

一. 测试工具

测试工具:JMeter 参考文档
5.6.3版本下载链接:下载链接

二. 超卖现象演示

在这里插入图片描述

以一等座票为例,设置有8张票,此时开启500个线程同时购票:

在这里插入图片描述
两秒内请求全部发送完成。此时查看数据库车票表:
在这里插入图片描述

显示为负数,超卖了55张!!

三. 原因分析

问题代码段:

        // 查出余票记录,需要得到真实的库存DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);LOG.info("查出余票记录:{}", dailyTrainTicket);// 扣减余票数量,并判断余票是否足够reduceTickets(req, dailyTrainTicket);

若某时刻余票为1,如果这时候有多个线程并发进入到这里,那么它们同时查数据库,均可查到有余票1,并且都能校验为余票足够,从而执行下面的购票逻辑

因此为了避免这样的问题产生,应该对这一段代码加锁,限制并发访问

四. 解决办法

方法一:加synchronized锁

1. 单个服务节点情况

在上述问题代码段产生的方法上加上synchronized

public synchronized void doConfirm(ConfirmOrderDoReq req) {

再次执行500个线程同时购票:
在这里插入图片描述

优点:可以看到,此时成功解决超卖问题,也没有重卖
不足:由于上锁,相当于是一个个的执行,会导致售票效率降低!响应较慢!(线程花了十多秒才结束)

2. 增加服务器节点,分布式环境synchronized失效演示

在这里插入图片描述

增加一个服务节点,模拟分布式环境,再次测试

在这里插入图片描述

可以看到,超卖了一张票。
因此,分布式环境下,synchronized会失效! 只能解决单机锁问题,不能解决多机锁问题

方法二:使用Redis分布式锁锁解决超卖问题

1. 添加Redis分布式锁

在问题代码段的方法中,添加Redis分布式锁:

public void doConfirm(ConfirmOrderDoReq req) {String key = req.getDate() + "-" + req.getTrainCode();// 设置超时时间为5秒Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(key, key, 5, TimeUnit.SECONDS);if (Boolean.TRUE.equals(setIfAbsent)) {LOG.info("恭喜,抢到锁了!");} else {// 只是没抢到锁,并不知道票抢完了没,所以提示稍候再试LOG.info("很遗憾,没抢到锁!");throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);}try{...}finally{LOG.info("购票流程结束,释放锁!lockKey:{}", lockKey);redisTemplate.delete(lockKey);LOG.info("购票流程结束,释放锁!");}
}

将key设置为日期和车次的拼接,意为只有同一日期同一车次之间进行上锁

2. 结果

解决超卖问题,但有时还会剩票没卖完。因此redis分布式锁也会导致效率降低,同时,由于设置了timeout为5秒,若购票业务或者其他业务耗时超过5秒,则此时会提前释放锁,造成并发问题

方法三:使用Redisson看门狗解决锁超时的问题

由于redis分布式锁存在业务结束时间比设置的超时时间长的问题,因此可以引入守护线程,在一定时间的时候,延长超时时间,同时守护线程会随着主线程终止而终止,也不会无限延长终止时间

1. 添加Redisson依赖

            <!--至少3.18.0版本,才支持spring boot 3--><!--升级到3.20.0,否则打包生产会报错:Could not initialize class org.redisson.spring.data.connection.RedissonConnection--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.21.0</version></dependency>

2. 使用Redisson看门狗

修改上锁与解锁内容,使用Redission自带的方法实现看门狗:

public void doConfirm(ConfirmOrderDoReq req) {// 获取分布式锁String lockKey = RedisKeyPreEnum.CONFIRM_ORDER + "-" + DateUtil.formatDate(req.getDate()) + "-" + req.getTrainCode();RLock lock = null;try {// 使用redisson,自带看门狗lock = redissonClient.getLock(lockKey);/**waitTime – the maximum time to acquire the lock 等待获取锁时间(最大尝试获得锁的时间),超时返回falseleaseTime – lease time 锁时长,即n秒后自动释放锁time unit – time unit 时间单位*/// boolean tryLock = lock.tryLock(30, 10, TimeUnit.SECONDS); // 不带看门狗boolean tryLock = lock.tryLock(0, TimeUnit.SECONDS); // 带看门狗if (tryLock) {LOG.info("恭喜,抢到锁了!");// 可以把下面这段放开,只用一个线程来测试,看看redisson的看门狗效果// for (int i = 0; i < 30; i++) {//     Long expire = redisTemplate.opsForValue().getOperations().getExpire(lockKey);//     LOG.info("锁过期时间还有:{}", expire);//     Thread.sleep(1000);// }} else {// 只是没抢到锁,并不知道票抢完了没,所以提示稍候再试LOG.info("很遗憾,没抢到锁");throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);}// 业务逻辑...}catch (InterruptedException e){LOG.error("购票流程异常", e);}finally {LOG.info("购票流程结束,释放锁!");if (null != lock && lock.isHeldByCurrentThread()) {lock.unlock();}}}

3. 结果

设置多线程进行模拟抢票,实验多次后,发现第一次抢票始终是会还剩下一些票,多次抢票后票数为0,且不会出现超卖。

方法四:Redis红锁解决单机Redis宕机问题

以上带看门狗的锁存在以下问题:
如果Redis节点宕机挂了,同时又有一个线程拿到了某个锁,那么此时如果Redis节点自动切换,又一个线程进来就有可能拿到同一个锁,因为此时Redis中没有该锁的key,违反了锁的互斥性,就会出现一些问题。
此时就可以利用Redis红锁来解决单机Redis宕机问题

Redis红锁用于Redis多节点情况。通常设有奇数个节点的Redis服务,每个线程在进入上锁代码段时,只有拿到半数以上的Redis的锁,才算拿到锁。

例如:
有 A B C D E 五个节点的Redis服务
线程1:拿到ABC
线程2:拿到DE
则此时仅算线程1拿到锁,线程二没拿到锁

相关文章:

【仿12306项目】通过加“锁”,解决高并发抢票的超卖问题

文章目录 一. 测试工具二. 超卖现象演示三. 原因分析四. 解决办法方法一&#xff1a;加synchronized锁1. 单个服务节点情况2. 增加服务器节点&#xff0c;分布式环境synchronized失效演示 方法二&#xff1a;使用Redis分布式锁锁解决超卖问题1. 添加Redis分布式锁2. 结果 方法三…...

wow-agent---task4 MetaGPT初体验

先说坑&#xff1a; 1.使用git clone模式安装metagpt 2.模型尽量使用在线模型或本地高参数模型。 这里使用python3.10.11调试成功 一&#xff0c;安装 安装 | MetaGPT&#xff0c;参考这里的以开发模型进行安装 git clone https://github.com/geekan/MetaGPT.git cd /you…...

MVANet——小范围内捕捉高分辨率细节而在大范围内不损失精度的强大的背景消除模型

一、概述 前景提取&#xff08;背景去除&#xff09;是现代计算机视觉的关键挑战之一&#xff0c;在各种应用中的重要性与日俱增。在图像编辑和视频制作中有效地去除背景不仅能提高美学价值&#xff0c;还能提高工作流程的效率。在要求精确度的领域&#xff0c;如医学图像分析…...

94,【2】buuctf web [安洵杯 2019]easy_serialize_php

进入靶场 可以查看源代码 <?php // 从 GET 请求中获取名为 f 的参数值&#xff0c;并赋值给变量 $function // 符号用于抑制可能出现的错误信息 $function $_GET[f];// 定义一个名为 filter 的函数&#xff0c;用于过滤字符串中的敏感词汇 function filter($img) {// 定义…...

LabVIEW如何有效地进行数据采集?

数据采集&#xff08;DAQ&#xff09;是许多工程项目中的核心环节&#xff0c;无论是测试、监控还是控制系统&#xff0c;准确、高效的数据采集都是至关重要的。LabVIEW作为一个图形化编程环境&#xff0c;提供了丰富的功能来实现数据采集&#xff0c;确保数据的实时性与可靠性…...

6 [新一代Github投毒针对网络安全人员钓鱼]

0x01 前言 在Github上APT组织“海莲花”发布存在后门的提权BOF&#xff0c;通过该项目针对网络安全从业人员进行钓鱼。不过其实早在几年前就已经有人对Visual Studio项目恶意利用进行过研究&#xff0c;所以投毒的手法也不算是新的技术。但这次国内有大量的安全从业者转发该钓…...

《Origin画百图》之脊线图

1.数据准备&#xff1a;将数据设置为y 2.选择绘图>统计图>脊线图 3.生成基础图形&#xff0c;并不好看&#xff0c;接下来对图形属性进行设置 4.双击图形>选择图案>颜色选择按点>Y值 5.这里发现颜色有色阶&#xff0c;过度并不平滑&#xff0c;需要对色阶进行更…...

linux 函数 sem_init () 信号量、sem_destroy()

&#xff08;1&#xff09; &#xff08;2&#xff09; 代码举例&#xff1a; #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h>sem_t semaphore;void* thread_function(void* arg) …...

Kafka架构

引言 Kafka 凭借其独树一帜的分区架构&#xff0c;在消息中间件领域展现出了卓越的性能表现。其分区架构不仅赋予了 Kafka 强大的并行计算能力&#xff0c;使其能够高效处理海量数据&#xff0c;还显著提升了系统的容灾能力&#xff0c;确保在复杂的运行环境中始终保持稳定可靠…...

刷题记录 动态规划-2: 509. 斐波那契数

题目&#xff1a;509. 斐波那契数 难度&#xff1a;简单 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n…...

RDP协议详解

以下内容包含对 RDP&#xff08;Remote Desktop Protocol&#xff0c;远程桌面协议&#xff09;及其开源实现 FreeRDP 的较为系统、深入的讲解&#xff0c;涵盖协议概要、历史沿革、核心原理、安全机制、安装与使用方法、扩展与未来发展趋势等方面&#xff0c; --- ## 一、引…...

设计模式的艺术-观察者模式

行为型模式的名称、定义、学习难度和使用频率如下表所示&#xff1a; 1.如何理解观察者模式 一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变&#xff0c;它们之间将产生联动&#xff0c;正所谓“触一而牵百发”。为了更好地描述对象之间存在的这种一对多&…...

【C语言设计模式学习笔记1】面向接口编程/简单工厂模式/多态

面向接口编程可以提供更高级的抽象&#xff0c;实现的时候&#xff0c;外部不需要知道内部的具体实现&#xff0c;最简单的是使用简单工厂模式来进行实现&#xff0c;比如一个Sensor具有多种表示形式&#xff0c;这时候可以在给Sensor结构体添加一个enum类型的type&#xff0c;…...

Baklib如何优化企业知识管理提升团队协作与创新能力分析

内容概要 在现代企业中&#xff0c;知识管理已经成为提升竞争力的关键因素之一。Baklib作为一种全面的知识管理解决方案&#xff0c;致力于帮助企业高效整合和运用内部及外部知识资源。它通过建立统一的知识管理框架&#xff0c;打破了部门之间的信息壁垒&#xff0c;实现了跨…...

Dubbo view

1、 说说Dubbo核心的配置有哪些&#xff1f; 答&#xff1a; 配置 配置说明 dubbo:service 服务配置 dubbo:reference 引用配置 dubbo:protocol 协议配置 dubbo:application 应用配置 dubbo:module 模块配置 dubbo:registry 注册中心配置 dubbo:monitor 监控中心配置 dubbo:pr…...

分享刷题过程中有价值的两道题目

小编在这里先祝大家新的一年里所愿皆得&#xff0c;万事顺意&#xff0c;天天开心&#xff01;&#xff01;&#xff01; 一.水仙花数 题目描述&#xff1a; 求100∼999中的水仙花数。若三位数ABCA^3B^3C^3&#xff0c;则称ABC为水仙花数。例如153&#xff0c;135333112527153&…...

蓝桥杯例题六

奋斗是一种态度&#xff0c;也是一种生活方式。无论我们面对什么样的困难和挑战&#xff0c;只要心怀梦想&#xff0c;坚持不懈地努力&#xff0c;就一定能够迈向成功的道路。每一次失败都是一次宝贵的经验&#xff0c;每一次挫折都是一次锻炼的机会。在困难面前&#xff0c;我…...

DeepSeek 详细使用教程

1. 简介 DeepSeek 是一款基于人工智能技术的多功能工具&#xff0c;旨在帮助用户高效处理和分析数据、生成内容、解答问题、进行语言翻译等。无论是学术研究、商业分析还是日常使用&#xff0c;DeepSeek 都能提供强大的支持。本教程将详细介绍 DeepSeek 的各项功能及使用方法。…...

《tcp/ip协议详解》,tcp/ip协议详解

TCP/IP协议&#xff08;Transmission Control Protocol/Internet Protocol&#xff09;是网络通信协议的一种&#xff0c;也被称为“Internet协议”&#xff0c;是Internet上运行的基本协议&#xff0c;广泛应用于各种网络环境和应用场合。以下是对TCP/IP协议的详细解析&#x…...

游戏引擎 Unity - Unity 设置为简体中文、Unity 创建项目

Unity Unity 首次发布于 2005 年&#xff0c;属于 Unity Technologies Unity 使用的开发技术有&#xff1a;C# Unity 的适用平台&#xff1a;PC、主机、移动设备、VR / AR、Web 等 Unity 的适用领域&#xff1a;开发中等画质中小型项目 Unity 适合初学者或需要快速上手的开…...

【数据结构】_时间复杂度相关OJ(力扣版)

目录 1. 示例1&#xff1a;消失的数字 思路1&#xff1a;等差求和 思路2&#xff1a;异或运算 思路3&#xff1a;排序&#xff0b;二分查找 2. 示例2&#xff1a;轮转数组 思路1&#xff1a;逐次轮转 思路2&#xff1a;三段逆置&#xff08;经典解法&#xff09; 思路3…...

[Java]异常

在程序运行时&#xff0c;如果遇到问题&#xff08;比如除以零、文件找不到等&#xff09;&#xff0c;程序会发生异常。异常就像是程序的“错误提醒”&#xff0c;当程序运行中出错时&#xff0c;它会停止&#xff0c;给出一个错误信息。我们可以通过异常处理来控制这些错误&a…...

【C++语言】卡码网语言基础课系列----13. 链表的基础操作I

文章目录 背景知识链表1、虚拟头节点(dummyNode)2、定义链表节点3、链表的插入 练习题目链表的基础操作I具体代码实现 小白寄语诗词共勉 背景知识 链表 与数组不同&#xff0c;链表的元素存储可以是连续的&#xff0c;也可以是不连续的&#xff0c;每个数据除了存储本身的信息…...

Vue.js组件开发-实现图片浮动效果

使用Vue实现图片浮动效果 实现思路 将使用Vue的单文件组件&#xff08;.vue&#xff09;来实现图片浮动效果。主要思路是通过CSS的transform属性结合JavaScript的定时器来改变图片的位置&#xff0c;从而实现浮动效果。 代码实现 <template><!-- 定义一个包含图片…...

自制Windows系统(十一、Windows11GUI)

开源地址&#xff1a;下载&#xff08;Work(Windows11gui).img&#xff09; 上图 部分代码&#xff1a; void init_screen8(char *vram, int x, int y) { int *fat; unsigned char c; struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR; boxfill8(vram, x, 136, 0, …...

索罗斯的“反身性”(Reflexivity)理论:市场如何扭曲现实?(中英双语)

索罗斯的“反身性”&#xff08;Reflexivity&#xff09;理论&#xff1a;市场如何扭曲现实&#xff1f; 一、引言&#xff1a;市场是镜子&#xff0c;还是哈哈镜&#xff1f; 在传统经济学中&#xff0c;市场通常被认为是一个理性、有效的反映现实的系统。按照经典经济学理论…...

力扣257. 二叉树的所有路径(遍历思想解决)

Problem: 257. 二叉树的所有路径 文章目录 题目描述思路复杂度Code 题目描述 思路 遍历思想(利用二叉树的先序遍历) 利用先序遍历的思想&#xff0c;我门用一个List变量path记录当前先序遍历的节点&#xff0c;当遍历到根节点时&#xff0c;将其添加到另一个List变量res中&…...

使用朴素贝叶斯对散点数据进行分类

本文将通过一个具体的例子&#xff0c;展示如何使用 Python 和 scikit-learn 库中的 GaussianNB 模型&#xff0c;对二维散点数据进行分类&#xff0c;并可视化分类结果。 1. 数据准备 假设我们有两个类别的二维散点数据&#xff0c;每个类别包含若干个点。我们将这些点分别存…...

如何实现滑动列表功能

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了沉浸式状态栏相关的内容&#xff0c;本章回中将介绍SliverList组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的SliverList组件是一种列表类组件&#xff0c;类似我们之前介…...

计算机网络一点事(22)

地址解析协议ARP ARP&#xff1a;查询Mac地址 ARP表&#xff08;ARP缓存&#xff09;&#xff1a;记录映射关系&#xff0c;一个数据结构&#xff0c;定期更新ARP表 过程&#xff1a;请求分组&#xff0c;响应分组 动态主机配置协议DHCP 分配IP地址&#xff0c;配置默认网关…...