在分布式系统中,多个服务实例可能需要访问共享资源,此时需要分布式锁来保证操作的互斥性。Redis凭借高性能和原子操作特性,成为实现分布式锁的理想选择。
Redis实现分布式锁主要基于以下命令:
SET key value NX PX timeout
import redis.clients.jedis.Jedis;
import java.util.UUID;
public class RedisLock {
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private Jedis jedis;
private String lockKey;
private String requestId;
private int expireTime;
public RedisLock(Jedis jedis, String lockKey, int expireTime) {
this.jedis = jedis;
this.lockKey = lockKey;
this.expireTime = expireTime;
this.requestId = UUID.randomUUID().toString();
}
/**
* 获取锁
* @return 是否成功获取锁
*/
public boolean acquire() {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
return LOCK_SUCCESS.equals(result);
}
/**
* 释放锁
* @return 是否成功释放锁
*/
public boolean release() {
// 使用Lua脚本保证原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, 1, lockKey, requestId);
return RELEASE_SUCCESS.equals(result);
}
public String getRequestId() {
return requestId;
}
}
import redis.clients.jedis.Jedis;
public class RedisLockExample {
public static void main(String[] args) {
// 初始化Redis客户端
Jedis jedis = new Jedis("localhost", 6379);
// 创建锁实例
RedisLock lock = new RedisLock(jedis, "my_distributed_lock", 30000);
// 获取锁
boolean acquired = lock.acquire();
if (acquired) {
try {
// 执行临界区代码
System.out.println("获取锁成功,执行临界区操作");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.release();
System.out.println("释放锁");
}
} else {
System.out.println("获取锁失败");
}
// 关闭Redis连接
jedis.close();
}
}
为避免业务执行时间超过锁的过期时间导致锁提前释放,可以使用"看门狗"机制自动续期:
import redis.clients.jedis.Jedis;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class RedisLockWithAutoRenew {
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private Jedis jedis;
private String lockKey;
private String requestId;
private int expireTime;
private ScheduledExecutorService renewExecutor;
private int renewInterval;
private boolean isLocked = false;
public RedisLockWithAutoRenew(Jedis jedis, String lockKey, int expireTime) {
this.jedis = jedis;
this.lockKey = lockKey;
this.expireTime = expireTime;
this.requestId = UUID.randomUUID().toString();
this.renewExecutor = Executors.newSingleThreadScheduledExecutor();
this.renewInterval = expireTime / 3; // 每三分之一的过期时间续期一次
}
/**
* 获取锁
* @return 是否成功获取锁
*/
public boolean acquire() {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
isLocked = true;
startAutoRenew();
return true;
}
return false;
}
/**
* 释放锁
* @return 是否成功释放锁
*/
public boolean release() {
isLocked = false;
renewExecutor.shutdownNow();
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, 1, lockKey, requestId);
return RELEASE_SUCCESS.equals(result);
}
/**
* 启动自动续期
*/
private void startAutoRenew() {
renewExecutor.scheduleAtFixedRate(() -> {
if (isLocked) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('pexpire', KEYS[1], ARGV[2]) else return 0 end";
jedis.eval(script, 1, lockKey, requestId, String.valueOf(expireTime));
}
}, renewInterval, renewInterval, TimeUnit.MILLISECONDS);
}
}
Redisson是一个基于Redis的Java驻内存数据网格(In-Memory Data Grid),提供了分布式锁的实现:
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class RedissonLockExample {
public static void main(String[] args) {
// 配置Redisson
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
// 创建Redisson客户端
RedissonClient redisson = Redisson.create(config);
// 获取锁实例
RLock lock = redisson.getLock("myRedissonLock");
try {
// 尝试获取锁,最多等待100秒,锁持有时间为30秒
boolean acquired = lock.tryLock(100, 30, java.util.concurrent.TimeUnit.SECONDS);
if (acquired) {
try {
// 执行临界区代码
System.out.println("获取Redisson锁成功,执行临界区操作");
Thread.sleep(5000);
} finally {
// 释放锁
lock.unlock();
System.out.println("释放Redisson锁");
}
} else {
System.out.println("获取Redisson锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 关闭Redisson客户端
redisson.shutdown();
}
}
}
public class InventoryService {
private Jedis jedis = new Jedis("localhost", 6379);
public void deductInventory(String productId, int quantity) {
RedisLock lock = new RedisLock(jedis, "inventory_lock:" + productId, 30000);
try {
if (lock.acquire()) {
// 查询库存
int stock = getInventoryFromDB(productId);
if (stock >= quantity) {
// 扣减库存
boolean success = updateInventoryInDB(productId, stock - quantity);
if (success) {
System.out.println("库存扣减成功");
} else {
System.out.println("库存扣减失败");
}
} else {
System.out.println("库存不足");
}
} else {
System.out.println("获取锁失败,请稍后重试");
}
} finally {
lock.release();
}
}
private int getInventoryFromDB(String productId) {
// 从数据库查询库存
return 100; // 示例返回值
}
private boolean updateInventoryInDB(String productId, int newStock) {
// 更新数据库中的库存
return true; // 示例返回值
}
}
public class ScheduledTask {
private RedissonClient redisson;
public ScheduledTask() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
redisson = Redisson.create(config);
}
public void executeDailyReport() {
RLock lock = redisson.getLock("daily_report_task_lock");
try {
// 尝试获取锁,5秒内获取不到则放弃
boolean acquired = lock.tryLock(5, 30, TimeUnit.SECONDS);
if (acquired) {
try {
// 执行每日报告生成任务
generateDailyReport();
} finally {
lock.unlock();
}
} else {
System.out.println("另一个实例正在执行每日报告任务");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void generateDailyReport() {
System.out.println("开始生成每日报告...");
// 执行报告生成逻辑
}
}
import java.util.concurrent.atomic.AtomicInteger;
public class MonitorableRedisLock extends RedisLock {
private static final AtomicInteger totalAcquireCount = new AtomicInteger(0);
private static final AtomicInteger totalReleaseCount = new AtomicInteger(0);
private static final AtomicInteger currentLockedCount = new AtomicInteger(0);
private long acquireTime;
public MonitorableRedisLock(Jedis jedis, String lockKey, int expireTime) {
super(jedis, lockKey, expireTime);
}
@Override
public boolean acquire() {
long startTime = System.currentTimeMillis();
boolean result = super.acquire();
if (result) {
acquireTime = System.currentTimeMillis();
totalAcquireCount.incrementAndGet();
currentLockedCount.incrementAndGet();
// 记录获取锁耗时
long acquireTime = System.currentTimeMillis() - startTime;
if (acquireTime > 1000) { // 超过1秒的获取操作
System.err.println("警告: 获取锁耗时过长 - " + acquireTime + "ms, 锁: " + getLockKey());
}
}
return result;
}
@Override
public boolean release() {
boolean result = super.release();
if (result) {
totalReleaseCount.incrementAndGet();
currentLockedCount.decrementAndGet();
// 记录锁持有时间
long holdTime = System.currentTimeMillis() - acquireTime;
if (holdTime > getExpireTime() * 0.8) { // 接近过期时间
System.err.println("警告: 锁持有时间过长 - " + holdTime + "ms, 锁: " + getLockKey());
}
}
return result;
}
// 监控指标获取方法
public static int getTotalAcquireCount() {
return totalAcquireCount.get();
}
public static int getTotalReleaseCount() {
return totalReleaseCount.get();
}
public static int getCurrentLockedCount() {
return currentLockedCount.get();
}
}
1. 设置合理的过期时间:根据业务执行时间设置锁的过期时间,避免死锁
2. 使用唯一标识:锁的value使用唯一标识,确保锁只能由持有者释放
3. 保证解锁原子性:使用Lua脚本保证解锁操作的原子性
4. 考虑锁续期:对于长时间运行的任务,考虑使用"看门狗"机制自动续期
5. 高可用部署:在生产环境中使用Redis集群或哨兵模式保证可用性
6. 监控与告警:监控锁的持有时间和竞争情况,及时发现问题
7. 选择合适的锁类型:根据业务需求选择普通锁、红锁、读写锁等不同类型
通过合理使用Redis实现分布式锁,可以有效解决分布式系统中的资源竞争问题,提高系统的可靠性和稳定性。