SpringBoot实现分布式锁
SpringBoot.Redis实现分布式锁
[提前声明]
文章由作者:张耀峰 结合自己生产中的使用经验整理,最终形成简单易懂的文章
写作不易,转载请注明,谢谢!
spark代码案例地址: https://github.com/Mydreamandreality/sparkResearch
线程锁
哎?我们不是要实现分布式锁吗,为啥扯到了线程锁?
不要急,防止有些朋友不熟悉分布式锁的概念,在实现分布式锁之前,咱们先了解下线程锁
线程锁可能对这个概念也有些人不是很清楚,但如果我说synchronize
同步关键字,大家是不是就知道了
- 线程锁:主要用来给类,方法,代码加锁,当某个方法或者某块代码使用
synchronize
关键字来修饰,那么在同一时刻最多只能有一个线程执行该代码,如果同一时刻有多个线程访问该代码,其它未抢到资源的线程标记为阻塞状态,直到获取到锁的线程执行完毕自动释放其余线程才能执行 - 线程锁
synchronize
在单机部署的状态下,确实可以保证线程安全,但是如果是集群或者分布式部署呢?
集群模式下线程锁为何无法满足锁
需求?
- 分布式的CAP理论:
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项
- 现在许多项目考虑到性能和扩展性都通过分布式的方式进行部署,分布式中的数据一致性一直是一个比较重要的问题,基于CAP的理论,在系统最开始设计的时候,就需要对这三点进行取舍,在我们的场景中,一般来讲都是牺牲强一致性,来提升高可用,系统只需要保证最终一致性即可
- 如果要保证数据的最终一致性可以通过分布式锁,分布式事务等一些技术来实现,很多时候要达到最终一致性,也要保证一个方法在同一时刻只能被一个线程执行
- 上面我们也说了,在单机环境中,
synchronize
也确实可以解决我们的问题,但是在分布式环境中,就不可以了,为什么? - 我们要知道,分布式系统和单机系统最大的区别就是,单机系统中是多线程,分布式系统中是多进程
- 多线程可以共享一个Jvm中的堆内存,所以可以采取内存作为标记存储的位置,多进程的情况下,有可能这些进程都不在同一台物理机上,是无法共享同一Jvm中的堆内存,所以就需要把标记存储在一个第三方上面,保证所有进程可见
讲到这里大家也大概能反推出
synchronize
的底层实现了吧,其实就是Jvm在方法常量池中的方法表结构访问标志区来判断某个方法是否同步方法,方法被调用的时候,调用的指令就会检查方法的访问标记有没有被设置同步,如果设置了,当前执行线程就会持有一个标记,然后执行方法,最后执行完再释放这个标记
设计分布式锁
- 通过上面的了解,大家应该可以再次反推出分布式锁的实现了吧
- 在实现分布式锁之前,我们先设计下分布式锁的实现
我们需要什么样的分布式锁?
(我说一下如果是我们场景下的设计)
- 首先性能肯定要好,获取锁和释放锁的性能一定要高
- 阻塞锁就可以满足我们的需求(还有非阻塞,公平锁等等各种方式)
- 不能出现死锁的情况
然后基于以上的几点,我们来开发分布式锁
使用Redis实现分布式锁
- 增加Redis的Pom文件
<!--Redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- redis依赖commons-pool 这个依赖一定要添加 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>
我们使用Redis的
setnx
(Set If Not Exists)setnx
的解释:如果指定的key不存在则写入在RedisClient中
setnx
写入成功返回1,否则返回0在JavaRedisApi中写入成功返回True,否则返回False
- 可以成功写入代表当前的方法并没有被其它进程占用
- 写入失败代表当前的方法正在被其它进程占用
- Redis本身是单线程IO多路复用技术,不存在线程安全的问题,所以不用考虑
setInx
本身线程安全的问题
了解了setnx的用法后,我们的思路就是:
- 获取锁:使用setnx在Redis中标记当前方法正在被其它进程占用(指定Key)
- 释放锁:删除我们在Redis中的标记(指定Key)
- 避免死锁:如果极端情况下,获取锁后执行方法异常导致服务挂了,那么锁是不会释放的,有可能会死锁,所以在获取锁后,需要设置过期时间,防止死锁
如下所示:
思路整理清楚后,conding就简单多了
创建RedisUtils工具类
package com.util;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import v1.exceptionding.DingCloverExceptionEnum;
import v1.exceptionding.DingException;
import v1.util.ToolUtil;import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;/*** @author 孤* @version v1.0* @Developers 张耀烽* @serviceProvider 四叶草安全(SeClover)* @description Redis工具* @date 2020/1/7*/
@Component
public class RedisUtils {@Autowiredprivate StringRedisTemplate redisTemplate;/*** Redis分布式锁** @return*/public boolean tryLock(String key, String value, long timeout) {if (timeout == 0) {timeout = 60 * 3;}boolean isSuccess = redisTemplate.opsForValue().setIfAbsent(key, value);//设置过期时间,防止死锁if (isSuccess) {redisTemplate.expire(key, timeout, TimeUnit.SECONDS);}return isSuccess;}/*** Redis 分布式锁释放** @param key* @param value*/public void unLock(String key, String value) {try {String currentValue = redisTemplate.opsForValue().get(key);if (ToolUtil.isNotEmpty(currentValue) && ToolUtil.equals(currentValue, value)) {redisTemplate.opsForValue().getOperations().delete(key);}} catch (Exception e) {//这个是我的自定义异常,你可以删了throw new DingException(DingCloverExceptionEnum.InternalServerError);}}
}
- 获取锁和释放锁的工具写好后,定义我们的业务代码
public void mockLock() {RedisUtils redisUtils = SpringContextHolder.getBean(RedisUtils.class);InetAddress addr = null;try {addr = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}//获取本机ipString ip = addr.getHostAddress();//此key存放的值为任务执行的ip,// expire_time 不能设置为永久,避免死锁boolean lock = redisUtils.tryLock("lock_key", ip, 0);if (lock) {System.out.println("获取分布式锁成功");run();//释放锁redisUtils.unLock("lock_key",ip);System.out.println("释放分布式锁成功");} else {System.out.println("获得分布式锁失败");ip = (String) redisUtils.get(lock_key);System.out.println(ip+"正在执行该任务");return;}}public void run() throws InterruptedException {System.out.println("业务执行中");Thread.sleep(60 * 3);System.out.println("业务执行结束");}
关于锁的总结
- 锁的实现方式和设计有很多方式,Mysql,zookeeper等等都可以,其中的坑也有很多,锁的两大设计模式即是 乐观锁,悲观锁 之后我会抽空把锁这块的知识点汇总然后分享
- 有任何问题可以留言交流!
SpringBoot实现分布式锁相关推荐
- SpringBoot使用分布式锁
SpringBoot使用分布式锁 1.介绍 Redisson是一个基于NIO的Netty框架的企业级的开源Redis Client,也提供了分布式锁的支持,Redisson 是架设在 Redis 基础 ...
- Jmeter Springboot Redisson分布式锁并发订单操作(下单、取消单、完成单、加库存)
Jmeter+Springboot+Redisson分布式锁并发订单操作(下单.取消单.完成单.加库存) 涉及知识点: java+springboot+mybatis开发 redis分布式锁+Redi ...
- zookeeper-常用命令,集成springboot,分布式锁实现和原理 ,dock集群zookeeper搭建,
(一)zookeeper数据模型 树形结构 每个节点里面保存信息 节点拥有子节点 节点是临时的也可以是持久的 四大节点 PERSISTENT-持久化目录节点 客户端与zookeeper断开连接后,该节 ...
- SpringBoot + Redis 分布式锁:模拟抢单
作者:神牛003 cnblogs.com/wangrudong003/p/10627539.html 本篇内容主要讲解的是redis分布式锁,这个在各大厂面试几乎都是必备的,下面结合模拟抢单的场景来使 ...
- SpringBoot Redis分布式锁
maven依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>s ...
- springboot mysql行锁_SpringBoot基于数据库实现简单的分布式锁
本文介绍SpringBoot基于数据库实现简单的分布式锁. 1.简介 分布式锁的方式有很多种,通常方案有: 基于mysql数据库 基于redis 基于ZooKeeper 网上的实现方式有很多,本文主要 ...
- @transaction使自定义注解失效_【完美】SpringBoot中使用注解来实现 Redis 分布式锁...
一.业务背景 有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响. 二.分析流程 使用 Redis 作为分布式锁,将锁的状态放到 Red ...
- spring-boot 中实现标准 redis 分布式锁
一.前言 redis 现在已经成为系统缓存的必备组件,针对缓存读取更新操作,通常我们希望当缓存过期之后能够只有一个请求去更新缓存,其它请求依然使用旧的数据.这就需要用到锁,因为应用服务多数以集群方式部 ...
- Java基于redis实现分布式锁(SpringBoot)
前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...
最新文章
- 实战项目一:爬取西刺代理(获取代理IP)
- 改变libreOffice的Calc的背景颜色
- python程序设计实践教程陈东_Python
- Linux加密框架 crypto算法模板 以及HMAC算法模板实例
- 一个莫名的人,竞标,教训,韩国女人
- 蛋疼的strtok函数
- linux 丢包多少正常_干货总结!Linux运维故障排查思路
- 这是一本零基础学习 Python 的好书
- [USACO13JAN]Painting the Fence【扫描线】
- 符号_液压图形符号识别之减压阀符号原理
- 服装制图软件测试初学者,服装行业版软件测试文案.ppt
- 在hb中html怎么插图片,WKWebView加载HTML文本(图片自适应)
- 我的北京生活,2018面向新的开始
- 精彩回顾 | Dev.Together 2022 开发者生态峰会圆满落幕
- SAP采购中基于收货的发票校验应用逻辑及控制
- PS修改PSD源文件
- Flutter 插件库
- python小小爬虫(二)——爬取小说
- iOS textView return实现键盘收回
- 无法加载文件 C:\Users\Administrator\PycharmProjects\pythonProject\venv\Scripts\activate.ps1,因为在此系统上禁止运行脚本。
热门文章
- uni-app小程序使用小程序码绑定用户信息合成海报
- 青少儿科学小实验--不倒翁易拉罐(重心实验)
- android 高德地图移动卡顿_不只是高德地图 评天猫精灵高德版车盒
- android 如何给图片添加水印
- [iOS开发]高德地图SDK
- 群晖216j使用web station部署librespeed下载无速度的解决方法
- Prolific USB-to-Serial Comm Port最新程序不支持win11怎么处理
- Commit: Not all refs have been pushed.
- 关于Linux下文件删除文件时提示No such file or directory的解决办法
- 中国第一份OA系统用户实名口碑选型报告(选型宝重磅发布!)