建立多用户系统时出现的关键问题之一是扩展。 这些问题有多种解决方案:分片,服务模型,实体组件系统。 今天,我们将考虑所有选项,并讨论解决该问题的实际案例。 立即加入!
第一部分第二部分我把这个词传给了作者。构建多用户系统的传统方法。 服务架构
从历史上看,解决伸缩性问题的第一种方法是分片-按任何标准将整个系统划分为多个服务器,而无需了解一般情况。 也就是说,最多可以有一定数量的用户,他们可以在同一台服务器上,彼此见面并进行交互; 但是添加新虚拟机时,它们最终会存储在另一台服务器上运行的虚拟空间副本中,因此无法与其他虚拟机交互。 显然,这不是解决问题的方法,而是一种解决方法。 而且即使现在分片仍然有意义,但在许多情况下仍需要一些方法,这些方法确实可以增加服务器上可能的负载。
第二种常用技术是服务模型。 服务器具有许多可以轻松复制的组件。 例如,这是一个数据库并且正在使用该数据库,或者是将它们发送到客户端的资产服务器,或者是授权服务器。 所有这些服务的区别在于您可以将它们放在多个实例中,并并行处理它们的请求。
主要问题是共享状态
但是主要的问题是不同的。 如何处理特定的世界状态(虚拟空间状态)? 假设我们的“世界”由一个3D场景,其上的一组对象以及多个连接的用户组成。 从理论上讲,我们可以复制一些负责在服务器端处理场景的软件组件。 但是问题在于,场景状态是所有这些组件共有的一件事。 因此,在并行化处理程序时,我们需要以某种方式解决将工作与数据同步的问题,与此同时,就同步本身而言,我们在性能方面的损失可能要大于在并行性方面的优势。
解决方案:实体组件系统。 远东地区的问题
解决此类问题的相对较新的方法之一是ECS(实体-组件系统)。 在此版本中,我们将系统对象表示为具有某些属性的某个实体。 例如,这可能是物体在空间中的位置及其速度。 而且,我们存储在对象本身上的全部只是一些数据,而不是与它们一起工作的逻辑。 也就是说,在我们的情况下,将简单地为对象分配六个数字-坐标矢量和速度矢量。
ECS的第二部分是worker,该系统可与特定类型的组件一起使用。 例如,在我们的情况下,它可能是每秒更改对象坐标并为其增加速度的系统。 主要思想是,工作人员对此一无所知-它只有一个队列,它是必须根据某些规则处理的组件管道。 因此,我们可以并行化工作人员和并行化的服务。
代理系统作为编写并行代码的方法
多智能体方法也不是什么新颖的事物,但是最近,人们对智能体系统的兴趣不断增长。 有很多相当不错的文章详细介绍了它,因此在这里我们仅简要列出此类系统的最一般原理:
- 系统的主要组件是称为代理或参与者的组件。 在某些方面,它类似于每个人都熟悉的对象,但是演员没有公共方法,与他交流的唯一方法是向他发送消息。
- 为了向代理发送消息,存在“链接”的概念。 该链接提供了一个特定的接口(在各种实现中,它看起来可能非常不同),它使您可以发送消息。 此处的重要属性之一是位置透明性,以及每个座席都有一个地址-这是一个字符串,可让您获取到座席的链接,而无论其物理位置如何,即 代理可以位于同一台计算机上的代理系统中,也可以在另一台计算机上工作,在这种情况下,链接是从某个网络地址获得的;
- 该代理有一个消息队列,并且它们被顺序处理。 代理可以是状态机,可以按对状态和消息处理程序的反应顺序进行更改。
- 通常,多主体系统是分层的,也就是说,主体形成一种树。 在这种情况下,其中一个代理程序中的错误不会停止整个系统,只有特定的代理程序断开连接后,才会向其祖先发送错误消息。 处理此类错误的一种流行方法是使其崩溃-当代理崩溃时,我们只需为其创建一个新副本即可;
- 创建新代理不是耗费资源的操作,并且创建系统本身的成本非常高。
通常,仅在使用ECS的方法中使用代理系统。 由于代理系统使创建所需数量的工作人员并使其工作并行化非常容易,只需在他们之间分配消息流即可,这似乎是一种很有前途的方法。 例如,这就是Improbable中SpatialOS的工作方式。
问题在稍微不同的平面上出现。 ECS方法非常简单,但是原则上不能称其为直观的,尤其是对于没有经验的程序员而言。 因此,在这样的系统中创建用户代码是一项相当艰巨的任务。 此外,还会出现有关虚拟服务器实例之间各种对象的可移植性的问题,因为如果所有工作程序(对于这种类型的组件)不在另一台服务器上,则我们必须与该对象一起转移所有工作程序。 原则上,代理系统的某些实现可以解决其中的一些问题,但是我们选择了另一种方法。
我们的案例是远东代理的精髓
在我们的例子中,每个虚拟空间对象都是一个代理,或更确切地说,是一个代理系统。 与经典的ECS相比,我们可以说我们中的每个实体都带有与对象本身相关联的“微型工人”系统。 同时,保留了代理系统的所有优点(即,我们可以通过更改服务器设置,在单独的线程,单独的机器等上运行这样的对象,只需更改服务器设置即可),但是该对象仍然可移植,并且为此编写脚本不需要进行ECS划分。
在这种情况下,世界状态被分为单个对象的状态,并且每个对象都可以单独处理。 在客户端上,我们还构建了一个代理系统,这是服务器状态的一种反映,并且我们将每个客户端代理与服务器代理相关联。 除其他外,这还提高了系统的可靠性,因为如果单个对象发生故障,则仅禁用此对象,而不禁用整个虚拟空间。
更详细地说,它看起来像这样:

任何空间对象都是一个小型代理系统,由服务器启动时创建的实体的主要代理组成,它不是组件容器代理,也不是一组消息处理程序组件。 要连接客户端,将使用网络透明性属性,即,客户端上的每个特定对象都具有到服务器代理对象的链接。 同时,在连接时,将动态创建一个新代理,该代理是主代理的后代。

代理系统也是在客户端创建的,但是其中的实体代理是由服务器端的消息构成的。 创建后,代理会接收到服务器代理的链接,并创建一个消息处理组件,其中包括用于从服务器接收和发送消息的队列。 还创建一个Unity对象,该对象组件的客户端部分从MonoBehaviour继承。 同时,Unity部分和代理部分在不同的线程中工作,消息处理程序负责同步(如果可能,将其最小化)。
这样的事情(没有特别的细节)看起来像是JIF变体中动态虚拟空间的实现。 在下一篇文章中,我们将向您介绍有关个人大数据和统计数据以及区块链的知识。
作者
Jedium是Microsoft合作伙伴公司,致力于虚拟,增强现实和人工智能领域。 Jedium开发了一个框架来简化Unity上复杂项目的开发,其中一部分可以
在GitHub上公开获得。 Jedium计划用新的框架模块以及与Microsoft Azure的集成解决方案补充存储库。
Vitaliy Chashchin-软件开发人员,在设计和实施三维客户服务器应用程序方面拥有10多年的经验-从概念到虚拟现实领域中应用程序和解决方案的完整实现以及集成。 系统架构师Jedium LLC,IT硕士。
阿列克谢·萨拉法诺夫(Alexey Sarafanov)Jedium LLC的市场经理。
谢尔盖·库德里亚夫采夫Jedium LLC的首席执行官兼创始人。