详细解读云数据库Redis高性能分布式锁

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 分布式锁是大型应用中最常见的功能之一,基于Redis实现分布式锁的方式有很多。本章节先介绍并分析常见的分布式锁实现方式,之后结合阿里巴巴集团在使用云数据库Redis企业版和分布式锁方面的业务经验,介绍使用Redis企业版实现高性能分布式锁的实践方案。

云栖号快速入门:【点击查看更多云产品快速入门】
不知道怎么入门?这里分分钟解决新手入门等基础问题,可快速完成产品配置操作!

分布式锁是大型应用中最常见的功能之一,基于Redis实现分布式锁的方式有很多。本章节先介绍并分析常见的分布式锁实现方式,之后结合阿里巴巴集团在使用云数据库Redis企业版和分布式锁方面的业务经验,介绍使用Redis企业版实现高性能分布式锁的实践方案。

分布式锁及其应用场景

应用开发时,如果需要在同进程内的不同线程并发访问某项资源,可以使用各种互斥锁、读写锁;如果一台主机上的多个进程需要并发访问某项资源,则可以使用进程间同步的原语,例如信号量、管道、共享内存等。但如果多台主机需要同时访问某项资源,就需要使用一种在全局可见并具有互斥性的锁了。这种锁就是分布式锁,可以在分布式场景中对资源加锁,避免竞争资源引起的逻辑错误。
image

分布式锁的特性

  • 互斥性
    在任意时刻,只有一个客户端持有锁。
  • 不死锁
    分布式锁本质上是一个基于租约(Lease)的租借锁,如果客户端获得锁后自身出现异常,锁能够在一段时间后自动释放,资源不会被锁死。
  • 一致性
    硬件故障或网络异常等外部问题,以及慢查询、自身缺陷等内部因素都可能导致Redis发生高可用切换,replica提升为新的master。此时,如果业务对互斥性的要求非常高,锁需要在切换到新的master后保持原状态。

使用原生Redis实现分布式锁

BE430C1F_F020_4b97_85BA_4E6EF4304685

  • 加锁
    在Redis中加锁非常简便,直接使用SET命令即可。示例及关键选项说明如下:
SET resource_1 random_value NX EX 5

表 1. 关键选项说明
2371933C_2F71_491a_80A4_3DA69651B2B8

示例代码为resource_1这个key设置了5秒的过期时间,如果客户端不释放这个key,5秒后key将过期,锁就会被系统回收,此时其它客户端就能够再次为资源加锁并访问资源了。

  • 解锁
    解锁一般使用DEL命令,但可能存在下列问题。

image

(1).t1时刻,App1设置了分布式锁resource_1,过期时间为3秒。
(2).App1由于程序慢等原因等待超过了3秒,而resource_1已经在t2时刻被释放。
(3).t3时刻,App2获得这个分布式锁。
(4).App1从等待中恢复,在t4时刻运行3B4ADCAC_B3BD_4285_AE19_D18D1EA96E8F将App2持有的分布式锁释放了。

从上述过程可以看出,一个客户端设置的锁,必须由自己解开。因此客户端需要先使用GET命令确认锁是不是自己设置的,然后再使用DEL解锁。在Redis中通常需要用Lua脚本来实现自锁自解:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
  • 续租
    当客户端发现在锁的租期内无法完成操作时,就需要延长锁的持有时间,进行续租(renew)。同解锁一样,客户端应该只能续租自己持有的锁。在Redis中可使用如下Lua脚本来实现续租:
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("expire",KEYS[1], ARGV[2])
else
    return 0
end

使用Redis企业版实现分布式锁

使用Redis企业版性能增强型实例的String增强命令,无需Lua即可实现分布式锁。

  • 加锁
    加锁方式与原生Redis相同,使用SET命令:
SET resource_1 random_value NX EX 5
  • 解锁
    直接使用Redis企业版的CAD命令即可实现优雅而高效的解锁:
/* if (GET(resource_1) == my_random_value) DEL(resource_1) */
CAD resource_1 my_random_value
  • 续租
    续租可以直接使用CAS命令实现:
CAS resource_1 my_random_value my_random_value EX 10

FD416D6E_A930_47d6_9F6F_880A4CFB2A1A

基于Jedis的示例代码

  • 定义CAS/CAD命令
enum TairCommand implements ProtocolCommand {
    CAD("CAD"), CAS("CAS");

    private final byte[] raw;

    TairCommand(String alt) {
      raw = SafeEncoder.encode(alt);
    }

    @Override
    public byte[] getRaw() {
      return raw;
    }
}
  • 加锁
public boolean acquireDistributedLock(Jedis jedis,String resourceKey, String randomValue, int expireTime) {
    SetParams setParams = new SetParams();
    setParams.nx().ex(expireTime);
    String result = jedis.set(resourceKey,randomValue,setParams);
    return "OK".equals(result);
}
  • 解锁
public boolean releaseDistributedLock(Jedis jedis,String resourceKey, String randomValue) {
    jedis.getClient().sendCommand(TairCommand.CAD,resourceKey,randomValue);
    Long ret = jedis.getClient().getIntegerReply();
    return 1 == ret;
}
  • 续租
public boolean renewDistributedLock(Jedis jedis,String resourceKey, String randomValue, int expireTime) {
    jedis.getClient().sendCommand(TairCommand.CAS,resourceKey,randomValue,randomValue,"EX",String.valueOf(expireTime));
    Long ret = jedis.getClient().getIntegerReply();
    return 1 == ret;
}

如何保障一致性

