在线客服
扫描二维码
下载博学谷APP扫描二维码
关注博学谷微信公众号
分布式锁常常通过数据库乐观锁、基于Redis的分布式锁和基于Zookeeper的分布式锁三种方式来实现。本文是一个零基础新手教程,将教大家怎么写分布式锁框架。有兴趣的小伙伴就赶紧看下去吧!
一、核心代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate template;
@Autowired
private DefaultRedisScript<Long> redisScript;
private static final Long RELEASE_SUCCESS = 1L;
private long timeout = 3000;
public boolean lock(String key, String value) {
//执行set命令
Boolean absent = template.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);//1
//其实没必要判NULL,这里是为了程序的严谨而加的逻辑
if (absent == null) {
return false;
}
//是否成功获取锁
return true;
}
public boolean unlock(String key, String value) {
//使用Lua脚本:先判断是否是自己设置的锁,再执行删除
Long result = template.execute(redisScript, Arrays.asList(key,value));
//返回最终结果
return RELEASE_SUCCESS.equals(result);
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
@Bean
public DefaultRedisScript<Long> defaultRedisScript() {
DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(Long.class);
defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
return defaultRedisScript;
}
}
执行上面的setIfAbsent()方法就只会导致两种结果:
1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。
2. 已有锁存在,不做任何操作。
在任意时刻,该代码都能保证只有一个客户端能持有锁,并且每一个分布式锁都加了过期时间,保证不会出现死锁,容错性暂时不考虑的话,加锁和解锁通过key保证了对多个客户端而言都是同一把锁,value的作用则是保证对同一把锁的加锁和解锁操作都是同一个客户端。
二、为什么上述方案不够好
为了理解我们想要提高的到底是什么,我们先看下当前大多数基于Redis的分布式锁三方库的现状。 用Redis来实现分布式锁最简单的方式就是在实例里创建一个键值,创建出来的键值一般都是有一个超时时间的(这个是Redis自带的超时特性),所以每个锁最终都会释放(参见前文属性2)。而当一个客户端想要释放锁时,它只需要删除这个键值即可。 表面来看,这个方法似乎很管用,但是这里存在一个问题:在我们的系统架构里存在一个单点故障,如果Redis的master节点宕机了怎么办呢?有人可能会说:加一个slave节点!在master宕机时用slave就行了!但是其实这个方案明显是不可行的,因为这种方案无法保证第1个安全互斥属性,因为Redis的复制是异步的。 总的来说,这个方案里有一个明显的竞争条件(race condition),举例来说:
客户端A在master节点拿到了锁。
master节点在把A创建的key写入slave之前宕机了。
slave变成了master节点
B也得到了和A还持有的相同的锁(因为原来的slave里还没有A持有锁的信息)
当然,在某些特殊场景下,前面提到的这个方案则完全没有问题,比如在宕机期间,多个客户端允许同时都持有锁,如果你可以容忍这个问题的话,那用这个基于复制的方案就完全没有问题,否则的话我还是建议你对上述方案进行改进。比如,考虑使用Redlock算法。
三、Redlock算法
在分布式版本的算法里我们假设我们有N个Redis master节点,这些节点都是完全独立的,我们不用任何复制或者其他隐含的分布式协调算法。我们已经描述了如何在单节点环境下安全地获取和释放锁。因此我们理所当然地应当用这个方法在每个单节点里来获取和释放锁。在我们的例子里面我们把N设成5,这个数字是一个相对比较合理的数值,因此我们需要在不同的计算机或者虚拟机上运行5个master节点来保证他们大多数情况下都不会同时宕机。一个客户端需要做如下操作来获取锁:
获取当前时间(单位是毫秒)。
轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。
客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。
看完零基础新手教程,相信大家都明白了“怎么写分布式锁框架”。觉得本篇文章有用的小伙伴,不妨分享一下让更多人看见吧!
— 申请免费试学名额 —
在职想转行提升,担心学不会?根据个人情况规划学习路线,闯关式自适应学习模式保证学习效果
讲师一对一辅导,在线答疑解惑,指导就业!
相关推荐 更多
学C++应该看哪些书?零基础入门书籍推荐
近些年来,虽然各种编程语言层出不穷,但是C++的地位依然比较稳固,在某些领域具有不可替代的作用。那么学C++应该看哪些书?本文就是为零基础入门的小伙伴推荐十本书籍。
8558
2019-08-12 20:19:43
2019世界机器人大会是怎么回事?
从2015年起举办的世界机器人大会,到2019年已经是第五届了。在这五年期间,随着人工智能等科技领域的不断突破,机器人的发展也是日新月异。本文就带大家看看2019世界机器人大会的相关情况。
4949
2019-08-21 16:32:10
九款好用的Linux命令行终端工具推荐
作为一个专业的计算机人士,熟练操作Linux命令行是最低的要求。相信许多人每天要在Linux命令行的使用时花大量时间,为了解决大家的问题,本文将向大家推荐可以替代系统自带鸡肋终端软件的工具,它们分别是Tilda、Guake、Yakuake、Terminator、Stjerm、Terminology、TermKit、Wterm、Aterm和Rxvt。
8082
2019-11-08 12:32:04
新手如何开始C语言基础学习?
直到今天,C语言还一直排在各大编程语言榜单,作为程序员在开发过程中的首选语言,它几乎编写了所有的操作系统,而且许多高级编程语言都从C语言发展而来。因此,对于刚刚入门编程的新手来讲,从C语言开始你的编程生涯无疑是最好的选择。那么,新手如何开始C语言基础学习呢?下面我们来一起看一看。
4677
2020-03-25 08:41:51
计算机语言入门先学什么?
刚想开始计算机学习的小白难免会有这样的困惑:计算机语言入门先学什么?简单来讲,初学者需要先了解各种计算机语言,熟悉计算机组成原理,学习数据结构与算法、数据库、基础语法等等,这些都是必须经历的基础学习阶段。下面我们来详细聊一聊这些基础学习内容。
11412
2020-06-30 11:37:05