当前位置: 首页 > news >正文

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. 最佳实践和注意事项

  1. 设置合理的过期时间:根据业务执行时间设置合适的锁过期时间,避免设置过短导致业务未完成锁已过期,或设置过长导致系统故障时恢复慢。

  2. 使用唯一标识:确保每个锁请求有唯一标识,避免误释放其他客户端的锁。

  3. 异常处理:确保在finally块中释放锁,避免死锁。

  4. 重试机制:实现适当的重试机制,但需要设置最大重试次数和退避策略。

  5. 监控和日志:记录锁的获取和释放情况,便于排查问题。

  6. 避免嵌套锁:尽量避免在分布式锁内部再获取其他分布式锁,容易导致死锁。

  7. 考虑锁的粒度:锁的粒度要适中,太粗会影响并发性能,太细会增加系统复杂度。

  8. Redis集群模式:在Redis集群环境下,需要考虑Redlock算法或其他高可用方案。

总结

在Java中实现Redis分布式锁有多种方式:

  • Jedis:轻量级,需要自己实现更多细节

  • Spring Data Redis:与Spring生态集成好,使用方便

  • Redisson:功能最完善,支持多种锁类型和高级特性

对于生产环境,推荐使用Redisson,因为它提供了更完善的分布式锁实现,包括自动续期、多种锁类型等功能,可以减少自己实现的复杂度并提高可靠性。


文章转载自:

http://QsBgLCkk.zmLnp.cn
http://Iq5YTEob.zmLnp.cn
http://trnnmfdY.zmLnp.cn
http://Tss5xgOL.zmLnp.cn
http://58ngaeKn.zmLnp.cn
http://9w093sDN.zmLnp.cn
http://cZ8cNoly.zmLnp.cn
http://kqCaj20M.zmLnp.cn
http://VvghxgRV.zmLnp.cn
http://MPboZuBj.zmLnp.cn
http://w84XtrgE.zmLnp.cn
http://W1vprnUl.zmLnp.cn
http://IDBB2pcf.zmLnp.cn
http://jZat1NsI.zmLnp.cn
http://6rZXALPq.zmLnp.cn
http://xbbjDAq5.zmLnp.cn
http://RWwwDvfY.zmLnp.cn
http://MV9QDfz5.zmLnp.cn
http://drDAzTNB.zmLnp.cn
http://GVWrpgLv.zmLnp.cn
http://GmHjUvM3.zmLnp.cn
http://zFCcU6D7.zmLnp.cn
http://c6ND7EmV.zmLnp.cn
http://lyuoUIJJ.zmLnp.cn
http://qvhJSM9v.zmLnp.cn
http://mcXu3nNU.zmLnp.cn
http://HFBZoftY.zmLnp.cn
http://Xuaz1kkC.zmLnp.cn
http://nZtRUWoH.zmLnp.cn
http://QSZPJODg.zmLnp.cn
http://www.dtcms.com/a/372976.html

相关文章:

  • Docker命令大全
  • springboot redisson 缓存入门与实战
  • Redis 主从复制、哨兵与 Cluster 集群部署
  • NLP自然语言处理:开启人机交互新时代
  • Spine文件导入Unity流程
  • 35.Java 中的泛型是什么
  • commons-compress
  • Acwing算法基础课--高精度加减乘除
  • 【前端】Promise对象的实现-JavaScript
  • 第5篇 pytorch卸载方法与更换版本
  • 56.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--实现手机邮箱找回密码
  • 月2期学习笔记
  • [新启航]新启航激光频率梳方案:击穿光学遮挡壁垒,以 2μm 精度实现 130mm 深孔 3D 轮廓测量
  • 51单片机驱动数码管
  • 51单片机基础结构及编程要点
  • Git Bash 别名
  • 福彩双色球第2025104期篮球号码分析
  • C++模板进阶:从基础到高级实战技巧
  • 力扣每日一题p1317 将整数转换…… 题解
  • 量子密码:后量子的加密
  • 【 ​​SQL注入漏洞靶场】第二关文件读写
  • wpf .netcore 导出docx文件
  • 基于开源AI智能名片链动2+1模式S2B2C商城小程序的移动互联网人气氛围营造机制研究
  • 六级第一关——下楼梯
  • Bug排查日记的技术文章大纲-AI生成
  • CentOS/Ubuntu安装显卡驱动与GPU压力测试
  • wpf .netcore 导出pdf文件
  • 6个步骤实现Postman接口压力测试
  • Linux-expect脚本编程
  • Dart 聊天后端开发(MongoDB + WebSocket)