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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
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选举、分布式锁、配置管理等服务的实现。
|
2月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
4月前
|
NoSQL Redis
基于Redis的高可用分布式锁——RedLock
这篇文章介绍了基于Redis的高可用分布式锁RedLock的概念、工作流程、获取和释放锁的方法,以及RedLock相比单机锁在高可用性上的优势,同时指出了其在某些特殊场景下的不足,并提到了ZooKeeper作为另一种实现分布式锁的方案。
129 2
基于Redis的高可用分布式锁——RedLock
|
4月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
14天前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
40 5
|
17天前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
34 8
|
1月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
55 16
|
26天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
37 5
|
2月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
67 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
2月前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
51 1