找回密码
 立即注册
查看: 126|回复: 0

Wind分布式游戏处事器引擎的实现

[复制链接]
发表于 2024-7-15 17:35 | 显示全部楼层 |阅读模式
Wind

Wind是一款面向云的高性能、高效率以及高扩展性的大型分布式游戏处事器引擎。Wind操作Python语言的简洁语法以及丰硕的生态库来提高游戏业务的开发效率,针对一些对性能有要求的游戏业务功能(如实时战斗功能),Wind操作Golang的高并发特性来保证处事的高性能,同时Wind接入云的组件来保证游戏处事的动态扩展性,提高处事资源的操作率。
https://github.com/ferris1/wind
Wind是游戏处事器界初度结合go与python长处的处事器,如果Wind能解决你的问题的话,但愿能帮Wind点个Star,如果不能解决大师的问题的话,也欢迎大师提Issue和Request,我会持续开发和完善Wind。
本文是Wind处事器引擎设计与实现系列的第一篇

1:Wind单处事引擎功能的设计与实现

2:Wind分布式集群功能的设计与实现(待更新)

3:Wind处事云部署功能的设计与实现(待更新)

Wind单机引擎功能的设计与实现

本篇文章主要介绍Wind的呈现布景,Wind要解决什么问题,以及Wind的设计和处事器引擎实现方案。从游戏业务需求出发,介绍Wind拥有的特性以及引擎各个功能如何实现这些特性。
Wind呈现布景

得益于云计算的低成本、按需灵活配置和高资源操作率的特性,大量的互联网应用已经在云上部署。但游戏处事上云的进度却很迟缓,这此中的原因跟游戏产物特性有关系。相对于互联网产物,游戏产物对延时更敏感,尤其是一些强竞技性游戏,如果将游戏处事部署上云的话,会有一些额外运算与路由导致延时增加,这也导致游戏处事上云进程斗劲迟缓。
虽然一些强竞技性游戏上云后可能影响游戏体验,但是对于一些休闲类、放置类、弱竞技类游戏来说,这些游戏对时延并不敏感,还长短常适合上云的。而且随着云游戏的成长和游戏产物的国际化,游戏上云是一个必然趋势。游戏上云可以很好运用云上资源,更灵活的配置处事资源以及更高效的打点处事,降低游戏处事的成本。
早前开源处事器更多存眷的是单处事器内部的设计,如云风的Skynet,虽然单处事器性能很高,但没有提供一个很好的处事集群化方案,集群的配置、集群的监控打点以及集群间的通信交互都很麻烦,集群处事的动态扩展性也不强,导致搭建处事集群困难以及整体处事资源操作率低下。
因此Wind致力于解决上述问题,简化游戏处事集群方案,提高处事资源操作率,同时也保证游戏处事的开发效率以及运行性能。Wind是一款面向云的高性能、高效率以及高扩展性的大型分布式游戏处事器引擎。Wind操作Python语言的简洁语法以及丰硕的生态库来提高游戏业务的开发效率,针对一些对性能有要求的游戏业务功能(如实时战斗功能),Wind操作Golang的高并发特性来保证处事的高性能,同时Wind接入云的组件来保证游戏处事动态扩展性,提高处事资源的操作率。
游戏界有Unity和Unreal这样完善而且开箱即用的客户端引擎,这样的引擎大大缩短了游戏的开发周期,基本上一天就能做一个能跑的游戏。但是却并不存在一款大师熟知分布式处事器引擎,这样的处事器引擎可以快速上手而且能满足游戏各个阶段的开发需求。Wind致力于做一款易上手且完善的分布式处事器引擎,辅佐独立游戏开发者或者中小企业快速搭建处事器框架而且快速开发游戏业务,降低游戏处事器开发难度与成本。


Wind引擎实现

