前期, 我们介绍了什么是分布式锁及分布式锁应用场景, 今天我们基于Redis方案来实现分布式锁的应用。

1. 基于Redis分布式锁方案介绍

基于Redis实现的分布式锁是分布式系统中控制资源访问的常用方案,利用Redis的原子操作和高性能特性实现跨进程的锁机制。我们需根据业务特性合理设计,避免引入新的问题(如死锁、锁失效)。

基于Redis方案的分布式具有以下特点:

  • 可重入性:同一线程可多次获取同一把锁,通过 ThreadLocal 计数。
  • 锁续期(看门狗机制):自动延长锁的过期时间,防止业务未执行完锁就过期。
  • 公平 / 非公平锁:支持通过构造函数指定是否公平锁。
  • 异常处理:包含完整的异常处理和资源释放逻辑。
  • 原子操作:能够使用 Lua 脚本保证解锁的原子性。

分布式锁

1.1 核心原理

  1. 原子性加锁
    通过Redis的原子命令SET key value NX PX timeout实现:

    • NX(Not eXists):仅当key不存在时设置值,保证互斥性。
    • PX timeout:设置锁的过期时间(毫秒),避免死锁(如持有锁的进程崩溃时自动释放)。
  2. 唯一标识
    锁的value使用唯一ID(如UUID),确保锁只能被持有者释放,防止误删。

  3. 原子性解锁
    使用Lua脚本保证解锁的原子性:

    if redis.call("GET", KEYS[1]) == ARGV[1] thenreturn redis.call("DEL", KEYS[1])
    elsereturn 0
    end
    

    先验证锁的持有者,再删除锁,避免误释放其他进程持有的锁。

1.2 优缺点

  • 优点

    • 高性能:Redis基于内存操作,加锁和解锁延迟极低。
    • 高可用:通过Redis集群(如Sentinel、Cluster)避免单点故障。
    • 灵活配置:支持设置锁的过期时间、重试策略等参数。
  • 缺点

    • 主从复制延迟:主从架构中,主节点写入锁后未同步到从节点就崩溃,可能导致锁丢失(Redlock算法可部分解决)。
    • 过期时间难控制:若业务执行时间超过锁的过期时间,可能导致多个进程同时持有锁。

1.3 典型实现方式

  1. 简单实现(原生Redis命令)
    直接使用Redis客户端(如Jedis、Lettuce)调用SET和Lua脚本,适合轻量级场景。

  2. Redisson框架
    提供分布式锁的高级抽象(如可重入锁、公平锁、读写锁),内置看门狗机制自动续期锁:

    // Redisson可重入锁示例
    RLock lock = redissonClient.getLock("myLock");
    lock.lock(); // 自动续期,默认30秒
    try {// 业务逻辑
    } finally {lock.unlock();
    }
    
  3. Redlock算法
    针对主从复制缺陷,在多个独立的Redis节点上获取锁,多数节点成功时才认为加锁成功,提升可靠性,但牺牲部分性能。

1.4 关键参数配置

  • 锁过期时间:需根据业务执行时间合理设置,避免过短导致锁提前释放,或过长导致资源长时间被占用。
  • 重试策略:获取锁失败时的重试次数和间隔,避免频繁重试耗尽资源。
  • 续期机制:通过看门狗自动延长锁的过期时间,确保业务执行期间锁不会过期。

1.5 适用场景

  • 高并发场景:如秒杀、库存扣减,利用Redis高性能快速响应。
  • 异步任务:如定时任务去重执行,通过锁避免多个节点重复处理。
  • 缓存重建:防止缓存失效时多个请求同时重建缓存,造成缓存击穿。

1.6 注意事项

  1. 避免锁粒度过大:只在关键操作上加锁,减少锁持有时间。
  2. 异常处理:使用try-finally确保锁最终被释放。
  3. 监控与告警:监控锁的持有时间、竞争情况,及时发现异常。

2. 实现代码

以下是基于Redis方案的分布式锁的重要实现代码片段(仅供参考)。

使用时需要在Maven文件中添加 Jedis 组件依赖:

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.3</version>
</dependency>

