myeclipse启动weblogic后无法获得文件锁_Introduction to DDIA & 6.824(九):分布式锁...

a5a9239aa0a52a074cca2ee40a8613f3.png

Safety and Liveness

通常意义下的锁需要满足:

  • Safety:互斥访问
  • Liveness:
    • 有限等待
    • 空闲让进

分布式环境下多了一些properties

  • Liveness:容错:集群节点的大多数节点都存活,那么客户端就可以获取锁和释放锁

基于Redis单节点的锁

获取锁

Redis客户端向Redis节点发送命令:

SET resource_name my_random_value NX PX 30000

命令仅在resource_name不存在的情况下才会成功。成功才表示获取锁成功。下面解释各个参数:

  • my_random_value:客户端随机生成的一个字符串。这个字符串需要保证:在足够长的一段时间内全局唯一,不能有其他客户端使用相同的字符串来申请锁。
  • NX表示只有当resource_name对应的key值不存在的时候才能SET成功。这保证了只有第一个请求的客户端才能获得锁,而其它客户端在锁被释放之前都无法获得锁。
  • PX 30000表示这个键值对在30秒后自动过期(被自动删除)。锁的过期时间我们接下来会讨论。

释放锁

当客户端执行下面的Redis Lua脚本来释放锁

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
  • ARGV[1]:这个值应当是获取锁时使用的my_random_value
  • KEYS[1]:获取锁时使用的resource_name

实现分布式锁的挑战

持锁节点崩溃导致锁丢失

锁需要设置一个过期时间。

当一个客户端获取锁成功之后崩溃了,或由于网络分区(network partition)无法和Redis节点通信了,那么它就会一直持有这个锁,而其它客户端永远无法获得锁了。

锁获取和释放的原子性

  • 锁获取分两步:第一步设置值,第二步设置过期时间
    • 如果客户端在执行完SETNX后崩溃了,就不会执行EXPIRE了,导致它一直持有这个锁。
    • 因此锁获取需要使用一个原子的命令(如上所属,或者开启redis的mini transaction,或者使用redis lua)
  • 锁的释放分三步:
    • 查询锁的状态
    • 判断获得的锁是不是和自己持有的那把
    • 释放锁
    • 问题:
      • 客户端1准备释放锁,执行了GET操作获取value。
      • 客户端1将value与my_random_value进行对比。
      • 客户端1由于网络原因,DEL操作一直没到redis上。
      • 过期时间到了,锁自动释放。
      • 客户端2获取到了对应同一个资源的锁。
      • 客户端1的DEL操作到达redis,释放掉了客户端2持有的锁。
    • 因此上述的三步需要保证原子性,保证不会有其他人的指令插入

时延导致释放他人持有的锁

  • 设置一个随机字符串my_random_value很有必要,可以保证一个客户端释放的锁必须是自己持有的那个锁
  • 如果是一个固定值,我们假设释放锁的操作是原子的CompareAndDel那么:
    • 客户端1获取锁成功。
    • 客户端1的CompareAndDel操作一直在网络传输中
    • 过期时间到了,锁自动释放了。
    • 客户端2获取到了对应同一个资源的锁。
    • 客户端1的CompareAndDel操作到达,由于只判定固定值,因此可以释放掉客户端2持有的锁。

主备集群同步导致锁非互斥

  1. 客户端1从Master获取了锁。
  2. Master宕机了,存储锁的key还没有来得及同步到Slave上。
  3. Slave升级为Master。
  4. 客户端2从新的Master获取到了对应同一个资源的锁。

锁过期时间长度权衡

锁的有效时间(lock validity time)

  • 过期时间太短:锁在操作共享资源前就过期了,访问失去保护;
  • 过期时间太长:持有锁的客户端crash,那么整体需要等待锁过期,效率不高

在访问共享资源时锁过期

95bd125fe488388a385cf88dc16565d7.png
  • 难点:这个问题棘手在即使分布式锁服务完全没有问题,访问资源的这个操作还是会有一定的问题
  • 描述:客户端1在获取到锁后,对共享资源的访问实质位于实质的锁过期之后
  • 这种情况可能是因为系统调度、语言GC或对共享资源的网络访问延时

一个解决手段

f0f0254b89718f138ba542533298bb4e.png
  • 手段:自增的token,服务收到小的token就拒绝掉
  • 麻烦点:需要改造服务
  • 潜在的问题点:client2如果也产生了GC,到达Storage的请求还是保持fence token小的先到,那么是否就一定没问题呢?

