使用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秒内只能被调用一次。

相关推荐
W2155几秒前
Liunx下MySQL:表的约束
数据库·mysql
星河梦瑾3 分钟前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富6 分钟前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
love静思冥想8 分钟前
JMeter 使用详解
java·jmeter
NiNg_1_23410 分钟前
基于Hadoop的数据清洗
大数据·hadoop·分布式
言、雲10 分钟前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
TT哇17 分钟前
【数据结构练习题】链表与LinkedList
java·数据结构·链表
Yvemil71 小时前
《开启微服务之旅:Spring Boot 从入门到实践》(三)
java
Anna。。1 小时前
Java入门2-idea 第五章:IO流(java.io包中)
java·开发语言·intellij-idea
G_whang1 小时前
centos7下docker 容器实现redis主从同步
redis·docker·容器