redis分布式锁,面试官请随便问,我都会
前言
现在的业务场景越来越复杂,使用的架构也就越来越复杂,分布式、高并发已经是业务要求的常态。像腾讯系的不少服务,还有CDN优化、异地多备份等处理。
说到分布式,就必然涉及到分布式锁的概念,如何保证不同机器不同线程的分布式锁同步呢?
实现要点
- 互斥性,同一时刻,智能有一个客户端持有锁。
- 防止死锁发生,如果持有锁的客户端崩溃没有主动释放锁,也要保证锁可以正常释放及其他客户端可以正常加锁。
- 加锁和释放锁必须是同一个客户端。
- 容错性,只有redis还有节点存活,就可以进行正常的加锁解锁操作。
正确的redis分布式锁实现
错误加锁方式
错误方式一
保证互斥和防止死锁,首先想到的使用redis的setnx命令保证互斥,为了防止死锁,锁需要设置一个超时时间。
public static void wrongLock(Jedis jedis, String key, String uniqueId, int expireTime) {
Long result = jedis.setnx(key, uniqueId);
if (1 == result) {
//如果该redis实例崩溃,那就无法设置过期时间了
jedis.expire(key, expireTime);
}
}
在多线程并发环境下,任何非原子性的操作,都可能导致问题。这段代码中,如果设置过期时间时,redis实例崩溃,就无法设置过期时间。如果客户端没有正确的释放锁,那么该锁(永远不会过期),就永远不会被释放。
错误方式二
比较容易想到的就是设置值和超时时间为原子原子操作就可以解决问题。那使用setnx命令,将value设置为过期时间不就ok了吗?
public static boolean wrongLock(Jedis jedis, String key, int expireTime) {
long expireTs = System.currentTimeMillis() + expireTime;
// 锁不存在,当前线程加锁成果
if (jedis.setnx(key, String.valueOf(expireTs)) == 1) {
return true;
}
String value = jedis.get(key);
//如果当前锁存在,且锁已过期
if (value != null && NumberUtils.toLong(value) < System.currentTimeMillis()) {
//锁过期,设置新的过期时间
String oldValue = jedis.getSet(key, String.valueOf(expireTs));
if (oldValue != null && oldValue.equals(value)) {
// 多线程并发下,只有一个线程会设置成功
// 设置成功的这个线程,key的旧值一定和设置之前的key的值一致
return true;
}
}
// 其他情况,加锁失败
return true;
}
乍看之下,没有什么问题。但仔细分析,有如下问题:
- value设置为过期时间,就要求各个客户端严格的时钟同步,这就需要使用到同步时钟。即使有同步时钟,分布式的服务器一般来说时间肯定是存在少许误差的。
- 锁过期时,使用 jedis.getSet虽然可以保证只有一个线程设置成功,但是不能保证加锁和解锁为同一个客户端,因为没有标志锁是哪个客户端设置的嘛。
错误解锁方式
解锁错误方式一
直接删除key
public static void wrongReleaseLock(Jedis jedis, String key) {
//不是自己加锁的key,也会被释放
jedis.del(key);
}
简单粗暴,直接解锁,但是不是自己加锁的,也会被删除,这好像有点太随意了吧!
解锁错误方式二
判断自己是不是锁的持有者,如果是,则只有持有者才可以释放锁。
public static void wrongReleaseLock(Jedis jedis, String key, String uniqueId) {
if (uniqueId.equals(jedis.get(key))) {
// 如果这时锁过期自动释放,又被其他线程加锁,该线程就会释放不属于自己的锁
jedis.del(key);
}
}
看起来很完美啊,但是如果你判断的时候锁是自己持有的,这时锁超时自动释放了。然后又被其他客户端重新上锁,然后当前线程执行到jedis.del(key),这样这个线程不就删除了其他线程上的锁嘛,好像有点乱套了哦!
正确加锁释放锁方式
基本上避免了以上几种错误方式之外,就是正确的方式了。要满足以下几个条件:
- 命令必须保证互斥
- 设置的key必须要有过期时间,防止崩溃时锁无法释放
- value使用唯一id标志每个客户端,保证只有锁的持有者才可以释放锁
加锁直接使用set命令同时设置唯一id和过期时间;其中解锁稍微复杂些,加锁之后可以返回唯一id,标志此锁是该客户端锁拥有;释放锁时要先判断拥有者是否是自己,然后删除,这个需要redis的lua脚本保证两个命令的原子性执行。
下面是具体的加锁和释放锁的代码:
@Slf4j
public class RedisDistributedLock {
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
// 锁的超时时间
private static int EXPIRE_TIME = 5 * 1000;
// 锁等待时间
private static int WAIT_TIME = 1 * 1000;
private Jedis jedis;
private String key;
public RedisDistributedLock(Jedis jedis, String key) {
this.jedis = jedis;
this.key = key;
}
// 不断尝试加锁
public String lock() {
try {
// 超过等待时间,加锁失败
long waitEnd = System.currentTimeMillis() + WAIT_TIME;
String value = UUID.randomUUID().toString();
while (System.currentTimeMillis() < waitEnd) {
String result = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, EXPIRE_TIME);
if (LOCK_SUCCESS.equals(result)) {
return value;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (Exception ex) {
log.error("lock error", ex);
}
return null;
}
public boolean release(String value) {
if (value == null) {
return false;
}
// 判断key存在并且删除key必须是一个原子操作
// 且谁拥有锁,谁释放
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = new Object();
try {
result = jedis.eval(script, Collections.singletonList(key),
Collections.singletonList(value));
if (RELEASE_SUCCESS.equals(result)) {
log.info("release lock success, value:{}", value);
return true;
}
} catch (Exception e) {
log.error("release lock error", e);
} finally {
if (jedis != null) {
jedis.close();
}
}
log.info("release lock failed, value:{}, result:{}", value, result);
return false;
}
}
单是一个redis的分布式锁就有这么多道道,不知道你是否看明白了?留言讨论下吧!
程序员的小伙伴们,觉得自己孤单么,那就加入公众号[程序员之道],一起交流沟通,走出我们的程序员之道!
redis分布式锁,面试官请随便问,我都会的更多相关文章
- 学习任务,阅读一下Redis分布式锁的官方文档
地址: https://redis.io/topics/distlock 这是一篇质疑RedLock的论文:https://martin.kleppmann.com/2016/02/08/how-to ...
- 面试官问我,Redis分布式锁如何续期?懵了。
前言 上一篇[面试官问我,使用Dubbo有没有遇到一些坑?我笑了.]之后,又有一位粉丝和我说在面试过程中被虐了.鉴于这位粉丝是之前肥朝的粉丝,而且周一又要开启新一轮的面试,为了回馈他长期以来的支持,所 ...
- 面试官再问Redis分布式锁如何续期?这篇文章甩 他一脸
一.真实案例 二.Redis分布式锁的正确姿势 据肥朝了解,很多同学在用分布式锁时,都是直接百度搜索找一个Redis分布式锁工具类就直接用了.关键是该工具类中还充斥着很多System.out.prin ...
- 面试官:你真的了解Redis分布式锁吗?
什么是分布式锁 说到Redis,我们第一想到的功能就是可以缓存数据,除此之外,Redis因为单进程.性能高的特点,它还经常被用于做分布式锁. 锁我们都知道,在程序中的作用就是同步工具,保证共享资源在同 ...
- 面试必问:如何实现Redis分布式锁
摘要:今天我们来聊聊分布式锁这块知识,具体的来看看Redis分布式锁的实现原理. 一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubb ...
- Redis分布式锁实现Redisson 15问
大家好,我是三友. 在一个分布式系统中,由于涉及到多个实例同时对同一个资源加锁的问题,像传统的synchronized.ReentrantLock等单进程情况加锁的api就不再适用,需要使用分布式锁来 ...
- 如何正确使用redis分布式锁
前言 笔者在公司担任技术面试官,在笔者面试过程中,如果面试候选人提到了reids分布式锁,笔者都会问一下redis分布式锁的知识点,但是令笔者遗憾的是,该知识点十个人中有九个人都答得不清楚,或者回 ...
- Redis分布式锁 (图解-秒懂-史上最全)
文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...
- 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
随机推荐
- 前端知识体系:JavaScript基础-原型和原型链-理解 es6 中class构造以及继承的底层实现原理
理解 es6 中class构造以及继承的底层实现原理 原文链接:https://blog.csdn.net/qq_34149805/article/details/86105123 1.ES6 cla ...
- J-Link OB F103 固件提取及维修
焊接心得挺不错的,可以学习到了 事情起因 某日在调试stm32的时候,错将5v接入3.3v电源输入,开发板烧掉.而且因为jlink没拔掉,也一同阵亡了.光烧了个芯片把整个板换掉太亏,遂打算动手修复. ...
- Vue 组件中锚点定位的问题
1 当前组件的顶部 this.$el.scrollIntoView() 2 指定的 Element this.$el.querySelector(selector).scrollIntoView() ...
- Oracle 物理结构(四) 文件-控制文件
一.什么是控制文件 控制文件是Oracle数据库中十分重要的文件.Oracle启动时,首先会读取参数文件,读取了参数文件,实例所需要的共享内存和后台进程就可以启动了,这就是数据库实例的nomunt阶段 ...
- [USACO5.5] 矩形周长Picture
https://www.luogu.org/problemnew/show/P1856 1.每个矩形由两条横向边和两条纵向边组成. 2.对于横向边,按纵坐标排序.设当前讨论的边为 A [s , t] ...
- 费马小定理 x
费马小定理(Fermat Theory) 是数论中的一个重要定理,其内容为: 假如p是质数,且gcd(a,p)=1,那么 a(p-1)≡1(mod p).即:假如a是整数,p是质数,且a,p互质(即两 ...
- 如何更改电脑ip
首先打开控制面板==>点击网络和internet==>点击网络和共享中心==>点击更改适配器设置==>右键无线连接或宽带连接(视情况而定)==>属性==>双击ipv ...
- Django基础之ORM操作
################################################################## # PUBLIC METHODS THAT ALTER ATTRI ...
- Echarts案例-折线图
一:先在官网下载 https://www.echartsjs.com/zh/download.html 然后再建立工程,导入这两个包: 写代码: <!DOCTYPE html> <h ...
- global 和 nonlocal关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了. def fun(): global num1 num1=2 print("函数内修改后num1=&qu ...