翻译 | 我所知道的分布式锁

2019-10-201564

原文自国外技术社区dzone,作者为 Davide Cerbo,传送门

上锁在应用上并不是一个好的主意,并且在分布式环境中尝试去将某些东西锁住会更加显得危险。但是有时候我们需要承担这个风险来去用分布式锁,原因有这两个:

  • 效率:锁可以使我们的应用将更多的性能托付于有用的事情上而非无用功中,例如两次出发计时器
  • 正确性:锁能够防止相同数据的并发处理发生的意外,避免数据损坏、数据丢失、不一致等情况

我们有两种锁:

  • 乐观锁:比起使用阻塞来防止危机发生,我们趋向于持续执行,来确保所有事项能够正常发起
  • 悲观锁:在操作资源前将其锁住以防止其他渠道的访问,并且在最后释放锁

为了使用乐观锁,我们通常会使用一个版本字段放置于我们需要操作的数据库记录中,并且当我们更新的时候,我们会检查数据在读的时候的版本是否和写的版本相同。

像 Hibernate 这样的数据库访问框架通常会提供使用乐观锁的功能。

相反,悲观锁则依赖于额外的系统,在我们的微服务中保留这个锁。

至于像乐观锁,虽然 Hebernate 有提供这样的功能,但是在分布式的情况下,我们一般会使用能提供更复杂的算法的解决方法来处理,例如:

  • Redis 使用像 ShedLock 和 Redisson 这种实现了锁算法的应用。是第一个提供锁机制给到其他系统(像 MongoDB,DynamoDB等)的一个数据库
  • Zookeeper 提供某些有关锁的方法
  • Hazelcast 提供一个基于 CP subsystem 的锁系统

在实现悲观锁的时候,我们有一个大问题;如果锁的持有者不释放它时,会发生什么事?锁会被永远持有并且出现死锁现象。为了防止这事情发生,我们会在锁当中设置一个过期时间,这样锁就会自动释放了。

但是,如果锁在第一个持有者的任务完成之前释放掉,其他微服务可以获取该锁,以致两个锁持有者都可以释放锁,从而导致了不一致。所以请记住,在异步网络汇总没有一个计时器的方案是可靠的。

我们需要使用隔离令牌,这个令牌在微服务每次获取锁的时候都会增加次数。当我们释放锁的时候,必须将此令牌传递给锁管理器,因此如果第一个锁所有者在第二个所有者之前释放了锁,系统会拒绝第二个所有者释放锁。根据实现方式,我们也可以决定让第二个所有者优先级更高。

就像上面的图那样,数据库在这个情况下有着更灵活的作用。

最后,我想到了另外一个问题:在面对出现数据库灾祸的情况,是否持有一个副本节点会更好,或者搭建集群会更好?而答案就是:这取决于具体的情况:)

如果我们希望使用 Redis,我们更需要留意上述的情况,并且最好避免同时运行5台 Redis 服务器,并且当在使用副本 Redis 服务器的情况下,需要检查能够安全地获得锁。如果能够做到这一步,这样锁就会变得更快和更方便了。

但总的来说,在我们讨论锁的时候,为了防止出现 split-brain 现象,最好只让一个节点持有锁。如果我们想分担负载,可以为一组锁指定一个持有人,并且始终连接着同一个节点。

像 Hazelcast 和 Zookeeper 之类的系统会使用 leader election 来工作,但是它们的协议在某些网络场景中无法工作,或者在配置上会非常困难(例如 AWS BeanStalk),因此带有集群和数据分片配置的 Redis 服务器可能更容易设置和管理。

分享
点赞0
打赏
上一篇:Docker常用命令笔记(一)
下一篇:写一个easyexcel的工具类