什么是分布式锁
分布式锁是一种用于在分布式系统中控制多个节点对共享资源进行访问的机制。在分布式系统中,由于多个节点可能同时访问和修改同一个资源,因此需要一种方法来确保在任意时刻只有一个节点能够对资源进行操作,以避免数据不一致或冲突。分布式锁就是用来实现这种互斥访问的工具。
为什么 Redis 的 SETNX 可以实现分布式锁
Redis 的 SETNX
命令(即 SET if Not eXists
)可以用来实现分布式锁,原因如下:
-
原子性:
SETNX
是一个原子操作,这意味着在同一时间只有一个客户端能够成功设置键值对。如果键已经存在,SETNX
将不会执行任何操作。这种原子性确保了锁的获取和释放是线程安全的。 -
唯一性:
SETNX
确保了锁的唯一性。只有第一个尝试设置键的客户端能够成功,其他客户端在尝试设置相同的键时会失败。这模拟了锁的“获取”和“释放”行为。 -
过期时间:通过结合
EXPIRE
命令或使用SET
命令的EX
选项,可以为锁设置一个过期时间。这防止了锁被永久占用,即使客户端在持有锁期间崩溃或未能正确释放锁。 -
分布式环境:Redis 是一个分布式内存数据库,可以在多个节点之间共享数据。因此,使用 Redis 实现的锁可以在分布式系统中的多个节点之间共享,从而实现分布式锁。
-
高性能:Redis 是一个高性能的数据库,能够处理大量的并发请求。这使得 Redis 非常适合作为分布式锁的实现基础。
准备工作
创建一个Spring Boot项目,并引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在application.yml
文件中配置 Redis 连接信息:
server:
port: 8080
spring:
redis:
host: xxx.xxx.xxx.xxx
port: 6379
password: xxxxxx
具体实现
创建分布式锁工具类
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;
}
}
创建业务类用来测试
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
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
,模拟分布式服务:
使用接口调试工具分别向8080
和8081
端口发送请求:
结果如下:
可见在分布式锁的影响下,someMethod
方法在10秒内只能被调用一次。