当前位置: 首页 > news >正文

基于Redis实现简单的分布式锁【理论】

摘要

分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁释放,这样才能保证数据的安全访问。分布式锁实现的方案有很多,比如基于ZooKeeper实现、或者基于Mysql实现等等,今天我们来一起看看如何基于Redis实现分布式锁服务

分布式锁要点

对于分布式锁的目标,我们必须首先明确三点:

  1. 任何一个时间点必须只能够有一个客户端拥有锁。
  2. 不能够有死锁,也就是最终客户端都能够获得锁,尽管可能会经历失败。
  3. 错误容忍性要好,只要有大部分的Redis实例存活,客户端就应该能够获得锁。

一种简单的方法

理解了上面我们列出的三个点,我们来分析一下一般的基于Redis实现的分布式锁:

使用Redis实现锁最简单的办法是创建一个key,且这个key通常有有限的存活时间,这一点可以利用Redis的过期时间特性,所以锁最终会被释放掉,当客户端需要释放资源的时候,客户端delete这个key即可。

So far so good!但是有个单点问题,假如Redis master挂掉怎么办,因此我们需要加个slave,当master挂掉的时候可以切换到slave。这又带来了新的问题,由于Redis的复制是异步的,因此我们不能保证同时只有一个客户端获得锁。

这个模型有很显然的竞态:

  1. Client A在Master上面获得了锁。
  2. master在数据同步到slave之前挂掉了。
  3. slave升级成为master。
  4. Client B申请了同样的资源的锁,成功了!

在特定条件下这种情况是会发生的,当出现多个客户端同时获得锁的时候,我们就认为可以这种锁方案是不可靠的。

基于Redis单例的实现

为了后面更好的了解分布式锁的实现,我们先来看看如何基于Redis单例实现锁服务。我们可以用下面方法获得锁:

SET resource_name my_random_value NX PX 30000

上面的命令在只有当key不存在的时候会执行成功(NX选项),同时会设置过期时间为30000ms(PX选项)。key的值会被设置为my_random_value。这个值在多个客户端和锁中必须是唯一的,我们使用random value是为了方便安全地释放锁,看看下面的脚本:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

只有当key存在且值是预期的值的时候才会删除key。这种方式可以避免误删除其他客户端创建的锁。例如,当客户端获取锁之后执行一个很长时间的逻辑,一直过了锁的过期时间,这个时候锁会被自动释放掉,而另外一个客户端又获取了这个锁,前一个客户端终于执行完了逻辑执行,回头释放锁,删除key,其实这个时候释放的已经是另外一个客户端持有的锁了。使用DEL是不安全的,因为客户端有可能误删其他客户端持有的锁。上面脚本的方法的好处是每次获得锁的时候加上一个随机的签名,当释放锁的时候去看看是不是自己持有的锁,这个时候就不会误删。    

现在我们学会了如何在Redis单例上获取锁和释放锁,那么接下来我们看看如何在Redis集群上获取锁和释放锁。

基于Redlock算法的实现

在分布式环境下,假设我们有N个master,这些节点都是独立的,因此我们没有配置复制策略。上面我们已经学会了如何在单机环境下获取锁和释放锁,我们假设的更具体一些,N=5,为了能获取锁,客户端的步骤为:

  1. 获取当前系统的时间,以毫秒为单位。
  2. 顺序的获取N个Redis实例上的锁,在每个实例中都用同样的key和value。在步骤2中,客户端需要一个比过期时间小很多的超时时间,例如,如果自动过期时间为10s,那么超时时间大概是5~50ms,这样可以避免客户端一直被阻塞,而不能继续请求下一个实例。
  3. 客户端每次都要计算已经过去了多长时间,使用的时间是否小于key自动过期的时间同时又获取了至少3个实例的锁。如果是,那么我们认为客户端此次获取锁成功。
  4. 如果锁被获取了,锁的过期时间必须要减去获取锁花费的时间。
  5. 如果当前客户端获取锁失败,客户端需要释放所有之前获取到的锁。

总结

这篇文章主要介绍Redis实现分布式锁的基本方法,然后分别介绍通过Redis单例和Redis集群实现分布式锁的方法。

参考文献

《Redis官方文档》

原文链接:https://my.oschina.net/andylucc/blog/677797

转载于:https://www.cnblogs.com/hongmoshui/p/10598824.html

相关文章:

  • 学习java编程艺术文章的储存与整理第一章
  • 关于BRDF的一些总结——转自其他博客
  • js map 、filter 、forEach 、every、some 的用法
  • 【视频版】PDF合并器破解视频教程
  • git 随笔(随时更新)
  • C# 使用Exchange WebService读取联系人
  • Crafting 手工 Physically Motivated Shading Models for Game Development
  • 001 Android TextUtils工具类的使用
  • windows下安装oracle11g测试是否成功与监听器问题和网页控制台登录
  • unity复制到剪切板
  • 软件开发的权限控制和权限验证
  • PBR论文链接
  • 词频统计
  • 云时代架构阅读笔记四——深入的、详细的介绍Map以及HashMap
  • The Blinn-Phong Normalization Zoo
  • 0x05 Python数据分析,Anaconda八斩刀
  • 230. Kth Smallest Element in a BST
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • Cookie 在前端中的实践
  • Docker下部署自己的LNMP工作环境
  • GraphQL学习过程应该是这样的
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • jquery ajax学习笔记
  • js学习笔记
  • PHP 小技巧
  • Redis学习笔记 - pipline(流水线、管道)
  • TypeScript实现数据结构(一)栈,队列,链表
  • Vue学习第二天
  • windows下如何用phpstorm同步测试服务器
  • 阿里云Kubernetes容器服务上体验Knative
  • 笨办法学C 练习34:动态数组
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 聊聊hikari连接池的leakDetectionThreshold
  • 排序算法之--选择排序
  • 前端技术周刊 2019-01-14:客户端存储
  • 使用权重正则化较少模型过拟合
  • 我的zsh配置, 2019最新方案
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • # .NET Framework中使用命名管道进行进程间通信
  • #Z0458. 树的中心2
  • (2)nginx 安装、启停
  • (3)llvm ir转换过程
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (WSI分类)WSI分类文献小综述 2024
  • (待修改)PyG安装步骤
  • (二十三)Flask之高频面试点
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (三)模仿学习-Action数据的模仿
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (转)Oracle存储过程编写经验和优化措施
  • ***原理与防范
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .net 生成二级域名
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48