之前码甲哥写了两篇有关线程安全的文章:

  • 你管这叫线程安全?
  • .NET八股文:线程同步技术解读

分布式锁是"线程同步"的延续

最近首度应用"分布式锁",现在想想,分布式锁不是孤立的技能点,这其实就是跨主机的线程同步

进程内 跨进程 跨主机
Lock/Monitor、SemaphoreSlim Metux、Semaphore 分布式锁
用户态线程安全 内核态线程安全

单机服务器可以通过共享某堆内存来标记上锁/解锁,线程同步说到底是建立在单机操作系统的用户态/内核态对共享内存的访问控制。

而分布式服务器不是在同一台机器上:跨主机,因此需要将内存标记存储在所有机器进程都能看到的地方。

在开发很多业务场景会使用到锁,例如库存控制,抽奖等。

例如库存只剩1个商品,有三个用户同时打算购买,谁先购买库存立即清零,不能让其他二人也购买成功。

解读分布式锁

我们常说的线程安全、线程同步方案,包括此次的分布式锁都是基于“多线程/多进程对特定资源有更新操作”。

基本考量:

  1. 分布式系统,一个锁在同一时间只能被一个服务器获取 (这是分布式锁的基础)
  2. 具备锁失效机制,防止死锁 (防止某些意外,锁没有得到释放,那别人也无法得到锁)

Redis SET resource-name anystring NX EX max-lock-time 是一种最简单的分布式锁实现方案。

SET 命令支持多个参数:

  • EX seconds-- 设置过期时间(s)
  • NX -- 如果key不存在,则设置

    ......

    因为SET命令参数可以替代SETNX,SETEX,GETSET,这些命令在未来可能被废弃。

上面的命令返回OK(或经过重试),客户端就获取到这个锁;

使用DEL命令解锁;

到达超时时间会自动释放锁。

在解锁时,增加一些设计,让系统更加健壮:

  1. 不要使用固定的String值,而是使用一个不易被猜中的随机值, 业内称为token
  2. 不使用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;

以上代码新增了第五点考量:

  1. 为避免无限制抢锁,增加了非阻塞锁: 轮询_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分布式锁的更多相关文章

  1. 一篇文章带你解读Redis分布式锁的发展史和正确实现方式

    前言 近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布式实现方式为Redi ...

  2. Redis 分布式锁进化史(解读 + 缺陷分析)

    Redis分布式锁进化史 近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布 ...

  3. 一文看透 Redis 分布式锁进化史(解读 + 缺陷分析)(转)

    近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布式实现方式为Redis,Z ...

  4. 又长又细,万字长文带你解读Redisson分布式锁的源码

    前言 上一篇文章写了Redis分布式锁的原理和缺陷,觉得有些不过瘾,只是简单的介绍了下Redisson这个框架,具体的原理什么的还没说过呢.趁年前项目忙的差不多了,反正闲着也是闲着,不如把Rediss ...

  5. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  6. 解读Google分布式锁服务

    解读Google分布式锁服务  背景介绍 在2010年4月,Google的网页索引更新实现了实时更新,在今年的OSDI大会上,Google首次公布了有关这一技术的论文. 在此之前,Google的索引更 ...

  7. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

  8. redis分布式锁和消息队列

    最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...

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

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

随机推荐

  1. 【python】Leetcode每日一题-不同的子序列

    [python]Leetcode每日一题-不同的子序列 [题目描述] 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数. 字符串的一个 子序列 是指,通过删除一些(也可以 ...

  2. 数据库导入时出现“2006 - MySQL server has gone away”问题的解决(windows)

    1.查到文件my.ini,在文件最后([mysqld]段最后),修改"max_allowed_packet = 50M",添加"interactive_timeout = ...

  3. 中文NER的那些事儿2. 多任务,对抗迁移学习详解&代码实现

    第一章我们简单了解了NER任务和基线模型Bert-Bilstm-CRF基线模型详解&代码实现,这一章按解决问题的方法来划分,我们聊聊多任务学习,和对抗迁移学习是如何优化实体识别中边界模糊,垂直 ...

  4. Azure Storage 利用 azCopy 复制迁移数据

    一,引言 前两天遇到了Azure Blob Storage 需要迁移到另外的一个 Azure Blob Storage 中.手动下载.上传已经无法满足了,得另寻一种方式了 AzCopy.Azure 为 ...

  5. Pytorch_Part5_迭代训练

    VisualPytorch beta发布了! 功能概述:通过可视化拖拽网络层方式搭建模型,可选择不同数据集.损失函数.优化器生成可运行pytorch代码 扩展功能:1. 模型搭建支持模块的嵌套:2. ...

  6. mysql枚举和集合

    create table consumer( id int, name char(16), sex enum('male','female','other'), level enum('vip1',' ...

  7. iUploader 2.0 七牛云上传工具

    iUploader 软件介绍: iUploader主要功能将文件上传至七牛云,返回 Markdown 格式的链接到剪贴板 功能介绍: 图片本地压缩 图片右键上传 图片截取上传 图片复制上传 图片拖拽上 ...

  8. [DB] CDH集群规划

    配置 三台机器:node01.node02.node03 node01:6G+60G node02:2G+40G node03:2G+40G 组件 Cloudera Managerment Servi ...

  9. [刷题] PTA 6-7 统计某类完全平方数

    要求: 实现一个函数,判断任一给定整数N是否满足条件:它是完全平方数,又至少有两位数字相同,如144.676等. 函数接口定义: int IsTheNumber ( const int N ); 其中 ...

  10. 自动做bond的脚本

    #!/bin/bash # 先备份原来的网卡配置文件 find /etc/sysconfig/network-scripts/ -type f -name "ifcfg*"|xar ...