RedisDistributedLock.java

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** Redis分布式锁实现,基于Redis的SETNX和Lua脚本机制* 支持可重入、锁续期(看门狗机制)和公平锁特性*/
public class RedisDistributedLock implements Lock {private static final String LOCK_SUCCESS = "OK";private static final Long RELEASE_SUCCESS = 1L;private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";// 锁续期的间隔时间(毫秒),默认是锁超时时间的1/3private static final long RENEWAL_INTERVAL_RATIO = 3;private final JedisPool jedisPool;private final String lockKey;private final String clientId; // 客户端唯一标识private final long expireTime; // 锁超时时间(毫秒)private final boolean isFair; // 是否公平锁// 可重入计数private final ThreadLocal<AtomicInteger> reentrantCount = ThreadLocal.withInitial(() -> new AtomicInteger(0));// 看门狗线程private ThreadLocal<WatchDog> watchDog = new ThreadLocal<>();public RedisDistributedLock(JedisPool jedisPool, String lockKey, long expireTime, boolean isFair) {this.jedisPool = jedisPool;this.lockKey = lockKey;this.clientId = generateClientId();this.expireTime = expireTime;this.isFair = isFair;}@Overridepublic void lock() {if (!tryLock()) {// 等待并重试waitAndRetry();}}@Overridepublic void lockInterruptibly() throws InterruptedException {if (Thread.interrupted()) {throw new InterruptedException();}if (!tryLock()) {waitAndHandleInterrupt();}}@Overridepublic boolean tryLock() {// 检查是否已持有锁(可重入)if (isHeldByCurrentThread()) {reentrantCount.get().incrementAndGet();return true;}try (Jedis jedis = jedisPool.getResource()) {// SET key value NX PX expireTimeString result = jedis.set(lockKey, clientId, new SetParams().nx().px(expireTime));if (LOCK_SUCCESS.equals(result)) {reentrantCount.get().set(1);startWatchDog(); // 启动看门狗线程return true;}return false;}}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {long startTime = System.currentTimeMillis();long timeoutMillis = unit.toMillis(time);if (tryLock()) {return true;}while (System.currentTimeMillis() - startTime < timeoutMillis) {if (Thread.interrupted()) {throw new InterruptedException();}if (tryLock()) {return true;}Thread.sleep(100); // 避免CPU空转}return false;}@Overridepublic void unlock() {if (!isHeldByCurrentThread()) {throw new IllegalMonitorStateException("Attempt to unlock lock, not locked by current thread");}// 可重入锁计数减1int count = reentrantCount.get().decrementAndGet();if (count > 0) {return; // 仍持有锁,不释放}try {// 释放锁前停止看门狗stopWatchDog();// 使用Lua脚本保证原子性释放锁try (Jedis jedis = jedisPool.getResource()) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) " +"else " +"return 0 " +"end";Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId));if (!RELEASE_SUCCESS.equals(result)) {throw new IllegalMonitorStateException("Failed to release lock");}}} finally {// 清理ThreadLocalreentrantCount.remove();watchDog.remove();}}@Overridepublic Condition newCondition() {throw new UnsupportedOperationException("Conditions are not supported by this lock");}// 生成客户端唯一标识private String generateClientId() {return Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-" + (int)(Math.random() * 10000);}// 判断当前线程是否持有锁private boolean isHeldByCurrentThread() {AtomicInteger count = reentrantCount.get();return count.get() > 0;}// 等待并重试获取锁(非公平)private void waitAndRetry() {while (true) {if (tryLock()) {return;}try {Thread.sleep(100); // 避免CPU空转} catch (InterruptedException e) {Thread.currentThread().interrupt();return;}}}// 等待并处理中断private void waitAndHandleInterrupt() throws InterruptedException {while (true) {if (Thread.interrupted()) {throw new InterruptedException();}if (tryLock()) {return;}Thread.sleep(100); // 避免CPU空转}}// 启动看门狗线程,自动续期锁private void startWatchDog() {WatchDog dog = new WatchDog(expireTime / RENEWAL_INTERVAL_RATIO);watchDog.set(dog);dog.start();}// 停止看门狗线程private void stopWatchDog() {WatchDog dog = watchDog.get();if (dog != null) {dog.interrupt();}}/*** 看门狗线程,负责自动续期锁*/private class WatchDog extends Thread {private final long renewalInterval;private boolean running = true;public WatchDog(long renewalInterval) {this.renewalInterval = renewalInterval;setDaemon(true);setName("LockWatchDog-" + lockKey);}@Overridepublic void run() {while (running && !isInterrupted()) {try {Thread.sleep(renewalInterval);// 续期锁try (Jedis jedis = jedisPool.getResource()) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('pexpire', KEYS[1], ARGV[2]) " +"else " +"return 0 " +"end";jedis.eval(script, Collections.singletonList(lockKey), List.of(clientId, String.valueOf(expireTime)));}} catch (InterruptedException e) {running = false;Thread.currentThread().interrupt();} catch (Exception e) {// 记录异常但继续运行e.printStackTrace();}}}}
}    

