专栏名称: 说给开发游戏的你
游戏开发原创文章分享,游戏圈面试指南,游戏开发问题探讨。内容不限于Unity/UE开发,图形学基础,AI,服务端架构,面试题解惑等等。现猪厂、前鹅厂码农个人维护,诚意分享,欢迎关注。
目录
相关文章推荐
Jump x Switch  ·  Switch ... ·  昨天  
林小北Lindo  ·  大嘴上调 幻灵下调 云顶14.6版本推荐 ·  2 天前  
51好读  ›  专栏  ›  说给开发游戏的你

聊聊分布式锁

说给开发游戏的你  · 公众号  · 游戏开发  · 2016-11-28 17:56

正文

请到「今天看啥」查看全文


实现分布式锁的第一反应,就是照搬一套单机环境最常用的 悲观锁。也就是如果要进行修改操作,就需要在读依赖数据的时候就都加上锁,最后写成功的时候释放锁。获得锁所有权期间其他服务的任意读写请求都是非法的。

具体实现往往是借助一些强一致性的数据库设施,比如mysql。游戏圈的服务端比较喜欢这种方案,毕竟游戏服务端也并不需要提供什么可用性保证,节点挂掉往往就没办法fail over了。


把之前的问题抽象一下,换一种描述:

  • 服务A和服务B(下面称为Proposer)处理client的请求a和请求b的时候都依赖资源R。

  • Proposer 接到请求后,都需要先向某个中间人(下面称为Acceptor)请求资源R的互斥访问权Mr,请求成功,才能进行后续处理,否则阻塞。

  • Proposer 处理完请求,修改状态,最后释放Mr。


对于 Proposer 来说,就是三个API:

  • Prepare,用来获取互斥访问权,参数是资源名称,返回值表示成功或失败。

  • Accept,用来修改状态。

  • Release,用来释放互斥访问权。




但是,如之前所说,分布式不比单机 多线程执行环境,没有语言或平台做自动锁释放的保证。获取悲观锁的服务节点不能保证一定会将锁释放掉——拿到锁之后节点挂掉的可能性非常大。

这样,就需要给这种锁实现增加一种超时机制,换句话说,互斥访问权是可抢占的。


有趣的是,如此一来,悲观锁总是会变成乐观锁。因为互斥访问权是可抢占的,所以在Acceptor在接收Accept时总是会检查 Proposer的互斥访问权是否还合法,不合法的话 Proposer需要重新申请互斥访问权——与文章最开始提到的乐观锁机制类似。


如何实现可抢占的互斥访问权?

跟单机环境的乐观锁实现类似,一种方案是基于时间戳的,一种方案是基于版本号的。

由于我们已经将问题简化为单实例维护共享状态,那两种方案其实没太大区别,无非就是看需求是 「先到先得 」,还是 「后到先得 」。


我们先来看一种基于时间戳的,后到先得的方案。

  • Proposer向Acceptor申请访问权时需要指定epoch(可以理解为一种时间戳),获取到访问权之后,才能修改状态。

  • Acceptor 一旦收到更大的新epoch的申请,马上让旧的访问权失效, 给新的epoch访问权。


API修改如下:

  • Prepare,参数需要额外指定一个epoch。Acceptor处理时不再考虑小于等于记录的 prepared_epoch的Proposer。

  • Accept,参数需要额外指定Proposer的经过Prepare API check的epoch。只有跟 记录的 prepared_epoch一致时,才会修改状态。

  • Release不再需要。









请到「今天看啥」查看全文