redis实现分布式锁

Redis 分布式锁是一个轻量级的分布式锁实现方式,利用 Redis 提供的原子性和高性能特性,来保证分布式环境下资源的独占访问。

一、Redis 分布式锁原理

Redis 分布式锁的实现主要依赖于以下几个关键步骤:

  1. 加锁(SETNX + EXPIRE):使用 SETNX(Set if Not Exists)命令实现加锁。如果键不存在则创建,表示加锁成功。

    • 通常使用唯一标识作为锁的值,以便后续解锁时能够验证锁的持有者。
    • 为防止因进程崩溃导致锁永久占用,设置一个过期时间(EXPIRE),锁在超时后自动释放。
  2. 释放锁(DEL):当加锁的操作完成后,持有锁的客户端执行解锁。

    • 为了避免误删锁的情况(例如其他客户端误删除当前锁),通常在删除前会校验锁的唯一标识。
    • 验证值与锁的持有者匹配,确保解锁的是同一客户端。
  3. 锁自动续期(可选):为确保锁的安全性和保持稳定的锁持有状态,可以设置自动续期机制。

示例:使用 SET NX PX 原子命令
SET lock_key unique_value NX PX 30000
  • lock_key:锁的键名
  • unique_value:用于唯一标识的值
  • NX:保证在键不存在时才设置
  • PX 30000:设置锁的过期时间为 30 秒

二、Redis 分布式锁存在的漏洞

  1. 锁的误删除问题

    • 当加锁客户端的任务超时未完成,锁过期释放,而其他客户端重新获取了锁。此时,第一个客户端如果未完成任务,就可能误删他人的锁。
    • 解决方案:通过唯一标识来删除锁,确保删除的是自己持有的锁。
  2. 锁的超时释放

    • 当任务执行时间超过锁的过期时间时,锁会自动释放,导致锁的控制失效,其他客户端也可能获取到锁。
    • 解决方案:在任务执行期间,进行自动续约操作,以防止锁过期释放。
  3. Redis 主从一致性问题

    • Redis 是异步复制,锁的写入可能未同步到从节点,在主节点故障后,切换到从节点时,锁的数据可能丢失。
    • 解决方案:使用 Redis 官方提供的分布式锁算法 Redlock 或使用 Redis 集群模式。

三、Spring Boot 集成 Redisson 实现分布式锁

Redisson 是一个 Redis 的高级客户端,支持分布式锁、同步器等常用的分布式工具,能够简化分布式锁的操作。

3.1 添加依赖

pom.xml 中添加 Redisson 依赖:

<dependency>
    <groupId>org.redissongroupId>
    <artifactId>redisson-spring-boot-starterartifactId>
    <version>3.17.7version>
dependency>
3.2 配置 Redisson 客户端

application.yml 文件中添加 Redis 配置:

spring:
  redis:
    host: localhost
    port: 6379

配置 Redisson 的配置类:

import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
import org.redisson.Redisson;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        config.setCodec(new StringCodec());  // 使用 String 编码
        return Redisson.create(config);
    }
}
3.3 使用 Redisson 实现分布式锁

在业务逻辑中使用 Redisson 的分布式锁实现资源控制。

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class MyService {

    @Autowired
    private RedissonClient redissonClient;

    public void executeTaskWithLock() {
        // 获取分布式锁对象
        RLock lock = redissonClient.getLock("myLock");

        try {
            // 尝试获取锁,并设置等待时间与锁的超时时间
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                try {
                    // 执行需要加锁的逻辑
                    System.out.println("Lock acquired, executing protected code.");
                    Thread.sleep(2000); // 模拟业务逻辑
                } finally {
                    // 确保锁在执行后释放
                    lock.unlock();
                }
            } else {
                System.out.println("Unable to acquire lock, another process is running.");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Task interrupted");
        }
    }
}
  • tryLock 参数说明
    • 第一个参数 10 表示等待时间,在等待时间内如果获取到锁就执行,否则放弃。
    • 第二个参数 30 表示锁的持有时间,在此时间内没有释放会自动释放。
    • 注意:确保在业务逻辑执行完后释放锁,以免锁被长期占用。
3.4 使用定时续期机制(Watchdog)

Redisson 提供了 “看门狗” 自动续期机制,默认持锁 30 秒。只要持锁的客户端不断地续期,锁将一直有效,直到显式释放。

public void executeTaskWithWatchdog() {
    RLock lock = redissonClient.getLock("myLock");
    
    try {
        lock.lock();  // 默认 30s 超时时间,Redisson 自动续期
        System.out.println("Lock acquired with watchdog, executing protected code.");
        Thread.sleep(2000);  // 执行业务逻辑
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        lock.unlock();  // 显式释放锁
    }
}

四、总结

使用 Redis 分布式锁实现并发控制是一个高效的解决方案,Redisson 提供了良好的封装,支持简单使用分布式锁以及复杂的 Watchdog 自动续期机制:

  • 通过唯一标识值和原子操作,保证了锁的基本原子性和持有者的唯一性。
  • Watchdog 自动续期确保任务执行超时时不会因锁过期被其他线程抢占。
  • Redisson 提供了更高的易用性和可靠性,非常适合在 Spring Boot 中集成。

你可能感兴趣的:(redis,分布式)