Redis系列1:深刻理解高性能Redis的本质

Redis系列2:数据持久化提高可用性

Redis系列3:高可用之主从架构

Redis系列4:高可用之Sentinel(哨兵模式)

Redis系列5:深入分析Cluster 集群模式

追求性能极致:Redis6.0的多线程模型

追求性能极致:客户端缓存带来的革命

Redis系列8:Bitmap实现亿万级数据计算

Redis系列9:Geo 类型赋能亿级地图位置计算

Redis系列10:HyperLogLog实现海量数据基数统计

Redis系列11:内存淘汰策略

Redis系列12:Redis 的事务机制

1 先来了解下分布式锁

1.1 什么是分布式锁

分布式锁,即分布式系统中的锁,我们通过锁解决 控制共享资源访问 的问题,来保证只有一个线程可以访问被保护的资源。

1.2 分布式锁的实现方案

  • 基于数据库实现分布式锁
  • 基于Zookeeper实现分布式锁
  • 基于Redis实现分布式锁

等等,本篇基于Redis角度进行讨论

1.3 分布式锁满足哪些特性

  • 互斥性:在分布式系统下,一个事件在同一个时间内只能被一个线程执行,即只能有一个线程持有锁。
  • 安全性:可以方便的获取锁和释放锁,不产生死锁情况
  • 过期性:具备锁失效机制,即可以在时效预期外自动解锁,防止死锁
  • 可重入:具备可重入特性(可理解为重新进入,由多于一个任务并
  • 高性能:高性能的获取锁与释放锁
  • 高可用性:高可用的获取锁与释放锁

1.4 互斥特性

1.4.1 实现互斥特性

1.4.1.1 SETNX命令

SETNX 是 set if not exists 的缩写,当且仅当 key 不存在时,则设置 value 给这个key。若给定的 key 已经存在,则 SETNX 不做任何动作。

命令的返回值说明:

  • 1:说明该进程获得锁,将 key 的值设为 value
  • 0:说明其他进程已经获得了锁,进程不能进入临界区。

举例说明:setnx lock.key lock.value

> SETNX lock.user_063105015 1
(integer) 1 # 获取编号为 063105015 用户成功

如果已经被获取过了,则获取失败

> SETNX lock.user_063105015 1
(integer) 0 # 获取编号为 063105015 用户失败

1.4.1.2 get命令

获取key的值,如果存在,则返回;如果不存在,则返回nil

# 获取成功
> GET lock.user_063105015
"1" # 获取失败
> GET lock.user_123456789
(nil)

1.4.1.3 getset命令

原子的设置值的办法,对key设置newValue这个值,并且返回key原来的旧值。

# 重置用户信息
> getset lock.user_063105015 0
"1" # 原值为1 # 再次重置
> getset lock.user_063105015 1
"0" # 原值为0

1.4.1.4 删除命令,用完之后进行锁释放

> DEL lock.user_063105015
(integer) 1

具体执行流程如下:

1.3.1.5 异常导致的锁释放问题

可能会因为一些场景,造成锁无法释放,如下:

  • 调用服务或者客户端崩溃,无法正确的处理锁释放的工作。
  • 业务程序的异常执行,没有操作释放锁的 DEL指令。

    这种情况下,锁就会一直占用着,不会被释放,其他线程也无法获得。所以必须得有个自动释放锁的过程。

1.4.2 超时释放

超时释放其实就是重置,目的是避免因为各种原因导致的锁长时间无法释放。

做法就是我们给锁加个过期时间(EXPIRE Time):

# 给用户 063105015 加锁
> SETNX lock.user_063105015 1
(integer) 1 # 设置过期时间,到时间没删除则自动释放
> EXPIRE lock.user_063105015 120 # 120秒之后自动释放
(integer) 1

为了保证执行时的原子性,Redis 官方扩展了 SET 命令,既能满足获取对象,又能保证设置超时的时间语义。

避免出现了获取锁完成之后,执行超时设置失败微软无法释放锁的情况。保证要么都成功,要么都不执行。

# 示例如下:
SET lock.user_063105015 1 NX PX 60000
  • NX:就是Not Exist,表示只有用户编号为 063105015 不存在的时候才可以 SET 成功,并且只有单个线程可以获取锁;
  • PX 60000:表示对这个锁设置一个60s的过期时间。

1.4.3 对锁进行唯一标识

经常会出现一种情况,就是你获取到锁之后,因为各种原因(比如你的服务线程故障、网络抖动 等等),没有执行完成,或者没有释放锁,

这时候锁也过了 EXPIRE TIME,就自动释放了。当另外一个线程开锁成功,你的线程响应过来了,把人家的锁给释放了,这样就有问题了。

为了避免这种操作,我们要对同一个的锁做唯一识别码,在释放锁之前,先判断下是不是自己设置的那个锁,如下:

# 设置10086专用值
> SET lock.user_063105015 10086 NX PX 60000
OK # 设置成功,获取检查确实是10086
> get lock.user_063105015
"10086" # 伪代码:删除前进项确认是不是自己加的那个锁
if ( redis.get("lock.user_063105015").equals("10086")) {
redis.del("lock.user_063105015"); // 只有对比成功才进行删除,释放锁
}

1.4.4 实现可重入锁

可重入锁可以理解为重新进入,由多于一个任务并发使用,而不必担心数据错误。

  • 可重入性就就保证线程能继续执行,防止在同一线程中多次获取锁而导致死锁发生
  • 不可重入就是需要等待锁释放之后,再次获取锁成功,才能继续往下执行

这边说说可重入锁,比如你执行线程的方案a获取锁之后,你的a方法后,线程继续执行b方法也需要获取锁,如果这时候不可重入,

线程就需要等待锁的释放,进入争抢。

这边的解法就是对线程加锁的锁值进行增减,同一个线程的方法遇到加锁则锁值+1,遇到退锁则锁值-1,当前仅当锁值=0的时候,说明这个锁真正的被释放了。

Java中的Redisson 类库就是通过 Redis Hash 来实现可重入锁。

加锁的逻辑

我们可以使用 Redis hash 结构实现,key 表示被锁的共享资源, hash 结构的 fieldKey 的 value 则保存加锁的次数。



实现如下( KEYS1 = "lock.user_063105015", ARGV [10000,uuid):

KEYS[1] = key的值

ARGV[1]) = 持有锁的时间

ARGV[2] = getLockName(threadId) 下面id就算系统在启动的时候会全局生成的uuid 来作为当前进程的id,加上线程id就是getLockName(threadId)了,可以理解为:进程ID+系统ID = ARGV[2]

# 1 为 true
# 0 为 false if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);

参数说明

  • hincrby :将hash中指定域的值增加给定的数字
  • pexpire:设置key的有效时间以毫秒为单位
  • hexists:判断field是否存在于hash中
  • pttl:获取key的有效毫秒数

程序说明

  • Redis exists 命令判断 lock.user_063105015 锁是否存在
  • 锁不存在,hincrby 创建一个键为 lock.user_063105015 的 hash 表,键为 uuid,初始化值为 0,然后再次加 1,最后设置过期时间。
  • 锁存在,hexists判断 lock 对应的 hash 表中是否存在 uuid 键,存在则 + 1,并重置过期时间
  • 不符合以上的条件的都走到默认返回

Redis系列13:分布式锁实现的更多相关文章

  1. redis系列:分布式锁

    redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  2. Redis系列(二)--分布式锁、分布式ID简单实现及思路

    分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...

  3. Redis系列四 - 分布式锁的实现方式

    前言 分布式锁一般有3中实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 以下将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁的可 ...

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

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

  5. 基于redis实现的分布式锁

    基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...

  6. Redis高并发分布式锁详解

    为什么需要分布式锁 1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock. 2.但是随 ...

  7. 一个Redis实现的分布式锁

    import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.conne ...

  8. 基于Redis的简单分布式锁的原理

    参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...

  9. redis客户端、分布式锁及数据一致性

    Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...

  10. 在redis上实现分布式锁

    /** *在redis上实现分布式锁 */ class RedisLock { private $redisString; private $lockedNames = []; public func ...

随机推荐

  1. 【个人首测】百度文心一言 VS ChatGPT GPT-4

    昨天我写了一篇文章GPT-4牛是牛,但这几天先别急,文中我测试了用GPT-4回答ChatGPT 3.5 和 Notion AI的问题,大家期待的图片输入也没有出现. 昨天下午百度发布了文心一言,对标C ...

  2. 音频和视频流最佳选择?SRT 协议解析及报文识别

    我们所知道 SRT 是由 Haivision 和 Wowza 开发的开源视频流协议.很多人会认为在不久的将来,它被是 RTMP 的替代品.因为 RTMP 协议安全性稍低,延迟相对较高 ,而相对于 SR ...

  3. 原来还能这样看Java线程的状态及转换

    作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功.JAVA底层.面试.职业成长相关资料等更多精彩文章在公众号「小牛呼噜噜」 大家好,我是呼噜噜,最近一直在梳理Jav ...

  4. vulnhub靶场之HACKSUDO: 2 (HACKDUDO)

    准备: 攻击机:虚拟机kali.本机win10. 靶机:hacksudo: 2 (HackDudo),下载地址:https://download.vulnhub.com/hacksudo/hackdu ...

  5. 随机服务系统模拟—R实现(二)

    M/M/1随机服务系统的模拟 M/M/1模型是一种出生-死亡过程,此随机过程中的每一个状态代表模型中人数的数目.因为模型的队列长度无限且参与人数亦无限,故此状态数目亦为无限.例如状态0表示模型闲置.状 ...

  6. pysimplegui之光标 - 元素和窗口的设置

    光标 - 元素和窗口的设置 通过将光标设置为元素或整个窗口,可以将普通箭头光标更改为其他内容.当您将鼠标移到元素或窗口上时,结果将是光标发生变化. 最好的例子之一是 URL.当鼠标移到链接上时,用户习 ...

  7. pysimplegui之画布,图形,表格和树结构元素

    画布元素 在我看来,tkinter Canvas 小部件是 tkinter 小部件中功能最强大的.虽然我尽我所能将用户与任何与 tkinter 相关的东西完全隔离,但 Canvas 元素是一个例外.它 ...

  8. [Android]ADB调试: SecurityException: Injecting to another application requires INJECT_EVENTS permission

    问题描述 使用ADB工具调试安卓设备时报此错误: C:\Users\Johnny>adb shell input text "Hello" java.lang.Securit ...

  9. SPI-SPI主机硬件片选功能使用说明

    SPI主机硬件片选功能使用说明 SPI协议最早的标准,是由摩托罗拉公司制定.在协议使用的过程中,根据实际需求可能会进行一些扩展和修改. 在一份由飞思卡尔半导体发布的SPI V4.01版本规范中,对片选 ...

  10. 突破传统监测模式:业务状态监控HM的新思路

    作者:京东保险 管顺利 一.传统监控系统的盲区,如何打造业务状态监控. 在系统架构设计中非常重要的一环是要做数据监控和数据最终一致性,关于一致性的补偿,已经由算法部的大佬总结过就不在赘述.这里主要讲如 ...