Database · 理论基础 · 关于一致性协议和分布式锁

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: 关于一致性协议, 分布式锁以及如何使用分布式锁最近看antirez 和 Martin 关于redlock 的分布式锁是否安全的问题的争吵, 非常有意思http://martin.kleppmann.

关于一致性协议, 分布式锁以及如何使用分布式锁

最近看antirez 和 Martin 关于redlock 的分布式锁是否安全的问题的争吵, 非常有意思

http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

http://antirez.com/news/101

https://news.ycombinator.com/item?id=11065933

https://news.ycombinator.com/item?id=11059738

背景

关于分布式锁其实这里面是包含3个问题, 每一个都是独立的不相关的问题

  1. 一致性协议的问题 consensus
  2. 如何通过一致性协议实现分布式锁的问题 lock
  3. 如何结合业务场景使用分布式锁 usage

所以我们一般称这三个问题为”ULC”

问题1和问题2 容易混淆, 其实实现一个分布式锁只是需要一个key-value store 就可以了, 但是因为需要这个key-value store 高可用, 那么就必然需要这个key-value store 多副本, 多副本又需要强一致, 那么就必然引入一致性协议了

chubby 的论文里面讲的就是如何基于一致性协议paxos 实现一个分布式锁服务, 跟一致性协议一点关系都没有. 里面很重要的一个观点是为什么要实现一个锁服务, 用户使用一个锁服务相比于直接提供一个一致性协议的库有哪些地方更方便

  1. lock 提供的高可用性肯定比直接提供一致性协议的库要来的低, 相当于通过lock 实现强一致和直接通过一致性协议提供强一致的业务逻辑
  2. 有些场景很难改成通过一致性协议的场景
  3. 分布式锁的使用方式和之前单机是使用Lock 的方式一样的直观, 对使用人员的要求无成本
  4. 有时候用户只有少量的机器, 比如只有两个机器, 就无法通过一致性协议提供强一致, 但是通过外部的锁服务可以

在分布式锁里面有两个要求

  1. safety

    任意时刻只能有一个 node 去获得这个lock

  2. liveness

    这个lock 是在某一个时刻是会终止的

其实这两点是互相矛盾的, 存在这个问题的本质原因是因为在分布式系统里面我们无法判断一个节点真的挂掉还是只是网络分区一会这个网络又会恢复

所有的distribute lock 的实现都存在的一个问题是, 在获得这个锁以后, 如果拿了这个锁的节点死掉了, 或者网络永久断开了, 那么这个锁也就死锁了. 就违背了 liveness 的问题, 为了解决这个问题, 几乎所有的distribute lock 都会加上一个时间的限制, 但是这个时间的限制又会有一个问题就是如果获得这个锁的节点, 在拿到锁以后, 执行的操作的时候超过了这个时间的限制, 那么我们改怎么办? 那么这个时候就有可能被其他的节点也获得这个锁, 那么就违背了 safety 的限制.

因此在我们操作系统的lock 里面选择的是死锁, 所以操作系统有liveness 的问题, 而大部分的distribute lock 的实现选择的是给这个lock 加上lease, 如果超过了这个lease, 依然返回, 我认为你这个lock 已经失效了, 可以把这个lock 给其他的节点. 因此在使用distribute lock 的时候需要注意的是尽可能在锁区间的操作应该是可预期的, 尽可能时间短的.

观点

主要喷antirez 有问题的地方在于两个:

  1. 这种auto release lock 会存在的问题是, 用户获得lock 操作以后, redlock 的做法有一个lease, 如果在这个lease 里面不执行unlock 操作, 系统只能认为你已经挂掉. 那么在过了lease 时间以后, 另外一个node 获得了这个Lock, 那么有可能第一个节点并没有挂掉(这里是java gc 黑的最惨的地方哈哈哈), 那么这个时候系统就无法保证只有一个leader, 这个lock 也就没用了
  2. 第二个就比较直接了, 就是通过时间戳来保证底下的强一致. 这个是被喷的最惨的, 这个就没什么好解释的了

那么Martin 提供的带递增Token 的方法是不是解决了这个问题呢?

Imgur

其实我觉得Martin 说的带fencing Token 的方法是通过拿到锁的系统必须能够保证提供一个支持cas 操作检查的系统才行, 能够检查写入的Token 是否比之前的token 都大, zk 使用的也是类似的方法. 但是不解决刚才我们说的safety 的问题, 因为在使用带Token 的方法里面也是无法保证某一个时刻只能有一个节点获得这个lock, 只是通过额外的一个系统里面写入的时候检查这个Token 来避免这个问题. 所以从整体上来看是解决了这个问题, 但是其实是需要使用lock 的服务提供 cas 操作才行.

所以我认为从整体上看, 除非你底下获得Lock 操作以后, 需要做的事情非常简单, 那么可以通过fencing token 来做保证, 但是更多的工程里面获得lock 以后的操作比较复杂, 所以很难想Martin 说的那样能够实现这个cas 操作. 所以floyd 提供的lock 基本就是基于一致性协议raft + lease 实现的auto release lock

