redis分布式锁 Redisson在电商平台开发中的实际应用

目录

概述

Redis分布式锁的实现方式

1. 基于SETNX命令(String类型)

2. 使用SET命令的NX和EX参数(推荐方式)

3. 基于Lua脚本实现复杂逻辑

4. RedLock算法(多节点Redis实现)

Redisson的分布式锁

Redisson的锁类型

Redisson可重入锁的实现

Redis其他实现分布式锁的方式

1. 基于Sorted Set实现公平锁

2. 基于List实现分布式锁

总结

实际应用场景

场景一:库存超卖控制

业务场景

分布式锁解决方案

关键实现细节

实际开发注意事项

场景二:订单防重复提交

业务场景

分布式锁解决方案

关键实现细节

实际开发注意事项

场景三:促销活动限流与防超卖

业务场景

分布式锁解决方案

关键实现细节

实际开发注意事项

分布式锁在电商中的最佳实践


概述

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。下面详细介绍Redis分布式锁的实现方式以及Redisson的锁类型。

Redis分布式锁的实现方式

1. 基于SETNX命令(String类型)

最基本的实现方式是使用Redis的SETNX(SET if Not eXists)命令:

# 使用Python和Redis-py库实现简单分布式锁
import redis
import timedef acquire_lock(redis_client, lock_name, acquire_timeout=10, lock_timeout=10):end_time = time.time() + acquire_timeoutlock_value = str(time.time() + lock_timeout)  # 锁的过期时间while time.time() < end_time:# 使用SETNX尝试获取锁if redis_client.setnx(lock_name, lock_value):# 设置锁的过期时间,防止死锁redis_client.expire(lock_name, lock_timeout)return True# 检查锁是否过期current_value = redis_client.get(lock_name)if current_value and float(current_value) < time.time():# 锁已过期,尝试竞争并获取锁old_value = redis_client.getset(lock_name, lock_value)if old_value == current_value:redis_client.expire(lock_name, lock_timeout)return Truetime.sleep(0.1)  # 短暂休眠避免频繁重试return Falsedef release_lock(redis_client, lock_name):redis_client.delete(lock_name)

这种方式的问题是:

  • 锁的释放缺乏原子性
  • 不支持可重入
  • 没有锁失效机制(需手动设置过期时间)
2. 使用SET命令的NX和EX参数(推荐方式)

Redis 2.6.12版本后,SET命令支持原子化操作:

# 使用SET命令的NX和EX参数实现分布式锁
def acquire_lock(redis_client, lock_name, acquire_timeout=10, lock_timeout=10):end_time = time.time() + acquire_timeoutwhile time.time() < end_time:# 使用SET命令原子化操作:NX表示只有键不存在时才设置,EX设置过期时间result = redis_client.set(lock_name, "locked", nx=True, ex=lock_timeout)if result:return Truetime.sleep(0.1)return False

这种方式解决了原子性问题,但仍不支持可重入。

3. 基于Lua脚本实现复杂逻辑

使用Lua脚本可以实现更复杂的原子操作,例如锁的释放:

# 使用Lua脚本确保锁释放的原子性
RELEASE_SCRIPT = """
if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])
elsereturn 0
end
"""def release_lock(redis_client, lock_name, lock_value):return redis_client.eval(RELEASE_SCRIPT, 1, lock_name, lock_value)
4. RedLock算法(多节点Redis实现)

为了提高可靠性,Redis作者提出了RedLock算法,基于多个Redis节点:

# RedLock算法的简化实现
def redlock_acquire(redis_clients, lock_name, acquire_timeout=10, lock_timeout=10):start_time = time.time()lock_value = str(uuid.uuid4())  # 唯一标识锁acquired_locks = 0for client in redis_clients:if time.time() > start_time + acquire_timeout:breakif client.set(lock_name, lock_value, nx=True, px=lock_timeout):acquired_locks += 1# 需要获得多数节点的锁才算成功if acquired_locks >= (len(redis_clients) // 2 + 1):return lock_value# 获取锁失败,释放已获得的锁for client in redis_clients:client.eval(RELEASE_SCRIPT, 1, lock_name, lock_value)return False

