游戏开发论坛

 找回密码
 立即注册
搜索
查看: 20698|回复: 0

为什么luacluster可以实现万人同屏?

[复制链接]

58

主题

1437

帖子

2207

积分

金牌会员

Rank: 6Rank: 6

积分
2207
发表于 2022-4-18 10:06:33 | 显示全部楼层 |阅读模式
这篇文章我们来谈谈luacluster的整体架构和性能。以及为什么luacluster可以实现万人同屏?
luacluster整体架构非常的简洁。node对应进程, docker对应线程,每个docker有一个luavm和一个消息队列,在luavm里创建entity对象。net网络和log日志分别单独线程运行。没有任何多余累赘的东西非常简洁明了。
luacluster的设计目标是让任何entity之间,可以通过rpc的方式无脑异步调用。所谓rpc的方式就是在任何entity中使用entity的id,函数名,参数就可以异步调用任何entity。
luacluster是一个分布式和并行的系统。分布式代表着多进程,并行代表着多多线程。也就是说luacluster要想实现一个穿透进程和线程阻隔。能够让任意的entity之间像调用普通系统接口一样互相调用。哪么luacluster必然是一个异步的系统。也就是说任何在luacluster的entity之间的功能调用都是异步的。
例如在bigworld对象中通知entity进入sudoku空间的部分
local entityProxy = udpproxy.New(id)entityProxy:OnEntryWorld(self.spaceType, self.beginx, self.beginz, self.endx, self.endz)
就是通过entity id创建一个远程对象代理。然后通过远程对象代理调用account的OnEntryWorld函数。如果entity是在当前进程内,就会查找docker id并投递到指定消息队列。如果在其他node就会使用udp协议发送过去。通过id就能找到对应entity的诀窍,是因为把ip地址,docker id,entity id都塞进了这个unint64里。你可以在entityid.h中找到entity id的定义。
typedef struct _EID {    unsigned short id;    unsigned int addr;//ipv4    unsigned char dock;    unsigned char port;//UDP端口号的偏移}*PEID, EID;​typedef union idl64{    volatile EID eid;    volatile unsigned long long u;    volatile double d;} idl64;
这样我们就实现了一个非常惊人和高效的异步通信系统。任意进程和线程中的对象通信只要最多2步就可以完成。找到upd端口发过去,找到线程队列发过去。在任意环境下只要拿到entity id就可以快速知道封包的目的地。实现在分布式网络内的任何对象之间像普通函数调用一样的调用。什么网络编程,什么多线编程可以统统见鬼去了。
第二个是关于消息风暴的问题。
万人同屏顾名思义要做服务器上处理1万个玩家的位置同步问题。1万个玩家的位置同步每次要产生1亿个消息。1万乘1万产生1亿个消息。请记住1亿这个数字后面我们要反复提及到。
首先我们先分析1亿个消息的产生流程。服务器会收到1万个客户端发起移动的请求。1万个请求是没有问题的,现在服务器处理10万个链接问题都不大。所以这1万个请求一般的服务器压力都不大。问题是这1万个请求,每个请求要产生1万个新的请求发送给其他的玩家。这样服务器就扛不住了。一下产生了1个亿的io需求,哪种消息队列都扛不住,直接mutex就锁死了。
所以我使用了CreateMsgList接口创建了一个消息list。哪么io请求就转变为插入1万个list的操作。然后将这个list和消息队列合并在一起。这样就1亿个io请求变为1万个io请求,io请求一下就压缩了1万倍。同样的道理我们也可以把发送给给客户端的封包进行压缩。处理1亿个封包请求很难但处理1万个封包难度就低很多了。下面是封包压缩的部分代码。
void DockerSendToClient(void* pVoid, unsigned long long did, unsigned long long pid, const char* pc, size_t s) {    if (pVoid == 0)return;    PDockerHandle pDockerHandle = pVoid;    idl64 eid;    eid.u = pid;    unsigned int addr = eid.eid.addr;    unsigned char port = ~eid.eid.port;    unsigned char docker = eid.eid.dock;    unsigned int len = sizeof(ProtoRoute) + s;    PProtoHead pProtoHead = malloc(len);    if (pProtoHead == 0)        return;...        sds pbuf = dictGetVal(entryCurrent);    size_t l = sdslen(pbuf) + len;    if (l > pDocksHandle->packetSize) {        DockerSendPacket(pid, pbuf);        sdsclear(pbuf);    }    if (sdsavail(pbuf) < len) {        pbuf = sdsMakeRoomFor(pbuf, len);        if (pbuf == NULL) {            n_error("DockerSendToClient2 sdsMakeRoomFor is null");            return;        }        dictSetVal(pDockerHandle->entitiesCache, entryCurrent, pbuf);    }    memcpy(pbuf + sdslen(pbuf), pProtoHead, len);    sdsIncrLen(pbuf, (int)len);    pDockerHandle->stat_packet_count++;
当然封包压缩并不能解决我们的所有问题。处理1亿个请求并压缩的挑战也极为艰巨。在128核的服务器上,每个核心每秒钟只能处理20万次。而1亿个请求需要78万次的处理能力。1亿个请求在128核上需要大概4秒钟以上才能处理完成。或者...每秒钟只处理20%的玩家。也就是说我们只能保证每秒钟处理20%的玩家请求。这样就要祭出我们另一个大杀器“状态同步”。我们要把玩家的移动描述成一段时间的状态。有位置,方向,速度,开始时间,停止时间的完整状态。这样在每个客户端就可以根据这些信息,推断出玩家移动的正确状态。
#MoveTo(x, y, z)功能中同步玩家状态的部分代码local list = docker.CreateMsgList()    for k, v1 in pairs(self.entities) do      local v = entitymng.EntityDataGet(k)      if v ~= nil then        local view = udpproxylist.New(v[1], list)        view:OnMove(self.id, self.transform.position.x, self.transform.position.y, self.transform.position.z        ,self.transform.rotation.x, self.transform.rotation.y, self.transform.rotation.z, self.transform.velocity        , self.transform.stamp, self.transform.stampStop)      end    end    docker.PushAllMsgList(list)    docker.DestoryMsgList(list)
这样我们就能保证在任意状态下只有小于20%的玩家请求需要处理。但不要乐观虽然可以解决移动的问题。但当新玩家进入场景时,还必然要同步所有玩家的数据并把新玩家的数据同步给其他玩家。如果场景内有5千个玩家,再放入5千个玩家。两边玩家需要同步的数据是“5千 * 5千+5千 * 5千”一共5千万。虽然比1亿要少一半但前面分析过128核服务器只能处理2560万。“我们可以上256核心服务器”,“哦对对对”。
当然不用上256核心服务器了。我们可以换一个方式,就是每次只放进去500人。这样需要同步的数据最多就变成500*1万 + 500万,1000万。这样就能满足我们的硬件需求了。好了今天就到这里吧,祝大家周末愉快。
原文地址:https://zhuanlan.zhihu.com/p/499296245
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-3-29 22:43

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表