结论

回头开头的3个问题, 如果实现一致性协议就不说了.

如何实现分布式锁呢?

那么大体的实现就是给在节点lock 的时候, 对于这个lock 操作有一个lease的时候, 如果在这个租约的时间内, 这个节点没有来续租, 那么就认为这个操作是超时的, 一般情况为了实现的可靠性保证, 会在这个租约失效前就提前续租, 比如租约的时间是 10s, 我在0s 的时候就获得这个lock, 那么我在6s 的时候就必须去做这个续租操作, 如果没有执行成功的话, 那么我就认为你这个lease 失效了, 其他的节点可以在6s 时刻就获得这个lock, 但是只能在10s以后提供服务, 新的节点的租约时间是10s~20s. 那么从6s~10s 这段时间即使新节点获得了lock, 但是也无法提供服务的, 这个是典型的CAP 场景里面系统availability 换取consistenty 的例子.

那么如何使用分布式锁呢?

在pika_hub 的场景里面, 有一个专门的线程每3s去获得这个lock, 在获得这个lock 以后, 就认为自己是 pika_hub 的leader, 然后建立与所有的pika 节点的连接. 如果在某一个时刻其他的pika_hub 节点抢到了这个lock, 那么就说明之前的pika_hub 节点已经挂掉, 或者超时. 那么如何Pika_hub 节点在6s的时刻发现自己获得这个lock 失败, 那么该如何操作呢? 这个时刻 pika_hub 将与 pika 建立连接的线程都杀死, 这个时候其实有6s~10s 这一段4s 的时间, 我们认为在工程实现里面4s 可以完成这个操作. 那么其他节点就算在6s的时候执行Lock() 操作依然是获得不了这个lock(), 因为这个lock() 虽然没有被更新lease, 但是lease 依然在有效期内的. 那么等到10s 以后才有一个新的节点抢到这个lock(). 这个时候新的节点成为leader 与其他的Pika 节点建立连接, 所以系统中可能存在最多13s的没有leader 的时间

在zeppelin 里面, 也一样有专门的线程去获得这个lock, 在获得这个lock 以后, 将自己是leader 的信息写入到floyd 里面, 然后做leader 该做的事情. 和pika_hub 的处理方式一样, 如果发现无法和floyd 交互了, 那么就把自己改成follower 的信息. 不一样的地方在于因为这里使用floyd 作为storage, 那么其实可以通过类似Martin 提供的方式进行类似cas 的操作来更新. 这里也可以看出就像 antirez 喷 Martin 一样, 并不是所有的获得锁以后的操作都可以改成cas 的操作, 比如pika_hub 就不可以

最后, 看到最后的肯定是真爱, 这个项目: floyd

目录
相关文章
|
存储 监控 API
分布式系统理论基础8:zookeeper分布式协调服务
分布式服务协调员zookeeper - 应用场景和监控 zookeeper在分布式系统中作为协调员的角色,可应用于Leader选举、分布式锁、配置管理等服务的实现。
|
3月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
257 2
|
3月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
184 6
|
4月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
8月前
|
数据采集 存储 数据可视化
分布式爬虫框架Scrapy-Redis实战指南
本文介绍如何使用Scrapy-Redis构建分布式爬虫系统,采集携程平台上热门城市的酒店价格与评价信息。通过代理IP、Cookie和User-Agent设置规避反爬策略,实现高效数据抓取。结合价格动态趋势分析,助力酒店业优化市场策略、提升服务质量。技术架构涵盖Scrapy-Redis核心调度、代理中间件及数据解析存储,提供完整的技术路线图与代码示例。
739 0
分布式爬虫框架Scrapy-Redis实战指南
|
2月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
153 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
2月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
4月前
|
NoSQL Redis
Lua脚本协助Redis分布式锁实现命令的原子性
利用Lua脚本确保Redis操作的原子性是分布式锁安全性的关键所在,可以大幅减少由于网络分区、客户端故障等导致的锁无法正确释放的情况,从而在分布式系统中保证数据操作的安全性和一致性。在将这些概念应用于生产环境前,建议深入理解Redis事务与Lua脚本的工作原理以及分布式锁的可能问题和解决方案。
168 8
|
6月前
|
数据采集 存储 NoSQL
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
347 67
|
9月前
|
NoSQL Java 中间件
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
本文介绍了从单机锁到分布式锁的演变,重点探讨了使用Redis实现分布式锁的方法。分布式锁用于控制分布式系统中多个实例对共享资源的同步访问,需满足互斥性、可重入性、锁超时防死锁和锁释放正确防误删等特性。文章通过具体示例展示了如何利用Redis的`setnx`命令实现加锁,并分析了简化版分布式锁存在的问题,如锁超时和误删。为了解决这些问题,文中提出了设置锁过期时间和在解锁前验证持有锁的线程身份的优化方案。最后指出,尽管当前设计已解决部分问题,但仍存在进一步优化的空间,将在后续章节继续探讨。
1175 131
【📕分布式锁通关指南 02】基于Redis实现的分布式锁