Spring Boot Redis实现分布式锁,真香!!

Spring Boot Redis实现分布式锁,真香!!

作者:栈长 2020-07-15 16:50:57

开发

后端

分布式

Redis 本篇栈长以 Redis 为例(这也是用得最多的方案),教大家如何利用 Spring Boot 集成 Redis 实现缓存,如何简单、快速实现 Redis 分布式锁。

之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis、Zookeeper 中间件,另外还支持 JDBC。

本篇栈长以 Redis 为例(这也是用得最多的方案),教大家如何利用 Spring Boot 集成 Redis 实现缓存,如何简单、快速实现 Redis 分布式锁。

分布式锁介绍

Spring Boot 实现 Redis 分布式锁在 spring-integration 这个项目中,参考:

https://docs.spring.io/spring-integration/docs/5.3.1.RELEASE/reference/html/redis.html#redis-lock-registry

首先来看下 LockRegistry 锁注册接口的所有实现类结构图:

DefaultLockRegistry 就是纯单机的可重入锁,PassThruLockRegistry 是一个空实现类,也都没有什么利用价值。

Spring Integration 4.0 引入了基于 Redis 的分布式锁:RedisLockRegistry,并且从 5.0 开始实现了 ExpirableLockRegistry 接口,用来移除超时且没有用的锁。

分布式锁实战

添加依赖

上面提到 Spring Boot 实现 Redis 分布式锁在 spring-integration 这个项目中,所以需要这三个依赖:

  •  spring-boot-starter-data-redis
  •  spring-boot-starter-integration
  •  spring-integration-redis 
  1. <dependency>  
  2.  <groupId>org.springframework.boot</groupId>  
  3.  <artifactId>spring-boot-starter-data-redis</artifactId>  
  4. </dependency>  
  5. <dependency>  
  6.  <groupId>org.springframework.boot</groupId>  
  7.  <artifactId>spring-boot-starter-integration</artifactId> 
  8. </dependency>  
  9. <dependency>  
  10.  <groupId>org.springframework.integration</groupId>  
  11.  <artifactId>spring-integration-redis</artifactId>  
  12. </dependency> 

Spring Boot 基础知识就不介绍了,不熟悉的可以关注公众号Java技术栈,在后台回复:boot,可以阅读我写的历史实战教程。

配置分布式锁 

  1. @Bean(destroyMethod = "destroy" 
  2. public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {  
  3.     return new RedisLockRegistry(redisConnectionFactory, "lock");  

使用示例 

  1. @GetMapping("/redis/lock")  
  2. public String lock(@RequestParam("key") String key) {  
  3.     for (int i = 0; i < 10; i++) {  
  4.         new Thread(() -> {  
  5.             redisLockService.lock(key);  
  6.             try {  
  7.                 Thread.sleep(3000L);  
  8.             } catch (InterruptedException e) {  
  9.                 e.printStackTrace();  
  10.             }  
  11.             System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));  
  12.             redisLockService.unlock(key);  
  13.         }  
  14.         ).start();  
  15.     }  
  16.     return "OK";  

RedisLockService 是我封装了的一个 Redis 锁服务,代码有点多,这里就不贴了,完整的代码示例在 Github 上,大家可以 Star 一下:

https://github.com/javastacks/spring-boot-best-practice

测试:

http://localhost:8080/redis/lock?key=yeah

输出: 

  1. 2020-06-23 11:15:34  
  2. 2020-06-23 11:15:37  
  3. 2020-06-23 11:15:40  
  4. 2020-06-23 11:15:43  
  5. 2020-06-23 11:15:46  
  6. 2020-06-23 11:15:49  
  7. 2020-06-23 11:15:52  
  8. 2020-06-23 11:15:55 
  9. 2020-06-23 11:15:58  
  10. 2020-06-23 11:16:01 

可以看到每个线程需要等上一个线程休眠 3 秒后才能获取到锁。

源码分析

集成完了,会使用了,还得研究下 RedisLockRegistry 的源码,不然遇到什么坑还得再踩一篇。

RedisLockRegistry 有两个类构造器:

  •  connectionFactory:Redis 连接工厂
  •  registryKey:锁前缀
  •  expireAfter:失效时间(非必须项,默认60秒)

所以,我们要注册 expireAfter 这个选项,默认 60 秒是否满足业务需要,如果超过默认的 60 少时间,否则将导致锁失效。

还有两个和 RedisLockRegistry  相关且很重要的成员变量: 

  1. private final String clientId = UUID.randomUUID().toString();  
  2. private final Map<String, RedisLock> locks = new ConcurrentHashMap<>(); 
  •  clientId:

首先用来标识当前 RedisLockRegistry 实例ID,并且在设置、移除锁的时候都会要用到,用来判断是不是当前的锁注册实例。

  •  locks:

用来在内存中缓存当前锁注册实例所有锁对象。

获取锁对象

如下面获取锁对象源码所示:

每个 key 在内存中(ConcurrentHashMap)都对应一个锁对象,锁对象有生成过就直接返回,没有就生成再返回,有了这个锁对象才能进行上锁和解锁操作。

这个锁对象(RedisLock)其实也是实现了 Java 中的 java.util.concurrent.locks.Lock 锁接口:

锁对象(RedisLock)也有两个很重要的成员变量: 

  1. private final ReentrantLock localLock = new ReentrantLock();  
  2. private volatile long lockedAt; 
  •  localLock:

localLock 是一个本地内存可重入锁,每次去 Redis 上锁前,都要用本地 localLock 上锁先,这样能做到尽可能的少往 Redis 上锁,也能从一方面提升锁的性能。

  •  lockedAt:

lockedAt 上锁时间,移除过时锁会用到。

阻塞上锁

RedisLock#lock():

每隔 100 毫秒尝试获取一次锁,直到获取锁成功为止,不接受打断异常,遇到其他异常会释放本地锁返回异常并结束。

主要看下设置 Redis 锁的 Lua 脚本:

根据 key 查询其对应的值:clientId,如果和当前 clientId 一致则延长失效时间,如果 clientId 不存在就直接上锁,以上都不成立返回 false。

这样做的好处是,可以将整个 Redis Lua 脚本作为一个原子执行,而不用考虑并发及事务影响。

好了,核心的源码分析完了,其实也很简单,大家还不懂的或者有兴趣的可以再研究下。

本文完整示例源代码和上篇 Spring Boot 快速集成 Redis 的示例代码一起上传到了 Github,欢迎大家 Star 关注学习。

https://github.com/javastacks/spring-boot-best-practice

所以,你还在手写分布式锁吗?赶快 Get 起来吧! 

 

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/256263.html<

(0)
管理的头像管理
上一篇2025-05-01 05:39
下一篇 2025-05-01 05:40

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注