大型分布式处事器主要由早前单处事引擎成长而来,早前处事器处事玩家数量较少,基本上单进程处事器便能处事玩家。但由于互联网技术的成长,玩家越来越多,单进程处事器处事不了更多的玩家,因此成长分布式处事器来处事更多的玩家。得益于云的成长,游戏处事上云提高了资源操作率,降低了处事维护成本。Wind分布式引擎主要由这三个部门组成,第一部门是单处事器引擎,第二部门是分布式集群,第三部门是处事云部署。

  • 单处事引擎:
单处事引擎包含一个处事器能运转的所有功能,游戏客户端发送请求给单处事引擎,单处事引擎措置请求后并回包给客户端。单处事器引擎主要包罗法式语言、网络通信、并发模型、长途函数调用这些主要功能。

  • 分布式集群:
分布式集群由每个运行的单处事引擎组成,分布式集群主要是为了解决单处事器引擎只能处事少量玩家的问题,通过横向扩展处事器来解决单处事器压力过大的问题,分布式集群功能主要包含处事发现、负载均衡、动静队列和数据存储等功能。

  • 处事云部署:
对于一些只有几十个人的游戏,你可能就只需要起一个处事就行了,但是对于上百万玩家的游戏,这时就需要起上千或者上万个服,而且针对分歧地域有分歧的部署方案,面对这么复杂的部署,如果纯手动打点,那会非常耗时耗力,这时就需要一些云部署东西来撑持。云部署主要包含容器部署、K8S编排、处事治理与监控。
单处事器引擎

单处事引擎包含一个处事器运行的所有功能,能单独运行并处事一部门玩家。单处事引擎运行后,客户端通过网络通信将请求发送到处事器中,处事器通过并发模型将请求交给逻辑模块措置,逻辑模块通过序列化解码参数数据并将请求数据交给处事注册的RPC函数措置。
法式语言

目前游戏处事器开发语言使用斗劲广泛的组合是C/C++和Lua。C/C++属于静态语言,拥有很高的运行性能,但因为C/C++语法更倾向于计算机的理解方式,对法式员编写业务逻辑并不够友好,降低了产物的开发效率,而且C/C++热更方式斗劲有限,线上出问题时不能快速且便利的修复,可能要时不时的停服关机修复,斗劲影响游戏体验,因此引入语法更简洁、更便利而且撑持打补丁的高级动态解释语言Lua。引入Lua后,C/C++的分工有了变化,一些要求高性能的处事器模块用C/C++编写,比如网络库、数学计算库以及局内实时战斗逻辑等,Lua负责一些对性能要求不高的模块,但业务逻辑量斗劲大的模块,这样的模块其实占游戏业务的很大一部门,比如游戏的一些外围系统:等级系统,背包系统,聊天系统等等。对比于Lua,其实个人更喜欢Python,Python比Lua拥有更简洁的语法、更高的容错以及更完善的函数库,在开发产物业务时,拥有更高的开发效率,所以Wind的游戏业务逻辑语言使用Python开发。
使用C/C++编写一些模块,可以很好解决处事运行效率问题,但C/C++没有自动内存打点,写逻辑时很容易发生内存泄漏,而且C/C++语法复杂,法式员上手难度较高,开发成本大,因此引入Golang语言。Golang以高并发著称,拥有比C++更简明的语法特性,可提升开发效率,同时Golang提供自动内存打点机制,极大的简化了法式员开发的难度。因此Wind使用Golang来开发一些对效率有要求的模块,目前Wind的网络库是Golang写的。一些对性能要求高的游戏业务你也可以使用Golang来开发,比如战斗功能。
Python与Golang的交互

Wind的网络库由Golang编写,目前撑持TCP,之后会撑持KCP、Udp和Quic这些协议。每个客户端连接过来后,Golang会开一个线程去措置网络数据,有数据发过来后,Golang会将数据交给Python逻辑端措置,这里有个问题,就是Python线程和Golang线程是怎么进行交互的?Wind处事器引擎的主线程是在Python端,在起处事器时加载Golang编写的网络动态库(so文件或者DLL文件)而且开启网络线程措置客户端数据,目前Python与Golang的数据交互使用Socket通信交互,Python端启用一个TCP端口,Golang连接这个端口而且将数据传送给Python端。当然你也可以换Python与Golang的交互方式,比如换成ZMQ的zmq_inproc通信,使用zmq_inproc通信时,线程间共享一个ZMQ Context,可以通过共享内存来传递数据,不需要使用I/O线程,可以加快Python和Golang的交互


