在 Redis 上实现的分布式锁
由于近排很忙,忙各种事情,还有工作上的项目,已经超过一个月没写博客了,确实有点惭愧啊,没能每天或者至少每周坚持写一篇博客。这一个月里面接触到很多新知识,同时也遇到很多技术上的难点,在这我将对每一个有用的技术点做一个小小的分析理解和总结。每天去学会总结,才会有进步。
本次对我在工作上的项目中用到的技术---在redis上实现分布式锁,进行一个分析和总结。
先了解下什么时分布式锁,在百度上是这么定义的:
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
简单的理解就是:分布式锁是一个在很多环境中非常有用的原语,它是不同的系统或是同一个系统的不同主机之间互斥操作共享资源的有效方法。
背景:
在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等。大部分是解决方案基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。
我们的项目:
我们现在的项目中,任务队列用到分布式锁的情况比较多,在将业务逻辑中可以异步处理的操作放入队列,在其他线程中处理后出队,此时队列中使用了分布式锁,保证入队和出队的一致性。关于redis队列这块的逻辑分析,我将在下一次对其进行总结,此处先略过。
接下来对redis实现的分布式锁的逻辑代码进行详细的分析和理解:
1、为避免特殊原因导致锁无法释放, 在加锁成功后, 锁会被赋予一个生存时间(通过 lock 方法的参数设置或者使用默认值), 超出生存时间锁将被自动释放.
2、锁的生存时间默认比较短(秒级, 具体见 lock 方法), 因此若需要长时间加锁, 可以通过 expire 方法延长锁的生存时间为适当的时间. 比如在循环内调用 expire
3、系统级的锁当进程无论因为任何原因出现crash,操作系统会自己回收锁,所以不会出现资源丢失。
4、但分布式锁不同。若一次性设置很长的时间,一旦由于各种原因进程 crash 或其他异常导致 unlock 未被调用,则该锁在剩下的时间就变成了垃圾锁,导致其他进程或进程重启后无法进入加锁区域。
<?php require_once 'RedisFactory.php'; /**
* 在 Redis 上实现的分布式锁
*/
class RedisLock {
//单例模式
private static $_instance = null;
public static function instance() {
if(self::$_instance == null) {
self::$_instance = new RedisLock();
}
return self::$_instance;
} //redis对象变量
private $redis;
//存放被锁的标志名的数组
private $lockedNames = array(); public function __construct() {
//获取一个 RedisString 实例
$this->redis = RedisFactory::instance()->getString();
} /**
* 加锁
*
* @param string 锁的标识名
* @param int 获取锁失败时的等待超时时间(秒), 在此时间之内会一直尝试获取锁直到超时. 为 0 表示失败后直接返回不等待
* @param int 当前锁的最大生存时间(秒), 必须大于 0 . 如果超过生存时间后锁仍未被释放, 则系统会自动将其强制释放
* @param int 获取锁失败后挂起再试的时间间隔(微秒)
*/
public function lock($name, $timeout = 0, $expire = 15, $waitIntervalUs = 100000) {
if(empty($name)) return false; $timeout = (int)$timeout;
$expire = max((int)$expire, 5);
$now = microtime(true);
$timeoutAt = $now + $timeout;
$expireAt = $now + $expire; $redisKey = "Lock:$name";
while(true) {
$result = $this->redis->setnx($redisKey, (string)$expireAt);
if($result !== false) {
//对$redisKey设置生存时间
$this->redis->expire($redisKey, $expire);
//将最大生存时刻记录在一个数组里面
$this->lockedNames[$name] = $expireAt;
return true;
} //以秒为单位,返回$redisKey 的剩余生存时间
$ttl = $this->redis->ttl($redisKey);
// TTL 小于 0 表示 key 上没有设置生存时间(key 不会不存在, 因为前面 setnx 会自动创建)
// 如果出现这种情况, 那就是进程在某个实例 setnx 成功后 crash 导致紧跟着的 expire 没有被调用. 这时可以直接设置 expire 并把锁纳为己用
if($ttl < 0) {
$this->redis->set($redisKey, (string)$expireAt, $expire);
$this->lockedNames[$name] = $expireAt;
return true;
} // 设置了不等待或者已超时
if($timeout <= 0 || microtime(true) > $timeoutAt) break; // 挂起一段时间再试
usleep($waitIntervalUs);
} return false;
} /**
* 给当前锁增加指定的生存时间(秒), 必须大于 0
*
* @param string 锁的标识名
* @param int 生存时间(秒), 必须大于 0
*/
public function expire($name, $expire) {
if($this->isLocking($name)) {
if($this->redis->expire("Lock:$name", max($expire, 1))) {
return true;
}
}
return false;
} /**
* 判断当前是否拥有指定名称的锁
*
* @param mixed $name
*/
public function isLocking($name) {
if(isset($this->lockedNames[$name])) {
return (string)$this->lockedNames[$name] == (string)$this->redis->get("Lock:$name");
}
return false;
} /**
* 释放锁
*
* @param string 锁的标识名
*/
public function unlock($name) {
if($this->isLocking($name)) {
if($this->redis->deleteKey("Lock:$name")) {
unset($this->lockedNames[$name]);
return true;
}
}
return false;
} /** 释放当前已经获取到的所有锁 */
public function unlockAll() {
$allSuccess = true;
foreach($this->lockedNames as $name => $item) {
if(false === $this->unlock($name)) {
$allSuccess = false;
}
}
return $allSuccess;
}
}
此类很多代码都写上了注释,只要认真理解下,就很容易懂得如何在redis实现分布式锁了。
另外,我在网上找到另一篇关于redis实现分布式锁的文章,我感觉挺不错的,推荐给大家:
网址: http://www.oschina.net/translate/redis-distlock
结合我所总结的和我推荐的文章做对比,基本上能理解清楚是如何在redis实现分布式锁的了。
如果此博文中有哪里讲得让人难以理解,欢迎留言交流,若有讲解错的地方欢迎指出。
如果您觉得您能在此博文学到了新知识,请为我顶一个,如文章中有解释错的地方,欢迎指出。
互相学习,共同进步!
在 Redis 上实现的分布式锁的更多相关文章
- Redis 上实现的分布式锁
转载Redis 上实现的分布式锁 由于近排很忙,忙各种事情,还有工作上的项目,已经超过一个月没写博客了,确实有点惭愧啊,没能每天或者至少每周坚持写一篇博客.这一个月里面接触到很多新知识,同时也遇到很多 ...
- 使用Redis SETNX 命令实现分布式锁
基于setnx和getset http://blog.csdn.net/lihao21/article/details/49104695 使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其 ...
- Redis整合Spring实现分布式锁
spring把专门的数据操作独立封装在spring-data系列中,spring-data-redis是对Redis的封装 <dependencies> <!-- 添加spring- ...
- 使用Redis SETNX 命令实现分布式锁(转载)
使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其实现方法. SETNX命令简介 命令格式 SETNX key value 将 key 的值设为 value,当且仅当 key 不存在. 若 ...
- 【连载】redis库存操作,分布式锁的四种实现方式[一]--基于zookeeper实现分布式锁
一.背景 在电商系统中,库存的概念一定是有的,例如配一些商品的库存,做商品秒杀活动等,而由于库存操作频繁且要求原子性操作,所以绝大多数电商系统都用Redis来实现库存的加减,最近公司项目做架构升级,以 ...
- 基于 Redis 实现简单的分布式锁
摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问.分布式锁实现的方案有很多 ...
- 基于Redis实现简单的分布式锁【理论】
摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问.分布式锁实现的方案有很多 ...
- Redis、Zookeeper实现分布式锁——原理与实践
Redis与分布式锁的问题已经是老生常谈了,本文尝试总结一些Redis.Zookeeper实现分布式锁的常用方案,并提供一些比较好的实践思路(基于Java).不足之处,欢迎探讨. Redis分布式锁 ...
- 基于redis实现可靠的分布式锁
什么是锁 今天要谈的是如何在分布式环境下实现一个全局锁,在开始之前先说说非分布式下的锁: 单机 – 单进程程序使用互斥锁mutex,解决多个线程之间的同步问题 单机 – 多进程程序使用信号量sem,解 ...
随机推荐
- C++语言实现-邻接矩阵
一. 图的概念 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合.在图中的数据元素,我们称之为顶点( ...
- HTTP协议学习笔记(一)
HTTP协议学习笔记(一) 1.HTTP协议用于客户端和服务端之间的通信 客户端:请求访问文本或图像等资源的一端服务端:提供资源响应的一端 在两台计算机之间使用HTTP协议通信时,在一条通信线路上必定 ...
- P1939【模板】矩阵加速(数列)
P1939[模板]矩阵加速(数列)难受就难受在a[i-3],这样的话让k=3就好了. #include<iostream> #include<cstdio> #include& ...
- Autodesk系列软件下载
摘要: 写在前面:下载后如有需要压缩密码的请先使用压缩软件(如:2345好压)打开压缩包,在压缩包的注释或者文本信息中会给出压缩密码!如若没有请私信! 1.3ds Max软件(64位) Autodes ...
- 跳过从Win7/8升级,直接格式化全新安装 Windows 10 并自动永久激活系统的方法教程
跳过升级,直接激活全新 Win10 的方法步骤教程: 下载 Windows 10 系统的 ISO 镜像 在你当前的 Win7 / Win 8 / 8.1 系统中,使用 DaemonTools 或右键选 ...
- HttpClient之初步认识与使用1
今天在项目中看到HttpClient的内容,然后去看了一下资料,有了初步的见解,在此记录一下~ 一. 搭建环境 (1)创建一个java项目叫HttpClientTest (2)为了使用HttpClie ...
- BZOJ.4727.[POI2017]Turysta(哈密顿路径/回路 竞赛图)
题目链接 \(Description\) 给出一个n个点的有向图,任意两个点之间有且仅一条有向边.对于每个点v,求出从v出发的一条经过点数最多,且没有重复经过同一个点一次以上的简单路径. n<= ...
- 4826: [Hnoi2017]影魔
4826: [Hnoi2017]影魔 https://lydsy.com/JudgeOnline/problem.php?id=4826 分析: 莫队+单调栈+st表. 考虑如何O(1)加入一个点,删 ...
- Python3练习题系列(09)——物以类聚,人以群分
目标: 用类管理同类事物 解析: 用到“class”的编程语言被称作“Object Oriented Programming(面向对象编程)”语言.首先你需要做出“东西”来,然后你“告诉”这些东西去完 ...
- SpringMVC拷贝属性
A a = new A(); B b = new B(); //将A的属性拷贝到B上 BeanUtils.copyProperties(a,b)