Redlock 算法流程

算法条件:

  • 假设我们有 n = 5 个 Redis master
  • 我们认为基于Redis单节点的获取分布式锁为LockAcquire(resourceName, random_value)
  • 我们认为基于Redis单节点的释放分布式锁为LockRelease(resourceName, random_value)

获取锁算法流程:

  1. 以毫秒为单位获取当前时刻 t
  2. 对 n 个节点依次顺序执行LockAcquire(resourceName, random_value)
    1. 所有的请求resourceNamerandom_value 保持一致
    2. 假设锁过期时间是E,客户端和 redis 交互的超时时间阈值认为是T,那么 T << E
      1. 比如 过期时间是10s,那么超时应该是 50ms 左右
      2. 一旦 redis 在超时时间内没有返回,那么立马和下一个redis节点通信
  3. 如果客户端获得了 redis cluster 的 majority 回复,且当前时刻 - t 小于锁的过期时间 E ,那么认为锁获取成功。
  4. 如果获取锁失败,那么客户端顺序解锁

释放锁算法流程:

每个 redis master 发送 LockRelease

  • 为什么是每个?因为可能发送 LockAcquire 时,返回包丢失了;为了保证锁的清理,需要向每个节点发送 LockRelease

Redlock 解决了什么问题

Redlock只解决了分布式锁的提供者的单点问题。