并发模型

游戏客户端所做的工作基本上是将数据按必然逻辑显示在屏幕上,单个客户端并不需要与太多的处事器进行交互,多的也就两三个摆布,所以客户端基本上对并发没有太多的要求。但是处事器就纷歧样了,同一个处事器要处事的客户端可能是上千个,甚至可能是上万个,这时候就需要并发模型来合理分配处事器的计算资源并正确的为客户端处事。
为了最大化操作物理机的多核资源,一般会有两种并发模型,一种是单进程多线程模型,这种模型凡是是单个进程中存在多个游戏处事,每个处事分配一个核进行计算。游戏界应用这种并发模型斗劲成熟的是云风的Skynet,Skynet启用多个工作线程来措置处事的事件,各个线程间基于动静传递来通信。这种单进程多处事的错误谬误是,处事之间的隔离斗劲弱,处事之间共用了进程的数据,单个处事发生问题时可能影响其他处事,这样的设计也并不适合云部署,云部署所满足的一个基本要求就是处事间彼此独立,每个处事是云调剂的基本单元,每个处事可自由的被调剂,因此更适合云部署的是多进程单线程模型,每个进程是一个处事,同一台机器可以起多个进程来操作多核优势,同时单线程中使用异步来措置数据I/O,提高处事器的并发。
那么Wind使用的是哪个模型了? Wind使用是的混合模型,Wind是单进程单处事,网络库操作Golang的高并发特性,每个客户端连接启用一个线程来读取数据,同时为了减低业务逻辑编写难度,避免多线程锁问题,Python使用单线程异步协程来编写业务逻辑。网络层的多线程动静数据会通过一个队列来发给Python协程(asyncio)。


Python协程

Python协程本质上是异步,只不外这个协程是语言层面上的撑持,编写游戏业务时更清晰、更简单。Python启动线程时,会启动一个事件循环,这个循环会一直检测是否有事件可以措置,如果某个任务要进行I/O(磁盘I/O或者网络I/O),那么这个任务会被挂起,直到对应数据到来时,这个任务又会被事件循环措置。
  1. import asyncio
  2. async def main():
  3.     print('Hello ...')
  4.     await asyncio.sleep(1)
  5.     print('... World!')
  6. loop = asyncio.get_event_loop()
  7. loop.create_task(main())
  8. loop.run_forever()
复制代码
使用异步协程编写游戏业务逻辑时,同一个时刻只能有一个客户端请求被措置,降低了开发难度,与协程搭配使用的是各个模块的单例化。
网络通信

实现游戏处事器时,主要会接触到的是传输层以上的一些网络协议,传输层协议包罗UDP协议和TCP协议。UDP是一种无连接的协议,没有可靠性保证、挨次保证以及流量控制,但正是因为控制项斗劲少,UDP在数据传输过程中延迟小,速率高。游戏中一些对可靠性要求不高,但要求高速率的业务可以使用UDP传输,比如游戏语音处事
TCP是面向连接的、可靠的、基于字节流的传输层通信协议,TCP通过序号确认机制、超时重传机制、反复累计确认机制和查验和机制来实现可靠性传输,同时提供流量控制和拥塞控制来控制源端的发送速率,以确保对端能正确接收。相对于UDP啥都没做来说,TCP什么事都做了,导致TCP传输速率低,延迟大。游戏是实时性应用,游戏的一些外围功能(背包功能、个人信息)时延要求不高,TCP够用了,但是对于游戏战斗这类高实时性功能,TCP的延迟太大了,会导致游戏战斗时体验会非常差。
为了解决TCP传输延时大的问题,游戏界凡是会在UDP之上实现一个延时更低的可靠性传输协议,比如KCP,Enet。KCP能以比TCP浪费10%-20%的带宽的代价、换取平均延迟降低30%-40%,且最大延迟降低三倍的传输效果。ENet是专门为多人第一人称射击游戏Cube开发的可靠性传输协议,Enet提供连接打点,Enet最大的特点是提供多通道机制,每个通道包传送独立,单个通道会确保前一个序号动静达到才会发送下一个序号动静,以保证可靠性。
凡是游戏会集成好几个传输协议到网络层,以便在分歧需求场景下切换,比如集成UDP、KCP和TCP到网络传输层。Wind的网络层也会集成多个网络协议,以便在分歧游戏场景中进行切换。


