文档章节

关于redis分布式锁的使用

首席撸出血
 首席撸出血
发布于 2016/04/18 14:05
字数 1617
阅读 250
收藏 4

背景:比如我有100张点卡,有两台服务器同时进行卖这个点卡,但是今天我就想卖10张,超出10张我就不卖了,在多线程的额情况下很容易出现卖出了11张甚至更多,这也是超卖的问题,从实现来说也可能出现两个人获取到的是同一张点卡,那么也是不可取的 

出于一个涉世未深,对那些高大上的东西充满好奇的我希望可以使用自己没用过的东西来解决上面的问题,所以我选择了使用分布式锁来解决分布式情况下超卖的问题 

如果程序只在一台服务器里跑,那么我们可以很简单的在需要控制同步的地方使用锁lock,或者使用sychronized,但是在多台服务器的情况下如此使用是不可以的,但是可以选择在数据库层面通过悲观锁、乐观锁解决,亦或redis的消息队列..还有其他的一些方案

言归正传,本人将阐述分布式锁从无到有,遇到了哪些问题,网上有两种redis分布式锁的实现方案

1.采用setNX配合getSet超时方案

 

步骤如下:
1.使用setNX(lock,时间戳)方式,进行获取锁,如果获取到锁,返回为1 dosomething(),没有获取到返回为0并执行第2步
2.使用get(lock)方法去获取lock对应的时间戳,并判断时间是否已经超时,如果超时了进入第3步
3.使用getset方法将新锁添加到redis中,并判断getset的返回值,是否和2获取到的value值相同,如果相同说明获取到了锁 dosomething(),所以不同则说明锁已经被别人获取到了
4.执行成功释放锁del

dosomething()为自己操作的内容

以上的实现是有问题的,情况在于,如果1号线程在执行第3步并且也获取到锁了的时候2号线程执行了第4步,那么就会出现3号线程执行第1步的时候也获取到锁了,有点绕口,其实就是说,如果一个线程通过getset方法发现占用锁的线程超时了而且我也获取到新锁了,那个超时的线程正好也执行完了做了释放锁的操作,那么肯定会出现第三个锁通过 setNX方法又获取到了新锁。

如果你真正的去尝试写了这么一段代码,你也会发现这里会出现一个问题,经常报空指针异常,问题就在于,有线程释放锁了之后存在一个线程过了get操作,然后将获取的null做超时判断。

2.循环采用setNX方案

步骤如下:
1.使用setNX(lock,时间戳)方式,进行获取锁,如果获取到锁,返回为1并执行第二步,没有获取到返回为0重新执行第1步
2.通过expire设置超时时间,dosomething();
3.执行成功释放锁del;

以上的方法实现获取觉得没有任何的问题,但是依然存在很大的隐患,隐患在于,如果某个线程在第1步获取到锁了但是在执行第2步的时候出现了异常导致设置超时时间失败,那么该锁就一直不会失效,最后导致下面的所有的线程都无法再获取到锁

 

 

就在我觉得redis无法实现分布式锁的时候,我在公司的关于redis方法的封装中发现了一句代码

String result = jedis.set(key, value, "NX", "EX", seconds);

于是我上网查阅了一下资料,了解到这一句的代码的意思是:设置键key,超时时间为seconds,并且如果该键存在则设置失败,该键不存在则设置成功

于是我将第二个方案重新改变了一下

步骤如下:
1.set(key, value, "NX", "EX", seconds)方式获取锁,如果获取到锁,执行2,没有继续执行1
2.dosomething
3.执行成功释放锁del

这个时候我觉得下面的写法已经很正确了

    //获取锁
    public Boolean requireLock(String key, String value ,int seconds) {
        String result = jedis.set(key, value, "NX", "EX", seconds);
        return (result != null) && ("OK".equals(result) || "+OK".equals(result));
    }
    //释放锁
    public Boolean releaseLock(String key) {
        return jedis.del(key) == 1;
    }

就在那么一天我看到了一个redis的文档,里面有那么几段内容

分布式锁应该参考the Redlock algorithm的实现,应该这个方法只是复杂一点,但是却能保证更好的使用

避开上面说的不提,下面的一段内容吸引了我的注意

 

可以通过如下优化使得上面的锁系统变得更加棒:
不要设置固定的字符串,而是设置为随机的大字符串,可以称为token。
通过脚步删除指定锁的key,而不是DEL命令。
上述优化方法会避免下述场景:a客户端获得的锁(键key)已经由于过期时间到了被redis服务器删除,但是这个时候a客户端还去执行DEL命令。而b客户端已经在a设置的过期时间之后重新获取了这个同样key的锁,那么a执行DEL就会释放了b客户端加好的锁。

 

 

