分布式锁的实现之 redis 篇
为什么需要分布式锁
引入经典的秒杀情景,100件商品供客户抢。如果是单机版的话,我们使用synchronized 或者 lock 都可以实现线程安全。但是如果多个服务器的话,synchronized 和 lock 就不管用了(废话,怎么可能管用,都不在同一段代码了)。
分布式锁就是被设计出来实现多个服务器的线程安全。
很容易想到的方案是把共享变量(锁)抽取出来放在一个公共的数据库里(Redis、Memchhed)里,所有的服务器通过这个公共的资源实现数据的一致性,防止超卖。
具体实现
分布式锁的实现方式有:Memchched分布式锁、Redis分布式锁、Zookeeper分布式锁,这里我们以Redis分布式锁为例,Redis分布式锁也是现在使用得最多的
1. 思路
setnx加锁
setnx是实现分布式的核心,意思是只有当前key不存在才返回1,当前key存在返回0
这个key就是我们的“锁”,只有线程获得锁才能继续执行,执行完del这个key相当于解锁操作。这个就是redis实现分布式锁的核心,怎么样,很好理解吧
del解锁
2. 第一个问题:锁无法被释放
试想一下,如果你执行完set命令服务器宕机了,来不及del解锁,那么这个锁永远无法被释放,其他线程无法执行。
解决方法是key必须设置一个超时时间,即使没有被显示释放,也在超时后自动释放。
redis为我们提供了这个命令设置超时时间
- expire key ttl 秒为单位
- pexpire key ttl 毫秒为单位
- expireat key timestamp
- pexpireat key timestamp
因此加锁的操作变成:
setnx lock 1
expire lock 10
但是这两个操作不保证原子性(Redis单条操作保证原子性),如果加完锁还没设置过期时间服务器就宕机了,同样会导致死锁,因此加锁整个操作必须保证原子性。
redis提供了set+过期时间的原子操作
set lock 1 EX 10 NX
// 最终的加锁命令
3. 第二个问题:错误释放锁
第二个问题,如果线程执行时间超过TTL,当前锁被自动销毁
但是等线程执行完了,原来的del方法还会执行,它就会去执行解锁操作,把其他线程占用的锁给del了,这会产生非常严重的问题
String REDIS_Lock="lock";
String value=1;
try{
redisUtil.setLockDistribute(REDIS_LOCK,1,10);
......业务逻辑
}finally{
// 这个操作有可能会误删锁
redisUtil.del(REDIS_LOCK);
}
解决方案是key的value不再是默认的了
String REDIS_Lock="lock";
String value=UUID.randomUUID().toString()+Thread.currentThread().getname();
try{
redisUtil.setLockDistribute(REDIS_LOCK,1,10);
......业务逻辑
}finally{
// 先判断后删除
if(redisUtil.get(REDIS_LOCK).equals(value)){
redisUtil.del(REDIS_LOCK);
}
}
这样写其实还有个问题,判断和删除无法保证原子性,还是有可能误删。因此解锁我们使用lua脚本来保证原子性:工具类有实现lua脚本的方法。
//lua脚本删除key原子操作
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
(解锁操作也可用事务来保证原子性,应付面试,实战还是lua脚本)
4. 第三个问题:超时解锁导致并发
加锁和解锁操作我们都搞定了,但是还有一个问题:如果你的线程执行时间超过ttl过期时间,锁还是被释放了,其他线程可以和次线程并发执行,这是我们并不想看到的。
因此我们要为ttl延时
我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”。

5. 集群环境下可能出现的问题
redis集群环境,多个master,多个slave的情况下:
当主节点挂掉时,从节点会取而代之,但客户端无明显感知。当客户端 A 成功加锁,指令还未同步,此时主节点挂掉,从节点提升为主节点,新的主节点没有锁的数据,当客户端 B 加锁时就会成功。
也就是主结点加了锁就宕机了,从节点还没同步,当该从节点提升为主节点时就会出错。

