luacluster整体架构设计

本文深入探讨了LuaCluster的简洁架构及其如何实现万人同屏的性能。通过进程和线程的高效管理,利用RPC实现跨进程线程的异步调用,解决了大规模并发下的消息风暴问题。通过消息压缩和状态同步技术,有效降低了IO压力,确保了在高并发场景下的稳定运行。此外,还介绍了动态调整玩家同步策略以适应服务器硬件限制的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇文章我们来谈谈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万。这样就能满足我们的硬件需求了。好了今天就到这里吧,祝大家周末愉快。

 原文地址:luacluster整体架构设计 - 知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值