redis分布式锁

一、什么是分布式锁?

在分布式系统中,多个服务实例可能同时访问共享资源(如数据库、缓存),分布式锁用于保证同一时刻只有一个服务实例执行关键操作 ,避免竞争条件(如超卖、数据不一致)。
Redis 分布式锁的核心思路:利用 Redis 的键值对存储特性,通过原子操作实现锁的获取和释放。

二、Redis 分布式锁的实现原理
1. 核心命令
  • 获取锁(SETNX)
    SET key value NX PX expire_time

    • NX:仅当键不存在时设置,保证原子性(防止多节点同时获取锁)。
    • PX expire_time:设置锁的过期时间(ms),避免死锁(如服务崩溃未释放锁)。
  • 释放锁(LUA 脚本)
    使用 LUA 脚本保证释放操作的原子性,避免误删其他节点的锁:

    lua

    复制代码
    if redis.call("GET",KEYS[1]) == ARGV[1] then
        return redis.call("DEL",KEYS[1])
    else
        return 0
    end
    • 先验证锁的持有者(通过唯一客户端 ID),再删除锁。
2. 关键特性
  • 互斥性:同一时刻只有一个客户端持有锁。
  • 安全性:锁只能由持有者释放,不能被其他客户端误删。
  • 容错性:Redis 节点崩溃后,锁需能在集群中重新分配(需结合 Redis 集群模式)。
三、若依框架中如何使用 Redis 分布式锁?

若依框架是基于 Spring Boot 的快速开发框架,集成了 Redis 作为缓存。以下是在若依中实现分布式锁的步骤:

1. 依赖准备

若依框架已默认集成 Redis,无需额外添加依赖。若需自定义锁逻辑,可引入 Redis 客户端工具(如 spring-data-redis)。

2. 自定义分布式锁工具类

在若依项目中创建 RedisLock 工具类,封装获取锁和释放锁的逻辑:

java

复制代码
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
public class RedisLock {
    private final RedisTemplate<String, String> redisTemplate;

    public RedisLock(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 锁的前缀(避免键冲突)
    private static final String LOCK_PREFIX = "ry_lock:";

    // 获取锁
    public boolean tryLock(String lockKey, long timeout, TimeUnit unit) {
        String clientId = UUID.randomUUID().toString(); // 唯一标识客户端
        long expireTime = unit.toMillis(timeout);
        // 使用 SET NX PX 命令获取锁
        Boolean result = redisTemplate.opsForValue().setIfAbsent(
                LOCK_PREFIX + lockKey, 
                clientId, 
                expireTime, 
                TimeUnit.MILLISECONDS
        );
        return result != null && result;
    }

    // 释放锁
    public boolean releaseLock(String lockKey, String clientId) {
        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);
        Long result = redisTemplate.execute(
                redisScript, 
                Collections.singletonList(LOCK_PREFIX + lockKey), 
                clientId
        );
        return result != null && result > 0;
    }
}
3. 在业务中使用分布式锁

以若依的订单创建场景为例,防止重复下单:

java

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

@Service
public class OrderService {
    @Autowired
    private RedisLock redisLock;

    public void createOrder(String userId, String orderId) {
        String lockKey = "order:" + userId; // 以用户 ID 作为锁键
        String clientId = UUID.randomUUID().toString();
        try {
            // 尝试获取锁(超时时间 5 秒)
            boolean locked = redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS);
            if (!locked) {
                throw new RuntimeException("获取锁失败,请勿重复操作!");
            }
            // 执行业务逻辑(如检查库存、创建订单)
            // ...
        } finally {
            // 释放锁(需传入客户端 ID)
            redisLock.releaseLock(lockKey, clientId);
        }
    }
}
4. 若依框架集成建议
  • 配置 Redis 连接 :在 application.yml 中确保 Redis 地址、密码等配置正确。
  • 异常处理 :在 try-finally 中释放锁,避免因业务异常导致锁未释放。
  • 锁粒度控制 :锁键(如 order:userId)需根据业务场景设计,避免锁范围过大影响性能。
