Laravel Redis分布式锁实现源码分析
首先是锁的抽象类,定义了继承的类必须实现加锁、释放锁、返回锁拥有者的方法。
namespace Illuminate\Cache;
abstract class Lock implements LockContract
{
use InteractsWithTime;
// 锁的名称
protected $name;
// 锁的时长
protected $seconds;
// 当前操作锁的拥有者
protected $owner;
// 获取锁失败时,重新获取锁需要等待的毫秒数
protected $sleepMilliseconds = 250;
// 构造函数
public function __construct($name, $seconds, $owner = null)
{
if (is_null($owner)) {
$owner = Str::random();
}
$this->name = $name;
$this->owner = $owner;
$this->seconds = $seconds;
}
// 加锁
abstract public function acquire();
// 释放锁
abstract public function release();
// 获取锁中保存的拥有者信息
abstract protected function getCurrentOwner();
// 1. 尝试获取锁,并返回获取结果
// 2. 尝试获取锁,获取成功后执行一个回调函数,执行完成后自动释放锁
public function get($callback = null)
{
$result = $this->acquire();
if ($result && is_callable($callback)) {
try {
return $callback();
} finally {
$this->release();
}
}
return $result;
}
// 尝试在指定的时间内获取锁,超时则失败抛出异常
public function block($seconds, $callback = null)
{
$starting = $this->currentTime();
while (! $this->acquire()) {
usleep($this->sleepMilliseconds * 1000);
if ($this->currentTime() - $seconds >= $starting) {
throw new LockTimeoutException;
}
}
if (is_callable($callback)) {
try {
return $callback();
} finally {
$this->release();
}
}
return true;
}
// 返回当前操作锁的拥有者
public function owner()
{
return $this->owner;
}
// 判断当前操作的拥有者是否为锁中保存的拥有者
protected function isOwnedByCurrentProcess()
{
return $this->getCurrentOwner() === $this->owner;
}
// 设置重试获取锁需要等待的毫秒数
public function betweenBlockedAttemptsSleepFor($milliseconds)
{
$this->sleepMilliseconds = $milliseconds;
return $this;
}
}
Redis 锁实现类,增加了强制删除锁的方法。
class RedisLock extends Lock
{
// Redis对象
protected $redis;
// 构造函数
public function __construct($redis, $name, $seconds, $owner = null)
{
parent::__construct($name, $seconds, $owner);
$this->redis = $redis;
}
// 加锁逻辑代码
public function acquire()
{
if ($this->seconds > 0) {
return $this->redis->set($this->name, $this->owner, 'EX', $this->seconds, 'NX') == true;
} else {
return $this->redis->setnx($this->name, $this->owner) === 1;
}
}
// 使用 Lua 脚本释放锁逻辑代码
public function release()
{
return (bool) $this->redis->eval(LuaScripts::releaseLock(), 1, $this->name, $this->owner);
}
// 无视锁的拥有者强制删除锁
public function forceRelease()
{
$this->redis->del($this->name);
}
// 返回锁中保存的拥有者信息
protected function getCurrentOwner()
{
return $this->redis->get($this->name);
}
}
原子性释放锁的 Lua 脚本。
class LuaScripts
{
/**
* 使用 Lua 脚本原子性地释放锁.
*
* KEYS[1] - 锁的名称
* ARGV[1] - 锁的拥有者,只有是该锁的拥有者才允许释放
*
* @return string
*/
public static function releaseLock()
{
return <<<'LUA'
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
LUA;
}
}
总结:
- 可以通过get()方法直接获取锁并传入回调函数在成功时执行。
- 可以通过block()方法在指定时间内不断获取锁,知道成功或超时为止,成功时会执行传入的回调函数。
- Redis 通过 set() 命令设置一个值为“拥有者”的字符串来作为锁。
- set() 通过 NX 参数来实现排他锁(只在键不存在时,才对键进行设置)。
- set() 通过 EX 参数来控制锁的生存时间(防止程序意外终止发生死锁)。
- 不能使用 set()+expire() 来代替set(),防止网络延迟或其他故障导致死锁。
- Redis 通过 Lua 脚本来达到原子性删除锁。
- Lua 脚本中会判断字符串的内容是否与参数中的拥有者一致,一致才执行删除操作。防止当前锁被其他进程误删除,或者误删除了其他进程的锁。
Laravel Redis分布式锁实现源码分析的更多相关文章
- [转]分布式锁-RedisLockRegistry源码分析
前言 官网的英文介绍大概如下: Starting with version 4.0, the RedisLockRegistry is available. Certain components (f ...
- ZooKeeper 分布式锁 Curator 源码 04:分布式信号量和互斥锁
前言 分布式信号量,之前在 Redisson 中也介绍过,Redisson 的信号量是将计数维护在 Redis 中的,那现在来看一下 Curator 是如何基于 ZooKeeper 实现信号量的. 使 ...
- ReentrantLock 锁释放源码分析
ReentrantLock 锁释放源码分析: 调用的是unlock 的方法: public void unlock() { sync.release(1); } 接下来分析release() 方法: ...
- ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放
ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放 前言 加锁逻辑已经介绍完毕,那当一个线程重复加锁是如何处理的呢? 锁重入 在上一小节中,可以看到加锁的过程,再回头看 ...
- ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁
前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...
- ZooKeeper 分布式锁 Curator 源码 01:可重入锁
前言 一般工作中常用的分布式锁,就是基于 Redis 和 ZooKeeper,前面已经介绍完了 Redisson 锁相关的源码,下面一起看看基于 ZooKeeper 的锁.也就是 Curator 这个 ...
- 又长又细,万字长文带你解读Redisson分布式锁的源码
前言 上一篇文章写了Redis分布式锁的原理和缺陷,觉得有些不过瘾,只是简单的介绍了下Redisson这个框架,具体的原理什么的还没说过呢.趁年前项目忙的差不多了,反正闲着也是闲着,不如把Rediss ...
- Redis学习——ae事件处理源码分析
0. 前言 Redis在封装事件的处理采用了Reactor模式,添加了定时事件的处理.Redis处理事件是单进程单线程的,而经典Reator模式对事件是串行处理的.即如果有一个事件阻塞过久的话会导致整 ...
- concurrent(三)互斥锁ReentrantLock & 源码分析
参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...
随机推荐
- Linux基础命令---httpd守护进程
httpd httpd是apache超文本传输协议的主程序,它被设计成一个独立运行的守护进程.httpd会建立一个线程池来处理http请求. 此命令的适用范围:RedHat.RHEL.Ubuntu.C ...
- mysql与clickhouse的字段类型对应表
- 【划重点】Python xlrd简介
一.用xlrd获取对应数据,并获取所有sheet的名字 import xlrd data=xlrd.open_workbook(r'C:\Users\ASUS\Desktop\重新开始\Python获 ...
- [BUUCTF]REVERSE——[ACTF新生赛2020]usualCrypt
[ACTF新生赛2020]usualCrypt 附件 步骤: 例行检查,无壳,32位程序 32位ida载入,直接看main函数 逻辑很简单,一开始让我们输入一个字符串,然后该字符串经过sub_4010 ...
- CF999A Mishka and Contest 题解
Content 能力值为 \(k\) 的小 M 参加一次考试,考试一共有 \(n\) 道题目,每道题目的难度为 \(a_i\).小 M 会选择两头中的一道难度不超过他的能力值题目去做,每做完一道,这道 ...
- 私有化 : _x: 单前置下划线,私有化属性或方法;__xx:双前置下划线;__xx__:双前后下划线;属性property
私有化 xx: 公有变量 _x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问 __xx:双前置下划线,避免与子类中的属性命名冲突,无 ...
- Mysq索引优化(什么情况创建索引,什么情况不创建索引)
一.以下情况需要创建索引 1.主键自动建立唯一索引 2.频繁作为查询条件的字段应该创建索引 3.查询中与其他表关联的字段,外键关系建立索引 4.单键/组合索引的选择问题,组合索引性价比更高 5.查询中 ...
- 使用unc0ver(5.3.1)执行jailbreak踩坑
官方网站: https://unc0ver.dev/ 目前版本: 5.3.1 使用mac os+altstore步骤: AltStore Download AltStore. Use the link ...
- 【LeetCode】1064. Fixed Point 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 暴力求解 日期 题目地址:https://leetco ...
- 【LeetCode】276. Paint Fence 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 动态规划 日期 题目地址:https://leetco ...