基于KV的服务器分布式加锁的设计
前言
为什么要加锁
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游戏中心后台开发。