加锁使用命令:set  lock_key   unique_value  NX  PX  1000

NX:等同于SETNX ,只有键不存在时才能设置成功

PX:设置键的过期时间为10秒

unique_value:一个必须是唯一的随机值(UUID),通常由客户端生成。解决误删他人锁的关键。

这条命令是原子性的,要么一起成功,要么一起失败。

    解锁:Lua 脚本保证原子性

需要先判断当前锁的值是否是自己设置的unique_value,如果是,才能使用DEL删除,两个操作必须保证原子性,使用Lua脚本安全的释放锁;

// unlock.lua

if redis.call("get", KEYS[1]) == ARGV[1] then

     return redis.call("del", KEYS[1])

else

     return 0

end

下面是一个完整的基于 Spring Boot 和 Vue 的秒杀案例,使用 Redis 分布式锁防止超卖。

后端实现 (Spring Boot)

1. 添加依赖 (pom.xml)

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>
</dependencies>

2. 应用配置 (application.yml)

spring:redis:host: localhostport: 6379password: database: 0lettuce:pool:max-active: 8max-wait: -1msmax-idle: 8min-idle: 0
server:port: 8080

3. Redis 分布式锁工具类

@Component
public class RedisDistributedLock {@Autowiredprivate StringRedisTemplate redisTemplate;// 锁的超时时间,防止死锁private static final long LOCK_EXPIRE = 30000L; // 30秒// 获取锁的等待时间private static final long LOCK_WAIT_TIME = 3000L; // 3秒// 锁的重试间隔private static final long SLEEP_TIME = 100L; // 100毫秒/*** 尝试获取分布式锁* @param lockKey 锁的key* @param requestId 请求标识(可以使用UUID)* @param expireTime 锁的超时时间(毫秒)* @return 是否获取成功*/public boolean tryLock(String lockKey, String requestId, long expireTime) {try {long startTime = System.currentTimeMillis();while (true) {// 使用SET命令代替SETNX,保证原子性Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);if (Boolean.TRUE.equals(result)) {return true; // 获取锁成功}// 检查是否超时if (System.currentTimeMillis() - startTime > LOCK_WAIT_TIME) {return false; // 获取锁超时}// 等待一段时间后重试try {Thread.sleep(SLEEP_TIME);} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}} catch (Exception e) {return false;}}/*** 释放分布式锁* @param lockKey 锁的key* @param requestId 请求标识* @return 是否释放成功*/public boolean releaseLock(String lockKey, String requestId) {// 使用Lua脚本保证原子性 ,先判断锁的键值是否等于requestId,等于才能进行删除String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) " +"else " +"return 0 " +"end";DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(script);redisScript.setResultType(Long.class);Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);return result != null && result == 1;}/*** 简化版获取锁(使用默认超时时间)*/public boolean tryLock(String lockKey, String requestId) {return tryLock(lockKey, requestId, LOCK_EXPIRE);}
}

4. 商品服务类

