基于KV的服务器分布式加锁的设计

发表于2016-08-05
评论0 1.9k浏览

前言


为什么要加锁

    KV读写无法保持一致性;或者能够保持一致性但是在流程中需要考虑的因素较多,处理比较麻烦,相比之下直接将流程加锁就方便很多


基本原理

    记录一个标志位,如果标志位存在则锁已存在,如果标志位不存在则设置标志位。


基于KV的设计(KV读写使用版本号保持一致性)



设计1

  • 加锁:检查 key 的标志位,如果不存在则写入

  • 解锁:将 key 中的标志位清除

  • 问题:万一解锁失败,将会导致死锁,用户再也无法进行相关的操作,维护困难

设计2

  • 原理:在设计1的基础上,再设置一个超时时间(默认5秒)。如果超时,则自动解锁

  • 死锁优化:如果解锁失败,最多对用户造成5秒的影响

  • 效率:KV的读写平均耗时3-5毫秒,加、解锁总共需要操作(读+写)4次KV,总共增加耗时10-20毫秒

  • 问题:我们使用的KV,数据会落地,将会导致很多的垃圾数据(目前影响不大)

    使用:(查看完整代码,请点击阅读原文)

    WG_KV_LOCK_AUTO("mmocgame_act_comm_lock_123_1234"/*, 5 */);

    加锁、解锁伪代码:


  • int KVLock::TryLock(const std::string &key, uint64_t expire_time) {

  •   wg::kvlock::KVLockData lock_data;

  •   int ret = KStrMod(key, 1, [&](std::string &val) {

  •       uint64_t now = NowStamp();

  •       do {

  •           if (val.empty())

  •               break;


  •           if (!lock_data.ParseFromString(val))

  •               break;


  •           if (!IsExpired(now, lock_data.lock_time() + lock_data.expire_time()))

  •               return (int)ERR_KVLOCK_LOCKED;

  •       } while (0);


  •       lock_data.set_lock_time(now);

  •       lock_data.set_expire_time(expire_time);


  •       if (!lock_data.SerializeToString(&val))

  •           return (int)ERR_KVLOCK_PB_ENCODE;

  •       return 0;

  •   });

  •   return ret;

  • }

  • int KVLock::Unlock(const std::string &key) {

  •   wg::kvlock::KVLockData lock_data;

  •   int ret = KStrMod(key, 1, [&](std::string &val) {

  •       if (val.empty())

  •           return (int)ERR_KVLOCK_UNLOCKED;


  •       if (!lock_data.ParseFromString(val))

  •           return (int)ERR_KVLOCK_PB_DECODE;


  •       uint64_t now = NowStamp();

  •       if (IsExpired(now, lock_data.lock_time() + lock_data.expire_time()))

  •           return (int)ERR_KVLOCK_UNLOCKED;


  •       lock_data.set_lock_time(0);

  •       lock_data.set_expire_time(0);


  •       if (!lock_data.SerializeToString(&val))

  •           return (int)ERR_KVLOCK_PB_ENCODE;

  •       return 0;

  •   });

  •   return ret;

  • }


独立服务器的设计



设计

    此设计是根据QT的locksvr逆推而成,对于locksvr我了解三点:1、名字;2、可以加锁;3、单点系统 这里要感谢一下wardlei,帮我了解到locksvr的

  • 首先,这是一个纯内存的服务,在内存里记录标志位和超时时间

  • 还可以使用共享内存,工作进程

  • 内存结构使用多阶hash

  • 机器之间的路由规则使用一致性hash,进程之间使用取模

承载能力(以下并不严谨,欢迎指正)

  • 以100W容量,5秒超时为例(平均每秒20W);并假设key的长度限制为256B,消息的大小限制为1KB

  • 内存:(256B * 100w + 1KB * 20W * 2 =)不超过700MB;再加上进程二进制文件大小、页表的大小、socket系统缓冲区(socket复用,不会有太多)、每个key还有些控制信息等;2GB绰绰有余了

  • 网络:吞吐量(1KB * 20W * 2 =)400M

  • CPU:假设机器20核,每个核平均每秒处理1W

维护

  • 服务器足够简单,也会足够稳定

  • 机器间路由使用的是一致性hash,在机器挂掉时,踢掉挂掉的机器对用户造成的影响很小(需要把探活机制做好)

  • 时间允许可以考虑主备机制

最终选择

    相信大家已经猜出来了,最后采用的方案是基于KV的设计2(代码都写出来了)。缺点不明显,开发简单,完全不需要维护(KV有专门的团队维护)。



作者介绍帅虫虫,湖南衡阳人,腾讯科技(深圳)有限公司wxg游戏中心后台开发。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引