分布式锁

分布式锁是一种用于在分布式系统中同步访问共享资源的机制。随着现代应用架构向微服务和分布式系统的转变,分布式锁成为确保数据一致性和防止竞争条件的关键工具。

使用场景

  1. 确保数据一致性:在分布式系统中,多个节点可能会同时尝试修改共享资源,如数据库中的数据。使用分布式锁可以确保一次只有一个节点能进行修改,从而保持数据一致性。
  2. 防止重复处理:在一些需要确保任务只被执行一次的场景中,分布式锁可以防止多个节点重复执行同一个任务。
  3. 顺序执行:当多个操作需要按照特定顺序执行时,分布式锁可以用来控制执行的顺序。

实现方式

Redis

如果只想在“尽力而为”的基础上上锁,而不是想要绝对正确性,可以使用Redis的简单单节点锁定算法。

  1. 获取锁:使用SET命令加上NX(只在键不存在时设置键)和EX(设置键的过期时间)选项尝试设置一个锁键。如果命令返回成功,表示获取锁成功。
  2. 保持锁:在持有锁的客户端执行操作期间,可以通过定期更新键的过期时间来保持锁。
  3. 释放锁:操作完成后,使用DEL命令删除锁键来释放锁。
  4. 安全释放锁:为了安全释放锁,客户端在设置锁的时候应该给锁赋予一个唯一值。释放锁之前先检查这个值,确认自己是锁的持有者,然后再删除锁键。这通常通过Lua脚本的原子操作完成。

注意,这个方法不能获得绝对正确性,因为由于 Stop the-world GC pause 的存在,即使在进行实际操作前检查锁是否依然处于有效期,也无法绝对保证锁的所有权。也因为这个原因,Redis官网提出的认为在集群部署下可以保证绝对正确性的 Redlock 算法被 How to do distributed locking — Martin Kleppmann’s blog 这篇博文质疑。博文作者提出可以用 Fencing 来确保锁的安全,即客户端每次获取锁的时候会得到一个自增的 Fencing token,客户端执行操作的时候会将 token 带上,被操作的系统会检查token的值是否比上一次操作的token的值大,如果不是则拒绝操作请求。Redisson的 RFencedLock 实现了这个算法。但这个算法要求被操作系统额外“记住” token 的信息,在很多情况下并不可行。

数据库

使用数据库主键唯一性实现分布式锁。

  1. 锁记录:在数据库中创建一个特定的表,用于存储锁的信息,如锁标识、持有者、过期时间等。
  2. 获取锁:尝试在锁表中插入一条记录。如果插入成功(即没有其他事务已经插入了相同的锁标识的记录),则表示获取锁成功。
  3. 释放锁:操作完成后,删除或更新锁表中的记录来释放锁。
  4. 处理死锁:设置一个合理的过期时间,并定期检查过期的锁,以防止死锁情况的发生。

使用数据库行锁或表锁,利用数据库事务的互斥性实现分布式锁。

  1. 获取锁:SELECT * from database_lock_table WHERE id=xx FOR UPDATE;
  2. 释放锁:COMMIT

ZooKeeper

TBD

Hazelcast

TBD

Etcd

TBD

参考资料