前面学习了Redis的数据结构以及命令、Redis中的事务和Redis对Lua脚本的支持。

这一章就对Redis这些特性做一下实战性应用——基于Redis的分布式锁实现。

Lock和Distributed Lock

在这之前先来认识下锁(Lock)和分布式锁(Distributed Lock):

In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechanism for enforcing limits on access to a resource in an environment where there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy.

参考wiki的解释:在计算机科学领域中,锁是为了限制多线程环境访问一个资源的一种同步机制。锁被设计相互排斥的并发策略。

Lock的前提条件:

  • 同一台机器上的共同资源;
  • 多线程环境访问共同资源;

Lock目标:

  • 保证多线程访问资源的一致性;

Lock的实现:

  • 单核处理器上禁用中断,使得同步资源能够被访问结束;
  • 硬件支持的原子指令,“比较和交换”等等,用于测试锁是否是空闲,如果空闲获取锁;

Operating systems use lock managers to organise and serialise the access to resources. A distributed lock manager (DLM) runs in every machine in a cluster, with an identical copy of a cluster-wide lock database. In this way a DLM provides software applications which are distributed across a cluster on multiple machines with a means to synchronize their accesses to shared resources.

参考wiki解释分布式锁:操作系统用锁管理器实现有组织有顺序的访问资源。分布式锁运行在集群环境中的每台机器上,使得数据具有相同的副本。分布式锁提供分布式软件应用同步访问共享资源。

Distribute Lock的前提条件:

  • 分布式软件应用;
  • 分布式软件中的共享资源;

Distribute Lock目标:

  • 保证分布式应用访问共享资源的一致性

Distribute Lock实现方式:

  • 基于Redis实现;
  • 基于Zookeeper实现;
  • 基于Etcd或者Consul实现;
  • Google开发的Chubby(Lock Service);

独占式锁的特点和影响

按照用途、场景划分,锁的类型非常多。如:排它锁(独占式锁)、共享锁,自旋锁、互斥锁,读锁、写锁。但是在分布式环境中的所谓的分布式锁,大多数情况下都是指:分布式独占式锁。

1.特点分析:

  • 每次只能一个占用锁;
  • 可以重复进入锁;
  • 只有占用者才可以解锁;
  • 获取锁和释放锁都需要原子
  • 不能产生死锁
  • 尽量满足性能

本质:同步互斥,使得处理任务能够一个一个逐步的过临界资源。

造成的影响:

  • 降低并发数,使得多任务处理,只能一个一个的进行;
  • 任务的换进换出造成切换上的开销;

本质:使得吞吐量大打折扣。

基于Redis的实现

Redis实现分布式锁的基础

1.Redis本身就是单线程:

  • 单个命令执行具有原子性、无竞态条件,这个特点符合一次只有一个客户端争用锁;

2.Redis提供了set if not exists操作:

  • 存在即不设置,这个特点符合锁的独占性(排它特点);

下面来先来看下获取锁:

return jedis.set(lockKey, lockValue, NX, EX, expireTime) != null ? true : false;

这里使用set指令,具有原子操作特点,不会被其他客户端操作中断,在分布式环境中,是安全的,没有竞态条件产生,一次只能有一个客户端争用锁;使用nx,即存在不设置,符合独占特点;设置ex,有过期效果,不会产生永久独占即死锁;最后设置了lockValue,这样就和当前加锁任务做了绑定,后面可以用其作为解锁的钥匙;

再来看下解锁操作:

static final String RELEASE_LOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(RELEASE_LOCK_LUA, 1, lockKey, lockValue);

这里解锁是用了Lua脚本,上篇文章中介绍了Redis内置一个Lua解释器,Redis调用解释器执行Lua脚本也是具有原子性的,即同一时刻只有一个客户端的操作能被执行,所以这里使用Lua脚本解锁无竞态条件;解锁符合是占用锁的任务释放的原理;

但是以上实现的分布式锁缺点是:

  • 不具有重入性,即当前任务获取了锁,在下次获取时将会死锁;
  • 不能自旋获取,即获取失败时,将会立即返回失败;

本人对其进行了改造,分别做了适应以上两种场景的分布式锁,详情可以戮[Distributed Lock],欢迎大家一起来完善。

总结

本文从What、Features、How的角度分析了分布式锁。总的来说,单机应用中的多线程或者多进程的锁的放大版基本上就是分布式锁了。万变不离其宗,实现独占锁的关键性要素:

  • 目标:互斥同步,资源访问原子化;
  • 实现:一次只能有一个争用到锁,争用过程是个原子过程,只能争用到的解锁,不会发生死锁;
参考

Redis 分布式锁的正确实现方式

Rewriting our lock

Lock

题外话