解决方案我也不清楚....以后碰到再找资料
开源框架Redisson
上面的流程如果手写的话会要人老命,开源框架Redisson帮我们摆平一切,现在用得十分多
直接上代码:
// 注入redisson
public Redisson redisson(){
Config config=new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return Redisson.create(config);
}
@Autowired
Redisson son;
String REDIS_Lock="lock";
String value=UUID.randomUUID().toString()+Thread.currentThread().getname();
RLock lock=son.getLock();
try{
lock.lock();
......业务逻辑
}finally{
lock.unlock();
}
// 这段代码会解决上述三个问题,集群环境下redis分布式锁的实现
结语
分布式锁看起来难其实原理还是很简单的,没事多看看官方文档,讲得挺细致的
参考
- 分布式锁的实现之 redis 篇
- 漫画:什么是分布式锁?
- [通俗讲解分布式锁,看完不懂算作者输](https://www.zhihu.com/search?type=content&q=分布式锁)
- 尚硅谷周阳-大厂面试题第二季
分布式锁的实现之 redis 篇的更多相关文章
- Redis分布式锁—SETNX+Lua脚本实现篇
前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ...
- 借读:分布式锁和双写Redis
本帖最后由 howtodown 于 2016-10-3 16:01 编辑问题导读1.为什么会产生分布式锁?2.使用分布式锁的方法有哪些?3.本文创造的分布式锁的双写Redis框架都包含哪些内容? ...
- 分布式锁-基于ZK和Redis实现
一.基于zookeeper实现分布式锁 1.1 Zookeeper的常用接口 package register; import java.util.List; import java.util.con ...
- 分布式锁(2) ----- 基于redis的分布式锁
分布式锁系列文章 分布式锁(1) ----- 介绍和基于数据库的分布式锁 分布式锁(2) ----- 基于redis的分布式锁 分布式锁(3) ----- 基于zookeeper的分布式锁 代码:ht ...
- springboot实现分布式锁(spring integration,redis)
Springboot实现分布式锁(Spring Integration+Redis) 一.在项目的pom.xml中添加相关依赖 1)Spring Integration依赖 <dependenc ...
- 面试官再问Redis分布式锁如何续期?这篇文章甩 他一脸
一.真实案例 二.Redis分布式锁的正确姿势 据肥朝了解,很多同学在用分布式锁时,都是直接百度搜索找一个Redis分布式锁工具类就直接用了.关键是该工具类中还充斥着很多System.out.prin ...
- Redis 分布式锁,C#通过Redis实现分布式锁(转)
目录(?)[+] 分布式锁一般有三种实现方式: 可靠性 分布式锁一般有三种实现方式: 1. 数据库乐观锁; 2. 基于Redis的分布式锁; 3. 基于ZooKeeper的分布式锁.本篇博客将介绍 ...
- 分布式锁中的基于redis的setnx的原理以及set和setnx的区别是什么
基于Redis实现分布式锁.虽然网上介绍的Redis分布式锁博客比较多,却有着各种各样的问题,本篇博客将详细介绍如何正确地使用setnx实现Redis分布式锁 这里就不介绍错误的示范了 大家直接看正确 ...
- 分布式锁实现(一):Redis
前言 单机环境下我们可以通过JAVA的Synchronized和Lock来实现进程内部的锁,但是随着分布式应用和集群环境的出现,系统资源的竞争从单进程多线程的竞争变成了多进程的竞争,这时候就需要分布式 ...
随机推荐
- 《C++ Primer》笔记 第2章 变量和基本类型
如果你的数值超过了int表示范围,选用long long 如果你需要使用一个不大的整数,那么明确指定它的类型是signed char或者unsigned char 执行浮点数运算选用double 当一 ...
- eureka server 配置
启动类 import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure. ...
- Magicodes.IE Excel合并行数据导入教程
说明 Magicodes.IE.Excel目前已支持合并行单元格导入,如本篇教程所示. 安装包Magicodes.IE.Excel Install-Package Magicodes.IE.Excel ...
- Java概述与准备
Java概述 Java语言背景介绍 Java之父:詹姆斯·高斯林(James Gosling) java语言的三个版本: JavaSE: Java 语言的(标准版),用于桌面应用的开发,是其他两个 ...
- 关于redis缓存数据库的一些思考
今晚无聊,躺在床上,在刷技术文章时,看见了一篇关于redis缓存的文章 写的蛮好,这也就引起了我对于redis思考! 不如往深了说 引起了我对于追求探索技术本质的一些思考 平时在网上刷到很多关于red ...
- External Libraries中没有Maven的jar包的原因(已解决)
**深坑!** ## External Libraries中没有Maven的jar包的原因(已解决) 2021年3月1日 --- 搭建一个新项目 IDEA 从 Git 上拉 拉去Maven项目然后 m ...
- 计算异质性H值(运用arcgis和Python进行区域分析)
最近需要对ecognition分割结果进行统计分析,以此来进一步判断其分割结果中的欠分割和过分割对象,在看了一篇论文后,发现了可以用一个参数H来判断每个切割对象的异质性,由于此方法需要用到arcgis ...
- Pytorch编程记录
搭建网络的方式: 1.用sequential方式搭建,只能适用于线性网络 2.用forward和init方式搭建
- FZU_1608 Huge Mission 【线段树区间更新】
一.题目 Huge Mission 二.分析 区间更新,用线段树的懒标记即可.需要注意的时,由于是在最后才查询的,没有必要每次更新都对$sum$进行求和.还有一点就是初始化的问题,一定记得线段树上每个 ...
- 【odoo14】第二十一章、性能优化
通过odoo框架,我们可以开发大型且复杂的应用.良好的性能是实现这一目标的基础.本章,我们将探讨如何提高应用性能.同时,我们也会讲解找出影响性能的因素. 本章包含以下内容: 记录集的预读取模式 将数据 ...