菜菜哥,复联四上映了,要不要一起去看看?

又想骗我电影票,对不对?

呵呵,想去看了叫我呀

看来你工作不饱和呀

哪有,这两天我刚基于redis写了一个分布式锁,很简单

不管你基于什么做分布式锁,你觉得很简单吗?来来来

在计算机世界里,对于锁大家并不陌生,在现代所有的语言中几乎都提供了语言级别锁的实现,为什么我们的程序有时候会这么依赖锁呢?这个问题还是要从计算机的发展说起,随着计算机硬件的不断升级,多核cpu,多线程,多通道等技术把计算机的计算速度大幅度提升,原来同一时间只能执行一条cpu指令的时代已经过去。随着多条cpu指令可以并行执行的原因,原来不曾出现的资源竞争随着出现,在程序中的体现就是随处可见的多线程环境。比如要更新数据库的一个信息,如果没有并发控制,多个线程同时操作的话,就会出现互相覆盖的现象发生。

锁要解决的就是资源竞争的问题,也就是要把执行的指令顺序化

01
为什么需要分布式锁

随着互联网的兴起,现代软件发生了翻天覆地的变化,以前单机的程序,已经支撑不了现代的业务。无论是在抗压,还是在高可用等方面都需要多台计算机协同工作来解决问题。现代的互联网系统都是分布式部署的,分布式部署确实能带来性能和效率上的提升,但为此,我们就需要多解决一个分布式环境下,数据一致性的问题。

当某个资源在多系统之间共享的时候,为了保证大家访问这个资源数据是一致的,那么就必须要求在同一时刻只能被一个客户端处理,不能并发的执行,否者就会出现同一时刻有人写有人读,大家访问到的数据就不一致了。

在分布式系统的时代,传统线程之间的锁机制,就没作用了,系统会有多份并且部署在不同的机器上,这些资源已经不是在线程之间共享了,而是属于进程(服务器)之间共享的资源。

因此,为了解决这个问题,我们就必须引入「分布式锁」。分布式锁,是指在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问。分布式锁的特点如下:

1
互斥性
和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
2
可重入性
同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
3
锁超时
和本地锁一样支持锁超时,防止死锁。
4
高效,高可用
加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
5
支持阻塞和非阻塞
和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)。
02
基于redis分布式锁

如果你通过网络搜索分布式锁,最多的就是基于redis的了。基于redis的分布式锁得益于redis的单线程执行机制,单线程在执行上就保证了指令的顺序化,所以很大程度上降低了开发人员的思考设计成本。但是,基于redis做分布式锁难道真的这么容易吗?

1
原子操作

基于redis的分布式锁常用命令是

SETNX key value

只在键 key 不存在的情况下,将键 key的值设置为value 。若键key 已经存在, 则SETNX 命令不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。代码示例:

redis> SETNX redislock "redislock"    # redislock 设置成功
(integer) 1
redis> SETNX redislock "redislock2"   # 尝试覆盖 redislock ,失败
(integer) 0
redis> GET redislock                   # 没有被覆盖
"redislock"

成功获取到锁之后,然后设置一个过期时间(这里避免了客户端down掉,锁得不到释放的问题)

redis> expire redislock 5

成功拿到锁的客户端顺利进行自己的业务,业务代码执行完,然后再删除该key

redis> DEL redislock

如果一切都想想象的那么顺利,程序员TMD就不用996了。假如客户端拿到锁之后,执行设置超时指令之前down掉了(现实总是那么悲剧),那这个锁就永远都释放不了.也许你会想到用 Redis 事务来解决。但是这里不行,因为 expire 是依赖于 setnx 的执行结果的,如果 setnx 没抢到锁,expire 是不应该执行的。事务里没有 if-else 分支逻辑,事务的特点是一口气执行,要么全部执行要么一个都不执行。公司几个亿的业务又被你耽误了...

以上情况的出现是因为两个命令并非一个原子性操作,所以在redis 2.8 版本之后出现了新的命令

SETEX key seconds value

所以现在可以利用一条原子性操作的命令来获取锁

redis> SETEX redislock 60 redislock
OK
redis> GET redislock  # 值
"redislock"
redis> TTL redislock  # 剩余生存时间
(integer) 49
2
超时问题

在正常的业务当中,当一个线程获取到锁并且设置了锁的过期时间之后,会出现由于业务代码执行时间过长,锁由于到达超时时间自动释放的情况。自动释放之后,其他的线程就会获取到分布式锁,导致业务代码不会串行执行。如果业务上允许这样的情况偶尔发生,那程序员就开干吧,最后顶多人工干预一下,update 一下数据库。

为了避免这类情况发生,在使用redis分布式锁的时候,业务方应尽量避免长时间执行的代码任务。

如果设置锁的超时时间比较长,在一定程度上可以缓解业务代码执行时间长锁自动到期的问题,但是一旦业务代码down掉,其他等待锁的线程等待的时间会比较长,这种情况下,确保获取到锁的程序不会down 成为了主要问题。

3
获取锁失败

当锁被一个调用方获取之后,其他调用方在获取锁失败之后,是继续轮询还是直接业务失败呢?如果是继续轮询的话,同步情况下当前线程会一直处于阻塞状态,所以这里轮询的情况还是建议使用异步。

4
可重入性

可重入性是指已经拥有锁的客户端再次请求加锁,如果锁支持同一个客户端重复加锁,那么这个锁就是可重入的。如果基于redis的分布式锁要想支持可重入性,需要客户端封装,可以使用threadlocal存储持有锁的信息。这个封装过程会增加代码的复杂度,所以菜菜不推荐这样做。

5
redis挂了