Redisson的分布式锁

Redisson是一个基于Redis的Java驻内存数据网格(In-Memory Data Grid),它提供了分布式锁的实现。

Redisson的锁类型

Redisson的分布式锁是可重入锁,但从锁的性质上来说,它属于悲观锁。因为:

  • 它在获取锁后会阻塞其他线程的访问
  • 遵循"先获取锁再操作"的悲观策略
Redisson可重入锁的实现

Redisson的可重入锁通过Hash结构实现:

// Redisson可重入锁的内部实现(简化版)
public class RedissonLock {// 锁的名称private String lockName;// 当前线程IDprivate String threadId;// 重入次数private int reentrantCount = 0;// 尝试获取锁public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();// 使用Hash结构存储锁信息// HSET lockName threadId 1Long currentLockCount = redisClient.hset(lockName, threadId, 1);if (currentLockCount == 0) {// 锁已存在,检查是否是当前线程持有String currentValueStr = redisClient.hget(lockName, threadId);if (currentValueStr != null) {int count = Integer.parseInt(currentValueStr);// 锁重入redisClient.hset(lockName, threadId, count + 1);reentrantCount = count + 1;return true;}return false;}// 设置锁的过期时间redisClient.expire(lockName, leaseTime, unit);reentrantCount = 1;return true;}// 释放锁public void unlock() {String threadId = Thread.currentThread().getId();// 获取当前锁的重入次数String currentValueStr = redisClient.hget(lockName, threadId);if (currentValueStr == null) {throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "+ threadId + " thread-id: " + Thread.currentThread().getId());}int count = Integer.parseInt(currentValueStr);if (count == 1) {// 完全释放锁redisClient.hdel(lockName, threadId);return;}// 减少重入次数redisClient.hset(lockName, threadId, count - 1);reentrantCount = count - 1;}
}

Redis其他实现分布式锁的方式

1. 基于Sorted Set实现公平锁
# 使用Sorted Set实现公平锁
def acquire_fair_lock(redis_client, lock_name, client_id, acquire_timeout=10):end_time = time.time() + acquire_timeoutwhile time.time() < end_time:# 使用当前时间戳作为分数timestamp = time.time()# ZADD命令添加成员,NX确保只有不存在时才添加result = redis_client.zadd(lock_name, {client_id: timestamp}, nx=True)if result:# 获取锁成功return True# 检查自己是否是队列中的第一个first_client = redis_client.zrange(lock_name, 0, 0)if first_client and first_client[0] == client_id:return Truetime.sleep(0.01)return False
2. 基于List实现分布式锁
# 使用List实现分布式锁
def acquire_lock_with_list(redis_client, lock_name, acquire_timeout=10):end_time = time.time() + acquire_timeoutwhile time.time() < end_time:# LPUSH返回列表的长度result = redis_client.lpush(lock_name, "locked")if result == 1:# 设置过期时间redis_client.expire(lock_name, 10)return Truetime.sleep(0.1)return False

总结

Redis实现分布式锁的核心思想是利用其原子操作和单线程特性。不同的实现方式适用于不同的场景:

  • 简单场景:使用SET命令的NX和EX参数
  • 复杂场景:使用Redisson等成熟框架
  • 高可靠性场景:使用RedLock算法

Redisson的分布式锁属于悲观锁范畴,同时支持可重入特性,是企业级应用中常用的分布式锁实现方案。在电商平台中,分布式锁是保障数据一致性和业务正确性的关键技术。以下是三个典型场景及实际开发中的使用方法:

实际应用场景

场景一:库存超卖控制

业务场景

用户下单时需要扣减库存,若多个用户同时购买同一商品,可能导致库存超卖(如库存1件,却被10个用户同时下单成功)。