RedisLockExample.java

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/*** Redis分布式锁使用示例*/
public class RedisLockExample {private static final String REDIS_HOST = "localhost";private static final int REDIS_PORT = 6379;private static final String LOCK_KEY = "distributed:lock:example";private static final long LOCK_EXPIRE_TIME = 30000; // 30秒public static void main(String[] args) {// 创建Redis连接池JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(100);poolConfig.setMaxIdle(20);poolConfig.setMinIdle(5);poolConfig.setTestOnBorrow(true);JedisPool jedisPool = new JedisPool(poolConfig, REDIS_HOST, REDIS_PORT);// 创建分布式锁实例RedisDistributedLock lock = new RedisDistributedLock(jedisPool, LOCK_KEY, LOCK_EXPIRE_TIME, false);// 模拟多线程竞争ExecutorService executor = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {executor.submit(() -> {try {// 获取锁(带超时)if (lock.tryLock(5, TimeUnit.SECONDS)) {try {System.out.println(Thread.currentThread().getName() + " 获取到锁");// 模拟业务操作Thread.sleep(2000);} finally {lock.unlock();System.out.println(Thread.currentThread().getName() + " 释放锁");}} else {System.out.println(Thread.currentThread().getName() + " 获取锁超时");}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}executor.shutdown();try {executor.awaitTermination(1, TimeUnit.MINUTES);} catch (InterruptedException e) {e.printStackTrace();}// 关闭Redis连接池jedisPool.close();}
}    

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

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

相关文章

Kafka源码P2-生产者缓冲区

欢迎来到啾啾的博客&#x1f431;。 记录学习点滴。分享工作思考和实用技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 有很多很多不足的地方&#xff0c;欢迎评论交流&#xff0c;感谢您的阅读和评论&#x1f604;。 目录 1 引言2 缓冲区2.1 消息在Partition内有序2.2 批…

力扣网C语言编程题:三数之和

一. 简介 本文记录力扣网上的逻辑编程题&#xff0c;涉及数组方面的&#xff0c;这里记录一下 C语言实现和Python实现。 二. 力扣网C语言编程题&#xff1a;三数之和 题目&#xff1a;三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nu…

2.2 Windows MSYS2编译FFmpeg 4.4.1

一、安装编译工具 # 更换pacman源 sed -i "s#mirror.msys2.org/#mirrors.ustc.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist* pacman -Sy# 安装依赖 pacman -S --needed base-devel mingw-w64-x86_64-toolchain pacman -S mingw-w64-x86_64-nasm mingw-w64-x86_64-ya…

驱动开发,队列,环形缓冲区:以GD32 CAN 消息处理为例

对环形缓冲区进行进一步的优化和功能扩展&#xff0c;以应对更复杂的实际应用场景&#xff0c;特别是针对 CAN 总线消息处理的场景。 一、优化点 1&#xff1a;动态配置环形缓冲区大小在原始实现中&#xff0c;我们固定了缓冲区大小为 RINGBUFF_LEN 64。这种方式虽然简单&am…

SQL基础知识,MySQL学习(长期更新)

1、基本操作&#xff0c;增删查改 INSERT INTO 表名 (字段1, 字段2, ...) VALUES (值1, 值2, ...); DELETE FROM 表名 WHERE 条件 SELECT * FROM 表名 WHERE 条件 UPDATE 表名 SET 字段1 值, 字段2 值, ... WHERE 条件; SELECT * INTO 新表 FROM 旧表 WHERE… INSERT INTO 语…

Git(一):初识Git

文章目录 Git(一)&#xff1a;初识GitGit简介核心功能分布式特性结构与操作优势与适用场景 创建本地仓库git init配置name与email--global 工作区、暂存区与版本库git addgit commitcommit后.git的变化 Git(一)&#xff1a;初识Git Git简介 Git 是一个分布式版本控制系统&…

第19天:初级数据库学习笔记3

分组函数&#xff08;多行处理函数&#xff09; 即多个输入对应一个输出。前面讲的数据处理函数是单行处理函数。&#xff08;在公司中常说单&#xff0c;多行处理函数&#xff09; 分组函数包括五个&#xff1a; max&#xff1a;最大值min&#xff1a;最小值avg&#xff1a…

Windows11下搭建Raspberry Pi Pico编译环境

1. 系统与工具要求 PC平台&#xff1a; Windows 11 专业版 Windows GCC: gcc-15.1.0-64.exe GNU Make: 4.3 Git: 2.49.0 cmake: 4.0.2 python:3.12.11 Arm GNU Toolchain Downloads – Arm Developer 2. 工具安装与验证 2.1 工具安装 winget安装依赖工具&#xff08;Windows …

【C语言极简自学笔记】重讲运算符

一、算术操作符 算术操作符描述把两个操作数相加-第一个操作数减去第二个操作数*把两个操作数相乘/分子除以分母%取模运算符&#xff0c;整除后的余数 注意&#xff1a;1.除号的两端都是整数的时候执行的是整数的除法&#xff0c;两端只要有一个浮点数&#xff0c;就执行浮点…