Redlock 的问题

  • 持久化失败 & 宕机 导致的互斥失效问题(默认情况下,Redis的AOF持久化每秒执行fsync写一次盘)
    • 问题描述
      • 假设一共有5个Redis节点,客户端A靠前三个节点的正确回复获得了资源N的锁;
      • 第三个节点持久化失败,并宕机重启
      • 客户端B靠后三个节点的正确回复获得了资源N的锁
    • 问题解决
      • 延迟重启,第三个节点宕机后,等待一个锁的过期时间后再重启
  • 锁过期时间长度权衡问题并没有解决
  • 在访问共享资源时锁过期的问题没有解决
  • Time skew 导致的锁资源提前释放
    • 描述
      • 假设一共有5个Redis节点,客户端A靠前三个节点的正确回复获得了资源N的锁;
      • 第三个节点收到了一个NTP的时钟校准,导致锁提前释放
      • 客户端B靠后三个节点的正确回复获得了资源N的锁
    • 问题解决
      • 把锁周期设大一点,这样time skew发生了也在合理范围内(额
      • 运维手段避免大的time skew

基于Zookeeper的不可扩展锁

基于Zookeeper
// Aquire Lock
LOCK():
    WHILE TRUE:
        IF CREATE("f", data, ephemeral=TRUE): 
            RETURN // 创建成功就返回,说明获得了锁
        IF EXIST("f", watch=TRUE):
            WAIT // wait 语义是有watch提供的
UNLOCK():
        DELETE("f")

问题:

  • 惊群效应。并发度的提高,效果越严重。每次锁释放通知所有人,然后所有人都会要执行一边CREATE的RPC,代价很高

基于Zookeeper的可扩展锁

基于Zookeeper
// Aquire Lock
LOCK():
        // 可以获得一个带有全局序列号的文件
    CREATE("f", data, sequential=TRUE, ephemeral=TRUE)
    WHILE TRUE:
        // 列出了所有以“f”开头的文件,也就是所有的Sequential文件
        // 也就是正在竞争锁的所有role
        LIST("f*")
        // 如果自己的file最小,说明自己最早到
        // FIFO 获取锁
        IF NO LOWER #FILE: 
                RETURN
        // 等待比自己次小的文件被删除,重新检查锁的获取情况
        IF EXIST(NEXT LOWER #FILE, watch=TRUE):
            WAIT
  • 为什么需要循环LIST?
    • 比自己小的可能已经挂了,因此需要时常检查一下
  • 和上面的区别是什么?
    • 上面的WATCH变化的RPC通知,是和进程数量成正比的
    • 下面的只需要等比自己次小的FILE的WATCH通知即可,是一个常数级

关于Zookeeper的锁

  • Zookeeper的锁并不能解决上文中提到的「在访问共享资源时锁过期」的问题。
  • 但是Zookeeper的watch操作,可以尽可能大限度的告诉agent,锁已经过期了,让agent可以有办法对相关情况进行处理

基于Chubby的锁

这里只谈谈「在访问共享资源时锁过期」这个问题

  • 分布式锁服务自己提供了一个全局递增的 lock generation number
  • 有效性检查:
    • CheckSequencer(sequencer) 共享资源服务器可以通过这个API可以帮助检查锁是否有效
    • 组织锁获取:即拿锁的人失联了不知死活,Chubby就会直接组织其他人获取同一把锁直到锁过期。很保守但相对保证正确性的手段。

Q&A

  • 分布式锁和内存锁的区别和关系
    • Safety一致,Liveness有扩展
  • 分布式锁和 linearizibility 之间的关系
    • 一个实现了 linearizibility 的系统解决了分布式锁的集群问题,提高了分布式锁服务的容错能力
  • kleppmann所提到的fence token 和 内存锁的mem fence/barrier 之间的关系和区别
    • 一点点类似Load-Load barrier禁止顺序的冲排序
    • fence token会把旧的给拒绝掉(禁止错误的顺序)
  • 分布式锁的实现方式有哪些,需要注意什么
    • 见「实现分布式锁的挑战」
    • 实现分布式锁一定要有CAS的能力在
    • 实现分布式锁,最好有能通知申请锁的客户端的能力
      • 想想看,进程间的mutex的实现,是把锁放在可写的共享内存(mmap)上
      • 如果持有锁的进程退出了,有一个全知全能的OS会告诉其他等在mutex的进程,「持有锁的进程退出了」这个信息
        • Windows下是: WaitForSingleObject(...) 返回 WAIT_ABANDONED
        • Linux下是:pthread_mutex_lock(...) 返回 EINVAL
      • 但是分布式锁服务没有一个 perfect 的 failure detector 去探知某个agent是否已经死透了
      • 因此和agent保持联系,在它重启之后告诉他「锁已经过期了」这件事是最不坏的结果
      • Refs:https://stackoverflow.com/questions/13110661/can-exiting-from-a-process-that-is-locking-a-mutex-cause-a-deadlock
  • 如何去衡量一个分布式锁的安全性
    • 即要看 Safety & Liveness 有没有受损。
    • 一个良好的设计应当保证 Safety,在特殊情况下可以有限损害 Liveness
  • 分布式锁的性能相关 应该怎么衡量
    • 好问题。知道的不是很确切。。几个考量的点:
      • 同时能承接多少资源的发锁的请求
      • Response Time

小结

  • 从「不存在一个完美的 failure detector」的角度出发,几乎可以断定不存在一个像内存锁版完美简洁的分布式锁
  • 实现分布式锁应当考虑效率和正确性
    • 使用分布式一致性协议,保证的是容错性
    • 如果考虑到效率,简单的单机 Redis 就已经足够了
  • 实现分布式锁的系统需要:
    • 【必须】系统能够提供原子的 CAS 语义
    • 【必须】系统必须为锁提供 lease 机制
    • 【可选】系统能够提供通知 agent 的机制
    • 【可选】系统能够提供 linearizability 的能力,以避免服务的单点故障
    • 【可选】系统需要能够提供一种fence机制,保证旧指令的无效
  • Zookeeper & Redis & etcd & 数据库 都是实现分布式锁的不坏的选择

REFS

  • SOFAJRaft-RheaKV 分布式锁实现剖析 | SOFAJRaft 实现原理https://www.sofastack.tech/blog/sofa-jraft-rheakv-distributedlock/
  • 基于Redis的分布式锁到底安全吗(上)? http://zhangtielei.com/posts/blog-redlock-reasoning.html
  • Distributed locks with Redis https://redis.io/topics/distlock
    • https://hn.matthewblode.com/item/11065933
  • How to do distributed locking https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
    • https://hn.matthewblode.com/item/11059738
  • Is Redlock safe?
  • https://web.archive.org/web/20160528200726if_/https://storify.com/martinkl/redlock-discussion
  • The Chubby lock service for loosely-coupled distributed systems
  • Youtube: The Chubby lock service for loosely-coupled distributed systems
  • Chubby分布式锁服务总结 - helices的文章 - 知乎 https://zhuanlan.zhihu.com/p/64554506
  • 分布式锁真的安全吗? - buckethead的文章 - 知乎 https://zhuanlan.zhihu.com/p/51562218
  • https://stackoverflow.com/questions/13110661/can-exiting-from-a-process-that-is-locking-a-mutex-cause-a-deadlock
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值