分布式锁解决方案
# 使用Redis分布式锁防止库存超卖
def deduct_stock(product_id, quantity):lock_name = f"stock_lock:{product_id}"with redisson_client.getLock(lock_name):  # 获取分布式锁# 查询当前库存current_stock = redis_client.get(f"stock:{product_id}")if current_stock >= quantity:# 扣减库存redis_client.decr(f"stock:{product_id}", quantity)return Truereturn False
关键实现细节
  1. 锁粒度:使用商品ID作为锁名,确保同一商品的库存操作互斥
  2. 原子性:库存查询和扣减操作必须在锁内完成
  3. 超时设置:设置合理的锁超时时间(如30秒),防止死锁
实际开发注意事项
  • 使用Redisson的可重入锁,避免同一线程重复获取锁时阻塞
  • 结合Lua脚本进一步优化库存扣减逻辑,确保原子性
  • 库存预扣减机制:先扣减缓存库存,异步同步到数据库

场景二:订单防重复提交

业务场景

用户点击提交订单按钮后,由于网络延迟等原因可能重复提交,导致创建多个相同订单。

分布式锁解决方案
// 使用Redis分布式锁防止订单重复提交
public Response createOrder(OrderRequest request) {String orderToken = request.getOrderToken(); // 前端生成的唯一标识RLock lock = redissonClient.getLock("order:" + orderToken);try {// 尝试获取锁,1秒后自动释放boolean isLocked = lock.tryLock(100, 1000, TimeUnit.MILLISECONDS);if (!isLocked) {return Response.failure("请勿重复提交订单");}// 处理订单创建逻辑Order order = orderService.createOrder(request);return Response.success(order);} catch (InterruptedException e) {Thread.currentThread().interrupt();return Response.failure("系统繁忙,请稍后再试");} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}
}
关键实现细节
  1. 唯一标识:前端生成唯一订单Token(如UUID),随请求一起传递
  2. 短超时:设置较短的锁超时时间(如1秒),防止用户正常操作被长时间阻塞
  3. 幂等性:结合数据库唯一索引,确保订单号唯一性
实际开发注意事项
  • 前端防抖处理:按钮点击后禁用,防止用户手动重复点击
  • 锁的粒度控制:按订单Token加锁,而非全局锁
  • 异常处理:确保锁在异常情况下仍能释放

场景三:促销活动限流与防超卖

业务场景

限时秒杀、优惠券领取等促销活动中,大量并发请求可能导致系统崩溃或资源超发。

分布式锁解决方案
# 使用Redis分布式锁和计数器实现秒杀限流
def seckill(product_id, user_id):lock_name = f"seckill_lock:{product_id}"with redisson_client.getLock(lock_name):# 检查活动是否结束if not is_activity_active(product_id):return "活动已结束"# 检查用户是否已参与if redis_client.sismember(f"seckill_users:{product_id}", user_id):return "每个用户限参与一次"# 检查库存stock = redis_client.get(f"seckill_stock:{product_id}")if stock <= 0:return "已售罄"# 扣减库存redis_client.decr(f"seckill_stock:{product_id}")# 记录用户参与redis_client.sadd(f"seckill_users:{product_id}", user_id)return "秒杀成功"
关键实现细节
  1. 双重检查:先检查库存再获取锁,减少锁竞争
  2. 用户限制:使用Set记录已参与用户,防止重复参与
  3. 库存预热:活动开始前将库存加载到Redis
  4. 异步处理:订单创建等耗时操作放入MQ异步处理
实际开发注意事项
  • 热点商品处理:对热门商品采用多节点RedLock或分段锁
  • 熔断机制:当并发过高时自动拒绝请求,保护系统
  • 降级策略:库存为0时直接返回,不再加锁

分布式锁在电商中的最佳实践

  1. 合理设置锁超时时间:根据业务处理时间设置合理的超时值,避免死锁
  2. 锁粒度控制:尽量使用细粒度锁(如按商品ID、订单ID),避免全局锁
  3. 异常处理:使用try-finally确保锁释放,或依赖Redisson的看门狗机制
  4. 性能优化
    • 减少锁内操作时间
    • 使用读写锁分离读操作
    • 采用分段锁处理热点数据
  1. 监控与报警:监控锁的持有时间、等待队列长度等指标,及时发现异常

