首先是锁的抽象类,定义了继承的类必须实现加锁、释放锁、返回锁拥有者的方法。

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;
}
}

总结:

  1. 可以通过get()方法直接获取锁并传入回调函数在成功时执行。
  2. 可以通过block()方法在指定时间内不断获取锁,知道成功或超时为止,成功时会执行传入的回调函数。
  3. Redis 通过 set() 命令设置一个值为“拥有者”的字符串来作为锁。
  4. set() 通过 NX 参数来实现排他锁(只在键不存在时,才对键进行设置)。
  5. set() 通过 EX 参数来控制锁的生存时间(防止程序意外终止发生死锁)。
  6. 不能使用 set()+expire() 来代替set(),防止网络延迟或其他故障导致死锁。
  7. Redis 通过 Lua 脚本来达到原子性删除锁。
  8. Lua 脚本中会判断字符串的内容是否与参数中的拥有者一致,一致才执行删除操作。防止当前锁被其他进程误删除,或者误删除了其他进程的锁。

Laravel Redis分布式锁实现源码分析的更多相关文章

  1. [转]分布式锁-RedisLockRegistry源码分析

    前言 官网的英文介绍大概如下: Starting with version 4.0, the RedisLockRegistry is available. Certain components (f ...

  2. ZooKeeper 分布式锁 Curator 源码 04:分布式信号量和互斥锁

    前言 分布式信号量,之前在 Redisson 中也介绍过,Redisson 的信号量是将计数维护在 Redis 中的,那现在来看一下 Curator 是如何基于 ZooKeeper 实现信号量的. 使 ...

  3. ReentrantLock 锁释放源码分析

    ReentrantLock 锁释放源码分析: 调用的是unlock 的方法: public void unlock() { sync.release(1); } 接下来分析release() 方法: ...

  4. ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放

    ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放 前言 加锁逻辑已经介绍完毕,那当一个线程重复加锁是如何处理的呢? 锁重入 在上一小节中,可以看到加锁的过程,再回头看 ...

  5. ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁

    前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...

  6. ZooKeeper 分布式锁 Curator 源码 01:可重入锁

    前言 一般工作中常用的分布式锁,就是基于 Redis 和 ZooKeeper,前面已经介绍完了 Redisson 锁相关的源码,下面一起看看基于 ZooKeeper 的锁.也就是 Curator 这个 ...

  7. 又长又细,万字长文带你解读Redisson分布式锁的源码

    前言 上一篇文章写了Redis分布式锁的原理和缺陷,觉得有些不过瘾,只是简单的介绍了下Redisson这个框架,具体的原理什么的还没说过呢.趁年前项目忙的差不多了,反正闲着也是闲着,不如把Rediss ...

  8. Redis学习——ae事件处理源码分析

    0. 前言 Redis在封装事件的处理采用了Reactor模式,添加了定时事件的处理.Redis处理事件是单进程单线程的,而经典Reator模式对事件是串行处理的.即如果有一个事件阻塞过久的话会导致整 ...

  9. concurrent(三)互斥锁ReentrantLock & 源码分析

    参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...

随机推荐

  1. tableView和tableViewCell的背景颜色问题

    当在tableView中添加cell数据时,我们会发现原本设置的tableView的背景颜色不见了,这是因为加载cell数据时,tableView的背景颜色被cell数据遮盖住了,此时,可以通过设置c ...

  2. java内存管理的小技巧

    1,尽量使用直接量.     采用String str="hello"; 而不是 String str = new String("hello"): 2,使用S ...

  3. 大数据处理系列之(一)Java线程池使用

    前言:最近在做分布式海量数据处理项目,使用到了java的线程池,所以搜集了一些资料对它的使用做了一下总结和探究, 前面介绍的东西大多都是从网上搜集整理而来.文中最核心的东西在于后面两节无界队列线程池和 ...

  4. 【力扣】188. 买卖股票的最佳时机 IV

    给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你最多可以完成 k 笔交易. 注意:你不能同时参 ...

  5. 企业级BI是自研还是采购?

    企业级BI是自研还是采购? 上一篇<企业级BI为什么这么难做?>,谈到了企业级BI项目所具有的特殊背景,以及在"破局"方面的一点思考,其中谈论的焦点主要是在IT开发项目 ...

  6. Mysql实例 表设计

    目录 一.介绍 二.设计表格 三.查询 查都有哪些公司 查A公司都放了哪些广告 查A公司10月份该交多少广告费 四.分析 表结构设置 sql语句 其它功能 一.介绍 有一个公司叫月亮集团,他们旗下有很 ...

  7. centos部署golang环境

    目录 一.简介 二.部署 一.简介 Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布.Go 是非常年轻的一门语言,它的主要目标是"兼具 Python 等动态语 ...

  8. LuoguB2104 矩阵加法 题解

    Content 给定两个 \(n\times m\) 的矩阵 \(A,B\),求 \(C=A+B\). 数据范围:\(1\leqslant n,m\leqslant 100\). Solution 我 ...

  9. Java 数据类型:集合接口Collection之Set接口HashSet类;LinkedHashSet;TreeSet 类

    Collection 之 Set 实现类: HashSet TreeSet 特点: 无序. 元素不可重复. (如果试图添加一个已经有的元素到一个Set集合中,那么会添失败,add()方法返回false ...

  10. 金智维RPA培训(一)产品基础架构-RPA学习天地

    1.产品组成分为:Server,control,agent三个组件,支持CS和BS架构.独有的中继服务器可以解决跨网段的问题,这里应该还是采用了多网卡模式. 其中:Agent负责对流程的执行工作.Co ...