spring boot学习第八篇:通过spring boot、jedis实现秒单
参考:Redis实现分布式锁的7种方案 - 知乎
1、 准备数据库表,如下SQL表示库存表,有主键ID和库存数量字段
CREATE TABLE `t_stock` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`quantity` bigint(20) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
初始数据id quantity
1111 9
2、pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.4</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.hmblogs</groupId><artifactId>hmblogs</artifactId><version>0.0.1-SNAPSHOT</version><name>hmblogs</name><description>hmblogs</description><properties><java.version>8</java.version><druid.version>1.2.8</druid.version><log4jdbc.version>1.16</log4jdbc.version></properties><dependencies><!-- druid数据源驱动 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- mybatis --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--Mysql依赖包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--监控sql日志--><dependency><groupId>org.bgee.log4jdbc-log4j2</groupId><artifactId>log4jdbc-log4j2-jdbc4.1</artifactId><version>${log4jdbc.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.9</version></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
3、应用配置文件
server:port: 8081servlet.context-path: /#配置数据源
spring:datasource:druid:db-type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpyurl: jdbc:log4jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:eladmin}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=falseusername: ${DB_USER:root}password: ${DB_PWD:123456}
4、StockMapper.xml如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.hmblogs.backend.dao.StockMapper"><resultMap id="BaseResultMap" type="com.hmblogs.backend.entity.Stock"><id column="id" property="id"/><result column="quantity" property="quantity"/></resultMap><sql id="BaseResultMap">id, quantity</sql><select id="findAll" resultMap="BaseResultMap">select<include refid="BaseResultMap"/>from t_stock</select><select id="findById" resultMap="BaseResultMap" parameterType="com.hmblogs.backend.entity.Stock">select<include refid="BaseResultMap"/>from t_stockwhere id=#{id}</select><update id="updateStockById" parameterType="com.hmblogs.backend.entity.Stock">update t_stock set quantity=quantity-1 where id=#{id}</update>
</mapper>
5、BackendApplication
package com.hmblogs.backend;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class BackendApplication {public static void main(String[] args) {SpringApplication.run(BackendApplication.class, args);}}
6、Stock.java如下
package com.hmblogs.backend.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("t_stock")
public class Stock {@TableId(value="id", type = IdType.AUTO)private Integer id;private Integer quantity;}
7、StockMapper
package com.hmblogs.backend.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hmblogs.backend.entity.Stock;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface StockMapper extends BaseMapper<Stock> {List<Stock> findAll();Stock findById(Stock stock);Integer updateStockById(Stock stock);
}
8、OrderController
package com.hmblogs.backend.controller;import com.hmblogs.backend.dao.StockMapper;
import com.hmblogs.backend.entity.Stock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;@RestController
@Slf4j
public class OrderController {@Autowiredprivate StockMapper stockMapper;//总库存private long nKuCuen = 0;//商品key名字private String shangpingKey = "computer_key";//获取锁的超时时间 秒private int timeout = 30 * 1000;@GetMapping("/qiangdan")public List<String> qiangdan() {//抢到商品的用户List<String> shopUsers = new ArrayList<>();//构造很多用户List<String> users = new ArrayList<>();IntStream.range(0, 10000).parallel().forEach(b -> {users.add("神牛-" + b);});//初始化库存nKuCuen = 10;//模拟开抢users.parallelStream().forEach(b -> {String shopUser = qiang(b);if (!StringUtils.isEmpty(shopUser)) {shopUsers.add(shopUser);}});return shopUsers;}/*** 模拟抢单动作** @param b* @return*/private String qiang(String b) {//用户开抢时间long startTime = System.currentTimeMillis();//未抢到的情况下,30秒内继续获取锁while ((startTime + timeout) >= System.currentTimeMillis()) {//商品是否剩余if (nKuCuen <= 0) {break;}Jedis jedisCom = new Jedis("localhost",6379);jedisCom.auth("heming");if (jedisCom.setnx(shangpingKey, b)==1) {//用户b拿到锁log.info("用户{}拿到锁...", b);try {//商品是否剩余if (nKuCuen <= 0) {break;}//模拟生成订单耗时操作,方便查看:神牛-50 多次获取锁记录try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//抢购成功,商品递减,记录用户nKuCuen -= 1;int id = 1111;Stock stock = new Stock();stock.setId(id);Stock stockDo = stockMapper.findById(stock);Integer quantity = stockDo.getQuantity();if(quantity!=null && quantity>0){stockMapper.updateStockById(stock);log.info("update success.");}else{log.info("no update.");}//抢单成功跳出log.info("用户{}抢单成功跳出...所剩库存:{}", b, nKuCuen);return b + "抢单成功,所剩库存:" + nKuCuen;} finally {log.info("用户{}释放锁...", b);//释放锁jedisCom.del(shangpingKey, b);}}}return "";}}
9、验证
浏览器访问http://localhost:8081/qiangdan

查看Idea的console内容,

将console内容拿到notepad++里面搜索

但是,搜索"update success."内容,预期是9次,实际也是9次,符合我的需要,没有让库存变成负数,
查看数据库表的库存,id为1111的记录的quantity为0,不是1,也不是负数

10、继续第二种纬度的验证
StockController的代码如下:
package com.hmblogs.backend.controller;import com.hmblogs.backend.dao.StockMapper;
import com.hmblogs.backend.entity.Stock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import java.util.Date;@RestController
@Slf4j
public class StockController {@Autowiredprivate StockMapper stockMapper;/*** redis test* @return*/@GetMapping(value = "/reduceStock")public void redisTestLock(){log.info("reduceStock");int id = 1111;String key = "reduceStock"+id;String time = new Date().getTime()+"";Jedis jedisCom = new Jedis("localhost",6379);jedisCom.auth("heming");if (jedisCom.setnx(key, time)==1) {log.info("{}生成锁...", time);try{Stock stock = new Stock();stock.setId(id);Stock stockDo = stockMapper.findById(stock);Integer quantity = stockDo.getQuantity();if(quantity!=null && quantity>0){stockMapper.updateStockById(stock);log.info("update success.");}else{log.info("no update.");}} finally {log.info("{}释放锁...", time);//释放锁jedisCom.del(key, time);}}}
}
将库存改为10,验证,通过压测验证有没有保证锁应该有的作用,控制查库存和当不小于0的时候减少库存这2个逻辑的原子性,预期做到,实际做到了。利用APIPost工具做压测,如下图所示

查看server的控制台


然后把这些内容复制到notepad++里面搜索,
搜索reduceStock,有100个结果

搜索"生成锁..."和"释放锁...",都有14次结果,说明jedisCom.setnx(key, time)==1成立的次数有14次。
搜索"update success.",有10次结果,说明减少库存减少了10次,

此时查看数据库表的库存,发现为0

搜索"no update.",发现有4个结果,说明有4次获得锁了,但是库存已经是0了,不能再减库存了,库存不能为负的。

相关文章:
spring boot学习第八篇:通过spring boot、jedis实现秒单
参考:Redis实现分布式锁的7种方案 - 知乎 1、 准备数据库表,如下SQL表示库存表,有主键ID和库存数量字段 CREATE TABLE t_stock (id bigint(20) NOT NULL AUTO_INCREMENT,quantity bigint(20) NOT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEF…...
NineAi 新版AI系统网站源码 ChatGPT
简介: Nine AI.ChatGPT是基于ChatGPT开发的一个人工智能技术驱动的自然语言处理工具,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流,甚至能完成撰写邮件、视频脚本、文案、翻译、代码,写论文等任务。 NineAi 新版A…...
开源监控服务一瞥:Prometheus、Grafana、Zabbix、Nagios、Icinga和Open-Falcon
前言 随着信息技术的发展,监控服务在维护系统稳定性和性能方面变得越来越重要。本文将比较一些流行的开源监控服务,以帮助你选择适合你需求的解决方案。 监控服务对比 监控服务特点优势不足性能扩展性安全性Prometheus- 多维度数据模型- 监控容器化环…...
使用WAF防御网络上的隐蔽威胁之扫描器
在网络安全领域,扫描器是用于侦察和识别网络系统漏洞的工具。 它们可以帮助网络管理员识别安全漏洞,也可能被攻击者用来寻找攻击目标。 扫描器的基本概念 定义:扫描器是一种自动化工具,用于探测网络和服务器中的漏洞、开放端口、…...
企业信息安全管理制度
随着信息化程度的日益推进,企业信息的脆弱性也日益暴露。如何规范日趋复杂的信息安全保障体系建设,如何进行信息风险评估保护企业的信息资产不受侵害,已成为当前行业实现信息化运作亟待解决的问题。 一、企业的信息及其安全隐患 在我公司&am…...
Qt中的线程池
Qt中的线程池 目录 1 为什么需要线程池 2 Qt中有哪些方式实现线程池 3 如何通过QThreadPool类实现线程池 4 如何通过QtConcurrent库实现线程池 5 如何通过自定义的方式实现线程池 5 小结 1 为什么需要线程池 线程池是多线程编程中常用的一种技术,可以帮助管理系统中…...
使用Spring Boot集成中间件:Elasticsearch基础->提高篇
使用Spring Boot集成中间件:Elasticsearch基础->提高篇 导言 Elasticsearch是一个开源的分布式搜索和分析引擎,广泛用于构建实时的搜索和分析应用。在本篇博客中,我们将深入讲解如何使用Spring Boot集成Elasticsearch,实现数…...
【Docker】Dockerfile构建最小镜像
🥳🥳Welcome 的Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 🥳🥳Welcome 的Huihuis Code World ! !🥳🥳 前言 一.Dockerfile是什么 二.Dock…...
【严重】GitLab 以其他用户身份执行 Slack 命令
漏洞描述 GitLab 是由GitLab公司开发的、基于Git的集成软件开发平台。使用 Slack 命令在 Slack 聊天环境中运行常见的 GitLab 操作。 GitLab 受影响版本中,由于配置Slack/Mattermost 集成时,未正确验证用户身份信息,导致攻击者可以使用其他…...
【卡梅德生物】纳米抗体文库构建
纳米抗体文库构建服务是一项提供定制化纳米抗体文库的服务,旨在满足研究者和生物制药公司对高质量抗体的需求。这项服务通常包括以下主要步骤: 1.抗原设计和制备: -客户提供目标抗原信息,或由服务提供商协助设计抗原。 -抗原制…...
MySQL修炼手册6:子查询入门:在查询中嵌套查询
目录 写在开头1 子查询基础概念1.1 了解子查询的基本概念1.2 子查询与主查询的关系 2 标量子查询详细展开2.1 学会使用标量子查询2.1.1 在SELECT语句中使用2.1.2 在WHERE子句中使用2.1.3 在ORDER BY子句中使用 2.2 标量子查询在条件判断中的应用2.2.1 使用比较运算符2.2.2 使用…...
01章【JAVA开发入门】
计算机基本概念 计算机组成原理 计算机组装 计算机:电子计算机,俗称电脑。是一种能够按照程序运行,自动、高速处理海量数据的现代化智能电子设备。由硬件和软件所组成,没有安装任何软件的计算机称为裸机。常见的形式有台式计算机、…...
ARM day1
一、概念 ARM可以工作的七种模式用户、系统、快中断、中断、管理、终止、未定义ARM核的寄存器个数 37个32位长的寄存器,当前处理器的模式决定着哪组寄存器可操作,且任何模式都可以存取: PC(program counter程序计数器) CPSR(current program…...
ImageNet Classification with Deep Convolutional 论文笔记
✅作者简介:人工智能专业本科在读,喜欢计算机与编程,写博客记录自己的学习历程。 🍎个人主页:小嗷犬的个人主页 🍊个人网站:小嗷犬的技术小站 🥭个人信条:为天地立心&…...
Spring Boot中加@Async和不加@Async有什么区别?设置核心线程数、设置最大线程数、设置队列容量是什么意思?直接在yml中配置线程池
在 Spring 中,Async 注解用于将方法标记为异步执行的方法。当使用 Async 注解时,该方法将在单独的线程中执行,而不会阻塞当前线程。这使得方法可以在后台执行,而不会影响主线程的执行。 在您提供的代码示例中,a1() 和…...
自动化理论基础(2)—开发语言之Python
一、知识汇总 掌握 Python 编程语言需要具备一定的基础知识和技能,特别是对于从事自动化测试等领域的工程师。以下是掌握 Python 的一些关键方面: 基本语法: 理解 Python 的基本语法,包括变量、数据类型、运算符、条件语句、循环…...
Spark算子(RDD)超细致讲解
SPARK算子(RDD)超细致讲解 map,flatmap,sortBykey, reduceBykey,groupBykey,Mapvalues,filter,distinct,sortBy,groupBy共10个转换算子 (一)转换算子 1、map from pyspark import SparkContext# 创建SparkContext对象 sc Spark…...
转盘寿司(100%用例)C卷 (JavaPythonC++Node.jsC语言)
寿司店周年庆,正在举办优惠活动回馈新老客户。 寿司转盘上总共有n盘寿司,prices[i]是第i盘寿司的价格,如果客户选择了第i盘寿司,寿司店免费赠送客户距离,第i盘寿司最近的下一盘寿司i,前提是prices[j]< prices[i],如果没有满足条件的j,则不赠送寿司。 每个价格的寿司都…...
【python】搭配Miniconda使用VSCode
现在的spyder总是运行出错,启动不了,尝试使用VSCode。 一、在VSCode中使用Miniconda管理的Python环境,可以按照以下步骤进行: a. 确保Miniconda环境已经安装并且正确配置。 b. 打开VSCode,安装Python扩展。 打开VS…...
从购买服务器到部署前端VUE项目
购买 选择阿里云服务器,地址:https://ecs.console.aliyun.com/home。学生会送一个300的满减券,我买了一个400多一年的,用券之后100多点。 使用SSH连接服务器 我选择的是vscode 中SSH工具。 安装一个插件 找到配置文件配置一下…...
CLIP-GmP-ViT-L-14图文匹配工具实测:电商搜图、智能相册应用场景解析
CLIP-GmP-ViT-L-14图文匹配工具实测:电商搜图、智能相册应用场景解析 你有没有想过,当你在电商平台用一张随手拍的照片搜索商品时,背后的技术是怎么工作的?或者,当你对着手机相册输入“去年夏天在海边拍的日落”&…...
告别桌面混乱:NoFences让文件管理效率提升80%的空间收纳方案
告别桌面混乱:NoFences让文件管理效率提升80%的空间收纳方案 【免费下载链接】NoFences 🚧 Open Source Stardock Fences alternative 项目地址: https://gitcode.com/gh_mirrors/no/NoFences 每天在杂乱的桌面图标中寻找文件,就像在堆…...
别再找插件了!手把手教你用uni-app的Canvas API画一个带渐变和刻度的环形进度条
原生Canvas魔法:在uni-app中打造高性能渐变环形进度条 每次看到那些酷炫的数据可视化图表,你是不是也想过自己动手实现?但面对复杂的第三方图表库文档和性能问题又望而却步。今天我要分享的是如何用uni-app原生Canvas API,从零开始…...
STM32duino多传感器库:X-NUCLEO-IKS01A2驱动详解
1. 项目概述STM32duino X-NUCLEO-IKS01A2 是一个面向 Arduino 兼容生态(特别是基于 STM32 的开发板,如 NUCLEO-F401RE、NUCLEO-F411RE、NUCLEO-L476RG 等)的硬件抽象库,专为驱动 STMicroelectronics 官方推出的 X-NUCLEO-IKS01A2 …...
TDAD:测试驱动的AI智能体开发
Test-Driven AI Agent Definition (TDAD) 论文核心原理解析与实例说明 TDAD 提示词演化逻辑与完整实例 TDAD的提示词演化,完全遵循测试驱动的闭环迭代逻辑:由TestSmith生成的visible tests(可见测试用例)作为唯一迭代标尺,PromptSmith智能体通过「失败用例根因分析→提示…...
Pixel Language Portal快速上手:使用Gradio前端快速验证Hunyuan-MT-7B能力
Pixel Language Portal快速上手:使用Gradio前端快速验证Hunyuan-MT-7B能力 1. 项目概览 Pixel Language Portal(像素语言跨维传送门)是一款基于腾讯Hunyuan-MT-7B大模型构建的创新翻译工具。它将传统翻译体验重构为16-bit像素冒险风格&…...
别再乱删C盘大文件了!一文搞懂pagefile.sys和hiberfil.sys的正确处理姿势
别再乱删C盘大文件了!一文搞懂pagefile.sys和hiberfil.sys的正确处理姿势 每次打开资源管理器看到C盘飘红,是不是总想找几个"大块头"开刀?先别急着对pagefile.sys和hiberfil.sys下手——这两个看似占空间的系统文件,其实…...
Alibaba DASD-4B Thinking 对话工具在网络安全领域的应用:智能威胁分析与响应
Alibaba DASD-4B Thinking 对话工具在网络安全领域的应用:智能威胁分析与响应 每天,安全运维团队的工程师们都要面对海量的安全告警。防火墙日志、入侵检测系统的报警、终端防护软件的提示……这些信息像潮水一样涌来。传统的处理方式,往往依…...
基于YOLOv8深度学习的花卉识别检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)
一、项目介绍 随着计算机视觉技术的快速发展,基于深度学习的图像识别技术在植物分类与识别领域展现出巨大的应用潜力。本系统基于先进的YOLOv8目标检测算法,构建了一个高效准确的花卉识别检测系统,能够实现对13种不同花卉的实时检测与识别。…...
别再只用#if DEBUG了!C#预处理器指令的5个实战妙用(含#warning、#pragma避坑)
别再只用#if DEBUG了!C#预处理器指令的5个实战妙用(含#warning、#pragma避坑) 在C#开发中,预处理器指令往往被简化为#if DEBUG的单一用途,这就像只把瑞士军刀当作开瓶器使用。实际上,这套工具能在代码质量管…...
