之前看很多人手写分布式锁,其实 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. (三)log4j常用配置

    控制台(console) log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = ...

  2. mysql字符串类型(TEXT 类型)

    TEXT 类型 TEXT 列保存非二进制字符串,如文章内容.评论等.当保存或查询 TEXT 列的值时,不删除尾部空格. TEXT 类型分为 4 种:TINYTEXT.TEXT.MEDIUMTEXT 和 ...

  3. 跨云厂商部署 k3s 集群

    原文链接:https://fuckcloudnative.io/posts/deploy-k3s-cross-public-cloud/ 最近一两年各大云服务商都出了各种福利活动,很多小伙伴薅了一波又 ...

  4. 我们是如何做go语言系统测试覆盖率收集的?

    工程效能领域,测试覆盖率度量总是绕不开的话题,我们也不例外.在七牛云,我们主要使用go语言构建云服务,在考虑系统测试覆盖率时,最早也是通过围绕原生go test -c -cover的能力来构建.这个方 ...

  5. 韩顺刚-tcp报文头协议详细分析第一包数据:序号是0,发送数据的长度是0,因为没有收到对端的数据,所以确认号是0, Syn的标志位设置成1,这里没有发送的数据,只发送TCP的20个字节的头部

    TCP报文段首部格式 大部分TCP报文头部都是20个字节,有的数据包要加上选项. 上面一行代表4个字节,源端口和目的端口都是2个字节. TCP协议是面向字节流的协议 TCP是一段一段分块的发送数据的 ...

  6. SpringMVC 学习笔记(六)拦截器

    5.1.处理器拦截器简介 Spring Web MVC的处理器拦截器(如无特殊说明,下文所说的拦截器即处理器拦截器) 类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理. ...

  7. Docker入门(1):概述

    1.摘要 在这篇文章中,我将介绍一下为什么需要虚拟化的环境. 然后我将介绍耳熟能详的虚拟化技术:虚拟机,并大致的介绍一下虚拟机的原理,希望能够让你知道虚拟机的优劣. 在之后,针对虚拟机存在的问题,引出 ...

  8. Apache POI 操作Excel(2)-- POI包引入项目

    Apache POI发行版包含对许多文档文件格式的支持.这种支持在几个Jar文件中提供.并非每种格式都需要所有jar.下表显示了POI组件.Maven存储库标记和项目的Jar文件之间的关系. (htt ...

  9. ionic2/Angularjs cordova node.js

    ionic 和 cordova 的区别 环境的搭建{node.js,jdk1.8/jdk1.7,android sdk,intellijIdea,ionic,cordova} nodejs+ionic ...

  10. day04 跳转

    一.事件跳转 1.标签绑定click事件 <view bindtap="clickme" data-nid="123" data-name="李 ...