分布式锁的实现

发布于:2024-04-15 ⋅ 阅读:(167) ⋅ 点赞:(0)

一、背景

  • 在分布式系统中,一个应用部署在多台机器中,在某些场景下,为了保证数据的一致性,要求在同一时刻,同一任务只在一个节点上运行,即保证某个行为在同一时刻只能被一个线程执行。
  • 单机单进程多线程环境下,通过锁很容易做到,比如 mutexspinlock信号量 等。
  • 多机多进程环境下,就需要使用 分布式锁 来解决了。
    • 分布式场景:我们的应用由多个节点构成,这些节点可能分布在不同的机器中,也有可能分布在不同的网络环境中,通常这些进程之间通过 socket 进行通信。

二、分布式锁

  • 是什么类型的锁 ?
    • 在分布式场景中实现互斥类型的锁
      • 互斥类型:同一时刻只允许一个执行体进入临界资源
  • 解决了什么问题 ?
    • 在分布式场景中,同时只允许一个节点执行某类任务。
  • 锁 = 资源 + 行为
    • 资源:
      • 要记录进程的全局唯一 ID
      • 放在 数据库zookeeperetcd 中,可以让所有的执行体访问
    • 行为:加锁、解锁。(以网络通信的方式)
      • 加锁:把当前进程的唯一标识打到当前数据库的某一个字段中,作为一个标记,说明当前进程持有锁。
        • 加锁的方式:原来没有标记,现在打上标记了。
      • 解锁:谁加的锁,谁释放锁,也就是加锁对象和解锁对象必须是同一个对象,除了因为网络异常而造成的锁超时情况
        • 解锁的方式:持锁方去清除标记,比如置为 0,这样其他的进程才能去加锁。

三、分布式锁特性

  • 互斥性。
    • 锁打上标记:加锁。
    • 锁取消标记:解锁。
    • 标记:执行体的唯一标识,可以通过雪花算法生成。
  • 锁超时。
    • 在分布式场景中,允许一个进程退出,并希望其他的进程能够继续工作。
    • 当持有锁的进程想要解锁的时候,由于某些原因,比如进程宕机、网络异常,导致无法清除数据库中的标记,也就是持锁的进程没有能力去释放锁了,那么我们应该提供一种机制,让数据库自动地去释放锁 → 时间一到,数据库自动地释放锁
  • 可用性。
    • 合理时间内得到合理的回复
    • 实现:
      • 计算型:开多个备份点。
      • 存储型:
        • 多个备份点。
        • 主从切换。
  • 容错性。
    • 一致性来解决(半数以上)。
      • raft 一致性算法。
      • redlock。
    • 高可用 = 可用性 + 容错性

四、分布式锁类型

  • 重入锁和非重入锁。
    • 重入锁:
      • 允许同一个线程多次获取同一把锁,而不会导致死锁。当一个线程持有锁时,它可以再次获取相同的锁而不被阻塞。
      • std::recursive_mutex
    • 非重入锁:
      • 不允许同一个线程在持有锁的情况下再次获取相同的锁,会导致死锁。
      • std::mutex
  • 公平锁和非公平锁。
    • 公平锁:排队,对应互斥锁。
    • 非公平锁:轮询,对应自旋锁。

五、实现分布式锁

  • 基于中间件来实现 → 所有的节点都能访问到。
  • 资源存储在中间件中。
  • 加锁、解锁行为基于中间件的特性来实现。

MySQL 实现分布式锁

  • 锁的存储:表。
  • 获取锁和释放锁:字段。
  • 互斥语义:
    • 唯一性约束:unique keyprimary key
    • 利用 innodb 中的 S 锁和 X 锁互斥。
      select * from lock where ... lock in share mode;
      update lock set ... where ...
      
  • 锁超时:
    • 表增加一个字段:加锁时间戳
    • 另起进程(超进程),定时检测是否超时。
  • 其他进程怎么获取锁:主动探寻,实现定时器 → MySQL 只能实现非公平锁
  • 递归锁:表中增加一个字段 count。
  • 创建表。
    DROP TABLE IF EXISTS `dislock`;
    CREATE TABLE 

网站公告

今日签到

点亮在社区的每一天
去签到