如果在多个客户端获取锁的过程中,redis 挂了怎么办呢?假如一个客户端已经获取到了锁,这个时候redis挂了(假如是redis集群),其他的redis服务器会接着提供服务,这个时候其他客户端可以在新的服务器上获取到锁了,这也导致了锁意义的丢失。有兴趣的同学可以去看看RedLock,这种方案以牺牲性能的代价解决了这个问题。

6
时钟跳跃问题

在某些时候,redis的服务器时间发生的跳跃,由于锁的过期时间依赖于服务器时间,所以也会出现两个客户端同时获取到锁的情况发生。

当把以上问题都有解决方案了之后,基于redis的分布式锁才可以放心使用

基于redis设计简单分布式锁容易,但是设计完美分布式锁不易, 还觉得基于redis的分布式锁好做吗?

程序员修神之路--redis做分布式锁可能不那么简单的更多相关文章

  1. 程序员修神之路--🤠分布式高并发下Actor模型如此优秀🤠

    写在开始 一般来说有两种策略用来在并发线程中进行通信:共享数据和消息传递.使用共享数据方式的并发编程面临的最大的一个问题就是数据条件竞争.处理各种锁的问题是让人十分头痛的一件事. 传统多数流行的语言并 ...

  2. 程序员修神之路--用NOSql给高并发系统加速(送书)

    随着互联网大潮的到来,越来越多网站,应用系统需要海量数据的支撑,高并发.低延迟.高可用.高扩展等要求在传统的关系型数据库中已经得不到满足,或者说关系型数据库应对这些需求已经显得力不从心了.关系型数据库 ...

  3. 程序员修神之路--kubernetes是微服务发展的必然产物

    菜菜哥,我昨天又请假出去面试了 战况如何呀? 多数面试题回答的还行,但是最后让我介绍微服务和kubernetes的时候,挂了 话说微服务和kubernetes内容确实挺多的 那你给我大体介绍一下呗 可 ...

  4. 程序员修神之路--为什么有了SOA,我们还用微服务?

    菜菜哥,我最近需要做一个项目,老大让我用微服务的方式来做 那挺好呀,微服务现在的确很流行 我以前在别的公司都是以SOA的方式,SOA也是面向服务的方式呀 的确,微服务和SOA有相同之处 面向服务的架构 ...

  5. 程序员修神之路--打通Docker镜像发布容器运行流程

    菜菜哥,我看了一下docker相关的内容,但是还是有点迷糊 还有哪不明白呢? 如果我想用docker实现所谓的云原生,我的项目该怎么发布呢? 这还是要详细介绍一下docker了 Docker 是一个开 ...

  6. 程序员修神之路--设计一套RPC框架并非易事

    菜菜哥,我最近终于把Socket通信调通了 这么底层的东西你现在都会了,恭喜你离涨薪又进一步呀 http协议不也是利用的Socket吗 可以这么说,http协议是基于TCP协议的,底层的数据传输可以说 ...

  7. Redis++:Redis做分布式锁真的靠谱吗

    Redis做分布式锁真的靠谱吗 Redis的分布式锁可以通过Lua进行实现,通过setnx和expire命令连用的方式 || 也可以使用高版本的方法同时设置失效时间,但是假如在以下情况下,就会造成无锁 ...

  8. Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流

    1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...

  9. redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

随机推荐

  1. python基础实战之猜年龄游戏

    目录 一.Python基础实战之猜年龄游戏 给定年龄,用户可以猜三次年龄 年龄猜对,让用户选择两次奖励 用户选择两次奖励后可以退出 age = 18 # 答案 count = 0 # 游戏次数控制 p ...

  2. LeetCode刷题总结-双指针、位运算和分治法篇

    本文总结LeetCode上有关双指针.位运算和分治法的算法题,推荐刷题总数14道.具体考点分析如下图: 一.双指针 1.字符串和数组问题 题号:424. 替换后的最长重复字符,难度中等 题号:828. ...

  3. kuangbin专题 数论基础 part1?

    线段树专题太难了,那我来做数学吧! 但数学太难了,我......(扯 这两天想了做了查了整理了几道数学. 除了一些进阶的知识,像莫比乌斯反演,杜教筛,min25学不会我跳了,一些基础的思维还是可以记录 ...

  4. [NOIP模拟]文本编辑器 题解

    bsoj5089 文本编辑器 /* 题意描述 九发明了一个完美的文本编辑器.这个编辑器拥有两个光标(cursor),所以九能够同时在两处地方插入和删除文本.这个编辑器除了正常的编辑功能以外,还有一些只 ...

  5. DevExpress的LookUpEdit怎样不显示列名

    场景 DevExpress的下拉框控件LookUpEdit的使用.添加item选项值.修改默认显示值: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/articl ...

  6. 安装Visual Studio Code并汉化

    安装很简单,直接点击安装文件,然后一直点击next就可以了.这款软件是免费的,不需要破解. 下载地址 这里需要使用快捷键[Ctrl+Shift+P],在弹出的搜索框中输入[configure lang ...

  7. 「SAP技术」SAP VL02N 执行批次拆分报错,说不允许批次拆分?

    1,如下新建的DN 80017843,storage location 字段值为空.VL02N 试图去做批次拆分失败,系统报错说,Batch split is not permitted for ma ...

  8. iOS Privacy Policy

    This application respects and protects the privacy of all users who use the service. In order to pro ...

  9. 基于XML的开发

    基于XML的开发 1.定义一个切面类 /** * Created by zejian on 2017/2/20.*/ public class MyAspectXML { public void be ...

  10. LINUX OS EXERCISE 08

    1 配置crontab计划任务时,记录的格式是什么? 分钟 小时 日期 月份 星期 可执行语句 2 配置crontab计划任务实例. 以root用户身份添加计划任务,每天早上7:30启动sshd服务, ...