分布式锁是电商系统的重要组成部分,但需结合业务场景选择合适的实现方式,并注意性能与可靠性的平衡。

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

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

相关文章

joomla 使用nginx服务器只能打开首页,其他页面404的解决方案

最近一个客户将Joomla4网站从原先的Apache服务器改为Nginx服务器&#xff0c;整个过程一切顺利&#xff0c;但还原网站后发现只能打开首页&#xff0c;其他页面都是404。这个问题需要修改nginx的配置文件来解决。 伪静态 在Apache中使用.htaccess来完成伪静态路由的转发&…

湖北理元理律师事务所企业债务纾困路径:司法重整中的再生之道

中小企业债务危机常呈现“担保链扩散”特征&#xff0c;单一债务可能引发企业崩盘。湖北理元理律师事务所通过预重整制度与企业债务重组技术&#xff0c;探索出“司法保护商业谈判”的纾困模式。 一、企业债务风险处置四步法 紧急止血 申请司法保护&#xff1a;通过诉前调解…

利用DeepWiki高效阅读项目源码

想获取更多高质量的Java技术文章&#xff1f;欢迎访问Java技术小馆官网&#xff0c;持续更新优质内容&#xff0c;助力技术成长 技术小馆官网 DeepWiki 是一个强大的工具&#xff0c;专为程序员提供开源项目源码的结构化文档和 AI 驱动的问答功能&#xff0c;帮助快速理解复杂…

django rest_framework 前端网页实现Token认证

rest_framework提供了几种认证方式&#xff1a;Session、Token等。Session是最简单的&#xff0c;几乎不用写任何代码就可以是实现&#xff0c;Token方式其实也不复杂&#xff0c;网上的教程一大把&#xff0c;但是最后都是用Postman这类工具来实现API调用的&#xff0c;通过这…

面试题-函数类型的重载是啥意思

在 TypeScript 中&#xff0c;函数重载&#xff08;Function Overload&#xff09; 是指为同一个函数提供多个不同的调用签名&#xff08;参数类型和返回值类型的组合&#xff09;&#xff0c;但函数体只有一个实现。这样可以让函数在不同的输入下表现出不同的行为&#xff0c;…

磐基PaaS平台MongoDB组件SSPL许可证风险与合规性分析(上)

#作者&#xff1a;任少近 文章目录 1.背景与问题1.1.背景1.2.问题 3.SSPL条款解读分析3.1.条款0&#xff1a;定义条款3.2.条款一&#xff1a;源代码条款3.3.条款二&#xff1a;基本授权条款3.4.条款三&#xff1a;反规避保护条款3.5.条款四&#xff1a;逐字传播条款3.6.条款五…

「Linux文件及目录管理」输入输出重定向与管道

知识点解析 输入/输出重定向 标准输入(stdin):默认从键盘读取,文件描述符为0。标准输出(stdout):默认输出到终端,文件描述符为1。标准错误(stderr):默认输出到终端,文件描述符为2。重定向符号: >:覆盖输出到文件(如command > file)。>>:追加输出…

【Node】最佳Node.js后端开发模板推荐

Node.js 后端开发模板推荐 以下是几个优秀的Node.js后端模板&#xff0c;它们都适合二次开发&#xff0c;各自有不同的特点和适用场景&#xff1a; 1. Express基础模板 Express Generator (官方工具) 官方提供的快速搭建工具基础MVC结构简单易上手 npm install express-ge…

HALCON相机标定

相机标定简介&#xff1a; 首先&#xff0c;相机会产生畸变&#xff0c;即实际图像和拍摄图像不一致&#xff0c;可以是凸性也可以是凹性形变&#xff0c;相机标定的过程就是将畸变图像还原为原始图像&#xff0c;并将图像中的像素坐标转换为世界坐标。 形如&#xff1a;相机内…

Solidity 入门教程(二):值类型全解 —— 布尔、整数、地址与字节数组

