Redis 分布式锁的 Java 实现
在 Java 中实现 Redis 分布式锁有多种方式,我将介绍几种主流的方法,包括使用 Jedis、Lettuce 和 Redisson 客户端库。
1. 基于 Jedis 的实现
添加依赖
xml
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.3</version>
</dependency>
基础实现
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.Collections;
import java.util.UUID;public class JedisDistributedLock {private final JedisPool jedisPool;private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";private static final Long RELEASE_SUCCESS = 1L;public JedisDistributedLock(String host, int port) {JedisPoolConfig poolConfig = new JedisPoolConfig();this.jedisPool = new JedisPool(poolConfig, host, port);}/*** 获取分布式锁* @param lockKey 锁的key* @param requestId 请求标识(用于标识锁的持有者)* @param expireTime 过期时间(毫秒)* @return 是否获取成功*/public boolean tryLock(String lockKey, String requestId, int expireTime) {try (Jedis jedis = jedisPool.getResource()) {String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);return LOCK_SUCCESS.equals(result);}}/*** 释放分布式锁* @param lockKey 锁的key* @param requestId 请求标识* @return 是否释放成功*/public boolean releaseLock(String lockKey, String requestId) {String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) " +"else " +"return 0 " +"end";try (Jedis jedis = jedisPool.getResource()) {Object result = jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(requestId));return RELEASE_SUCCESS.equals(result);}}/*** 带重试机制的获取锁* @param lockKey 锁的key* @param expireTime 锁的过期时间(毫秒)* @param timeout 获取锁的超时时间(毫秒)* @param retryInterval 重试间隔(毫秒)* @return 锁的标识(用于释放),如果获取失败返回null*/public String lockWithRetry(String lockKey, int expireTime, long timeout, long retryInterval) {String requestId = UUID.randomUUID().toString();long endTime = System.currentTimeMillis() + timeout;while (System.currentTimeMillis() < endTime) {if (tryLock(lockKey, requestId, expireTime)) {return requestId;}try {Thread.sleep(retryInterval);} catch (InterruptedException e) {Thread.currentThread().interrupt();return null;}}return null;}
}
使用示例
java
public class JedisLockExample {public static void main(String[] args) {JedisDistributedLock lock = new JedisDistributedLock("localhost", 6379);String lockKey = "order:lock:123";String requestId = null;try {// 尝试获取锁,最多等待3秒,每次重试间隔100msrequestId = lock.lockWithRetry(lockKey, 30000, 3000, 100);if (requestId != null) {System.out.println("成功获取分布式锁,执行业务逻辑");// 执行需要加锁的业务代码Thread.sleep(1000); // 模拟业务处理} else {System.out.println("获取分布式锁失败");}} catch (Exception e) {e.printStackTrace();} finally {// 释放锁if (requestId != null) {boolean released = lock.releaseLock(lockKey, requestId);if (released) {System.out.println("锁已释放");} else {System.out.println("释放锁失败");}}}}
}
2. 基于 Spring Data Redis 的实现
添加依赖
xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
实现类
java
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;@Component
public class RedisDistributedLock {private final RedisTemplate<String, String> redisTemplate;public RedisDistributedLock(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}private static final String LOCK_SCRIPT = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +"return redis.call('pexpire', KEYS[1], ARGV[2]) " +"else " +"return 0 " +"end";private static final String RELEASE_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) " +"else " +"return 0 " +"end";/*** 尝试获取锁*/public boolean tryLock(String lockKey, String requestId, long expireTime) {RedisScript<Long> script = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), requestId, String.valueOf(expireTime));return result != null && result == 1;}/*** 释放锁*/public boolean releaseLock(String lockKey, String requestId) {RedisScript<Long> script = new DefaultRedisScript<>(RELEASE_SCRIPT, Long.class);Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), requestId);return result != null && result == 1;}/*** 带超时的获取锁方法*/public String acquireLock(String lockKey, long expireTime, long timeout) {String requestId = UUID.randomUUID().toString();long end = System.currentTimeMillis() + timeout;while (System.currentTimeMillis() < end) {if (tryLock(lockKey, requestId, expireTime)) {return requestId;}try {Thread.sleep(100); // 重试间隔} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}return null;}/*** 自动续期的锁*/public String acquireLockWithRenewal(String lockKey, long expireTime, long timeout) {String requestId = UUID.randomUUID().toString();long end = System.currentTimeMillis() + timeout;// 获取锁while (System.currentTimeMillis() < end) {if (tryLock(lockKey, requestId, expireTime)) {// 启动续期线程startRenewalThread(lockKey, requestId, expireTime);return requestId;}try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}return null;}private void startRenewalThread(String lockKey, String requestId, long expireTime) {Thread renewalThread = new Thread(() -> {try {long renewalInterval = expireTime * 2 / 3; // 在过期时间2/3时续期while (!Thread.currentThread().isInterrupted()) {Thread.sleep(renewalInterval);// 检查锁是否仍然被当前线程持有String currentValue = redisTemplate.opsForValue().get(lockKey);if (requestId.equals(currentValue)) {// 续期redisTemplate.expire(lockKey, expireTime, TimeUnit.MILLISECONDS);} else {// 锁已被释放或过期,停止续期break;}}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});renewalThread.setDaemon(true);renewalThread.start();}
}
配置类
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, String> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new StringRedisSerializer());template.afterPropertiesSet();return template;}@Beanpublic RedisDistributedLock redisDistributedLock(RedisTemplate<String, String> redisTemplate) {return new RedisDistributedLock(redisTemplate);}
}
使用示例(Spring Boot)
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class OrderService {@Autowiredprivate RedisDistributedLock redisDistributedLock;private static final String ORDER_LOCK_PREFIX = "order:lock:";public void processOrder(String orderId) {String lockKey = ORDER_LOCK_PREFIX + orderId;String requestId = null;try {// 获取锁,超时时间5秒,锁过期时间30秒requestId = redisDistributedLock.acquireLock(lockKey, 30000, 5000);if (requestId != null) {// 执行需要加锁的业务逻辑System.out.println("开始处理订单: " + orderId);// 业务处理代码...Thread.sleep(2000); // 模拟业务处理System.out.println("订单处理完成: " + orderId);} else {throw new RuntimeException("系统繁忙,请稍后再试");}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("处理中断", e);} finally {// 释放锁if (requestId != null) {redisDistributedLock.releaseLock(lockKey, requestId);}}}
}
3. 使用 Redisson 客户端(推荐)
Redisson 提供了更完善的分布式锁实现,包括可重入锁、公平锁、联锁等。
添加依赖
xml
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.4</version>
</dependency>
配置类
java
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0).setConnectionPoolSize(64).setConnectionMinimumIdleSize(10).setIdleConnectionTimeout(10000).setConnectTimeout(10000).setTimeout(3000).setRetryAttempts(3).setRetryInterval(1500);return Redisson.create(config);}
}
使用 Redisson 实现分布式锁
java
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;@Component
public class RedissonDistributedLock {private final RedissonClient redissonClient;public RedissonDistributedLock(RedissonClient redissonClient) {this.redissonClient = redissonClient;}/*** 加锁*/public boolean lock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);lock.lock();return true;}/*** 加锁并设置过期时间*/public boolean lock(String lockKey, long leaseTime, TimeUnit unit) {RLock lock = redissonClient.getLock(lockKey);lock.lock(leaseTime, unit);return true;}/*** 尝试加锁*/public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {RLock lock = redissonClient.getLock(lockKey);return lock.tryLock(waitTime, leaseTime, unit);}/*** 释放锁*/public void unlock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}}/*** 检查锁是否被当前线程持有*/public boolean isHeldByCurrentThread(String lockKey) {RLock lock = redissonClient.getLock(lockKey);return lock.isHeldByCurrentThread();}
}
使用示例
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;@Service
public class InventoryService {@Autowiredprivate RedissonDistributedLock redissonDistributedLock;private static final String INVENTORY_LOCK_PREFIX = "inventory:lock:";public boolean reduceStock(String productId, int quantity) {String lockKey = INVENTORY_LOCK_PREFIX + productId;try {// 尝试获取锁,最多等待3秒,锁过期时间10秒boolean acquired = redissonDistributedLock.tryLock(lockKey, 3, 10, TimeUnit.SECONDS);if (acquired) {// 执行库存扣减逻辑System.out.println("开始扣减库存,产品ID: " + productId);// 业务代码...Thread.sleep(1000); // 模拟业务处理System.out.println("库存扣减完成,产品ID: " + productId);return true;} else {System.out.println("获取锁失败,产品ID: " + productId);return false;}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("处理中断", e);} finally {// 释放锁redissonDistributedLock.unlock(lockKey);}}
}
4. 高级特性:锁续期和看门狗机制
Redisson 内置了看门狗机制,可以自动为锁续期,避免业务执行时间超过锁过期时间导致的问题。
java
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;@Service
public class LongRunningTaskService {@Autowiredprivate RedissonClient redissonClient;public void processLongTask(String taskId) {RLock lock = redissonClient.getLock("task:lock:" + taskId);try {// 使用看门狗机制,锁默认过期时间为30秒,但看门狗会每10秒检查一次并续期lock.lock();// 或者指定锁过期时间,但不使用看门狗// lock.lock(60, TimeUnit.SECONDS);// 执行长时间任务System.out.println("开始执行长时间任务: " + taskId);for (int i = 0; i < 10; i++) {System.out.println("任务进度: " + (i + 1) + "/10");Thread.sleep(5000); // 模拟长时间处理,每次5秒}System.out.println("长时间任务完成: " + taskId);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("任务中断", e);} finally {if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}}}
}
5. 最佳实践和注意事项
设置合理的过期时间:根据业务执行时间设置合适的锁过期时间,避免设置过短导致业务未完成锁已过期,或设置过长导致系统故障时恢复慢。
使用唯一标识:确保每个锁请求有唯一标识,避免误释放其他客户端的锁。
异常处理:确保在finally块中释放锁,避免死锁。
重试机制:实现适当的重试机制,但需要设置最大重试次数和退避策略。
监控和日志:记录锁的获取和释放情况,便于排查问题。
避免嵌套锁:尽量避免在分布式锁内部再获取其他分布式锁,容易导致死锁。
考虑锁的粒度:锁的粒度要适中,太粗会影响并发性能,太细会增加系统复杂度。
Redis集群模式:在Redis集群环境下,需要考虑Redlock算法或其他高可用方案。
总结
在Java中实现Redis分布式锁有多种方式:
Jedis:轻量级,需要自己实现更多细节
Spring Data Redis:与Spring生态集成好,使用方便
Redisson:功能最完善,支持多种锁类型和高级特性
对于生产环境,推荐使用Redisson,因为它提供了更完善的分布式锁实现,包括自动续期、多种锁类型等功能,可以减少自己实现的复杂度并提高可靠性。