突然觉得好像是那么一回事哦

所以我给代码又做了修改

//获取锁
    public Boolean requireLock(String key, String value ,int seconds) {
        String result = jedis.set(key, value, "NX", "EX", seconds);
        return (result != null) && ("OK".equals(result) || "+OK".equals(result));
    }
    //释放锁
    public Boolean releaseLock(String key, String value) {
		//避免释放其他线程的锁
		if (value.equals(jedis.get(key))) {
			return jedis.del(key) == 1;
		}
        return false;
    }

上面的代码要保证value是一个随机字符串。如果不用时间戳的更好

 

 

附言:

可能很多人觉得使用最后的方案应该就成功了,但是笔者真真实实的遇到了一个问题——重排序,就是dosometing()方法的在releaseLock方法后面执行了,导致我在测试的时候,发现存在两个线程同时获取到了锁的情况,当我加了同步代码块之后,这个问题也就不再存在了。

 

© 著作权归作者所有

首席撸出血

首席撸出血

粉丝 55
博文 17
码字总数 3844
作品 0
浦东
程序员
私信 提问
如何用Redlock实现分布式锁

转载请标明出处: http://blog.csdn.net/forezp/article/details/70305336 本文出自方志朋的博客 之前写过一篇文章《如何在springcloud分布式系统中实现分布式锁?》,由于自己仅仅是阅读了相...

forezp
2017/04/20
0
0
redis分布式锁

关于分布式锁的概念网上太多了,这里就不罗嗦了。对于开发者来说,最关心的应该是什么情况下使用分布式锁。 使用分布式锁,一般要满足以下几个条件: · 分布式系统(关键是分布式) · 共享资...

明舞
2015/10/16
8.4K
3
分布式锁原理及常用实现

起因 前段时间,看到redis作者发布的一篇文章《Is Redlock safe?》,Redlock是redis作者基于redis设计的分布式锁的算法。文章起因是有一位分布式的专家写了一篇文章《How to do distributed...

偶尔诗文
2016/11/09
427
0
ASP.NET Core中借助CSRedis实现安全高效的分布式锁

引言 最近回头看了看开发的.NET Core 2.1项目的复盘总结,其中在多处用到Redis实现的分布式锁,虽然在OnResultExecuting方法中做了防止死锁的处理,但在某些场景下还是会发生死锁的问题,下面...

dotNET跨平台
02/09
0
0
node.js 中使用redis实现分布式事物锁

在node项目中,我们常会跑一些定时任务,比如定时发送通知、定时发送邮件等,项目部署的时候,我们往往是多机多实例部署,这就导致每个实例都会跑一次同样的任务,所以我们需要一个分布式事物...

小黎也
2018/06/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

关于ThinkPHP5.1+的Log无法记录SQL调试记录的小经历

项目开发阶段,除了基本编码外,性能也需要实时关注与优化。之前我的大部分项目都是使用ThinkPHP5.0以及ThinkPHP3.2,对于框架提供的日志记录和日志配置都差不多,然后使用ThinkPHP5.1的时候...

北桥苏
25分钟前
1
0
TiDB Binlog 源码阅读系列文章(四)Pump server 介绍

作者: satoru 在 上篇文章 中,我们介绍了 TiDB 如何通过 Pump client 将 binlog 发往 Pump,本文将继续介绍 Pump server 的实现,对应的源码主要集中在 TiDB Binlog 仓库的 pump/server.go...

TiDB
28分钟前
0
0
OSChina 周五乱弹 ——不知道假装开心,装的像么

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @巴拉迪维 :天黑了 你很忧愁, 你说世界上, 找不到四块五的妞, 行走在凌晨两点的马路上, 你疲倦地拿着半盒黄鹤楼。#今日歌曲推荐# 《四块...

小小编辑
今天
2.5K
19
Windows下学习C语言有哪些集成开发软件?

前言 初学者学习C语言遇到的最大困难想必就是搭建环境了,相当多的初学者就是被搭建环境导致放弃了学习编程,就我自己的经验而言,初学编程不应该受限于环境,使用成熟好用的环境就可以了,之...

Allen5G
昨天
2
0
Hello,Servlet!

Servlet来源 上文说过了servlet是什么,我们从servlet是什么中也可以了解到servlet的来源:servlet是Java的一个类,并且能够运行在web容器上,所以servlet是按照web容器的规范和Java的规范写...

蒙尘
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部