长途函数调用(RPC)

在单机游戏中,如果你要实现某个游戏效果,你可以通过函数名字直接调用对应函数来实现效果,但在网络游戏中,有些游戏效果需要向远端的处事器请求计算或者数据(比如匹配,背包)来实现,这时就会需要长途函数调用。长途函数调用凡是发生在同一个共享网络下的分歧地址空间中,比如分歧物理机。长途函数调用凡是采用Request-Response通信模式,每个长途函数名字以Request/Response(也可以用其他结尾,比如Packet)结尾,以此来区分当地函数。
封装好长途函数调用库后,写代码时就像写当地函数调用一样,法式员并不需要关心与远端的交互细节。但与当地函数调用分歧的是,长途函数调用需要颠末网络传输,网络传输增加了调用的时延与不确定性,为了防止主线程逻辑卡死,长途函数调用一般设计成异法式用,Request包发出后,不会等待包的返回,而是Response包返回后在措置之前的逻辑。
实现长途函数调用需要两个功能撑持,一个是序列化功能,一个是协议工厂功能。
序列化

序列化是一个将数据布局和对象信息转化成可以存储和传输形式的过程。我们在写代码时凡是以对象的形式读取数据,因为对象更符合人类思维习惯,我们能更快编写法式代码。但是对象信息数据凡是是不持续的内存,不能直接进行存储或者传输,所以序列化需要将对象数据转化成二进制或者持续的字符串。序列化技术有很多种,斗劲常见的是Json、Xml、Protobuf等。Json是直接将对象转化成字符串,拥有很强的可读性,但错误谬误也很明显,那就是序列化后的数据太大了,导致需要的网络带宽也会加大,而且Json不安全,没有错误措置机制,你可以将同一个字段值转化成其他类型的数据。此刻斗劲大型的游戏凡是采用的序列化是Protobuf,Protobuf是协议定义型的,在使用时你需要定义你的数据类型,而且因为Protobuf在序列化时是用ID作为标识符,而不是字段名来标识,所以序列化后的Protobuf数据很小。对于游戏来说,Protobuf撑持函数引用定义,解决了函数反复定义的问题。使用Protobuf后也可以用协议定义文件来生成MD5,以次来标识分歧游戏版本的网络协议接口,以防老版本的客户端连接新版本的处事器,造成数据错误。
协议工厂

Json序列化时可以将函数名序列化进去,数据包达到处事器后,处事器按照函数名调用注册的RPC函数,但Protobuf序列化时并不会将函数名的信息带进去,Protobuf只会序列化协议参数数据,所以要使用Protobuf进行处事器序列化时,还需要一个新字段来标识这些数据是来自哪个协议的数据。凡是是用一个ID来暗示协议函数,那么哪个协议ID代表哪个函数了?这时候就会需要协议工厂功能。定义好协议文件后,将协议文件转化成代码文件时,凡是会给每个协议一个ID,然后生成一个协议工厂代码文件,这个工厂代码就是按照ID来调用对应函数。


处事担任

Wind整个项目的核心代码是Engine目录下的SrvEngine.py 其他所有代码都是以组件的形式附加到这个文件下。Engine拥有一款引擎的所有功能,但并不作具体的逻辑处事使用,具体的逻辑处事需要担任Engine,然后添加各个逻辑处事的一些特定功能,具体处事在service 目录下,目前有Gateway处事和Game处事。各个服的具体设计可以参考这边文章

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 22:36 , Processed in 0.100396 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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