在上一章中&#xff0c;我们写下了第一个 Solidity 合约并在 Remix 中成功运行。本章我们将深入了解 Solidity 中的几种常用值类型&#xff08;Value Types&#xff09;&#xff0c;并通过示例代码在 Remix 进行验证。 一、Solidity 中的三种数据类型 在 Solidity 中&#xf…

16.大数据监控

0.说明 监控主要构成。 软件版本。 1.exporter监控配置 1.1 node_exporter 启动命令 nohup ./node_exporter &服务 创建文件 /etc/systemd/system/node_exporter.service&#xff1a; [Unit] DescriptionPrometheus Node Exporter Wantsnetwork-online.target Aft…

Tomcat项目本地部署(Servlet为例)

在Windows上部署 在idea中打开项目 首先我们需要准备一个Servlet项目&#xff0c;我之前的Servlet项目是用eclipse写的&#xff0c;这种情况下如果用idea直接打开的话会出现左侧目录无法显示的情况&#xff0c;这个时候我们就需要用别的方法打开 打开项目管理 如下图&#…

安装MySQL 5.7导入数据,修改密码,创建账号并授权

1. 准备工作 sudo yum update -y sudo yum install -y wget libaio numactl 2. 下载 MySQL 5.7 二进制包 wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz 3. 创建 MySQL 用户和组 sudo groupadd mysql sudo useradd -r -g m…

基础RAG实现,最佳入门选择(八)

RAG重排序 RAG重排序技术以提高RAG系统中的检索质量。重新排序充当初始检索后的第二个过滤步骤&#xff0c;以确保最相关的内容用于响应生成。 重排序的关键概念 1.初始检索&#xff1a;使用基本相似度搜索的第一遍&#xff08;准确度较低但速度更快&#xff09; 2.文档评分…

Spring Boot 常用注解整理

Spring & Spring Boot 常用注解整理 现代的 Spring 与 Spring Boot 应用大量使用注解来简化配置、管理组件和实现各种框架功能。本文系统整理了常用的 Spring/Spring Boot 注解&#xff0c;按照功能分类进行介绍。每个注解都会涵盖其含义、提供来源、应用场景以及代码示例…

深入理解 Cross-Entropy 损失函数:从原理到实践

在深度学习中&#xff0c;损失函数是衡量模型性能的关键指标之一。对于多分类问题&#xff0c;Cross-Entropy 损失函数 是最常用的选择之一。它不仅能够有效衡量模型输出与真实标签之间的差异&#xff0c;还能通过梯度下降法指导模型的优化。本文将深入探讨 Cross-Entropy 损失…

Vim-vimrc保存文件自动移除行末尾空格

Vim-vimrc保存文件自动移除行末尾空格 这段代码通过设置 autocmd 和自定义函数&#xff0c;确保每次保存文件时都自动删除文件中的行尾空格&#xff0c;同时不会影响光标和视图的位置。它适用于所有文件类型&#xff0c;并且删除操作不会引入错误&#xff0c;即使没有行尾空格的…

Occt几何内核快速入门

本文简单介绍 Open Cascade Technology&#xff08;OCCT&#xff09;&#xff0c;提供了下载地址和文档地址。通过OCCT的测试工具Draw&#xff0c;展示了OCCT的一些功能特性。介绍了OCCT集成开发的演示代码&#xff0c;提供了源代码下载地址和编译过程文件。 一、简介 Open C…

【Docker 08】Compose - 容器编排

&#x1f308; 一、Docker Compose 介绍 ⭐ 1. Docker Compose 是什么 Docker Compose 是由 Docker 官方提供的一个用于定义和运行多容器应用的工具&#xff0c;它让用户可以通过一个 YAML 文件&#xff08;通常是 docker-compose.yml&#xff09;来配置应用所需要的服务&…

CentOS Stream 9平台部署安装MySQL8.4.1

1、在线下载安装包 [rootlocalhost ~]# wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.4.1-1.el9.x86_64.rpm-bundle.tar 2、新建解压文件夹 [rootlocalhost ~]#mkdir /root/sql 3、离线解压安装包安装配置MySQL8 上传安装包到home下 [rootlocalhost ~]#c…