参考Rewriting our lock中使用setnx实现的分布式,严格意义上来说是有死锁问题的。setnx和expire不具有原子性。当setnx成功后,expire前应用发生宕机,这会导致锁永远不会过期,别的应用始终争用不到锁。当然这种情况比较特殊,但是做代码是一件严谨的事!

Redis(七)分布式锁的更多相关文章

  1. Redis学习系列七分布式锁

    一.简介 熟悉.Net多线程的都知道,当多个线程同时操作一个全局缓存对象(static对象实例.Dictionary.List等)时,会存在多线程争用问题,包括EF.Dapper等本身的缓存机制,都存 ...

  2. 利用多写Redis实现分布式锁原理与实现分析(转)

    利用多写Redis实现分布式锁原理与实现分析   一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...

  3. Redis实现分布式锁原理与实现分析

    一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子: 场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能在某一个时刻会有二笔一样的单子同时到达系统后台. ...

  4. 基于redis 实现分布式锁的方案

    在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...

  5. 用Redis构建分布式锁-RedLock(真分布)

    在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增 ...

  6. 用Redis实现分布式锁 与 实现任务队列(转)

    这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能.先扯点个人观点,之前我看了一篇博文说博客园的文章大部分都是分享代码,博文里强调说分享思路比分享代码更重要(貌似大概是这个意 ...

  7. Redis实现分布式锁

    http://redis.io/topics/distlock 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但 ...

  8. 基于redis的分布式锁

    <?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * ...

  9. Redis实现分布式锁与任务队列

    Redis实现分布式锁 与 实现任务队列 这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能.先扯点个人观点,之前我看了一篇博文说博客园的文章大部分都是分享代码,博文里强调说 ...

  10. 使用Redis实现分布式锁

    在天猫.京东.苏宁等等电商网站上有很多秒杀活动,例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时,会迎来一个用户请求的高峰期,可能会有几十万几百万的并发量,来抢这个手机,在高并发的情形 ...

随机推荐

  1. PlayJava Day009

    今日所学: /* 2019.08.19开始学习,此为补档. */ 1.Date工具类: Date date = new Date() ; //当前时间 SimpleDateFormat sdf = n ...

  2. CSS基本选择器是什么?基本选择器是如何工作

    基本选择器介绍 基本选择器又分为六种使用方式:如.通用选择器.标签选择器.类选择器.Id选择器.结合元素选择器.多类选择器. 基本选择器使用说明表. 选择器 语法格式 含义 举例 通用选择器 *{属性 ...

  3. HTTP中的2XX状态码

    HTTP状态码分类 1XX --信息,服务器收到请求,需要请求者继续执行操作 2XX--成功,操作被成功接收并处理 3XX--重定向,需要进一步的操作以完成请求 4XX--客户端错误,请求包含语法错误 ...

  4. RV32FDQ/RV64RDQ指令集(1)

    Risc-V架构定义了可选的单精度浮点指令(F扩展指令集)和双精度浮点指令(D扩展指令集),以及四精度浮点指令集(Q扩展指令集).Risc-V架构规定:处理器可以选择只实现F扩展指令子集而不支持D扩展 ...

  5. Java 线程与多线程

    Java是一门支持多线程的编程语言! 什么是进程? 计算机中内存.处理器.IO等资源操作都要为进程进行服务. 一个进程上可以创建多个线程,线程比进程更快的处理单元,而且所占用的资源也小,多线程的应用也 ...

  6. linux下搭建jenkins

    为了配合上一篇的ant+jenkins做持续集成,需要在linux环境下搭建一个jenkins平台.网上有很多安装的例子,我主要记录一下自己遇到的问题,真真的是特别惆怅的,每次我遇到的问题都格外多. ...

  7. c语言的全排列

    在c语言中实现全排列,对于刚接触c语言,还没学习算法的人来说,比较困难了吧.估计大佬也不会看这种基础的东西,全排列实现的办法很多,在c++中有一个专门的函数可以使用,但是在c中实现就有点困难了.如果你 ...

  8. 详解TCP与UDP

    一.TCP的特点 面向连接的.可靠的.基于字节流的传输层通信协议. 将应用层的分割成报文段,并发送发给目标节点的TCP层. 数据包都有序号,对方收到则发送ACK确认,未收到则重传. 使用效验和来效验数 ...

  9. Linux—使用man命令:man:command not found

    # 用echo $PATH查看该环境变量.这种问题一般是环境变量PATH不对的问题. [root@localhost ~]# echo $PATH # 用whereis命令查看,该指令的位置. [ro ...

  10. dockerfile和资源限制(五)

    镜像生成途径 dockerfile 基于容器制作 什么是dockerfile dockerfile说白就是用来构建docker 镜像的源码,大家看到源码俩字不用惊慌,所为的dockerfile源码只是 ...