使用Redis的SETNX命令实现分布式锁

什么是分布式锁

分布式锁是一种用于在分布式系统中控制多个节点对共享资源进行访问的机制。在分布式系统中,由于多个节点可能同时访问和修改同一个资源,因此需要一种方法来确保在任意时刻只有一个节点能够对资源进行操作,以避免数据不一致或冲突。分布式锁就是用来实现这种互斥访问的工具。

为什么 Redis 的 SETNX 可以实现分布式锁

Redis 的 SETNX 命令(即 SET if Not eXists)可以用来实现分布式锁,原因如下:

  1. 原子性SETNX 是一个原子操作,这意味着在同一时间只有一个客户端能够成功设置键值对。如果键已经存在,SETNX 将不会执行任何操作。这种原子性确保了锁的获取和释放是线程安全的。

  2. 唯一性SETNX 确保了锁的唯一性。只有第一个尝试设置键的客户端能够成功,其他客户端在尝试设置相同的键时会失败。这模拟了锁的"获取"和"释放"行为。

  3. 过期时间 :通过结合 EXPIRE 命令或使用 SET 命令的 EX 选项,可以为锁设置一个过期时间。这防止了锁被永久占用,即使客户端在持有锁期间崩溃或未能正确释放锁。

  4. 分布式环境:Redis 是一个分布式内存数据库,可以在多个节点之间共享数据。因此,使用 Redis 实现的锁可以在分布式系统中的多个节点之间共享,从而实现分布式锁。

  5. 高性能:Redis 是一个高性能的数据库,能够处理大量的并发请求。这使得 Redis 非常适合作为分布式锁的实现基础。

准备工作

创建一个Spring Boot项目,并引入相关依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml文件中配置 Redis 连接信息:

yml 复制代码
server:
  port: 8080
spring:
  redis:
    host: xxx.xxx.xxx.xxx
    port: 6379
    password: xxxxxx

具体实现

创建分布式锁工具类

java 复制代码
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class DistributedLock {

    private final StringRedisTemplate redisTemplate;

    // 通过构造函数注入 StringRedisTemplate
    public DistributedLock(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 尝试获取分布式锁
     *
     * @param lockKey    锁的键
     * @param requestId  请求标识,用于区分不同的锁持有者
     * @param expireTime 锁的过期时间,单位为毫秒
     * @return 如果成功获取锁,返回 true;否则返回 false
     */
    public boolean acquireLock(String lockKey, String requestId, long expireTime) {
        // 使用 setIfAbsent 方法尝试设置键值对,如果键不存在则设置成功并返回 true,否则返回 false
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);
        return result != null && result;
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey   锁的键
     * @param requestId 请求标识,用于确保只有锁的持有者才能释放锁
     * @return 如果成功释放锁,返回 true;否则返回 false
     */
    public boolean releaseLock(String lockKey, String requestId) {
        // 获取当前锁的值
        String currentValue = redisTemplate.opsForValue().get(lockKey);
        // 检查当前锁的值是否等于请求标识,确保只有锁的持有者才能释放锁
        if (currentValue != null && currentValue.equals(requestId)) {
            // 删除锁键
            return redisTemplate.delete(lockKey);
        }
        return false;
    }
}

创建业务类用来测试

java 复制代码
import com.wh.demo01.demos.web.utils.DistributedLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class MyRedisService {

    private final DistributedLock distributedLock;

    // 通过构造函数注入 DistributedLock
    @Autowired
    public MyRedisService(DistributedLock distributedLock) {
        this.distributedLock = distributedLock;
    }

    /**
     * 模拟需要同步执行的方法
     */
    public void someMethod() {
        String lockKey = "myLockKey";
        String requestId = "uniqueRequestId";
        long expireTime = 10000; // 10 seconds

        try {
            // 尝试获取锁
            if (distributedLock.acquireLock(lockKey, requestId, expireTime)) {
                // 获取到锁,执行需要同步的操作
                System.out.println(new Date() + "获取锁成功");
                // 模拟业务操作
                Thread.sleep(5000);
            } else {
                System.out.println(new Date() + "获取锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 确保锁在操作完成后被释放
            distributedLock.releaseLock(lockKey, requestId);
        }
    }
}

创建Controller

java 复制代码
import com.wh.demo01.demos.web.service.MyRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/redis")
public class RedisController {

    @Autowired
    private MyRedisService service;

    @GetMapping("/test")
    public void TestRedis(){
        service.someMethod();
    }
}

整个项目结构如下:

使用idea的复制配置功能将该服务复制一份,并指定端口为8081,模拟分布式服务:

使用接口调试工具分别向80808081端口发送请求:

结果如下:

可见在分布式锁的影响下,someMethod方法在10秒内只能被调用一次。

相关推荐
十叶知秋1 分钟前
【jmeter】jmeter的线程组功能的详细介绍
数据库·jmeter·性能测试
monkey_meng6 分钟前
【Rust中的迭代器】
开发语言·后端·rust
余衫马9 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
yx9o10 分钟前
Kafka 源码 KRaft 模式本地运行
分布式·kafka
monkey_meng12 分钟前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
七星静香13 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员14 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU14 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie618 分钟前
在IDEA中使用Git
java·git
Elaine20239133 分钟前
06 网络编程基础
java·网络