每日一面 - 假设 Redis 基本可靠,如何用单进程 Redis 实现分布式悲观锁

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 每日一面 - 假设 Redis 基本可靠,如何用单进程 Redis 实现分布式悲观锁
麻烦大家帮我 投一票哈,谢谢


什么是分布式锁


针对共享内存模型的程序(例如JAVA程序),锁就是一个非常常用的机制。 一般简单分为悲观锁和乐观锁。悲观锁就是你获取这块数据的锁之后,别人就无法访问或操作这块数据,直到你释放这个锁。乐观锁一般就是CAS更新。 在单进程内内存的锁,只控制进程内数据的,就是非分布式锁。相反的,跨进程,需要锁住多个进程访问数据的锁就是分布式锁。


悲观锁一般由Redis的SETNXEX实现,Key 为资源名,EX 设置一个合理的超时时间, Value 设置为一个客户端生成的在超时时间内不会重复的随机字符串。


lua脚本特性说明


由于redis是单线程的,执行lua脚本的时候,不会同时执行其他客户端命令,这一定程度上保证了并发安全


单进程Redis分布式悲观锁实现思路


悲观锁一般由Redis的SETNX实现,通过SETNX获取一个锁,释放锁就把这个Key删除即可。 例如,获取一个名为TestLock的锁,通过SETNX TestLock TestLockValue,TestLockValue随意设置一个值。SETNX是SET IF NOT EXISTS,如果不存在就会设置成功。 设置成功会返回1,没设置成功会返回0。返回1代表获取锁成功,0代表没获取成功。 释放锁调用DEL命令,通过DEL TestLock释放锁。返回1代表释放锁成功,0代表没有这个锁。


但是如果这么简单实现,在如下场景时,会有问题:

  1. 客户端1获取锁成功。
  2. 客户端1死掉了,没能释放锁。
  3. 其他客户端再也获取不了这个锁了。

所以,考虑这个,我们一般会给锁设置一个超时时间。在超过这个超时时间之后,redis会自动把这个key删除掉。这样,在客户端1死掉了之后,不会导致这个锁永远不释放。

这样redis命令集合就变成了: 获取锁:

> SETNX TestLock TestLockValue
> EXPIRE TestLock 8


释放锁:

> DEL TestLock


由于SETNX还有EXPIRE是两条命令,在如下场景还可能出问题:

  1. 客户端1执行SETNX TestLock TestLockValue成功。
  2. 客户端1死掉了。没有设置过期时间。
  3. 其他客户端再也获取不了这个锁了。

Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行


这样redis命令集合就变成了: 获取锁:

> SET TestLock TestLockValue EX 5 NX


释放锁:

> DEL TestLock

也可以通过Redis事务来执行这两条命令,但是在Redis被kill掉时,可能只有部分命令被执行,所以还是通过上面的命令更安全简洁。

但在如下场景又会有新的问题:

  1. 客户端1获取锁成功。
  2. 客户端1在某个操作上阻塞了很长时间(GC等等)或者很长时间的网络延迟。
  3. 过期时间到了,锁自动释放了。
  4. 客户端2获取到了对应同一个资源的锁。
  5. 客户端1从阻塞中恢复过来,释放掉了客户端2持有的锁。

为了避免这种情况发生,我们一般会将锁的KEY的VALUE设置为一个随机值,这个随机值只要在一段连续时间内不会重复就行。在释放锁的时候,判断当前锁的VALUE与自己设置的是否一致,只有一致的时候才会释放

这样redis命令集合就变成了: 获取锁:

> SET TestLock RandomValue EX 5 NX

释放锁:

> GET TestLock
//程序判断是否一致
> DEL TestLock

这个在如下场景也会有问题:

  1. 客户端1获取锁成功。
  2. 客户端1访问共享资源。
  3. 客户端1为了释放锁,先执行’GET’操作获取随机字符串的值。
  4. 客户端1判断随机字符串的值,与预期的值相等。
  5. 客户端1由于某个原因阻塞住了很长时间。
  6. 过期时间到了,锁自动释放了。
  7. 客户端2获取到了对应同一个资源的锁。
  8. 客户端1从阻塞中恢复过来,执行DEL操纵,释放掉了客户端2持有的锁。

所以,释放锁一定要放在一个事务内,通过lua脚本。也就是:

传入Lua脚本参数 TestLock,当前程序缓存的随机值
判断TestLock的值与传入值是否一致,一致则删除
返回是否解锁成功

这样在Redis单进程一直正常运行的情况下,分布式悲观锁就实现了。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
1月前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
166 5
|
2月前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
90 8
|
2月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
70 16
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
57 5
|
3月前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
64 1
|
3月前
|
NoSQL 算法 关系型数据库
Redis分布式锁
【10月更文挑战第1天】分布式锁用于在多进程环境中保护共享资源,防止并发冲突。通常借助外部系统如Redis或Zookeeper实现。通过`SETNX`命令加锁,并设置过期时间防止死锁。为避免误删他人锁,加锁时附带唯一标识,解锁前验证。面对锁提前过期的问题,可使用守护线程自动续期。在Redis集群中,需考虑主从同步延迟导致的锁丢失问题,Redlock算法可提高锁的可靠性。
96 4
|
1月前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
177 85
|
3月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
96 6
|
8天前
|
存储 缓存 NoSQL
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应

热门文章

最新文章