之前看很多人手写分布式锁,其实 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
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>

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

配置分布式锁

@Bean(destroyMethod = "destroy")
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, "lock");
}

使用示例

@GetMapping("/redis/lock")
public String lock(@RequestParam("key") String key) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
redisLockService.lock(key);
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
redisLockService.unlock(key);
}
).start();
}
return "OK";
}

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

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

测试:

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

输出:

2020-06-23 11:15:34
2020-06-23 11:15:37
2020-06-23 11:15:40
2020-06-23 11:15:43
2020-06-23 11:15:46
2020-06-23 11:15:49
2020-06-23 11:15:52
2020-06-23 11:15:55
2020-06-23 11:15:58
2020-06-23 11:16:01

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

源码分析

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

RedisLockRegistry 有两个类构造器:

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

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

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

private final String clientId = UUID.randomUUID().toString();

private final Map<String, RedisLock> locks = new ConcurrentHashMap<>();
  • clientId

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

  • locks

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

获取锁对象

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

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

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

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

private final ReentrantLock localLock = new ReentrantLock();

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 起来吧!

Spring Boot Redis 实现分布式锁,真香!!的更多相关文章

  1. Spring Boot 2实现分布式锁——这才是实现分布式锁的正确姿势!

    参考资料 网址 Spring Boot 2实现分布式锁--这才是实现分布式锁的正确姿势! http://www.spring4all.com/article/6892

  2. redisson spring boot starter 做分布式锁

    使用redisson做分布式锁 分布式锁 在java中单体应用中,我们如果想要保证一个接口或者服务.方法当下只有一个线程在运行,我们可以通过JDK提供的Lock.Semaphore.同步锁等多种方式实 ...

  3. spring boot redis分布式锁

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...

  4. spring boot 利用redisson实现redis的分布式锁

    原文:http://liaoke0123.iteye.com/blog/2375469 利用redis实现分布式锁,网上搜索的大部分是使用java jedis实现的. redis官方推荐的分布式锁实现 ...

  5. 用Redis构建分布式锁-RedLock(真分布)

    在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增 ...

  6. SpringBoot进阶教程(二十七)整合Redis之分布式锁

    在之前的一篇文章(<Java分布式锁,搞懂分布式锁实现看这篇文章就对了>),已经介绍过几种java分布式锁,今天来个Redis分布式锁的demo.redis 现在已经成为系统缓存的必备组件 ...

  7. 基于redis的分布式锁实现方案--redisson

    实例代码地址,请前往:https://gitee.com/GuoqingLee/distributed-seckill redis官方文档地址,请前往:http://www.redis.cn/topi ...

  8. 来吧,展示!Redis的分布式锁及其实现Redisson的全过程

    前言 分布式锁是控制分布式系统之间同步访问共享资源的一种方式. 在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要 ...

  9. 基于 Redis 的分布式锁

    前言 分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三. 首先谈到分布式锁自然也就联想到分布式应用. 在我们将应用拆分为分布式应用之前的单机系统 ...

随机推荐

  1. MySQL ORDER BY:对查询结果进行排序

    在 MySQL SELECT 语句中,ORDER BY 子句主要用来将结果集中的数据按照一定的顺序进行排序. 其语法格式为: ORDER BY {<列名> | <表达式> | ...

  2. CODING DevOps 系列第三课:云计算、云原生模式下 DevOps 的建设

    本文首先会和大家分享当前整个应用生命周期的演变历程,然后讲解云计算模式下 DevOps 建设包含的过程.流程规范和标准,最后讲解云原生时代到来会带来哪些改变,以及标准化的建设会有哪些改变和突破. 应用 ...

  3. 学习 SQL Server (5) :视图,索引,事务和锁+T_SQL

    --=============== 视图的创建 =================. --create view 视图名 as 查询语句--注意:视图查询中的字段不能重名-- 视图中的数据是‘假数据’ ...

  4. 如何在VMware虚拟机中安装CentOS6.7系统(下篇)

    上一篇文章讲到了CentOS6.7的安装教程,安装步骤到时区选择这块了,这篇文章接上篇文章,继续讲述CentOS6.7的安装教程,直至安装完成. 17.设置root的登录密码,日后登录虚拟机,用户名就 ...

  5. ViewPager2 学习

    ViewPager2 延迟加载数据 ViewPager2 延迟加载数据 ViewPager 实现预加载的方案 ViewPager2 实现预加载的方案 总结 ViewPager 实现预加载的方案 背景 ...

  6. Head First 设计模式

    OO基础 抽象 封装 多态 继承 OO原则 封装变化 多用组合,少用继承 针对接口编程,不针对实现编程 为交互对象之间的松耦合设计而努力 对扩展开放,对修改关闭 依赖抽象,不要依赖具体类 最少知识原则 ...

  7. weblogic之高级进阶JMS的应用

    不仅Activemq提供的jms服务,Weblogic也提供了jms服务,很多项目是部署在Weblogic中,使用其提供的jms服务也是很多企业的选择,一直没亲手试过,今天试了下,遇到的小问题还挺多, ...

  8. Struts2 执行流程 以及 Action与Servlet比较 (个人理解)

    上图提供了struts2的执行流程.如下: 1:从客户端发出请求(HTTPServletRequest). 2:请求经过各种过滤器(filter),注:一般情况下,如SiteMesh等其他过滤器要放在 ...

  9. 搞定ReentrantReadWriteLock 几道小小数学题就够了

    | 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...

  10. 主机Redis服务迁移到现有Docker Overlay环境

    记录最后一次对中型2C企业级项目的容器化改造 hello, 好久不见,之前文章记录了一个实战的2C分布式项目的改造过程,结果如下: 其中Redis并未完成容器化改造(目前是主机单点),本文记录将Red ...