Redis的主从同步(replication)是异步进行的,如果向master发送请求修改了数据后master突然出现异常,发生高可用切换,缓冲区的数据可能无法同步到新的master(原replica)上,导致数据不一致。如果丢失的数据跟分布式锁有关,则会导致锁的机制出现问题,从而引起业务异常。下文介绍三种保障一致性的方法。

  • 使用红锁(RedLock)
    红锁是Redis作者提出的一致性解决方案。红锁的本质是一个概率问题:如果一个主从架构的Redis在高可用切换期间丢失锁的概率是k%,那么相互独立的N个Redis同时丢失锁的概率是多少?如果用红锁来实现分布式锁,那么丢锁的概率是(1-k%)^N。鉴于Redis极高的稳定性,此时的概率已经完全能满足产品的需求。

A45E61E8_DE46_4ea1_89BB_544D08129864

红锁的问题在于:

(1).加锁和解锁的延迟较大。
(2).难以在集群版或者标准版(主从架构)的Redis实例中实现。
(3).占用的资源过多,为了实现红锁,需要创建多个互不相关的云Redis实例或者自建Redis。

  • 使用WAIT命令。
    Redis的WAIT命令会阻塞当前客户端,直到这条命令之前的所有写入命令都成功从master同步到指定数量的replica,命令中可以设置单位为毫秒的等待超时时间。在云Redis版中使用WAIT命令提高分布式锁一致性的示例如下:
SET resource_1 random_value NX EX 5
WAIT 1 5000

使用以上代码,客户端在加锁后会等待数据成功同步到replica才继续进行其它操作,最大等待时间为5000毫秒。执行WAIT命令后如果返回结果是1则表示同步成功,无需担心数据不一致。相比红锁,这种实现方法极大地降低了成本。

需要注意的是:

(1).WAIT只会阻塞发送它的客户端,不影响其它客户端。
(2).WAIT返回正确的值表示设置的锁成功同步到了replica,但如果在正常返回前发生高可用切换,数据还是可能丢失,此时WAIT只能用来提示同步可能失败,无法保证数据不丢失。您可以在WAIT返回异常值后重新加锁或者进行数据校验。
(3).解锁不一定需要使用WAIT,因为锁只要存在就能保持互斥,延迟删除不会导致逻辑问题。

  • 使用阿里云数据库Redis企业版

在不考虑组合方案的情况下:

(1).使用红锁最大优势是Redis节点越多则一致性越强。
(2).使用WAIT命令最大优势是实现成本低。

如果使用阿里云数据库Redis企业版:

(1).其特有的高可用HA和数据持久化机制能够有效保护数据安全、确保服务的稳定性,不使用多Redis节点或WAIT命令也能提供较高的一致性。
(2).性能增强型实例的CAS/CAD命令可以极大降低分布式锁的开发和管理成本,提升锁的性能。
(3).性能增强型实例的多线程性能增强特性使其能够提供三倍于原生Redis的性能,即使是大并发的分布式锁也不会影响正常的Redis服务。

本文来自 阿里云文档中心 云数据库Redis 高性能分布式锁

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/zhibo

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

相关实践学习
基于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 分布式锁有所帮助。
156 5
|
2月前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
82 8
|
2月前
|
存储 SQL Apache
Apache Doris 开源最顶级基于MPP架构的高性能实时分析数据库
Apache Doris 是一个基于 MPP 架构的高性能实时分析数据库,以其极高的速度和易用性著称。它支持高并发点查询和复杂分析场景,适用于报表分析、即席查询、数据仓库和数据湖查询加速等。最新发布的 2.0.2 版本在性能、稳定性和多租户支持方面有显著提升。社区活跃,已广泛应用于电商、广告、用户行为分析等领域。
Apache Doris 开源最顶级基于MPP架构的高性能实时分析数据库
|
2月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
66 16
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
53 5
|
2月前
|
监控 算法 网络协议
|
24天前
|
存储 Oracle 关系型数据库
数据库传奇:MySQL创世之父的两千金My、Maria
《数据库传奇:MySQL创世之父的两千金My、Maria》介绍了MySQL的发展历程及其分支MariaDB。MySQL由Michael Widenius等人于1994年创建,现归Oracle所有,广泛应用于阿里巴巴、腾讯等企业。2009年,Widenius因担心Oracle收购影响MySQL的开源性,创建了MariaDB,提供额外功能和改进。维基百科、Google等已逐步替换为MariaDB,以确保更好的性能和社区支持。掌握MariaDB作为备用方案,对未来发展至关重要。
55 3
|
24天前
|
安全 关系型数据库 MySQL
MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!
《MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!》介绍了MySQL中的三种关键日志:二进制日志(Binary Log)、重做日志(Redo Log)和撤销日志(Undo Log)。这些日志确保了数据库的ACID特性,即原子性、一致性、隔离性和持久性。Redo Log记录数据页的物理修改,保证事务持久性;Undo Log记录事务的逆操作,支持回滚和多版本并发控制(MVCC)。文章还详细对比了InnoDB和MyISAM存储引擎在事务支持、锁定机制、并发性等方面的差异,强调了InnoDB在高并发和事务处理中的优势。通过这些机制,MySQL能够在事务执行、崩溃和恢复过程中保持
61 3
|
24天前
|
SQL 关系型数据库 MySQL
数据库灾难应对:MySQL误删除数据的救赎之道,技巧get起来!之binlog
《数据库灾难应对:MySQL误删除数据的救赎之道,技巧get起来!之binlog》介绍了如何利用MySQL的二进制日志(Binlog)恢复误删除的数据。主要内容包括: 1. **启用二进制日志**:在`my.cnf`中配置`log-bin`并重启MySQL服务。 2. **查看二进制日志文件**:使用`SHOW VARIABLES LIKE 'log_%';`和`SHOW MASTER STATUS;`命令获取当前日志文件及位置。 3. **创建数据备份**:确保在恢复前已有备份,以防意外。 4. **导出二进制日志为SQL语句**:使用`mysqlbinlog`
80 2