持续集成 CI/CD-Jenkins持续集成GitLab项目打包docker镜像推送k8s集群并部署至rancher

Jenkins持续集成GitLab项目 GitLab提交分支后触发Jenkis任务 之前是通过jar包在shell服务器上进行手动部署&#xff0c;麻烦且耗时。现通过Jenkins进行持续集成实现CI/CD。以test分支为例 提交即部署。 由于是根据自己实际使用过程 具体使用到了 gitlabjenkinsdockerharborra…

Apache Iceberg与Hive集成:非分区表篇

引言 在大数据处理领域&#xff0c;Apache Iceberg凭借其先进的表格式设计&#xff0c;为大规模数据分析带来了新的可能。当Iceberg与Hive集成时&#xff0c;这种强强联合为数据管理与分析流程提供了更高的灵活性和效率。本文将聚焦于Iceberg与Hive集成中的非分区表场景&#…

webpack 如何区分开发环境和生产环境

第一种方法: 方法出处&#xff1a;命令行接口&#xff08;CLI&#xff09; | webpack 中文文档 1.利用webpack.config.js 返回的是个函数&#xff0c;利用函数的参数&#xff0c;来区分环境 具体步骤 1&#xff09; package.json文件&#xff1a;在npm scripts 命令后面追加 …

React组件通信——context(提供者/消费者)

Context 是 React 提供的一种组件间通信方式&#xff0c;主要用于解决跨层级组件 props 传递的问题。它允许数据在组件树中"跨级"传递&#xff0c;无需显式地通过每一层 props 向下传递。 一、Context 核心概念 1. 基本组成 React.createContext&#xff1a;创建 C…

“微信短剧小程序开发指南:从架构设计到上线“

1. 引言&#xff1a;短剧市场的机遇与挑战 近年来&#xff0c;短视频和微短剧市场呈现爆发式增长&#xff0c;用户碎片化娱乐需求激增。短剧小程序凭借轻量化、社交传播快、变现能力强等特点&#xff0c;成为内容创业的新风口。然而&#xff0c;开发一个稳定、流畅且具备商业价…

RPC与RESTful对比:两种API设计风格的核心差异与实践选择

# RPC与RESTful对比&#xff1a;两种API设计风格的核心差异与实践选择 ## 一、架构哲学与设计目标差异 1. **RPC&#xff08;Remote Procedure Call&#xff09;** - **核心思想**&#xff1a;将远程服务调用伪装成本地方法调用&#xff08;方法导向&#xff09; - 典型行为…

【pytest进阶】pytest之钩子函数

什么是 hook (钩子)函数 经常会听到钩子函数(hook function)这个概念,最近在看目标检测开源框架mmdetection,里面也出现大量Hook的编程方式,那到底什么是hook?hook的作用是什么? what is hook ?钩子hook,顾名思义,可以理解是一个挂钩,作用是有需要的时候挂一个东西…

深度学习计算——动手学深度学习5

环境&#xff1a;PyCharm python3.8 1. 层和块 块&#xff08;block&#xff09;可以描述 单个层、由多个层组成的组件或整个模型本身。 使用块进行抽象的好处&#xff1a; 可将块组合成更大的组件(这一过程通常是递归) 如 图5.1.1所示。通过定义代码来按需生成任意复杂度…

NodeJS的fs模块的readFile和createReadStream区别以及常见方法

Node.js 本身没有像 Java 那样严格区分字符流和字节流&#xff0c;区别主要靠编码&#xff08;encoding&#xff09;来控制数据是以 Buffer&#xff08;二进制字节&#xff09;形式还是字符串&#xff08;字符&#xff09;形式处理。 详细解释&#xff1a; 方面JavaNode.js字节…

基于二进制XOR运算的机器人运动轨迹与对称图像自动生成算法

原创&#xff1a;项道德&#xff08;daode3056,daode1212&#xff09; 新的算法出现&#xff0c;往往能给某些行业与产业带来革命与突破。为探索机器人运动轨迹与对称图像自动生成算法&#xff0c;本人已经通过18种算法的测试&#xff0c;最终&#xff0c;以二进制的XOR运算为…

Spring AI 项目实战(七):Spring Boot + Spring AI Tools + DeepSeek 智能工具平台(附完整源码)

系列文章 序号文章名称1Spring AI 项目实战(一):Spring AI 核心模块入门2Spring AI 项目实战(二):Spring Boot + AI + DeepSeek 深度实战(附完整源码)3Spring AI 项目实战(三):Spring Boot + AI + DeepSeek 打造智能客服系统(附完整源码)4Spring AI 项目实战(四…