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

使用 Redis 实现分布式锁

1. 分布式锁的原理

在分布式系统中,多个进程或服务可能同时访问共享资源,为了避免资源竞争,需要一种机制来确保同一时间只有一个进程可以执行某个关键操作。Redis 提供了实现分布式锁的能力,其核心原理如下:

  • 原子性操作:Redis 的 SET 命令支持 NX(不存在时设置)和 PX(设置过期时间)参数,可以确保在设置锁时是原子的。
  • 唯一标识:每个请求生成一个唯一的标识符(如 UUID),用于标识锁的持有者,避免误删其他进程的锁。
  • 锁的释放:使用 Lua 脚本确保只有锁的持有者才能释放锁,避免误删锁。
2. 实现步骤
  1. 获取锁
    • 使用 SET 命令尝试设置一个键值对,如果键不存在则设置成功,返回 OK,表示获取锁成功。
    • 设置锁的过期时间,避免锁被永久占用。
  2. 释放锁
    • 使用 Lua 脚本检查锁的持有者是否与当前请求的标识符一致,如果一致则删除锁。
3. 示例代码

以下是一个使用 Spring Boot 和 Redis 实现分布式锁的完整示例。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class DistributedLockService {

    @Autowired
    private StringRedisTemplate redisTemplate; // 注入 Redis 客户端

    private static final String LOCK_KEY = "resource_lock"; // 锁的键名
    private static final int LOCK_EXPIRE_TIME = 30000; // 锁的过期时间,单位毫秒

    /**
     * 获取分布式锁
     *
     * @param requestId 请求的唯一标识符(如 UUID)
     * @return true 表示获取锁成功,false 表示获取锁失败
     */
    public boolean acquireLock(String requestId) {
        // 使用 setIfAbsent 方法尝试设置锁,如果锁不存在则设置成功
        // 参数说明:
        // LOCK_KEY: 锁的键名
        // requestId: 锁的值,用于标识锁的持有者
        // LOCK_EXPIRE_TIME: 锁的过期时间
        // TimeUnit.MILLISECONDS: 时间单位(毫秒)
        Boolean result = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, requestId, LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        return result != null && result; // 返回是否成功获取锁
    }

    /**
     * 释放分布式锁
     *
     * @param requestId 请求的唯一标识符(如 UUID)
     * @return true 表示释放锁成功,false 表示释放锁失败
     */
    public boolean releaseLock(String requestId) {
        // 使用 Lua 脚本确保只有锁的持有者才能释放锁
        // 脚本逻辑:
        // 1. 检查锁的值是否与当前请求的标识符一致
        // 2. 如果一致,则删除锁;否则返回 0
        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<>(script, Long.class);

        // 执行 Lua 脚本
        // 参数说明:
        // redisScript: Lua 脚本
        // Collections.singletonList(LOCK_KEY): 脚本的键参数(锁的键名)
        // requestId: 脚本的参数值(锁的持有者标识符)
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(LOCK_KEY), requestId);
        return result != null && result == 1; // 返回是否成功释放锁
    }


    // 业务逻辑示例
    public void performTask() {
        String requestId = UUID.randomUUID().toString();
        try {
            if (acquireLock(requestId)) {
                // 获取锁成功,执行业务逻辑
                System.out.println("Lock acquired, executing critical section...");
                // 模拟业务逻辑
                Thread.sleep(1000);
            } else {
                // 获取锁失败
                System.out.println("Failed to acquire lock.");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            if (releaseLock(requestId)) {
                System.out.println("Lock released.");
            } else {
                System.out.println("Failed to release lock.");
            }
        }
    }
    
}
4. 代码分析
  • 获取锁
    • 使用 setIfAbsent 方法尝试设置锁,如果锁不存在则设置成功,并设置过期时间。
    • 返回 true 表示获取锁成功,false 表示获取锁失败。
  • 释放锁
    • 使用 Lua 脚本检查锁的持有者是否与当前请求的标识符一致,如果一致则删除锁。
    • 返回 true 表示释放锁成功,false 表示释放锁失败。
  • 业务逻辑
    • 在获取锁成功后执行业务逻辑,确保同一时间只有一个进程可以执行关键操作。
    • finally 块中释放锁,确保锁一定会被释放。
5. 注意事项
  • 锁的过期时间:设置合理的过期时间,避免锁被永久占用。
  • 锁的唯一标识:每个请求生成唯一的标识符,避免误删其他进程的锁。
  • 锁的释放:使用 Lua 脚本确保只有锁的持有者才能释放锁,避免误删锁。
6. 总结

通过 Redis 实现分布式锁是一种简单有效的方式,可以确保在分布式系统中同一时间只有一个进程可以执行关键操作。结合 Spring Boot 和 Redis,可以轻松实现分布式锁,并通过设置合理的过期时间和唯一标识来管理锁的生命周期。


http://www.kler.cn/a/562329.html

相关文章:

  • P8597 [蓝桥杯 2013 省 B] 翻硬币
  • TCP fast open
  • 存储产品和数据库产品之间有没有竞争关系
  • 了解大模型LLM:部署、优化与框架
  • 原生php实现redis缓存配置和使用方法
  • Android构建系统 - 01 环境准备
  • 深度学习-130-RAG技术之基于Anything LLM搭建本地私人知识库的应用策略问题总结(一)
  • 电脑不能正常启动了怎么办?查看解决方法
  • SQLite 删除表
  • 金和OA-C6 IncentivePlanFulfillAppprove sql注入漏洞复现(CNVD-2023-1)(附脚本)
  • UE5销毁Actor,移动Actor,简单的空气墙的制作
  • Redis面试题----MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?
  • Unity游戏制作中的C#基础(5)条件语句和循环语句知识点全解析
  • 【音视频】音视频录制、播放原理
  • 计算机网络:应用层 —— 电子邮件
  • 【ISP】畸变校正 LDC
  • 面试之《react近几个版本的更新要点》
  • [特殊字符]《封印adb的黑暗通道:让系统文件成为魔法禁书区的终极指南》[特殊字符]
  • char和varchar的不同
  • PHP403问题