四、常见问题与解决方案
1. 锁过期时间设置不合理
  • 问题:业务执行时间超过锁过期时间,导致锁提前释放,其他客户端获取锁,引发并发问题。
  • 解决方案
    • 合理评估业务耗时,设置稍长的过期时间(如 30 秒)。
    • 使用 看门狗(Watchdog) 机制:在持有锁的线程中启动定时任务,自动延长锁的过期时间,直到业务完成。
2. Redis 集群模式下的锁失效
  • 问题:主从模式中,主节点未及时同步锁数据到从节点,主节点宕机后,从节点升级为主节点,锁数据丢失,导致多个客户端获取锁。
  • 解决方案
    • 使用 RedLock 算法(Redis 官方推荐):通过多个独立 Redis 节点获取锁,多数节点成功则认为获取锁成功,提高可靠性。
    • 若依框架若使用 Redis 集群,需引入 RedLock 实现(可通过 redisson 客户端简化开发)。
3. 误删其他客户端的锁
  • 问题:客户端 A 获取锁后,因业务耗时较长,锁过期自动释放,客户端 B 获取锁,此时客户端 A 执行完业务后释放锁,可能误删客户端 B 的锁。
  • 解决方案
    • 释放锁时必须通过 客户端 ID 校验 (如工具类中的 clientId),确保仅释放自己的锁。
五、扩展:使用 Redisson 简化分布式锁开发

Redisson 是 Redis 的 Java 客户端,内置了分布式锁的完整实现,相比手动编写代码更简单可靠。在若依中集成 Redisson 的步骤:

1. 添加依赖

xml

复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.20.0</version>
</dependency>
2. 配置 Redisson

application.yml 中配置 Redis 连接(与若依原有 Redis 配置兼容):

yaml

复制代码
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 
3. 使用 Redisson 锁

java

复制代码
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

@Service
public class OrderService {
    private final RedissonClient redissonClient;

    public OrderService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public void createOrder(String userId, String orderId) {
        RLock lock = redissonClient.getLock("order:" + userId);
        try {
            // 尝试获取锁(5 秒过期,3 秒等待时间)
            boolean locked = lock.tryLock(3, 5, TimeUnit.SECONDS);
            if (!locked) {
                throw new RuntimeException("操作太频繁,请稍后再试!");
            }
            // 执行业务逻辑
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock(); // 自动校验客户端 ID,安全释放锁
        }
    }
}
六、总结
  • 核心逻辑:通过 Redis 的原子操作实现互斥,结合唯一 ID 保证安全释放。
  • 若依框架适配:利用现有 Redis 配置,编写工具类或集成 Redisson 实现分布式锁。
  • 注意事项:避免死锁、控制锁粒度、处理 Redis 集群下的一致性问题。

如果需要进一步调试或优化,可在若依的日志中监控锁的获取和释放情况,或通过 Redis 客户端工具(如 RedisInsight)观察键的生命周期。

相关推荐
陈丹阳(滁州学院)2 小时前
若依添加添加监听容器配置(删除键,键过期)
数据库·oracle
远方16093 小时前
14-Oracle 23ai Vector Search 向量索引和混合索引-实操
数据库·ai·oracle
GUIQU.4 小时前
【Oracle】数据仓库
数据库·oracle
恰薯条的屑海鸥4 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十六期-SSRF模块)
数据库·学习·安全·web安全·渗透测试·网络安全学习
咖啡啡不加糖4 小时前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存
曼汐 .4 小时前
数据库管理与高可用-MySQL高可用
数据库·mysql
MickeyCV4 小时前
使用Docker部署MySQL&Redis容器与常见命令
redis·mysql·docker·容器·wsl·镜像
2301_793102495 小时前
Linux——MySql数据库
linux·数据库
喵叔哟5 小时前
第4章:Cypher查询语言基础
数据库
刘 大 望5 小时前
数据库-联合查询(内连接外连接),子查询,合并查询
java·数据库·sql·mysql