深入解读Redis分布式锁
之前码甲哥写了两篇有关线程安全的文章:
- 你管这叫线程安全?
- .NET八股文:线程同步技术解读
分布式锁是"线程同步"的延续
最近首度应用"分布式锁",现在想想,分布式锁不是孤立的技能点,这其实就是跨主机的线程同步。
进程内 | 跨进程 | 跨主机 |
---|---|---|
Lock/Monitor、SemaphoreSlim | Metux、Semaphore | 分布式锁 |
用户态线程安全 | 内核态线程安全 |
单机服务器可以通过共享某堆内存来标记上锁/解锁,线程同步说到底是建立在单机操作系统的用户态/内核态对共享内存的访问控制。
而分布式服务器不是在同一台机器上:跨主机,因此需要将内存标记存储在所有机器进程都能看到的地方。
在开发很多业务场景会使用到锁,例如库存控制,抽奖等。
例如库存只剩1个商品,有三个用户同时打算购买,谁先购买库存立即清零,不能让其他二人也购买成功。
解读分布式锁
我们常说的线程安全、线程同步方案,包括此次的分布式锁都是基于“多线程/多进程对特定资源有更新操作”。
基本考量:
- 分布式系统,一个锁在同一时间只能被一个服务器获取 (这是分布式锁的基础)
- 具备锁失效机制,防止死锁 (防止某些意外,锁没有得到释放,那别人也无法得到锁)
Redis SET resource-name anystring NX EX max-lock-time
是一种最简单的分布式锁实现方案。
SET 命令支持多个参数:
- EX seconds-- 设置过期时间(s)
- NX -- 如果key不存在,则设置
......
因为SET命令参数可以替代SETNX,SETEX,GETSET,这些命令在未来可能被废弃。
上面的命令返回OK(或经过重试),客户端就获取到这个锁;
使用DEL
命令解锁;
到达超时时间会自动释放锁。
在解锁时,增加一些设计,让系统更加健壮:
- 不要使用固定的String值,而是使用一个不易被猜中的随机值, 业内称为
token
- 不使用DEL命令释放锁,而是发送script去移除key
第3、4点是为了解决 :“锁提前过期,客户端A还没有执行完,然后客户端B获取了锁,这时客户端A执行完了,会不会再删锁的时候把B的锁给删掉” -- 4是3技术上的推荐实现。
脚本如下:
if redis.call("get",KEYS1] ==ARGV[1])
then
return redis.call("DEL",KEYS[1])
else
return 0
end
下面使用StackExchange.Redis 写了基于以上考量的代码示例:
/// <summary>
/// Acquires the lock.
/// </summary>
/// <param name="key"></param>
/// <param name="token">随机值</param>
/// <param name="expireSecond"></param>
/// <param name="waitLockSeconds">非阻塞锁</param>
static bool Lock(string key, string token,int expireSecond=10, double waitLockSeconds = 0)
{
var waitIntervalMs = 50;
bool isLock;
DateTime begin = DateTime.Now;
do
{
isLock = Connection.GetDatabase().StringSet(key, token, TimeSpan.FromSeconds(expireSecond), When.NotExists);
if (isLock)
return true;
//不等待锁则返回
if (waitLockSeconds == 0) break;
//超过等待时间,则不再等待
if ((DateTime.Now - begin).TotalSeconds >= waitLockSeconds) break;
Thread.Sleep(waitIntervalMs);
} while (!isLock);
return false;
}
/// <summary>
/// Releases the lock.
/// </summary>
/// <returns><c>true</c>, if lock was released, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param>
/// <param name="value">value</param>
static bool UnLock(string key, string value)
{
string lua_script = @"
if (redis.call('GET', KEYS[1]) == ARGV[1]) then
redis.call('DEL', KEYS[1])
return true
else
return false
end
";
try
{
var res = Connection.GetDatabase().ScriptEvaluate(lua_script,
new RedisKey[] { key },
new RedisValue[] { value });
return (bool)res;
}
catch (Exception ex)
{
Console.WriteLine($"ReleaseLock lock fail...{ex.Message}");
return false;
}
}
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
ConfigurationOptions configuration = new ConfigurationOptions
{
AbortOnConnectFail = false,
ConnectTimeout = 5000,
};
configuration.EndPoints.Add("10.100.219.9", 6379);
return ConnectionMultiplexer.Connect(configuration.ToString());
});
public static ConnectionMultiplexer Connection => lazyConnection.Value;
以上代码新增了第五点考量:
- 为避免无限制抢锁,增加了非阻塞锁: 轮询_s等待锁,未等到则不再抢锁
使用方式:
下面并行开启三个任务,减少库存:
static void Main(string[] args)
{
// 尝试并行执行3个任务
Parallel.For(0, 3, x =>
{
string token = $"loki:{x}";
bool isLocked = Lock("loki", token, 5, 10);
if (isLocked)
{
Console.WriteLine($"{token} begin reduce stocks (with lock) at {DateTime.Now}.");
Thread.Sleep(1000);
Console.WriteLine($"{token} release lock {UnLock("loki", token)} at {DateTime.Now}. ");
}
else
{
Console.WriteLine($"{token} begin reduce stocks at {DateTime.Now}.");
}
});
}
输出总结
本文从基础的线程安全,八卦文线程同步,延伸到跨主机的资源线程/进程安全, 其中演示了利用RedisSET命令
做分布式锁的设计方案,虽然是面试八股文,我们依旧需要仔细揣摩Redis Lock的细节考量。
深入解读Redis分布式锁的更多相关文章
- 一篇文章带你解读Redis分布式锁的发展史和正确实现方式
前言 近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布式实现方式为Redi ...
- Redis 分布式锁进化史(解读 + 缺陷分析)
Redis分布式锁进化史 近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布 ...
- 一文看透 Redis 分布式锁进化史(解读 + 缺陷分析)(转)
近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布式实现方式为Redis,Z ...
- 又长又细,万字长文带你解读Redisson分布式锁的源码
前言 上一篇文章写了Redis分布式锁的原理和缺陷,觉得有些不过瘾,只是简单的介绍了下Redisson这个框架,具体的原理什么的还没说过呢.趁年前项目忙的差不多了,反正闲着也是闲着,不如把Rediss ...
- 利用redis分布式锁的功能来实现定时器的分布式
文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...
- 解读Google分布式锁服务
解读Google分布式锁服务 背景介绍 在2010年4月,Google的网页索引更新实现了实时更新,在今年的OSDI大会上,Google首次公布了有关这一技术的论文. 在此之前,Google的索引更 ...
- Redis分布式锁
Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...
- redis分布式锁和消息队列
最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...
- redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...
随机推荐
- 解决在Vim中鼠标右键不能粘贴问题
最近维护一台服务器,使用putty登录后,用vim时,鼠标右键不能 粘贴而是进入了visual模式.网上查找一番找到了解决方法: 方 法一:在普通模式下键入" :set mouse-=a&q ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...
- XCTF-unfinish
unfinish 之前做过这个题,这是之前写的WP:链接
- Java并发容器篇
作者:汤圆 个人博客:javalover.cc 前言 断断续续一个多月,也写了十几篇原创文章,感觉真的很不一样: 不能说技术有很大的进步,但是想法确实跟以前有所不同: 还没开始的时候,想着要学的东西太 ...
- maven打war包
测试/本地 mvn clean package -Dmaven.test.skip=true 生产服务器打包命令 mvn clean package -P prod -Dmaven.test.skip ...
- svn客户端访问失败,错误“请求的名称有效,但是找不到请求的类型”的解决
1.问题描述:visualSVN客户端在其他机子访问出现以下问题(服务端装在我的电脑上,我本机客户端可以正常访问) 2. 原因分析:SVN客户端访问服务器需要输入URL地址,URL地址分为两种方式.一 ...
- Java 并发编程(一) → LockSupport 详解
开心一刻 今天突然收到花呗推送的消息,说下个月 9 号需要还款多少钱 我就纳了闷了,我很长时间没用花呗了,怎么会欠花呗钱? 后面我一想,儿子这几天玩了我手机,是不是他偷摸用了我的花呗 于是我找到儿子问 ...
- python爬虫——汽车之家数据
相信很多买车的朋友,首先会在网上查资料,对比车型价格等,首选就是"汽车之家",于是,今天我就给大家扒一扒汽车之家的数据: 一.汽车价格: 首先获取的数据是各款汽车名称.价格范围以及 ...
- Codeforces Round #704 (Div. 2)
A. Three swimmers 题意:第一个人跳水是每隔a分钟去一次,第二个人跳水是每隔b分钟,第三个人跳水是每隔c分钟,一个人准备在p分钟的 时候去跳水,问需要最少等待多长时间才能轮到前三个人 ...
- [Qt]《开发指南》3.1源码分析
界面: ButterflyGraph: 可以看出,本工程在主程序main里调用窗口界面qmywidget,窗口界面继承了QWidget,并调用自定义类qperson,推测是qmywidget类中的一个 ...