@Service
public class ProductService {@Autowiredprivate RedisDistributedLock redisDistributedLock;@Autowiredprivate StringRedisTemplate redisTemplate;private static final String PRODUCT_STOCK_PREFIX = "product:stock:";private static final String PRODUCT_LOCK_PREFIX = "product:lock:";/*** 初始化商品库存  //从数据库中查询出对应商品的库存数量*/public void initProductStock(Long productId, Integer stock) {redisTemplate.opsForValue().set(PRODUCT_STOCK_PREFIX + productId, stock.toString());}/*** 获取商品库存*/public Integer getProductStock(Long productId) {String stockStr = redisTemplate.opsForValue().get(PRODUCT_STOCK_PREFIX + productId);return stockStr != null ? Integer.parseInt(stockStr) : 0;}/*** 秒杀下单(使用分布式锁)*/public boolean seckillProduct(Long productId, String userId) {String lockKey = PRODUCT_LOCK_PREFIX + productId;String requestId = UUID.randomUUID().toString();try {// 尝试获取锁if (!redisDistributedLock.tryLock(lockKey, requestId)) {return false; // 获取锁失败}// 检查库存Integer stock = getProductStock(productId);if (stock <= 0) {return false; // 库存不足}// 模拟业务处理耗时  //修改商品的库存try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 扣减库存redisTemplate.opsForValue().decrement(PRODUCT_STOCK_PREFIX + productId);// 记录订单(这里简化处理,实际应保存到数据库)System.out.println("用户 " + userId + " 成功秒杀商品 " + productId);return true;} finally {// 释放锁redisDistributedLock.releaseLock(lockKey, requestId);}}
}

前端实现 (Vue)

1. 安装依赖

npm install axios

2. 秒杀页面组件 (Seckill.vue)

<template><div class="seckill-container"><h1>商品秒杀</h1><div class="product-info"><h2>商品ID: {{ productId }}</h2><p>当前库存: {{ stock }}</p><button @click="initStock">初始化库存(100件)</button></div><div class="seckill-form"><input v-model="userId" placeholder="请输入用户ID" /><button @click="seckill" :disabled="isSeckilling">{{ isSeckilling ? '秒杀中...' : '立即秒杀' }}</button></div><div class="result"><h3>秒杀结果:</h3><p>{{ resultMessage }}</p></div><div class="logs"><h3>操作日志:</h3><ul><li v-for="(log, index) in logs" :key="index">{{ log }}</li></ul></div></div>
</template><script>
import axios from 'axios';export default {name: 'Seckill',data() {return {productId: 1001, // 商品IDstock: 0, // 当前库存userId: '', // 用户IDresultMessage: '', // 秒杀结果logs: [], // 操作日志isSeckilling: false // 是否正在秒杀};},mounted() {this.getStock();},methods: {// 获取商品库存async getStock() {try {const response = await axios.get(`http://localhost:8080/api/seckill/stock/${this.productId}`);this.stock = response.data;this.addLog(`获取库存成功: ${this.stock}`);} catch (error) {this.addLog('获取库存失败: ' + error.message);}},// 初始化库存async initStock() {try {await axios.post(`http://localhost:8080/api/seckill/init/${this.productId}/100`);this.addLog('初始化库存成功');this.getStock(); // 重新获取库存} catch (error) {this.addLog('初始化库存失败: ' + error.message);}},// 执行秒杀async seckill() {if (!this.userId) {this.resultMessage = '请输入用户ID';return;}this.isSeckilling = true;this.resultMessage = '秒杀中...';try {const response = await axios.post(`http://localhost:8080/api/seckill/${this.productId}?userId=${this.userId}`);this.resultMessage = response.data;this.addLog(`用户 ${this.userId} ${response.data}`);} catch (error) {this.resultMessage = '秒杀失败: ' + (error.response?.data || error.message);this.addLog(`用户 ${this.userId} 秒杀失败: ${error.response?.data || error.message}`);} finally {this.isSeckilling = false;this.getStock(); // 重新获取库存}},// 添加日志addLog(message) {const timestamp = new Date().toLocaleTimeString();this.logs.unshift(`[${timestamp}] ${message}`);// 只保留最近20条日志if (this.logs.length > 20) {this.logs.pop();}}}
};
</script><style scoped>
.seckill-container {max-width: 600px;margin: 0 auto;padding: 20px;
}.product-info, .seckill-form, .result, .logs {margin-bottom: 20px;padding: 15px;border: 1px solid #ddd;border-radius: 5px;
}input {padding: 8px;margin-right: 10px;width: 200px;
}button {padding: 8px 16px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;
}button:disabled {background-color: #cccccc;cursor: not-allowed;
}button:hover:not(:disabled) {background-color: #45a049;
}ul {list-style-type: none;padding: 0;max-height: 300px;overflow-y: auto;
}li {padding: 5px 0;border-bottom: 1px solid #eee;
}
</style>

3. 主应用文件 (App.vue)

<template><div id="app"><Seckill /></div>
</template><script>
import Seckill from './components/Seckill.vue'export default {name: 'App',components: {Seckill}
}
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;color: #2c3e50;margin-top: 20px;
}
</style>
  1. 原子性加锁:使用 setIfAbsent 方法的原子性操作,避免非原子操作带来的竞态条件

  2. 唯一请求标识:使用 UUID 作为请求标识,确保只能释放自己加的锁

  3. 超时机制:设置锁的超时时间,防止死锁

  4. Lua脚本释放锁:使用 Lua 脚本保证判断锁归属和删除操作的原子性

  5. 重试机制:在获取锁失败后等待一段时间重试,避免立即失败

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序携带token跳转h5, h5再返回微信小程序

需求: 在微信小程序内跳转到h5, 浏览完后点击返回按钮再返回到微信小程序中 微信小程序跳转h5: 微信小程序跳转h5,这个还是比较简单的, 但要注意细节 一、微信小程序代码 1.新建跳转h5页面, 新建文件夹,新建page即可 2.使用web-view标签 wxml页面 js页面 到此为止, 小程序…

【机器学习】通过tensorflow实现猫狗识别的深度学习进阶之路

【机器学习】通过tensorflow实现猫狗识别的深度学习进阶之路 简介 猫狗识别作为计算机视觉领域的经典入门任务&#xff0c;不仅能帮助我们掌握深度学习的核心流程&#xff0c;更能直观体会到不同优化策略对模型性能的影响。本文将从 “从零搭建简单 CNN” 出发&#xff0c;逐步…

异步处理(前端面试)

Promise 1&#xff1a;使用promise原因 了解回调地狱【什么是回调地狱】 1&#xff1a;回调地狱是异步获取结果后&#xff0c;为下一个异步函数提供参数&#xff0c;层层回调嵌入回调 2&#xff1a;导致回调层次很深&#xff0c;代码维护特别困难 3&#xff1a;在没有ES6时&…

3种XSS攻击简单案例

1、接收cookie端攻击机上用python写个接收web程序flask from flask import Flask, request, Responseapp Flask(__name__)app.route(/) def save_cookie():cookie request.args.get(cookie, )if cookie:with open(/root/cookies.txt, a) as f:f.write(f"{cookie}\n"…

Docker 部署生产环境可用的 MySQL 主从架构

简介跨云服务器一主一从&#xff0c;可以自己按照逻辑配置多个从服务器 假设主服务器ip: 192.168.0.4 从服务器ip&#xff1a;192.168.0.5 系统 CentOS7.9 &#xff08;停止维护了&#xff0c;建议大家用 Ubuntu 之类的&#xff0c;我这个没办法&#xff0c;前人在云服务器上…

DeepResearch(上)

概述 OpenAI首先推出Deep Research Agent&#xff0c;深度研究智能体&#xff0c;简称DRA。 通过自主编排多步骤网络探索、定向检索和高阶综合&#xff0c;可将大量在线信息转换为分析师级别的、引用丰富的报告&#xff0c;将数小时的手动桌面研究压缩为几分钟。 作为新一代…

附050.Kubernetes Karmada Helm部署联邦及使用

文章目录karmada简介karmada概述karmada Helm部署Kubernetes karmada介绍基础准备Kubernetes集群获取chat包正式部署karmada使用karmada纳管集群查看memeber集群导出kubeconfig切换集群测试应用karmada简介 karmada概述 参考&#xff1a;附049.Kubernetes Karmada Local-up部…

必知必会:基于Transformer 的预训练语言模型如何区分文本位置(sinusoidal位置编码、原理和详细推导)

预训练位置编码 基于Transformer 的预训练语言模型如何区分文本位置? 基于 Transformer 的预训练语言模型主要通过位置编码来区分文本位置。Transformer 核心的自注意力机制本身并不考虑词的顺序&#xff0c;它对输入序列的处理是与位置无关的。在 query 和 key 的矩阵乘法之后…

redis key过期后会立马删除吗

Redis 的 Key 过期后不会立即被删除。Redis 采用了一种结合 ​惰性删除 (Lazy Expiration)​​ 和 ​定期删除 (Periodic Expiration)​​ 的策略来管理过期 Key 的回收&#xff0c;这是为了在内存管理、性能和 CPU 开销之间取得平衡。&#x1f4cc; Redis 过期 Key 删除的两种…

关于 React 19 的四种组件通信方法

注意&#xff1a;使用的是UI库是 antd-mobile1.父子级组件通信父组件单向数据流&#xff1a;数据从父组件流向子组件。支持多种数据类型&#xff1a;字符串、数字、对象、数组、函数等。只读性&#xff1a;子组件不能直接修改 props 中的数据。import { useState } from react …

OC-单例模式

文章目录单例模式定义特点使用原因缺点模式介绍懒汉模式同步锁实现dispatch_once饿汉模式实现总结懒汉模式优点缺点饿汉模式优点缺点单例模式 定义 单例模式&#xff0c;简单的说就是一个类始终只对应同一个对象&#xff0c;每次获取这个类的对象获得的都是同一个实例 如果一…

[论文阅读] 算法 | 抗量子+紧凑!SM3-OTS:基于国产哈希算法的一次签名新方案

抗量子紧凑&#xff01;SM3-OTS&#xff1a;基于国产哈希算法的一次签名新方案 论文信息信息类别具体内容论文原标题SM3-OTS: 基于国密算法SM3的紧凑型后量子一次签名方案主要作者杨亚涛、殷方锐、陈亮宇、潘登研究机构1. 北京电子科技学院 电子与通信工程系&#xff08;北京 1…

C语言-指针用法概述

目录 1.指针基础概念 2. 指针与数组 3. 指针作为函数参数 4. 动态内存分配 5. 指针的高级用法 6. 常见错误与注意事项 7. 指针数组 vs. 数组指针 8.总结与建议 本文主要作为指针用法的复习&#xff0c;会对指针的大致用法进行举例和概述。 1.指针基础概念 ​什…

Java调用Whisper和Vosk语音识别(ASR)模型,实现高效实时语音识别(附源码)

简介 语音识别&#xff08;Automatic Speech Recognition, ASR&#xff09;是将人类的语音信号自动转换为对应文字的技术&#xff0c;它使计算机能够“听懂”人说的话&#xff0c;是人机语音交互的核心技术&#xff0c;广泛应用于智能助手、语音输入、客服系统等场景。 现在我…

第3周 机器学习课堂记录

1.学习问题的分类有监督的学习分类回归无监督学习聚类密度估计&#xff1a;确定输入空间中的数据的分布可视化&#xff1a;把高位空间中的数据投影到二维或三维空间强化学习不给定最优输出的示例&#xff0c;而是通过试错发现最优输出2.泛化versus过度拟合背景引入&#xff1a;…

消息队列(MQ)高级特性深度剖析:详解RabbitMQ与Kafka

一、引言&#xff1a;为什么需要关注高级特性&#xff1f; 在现代分布式系统架构中&#xff0c;消息队列&#xff08;Message Queue&#xff09;已成为不可或缺的核心组件。初级使用消息队列可能只需几行代码就能实现基本功能&#xff0c;但要真正发挥其在大规模生产环境中的威…

【GPT入门】第65课 vllm指定其他卡运行的方法,解决单卡CUDA不足的问题

【GPT入门】第65课 vllm指定其他卡运行的方法&#xff0c;解决单卡CUDA不足的问题&#xff11;.原理说明&#xff1a;&#xff12;.实践&#xff11;.原理 要将 vllm 部署在第二张 GPU 卡上&#xff08;设备编号为 1&#xff09;&#xff0c;只需在命令前添加 CUDA_VISIBLE_DE…

Spring Boot Actuator自定义指标与监控实践指南

Spring Boot Actuator自定义指标与监控实践指南 本篇文章以生产环境实战经验为主线&#xff0c;结合某电商系统的业务场景&#xff0c;讲解如何在Spring Boot Actuator中添加并暴露自定义指标&#xff0c;并使用Prometheus和Grafana进行完整的监控与告警配置。 一、业务场景描述…

Vue报错<template v-for=“option in cardOptions“ :key=“option.value“>

在Vue项目中遇到报错&#xff0c;原因是模板中使用了<template>标签内的v-for指令&#xff0c;而当前Vue版本不支持此用法。解决方案是移除<template>标签&#xff0c;直接在<el-option>上使用v-for。同时优化计算属性cardOptions&#xff0c;使其能够兼容历…

人工智能学习:Transformer结构中的规范化层(层归一化)

Transformer结构中的规范化层(层归一化) 一、规范化层(层归一化)介绍 概念 层归一化(Layer Normalization) 是一种用于提高深度神经网络训练稳定性和加速收敛的技术,广泛应用于现代深度学习模型中,尤其是在Transformer等序列建模网络中。它通过对每一层的输出进行归一化…