问题、品质需求
1. 系统的伸缩性需求。如大型在线游戏,需要满足大量用户。在线用户数量短时间内可能有很大的变化。
这其中隐含的需求是:
多用户
并行
分布式系统,系统运行在多台机器上
高可扩展性(用于加入新的故事情节,意味着新的代码)
高稳定性、可靠性(一个用户崩溃,不影响其他用户)
数据一致性(多个用户看到同一个东西的状态应该是一样的)
2. 架构设计目标
即另外一个需求,对其他开发者部署出一个简单的编程模型,程序员可以将系统视为一个单机开发环境。
隐藏分布式和并发是一件困难的事。需要一种严格限制的编程模型。
典型的游戏服务器开发模型:反应式
客户端(游戏机)(生成事件) - 服务端的事件监听器(监听事件,并生成任务) - 此任务可与多个客户端进行交互
或者是服务端自己周期性生成任务。
这是一种典型的胖客户端机制,适用于游戏和虚拟世界,也适用于J2EE和Web服务的应用。
区别另外一种经典的企业级架构:
瘦客户端 - 胖客户端 - 更胖的数据库服务器。服务器保存客户端的绝大部分信息,绝大多数真正的工作在服务器上完成。
在游戏的软件架构中。不被修改的数据都被放在客户端完成,只有共享的数据才放在服务器,服务器尽量保持简单,减少计算。保持共享事实的最终来源,防止玩家作弊。客户端只访问少量的状态数据,但访问的数据大部分会被改写。
而另外一种架构是90%的数据都是只读的,大多数任务会读取大量数据,再修改少量数据。
3. 延迟的需求
游戏架构要求用户体验好,大的延迟不被接受,甚至牺牲吞吐量换取少的延迟。
而企业环境的架构重在吞吐量,管理业务。有一点延迟可以接受。
一般情况下,处理拥塞的解决方案:
1. 基于地理位置来实现。游戏设计包含不同的游戏区域,每个虚拟区域运行一台服务器,每个区域拥有自我限制功能,当人数过多时,服务拥塞,游戏变慢,趣味性下降,用户就转向更有趣的区域,响应时间就会得到改进。(对于棋牌类游戏,每个房间或区域有人数限制,满的房间可以限制进入)
这种开发方法的问题:游戏设计时,需要决定哪些区域放在一台服务器上,而添加新的区域时比较容易,若改动原来的区域,可能需要改动代码,这些都是开发的工作量。
2. 分区sharding。一个分区是一个区域的副本,运行在自己的服务器上,独立于其他分区,不同的玩家进入同一个区域的不同副本(分区)。这样的缺点时,不允许不同副本的玩家彼此进行交互。
3. Darkstar架构就是克服以上缺点,支持随时伸缩,同时又不要求游戏逻辑受到伸缩影响。支持动态响应负载,而不是放在游戏设计中完成。
Darkstar的架构
DarkStar是由一组服务组成。每个服务定义为一个小的编程接口。这些接口很像经典操作系统的服务,支持对服务端的访问持久存储、调度并执行任务、与游戏的客户端进行通信。
这些服务的程序不会受低层实现变更的影响,因为每个服务由一个接口来描述。当接口不变时,一个服务的变更,不会影响其他服务的实现。这是一个"分治"的过程。
另外,将基础设施设计为一组服务,可以将这些服务在不同场景下进行不同的组合,更加灵活,复用性强。一组服务可以组成一个Darkstar栈,Darkstar栈中具体包含哪些服务可以由一个配置文件来设置。
从宏观结构上来看
(分层、模块化、通信机制)
每个Darkstar栈运行在一个服务器上,Darkstar栈就是服务的副本和游戏逻辑的副本。客户端连接到其中一个服务器,与该世界的抽象表示进行交互。
每个副本可以与客户端独立的交互,不需要处理相同事件。复制主要是用于支持伸缩性。游戏逻辑也不知道其他服务器上的副本。而不同服务器上的副本协作是由Darkstar项目的基础设施完成的。
游戏客户端与服务端的通信机制包含两种,一种是直接通信,另外一种是"发布-订阅"模式。
Darkstar栈由一组元服务来协调,这是一组网络访问服务,对游戏程序员是不可见的。这些服务支持在线的副本相互协作,共同运营整个游戏。而且副本间相互独立,某副本失效,会发起失效恢复操作。
此外,这些Darkstar元服务会跟踪各副本的负载,在需要的时间重新分配负载,或随时添加服务器,增加总体容量。
基本的组件/服务
对于游戏程序员而言,可见的架构就是栈中包含了一组服务,而4个基本服务是必须的。数据服务、通道服务、客户端会话服务、任务服务。
数据服务
数据存储、数据读取、数据操作。
特征:大部分都是持久化数据、数据间静态关系少、很少有复杂查询、需要对延迟进行优化、需要修改的数据多
选型:与标准数据库的使用方式有区别、数据库简单的命名策略,编程语言上对对象的引用
任务服务
用于调度、执行任务。
任务来源:响应某个事件、或游戏内部逻辑触发的。常见任务的操作包括:从数据服务中读取、修改一些数据,可能会有一些通信、或者生成其他的任务。
时间限制:执行任务的时间必须很短。配置中可以设置,默认值是100ms。
底层特征:Darkstar的底层在尽量的调度最多的任务。但对游戏程序员,因事件只响应一个任务。
并行性:客户端产生的任务、服务端响应逻辑的任务是并行执行的。
数据竞争:由并行性带来的数据竞争,同步、事务性操作(一个操作要么全部成功,要么回退)
通信机制:若任务包含事务操作,通信机制也必须支持事务
通信服务、会话服务:
登录、认证后建立会话,监听客户端消息,并响应生成任务。会话也负责维持消息的顺序,前一消息未处理完,后一消息就先不提交。这样也让"任务服务"得到了简化。因为任务服务假定其任何时候触发的任务都是并发的。
通道服务:
一对多的通信机制。客户端之间可以交互,这种方式不能采取让客户端直接通信的方式,虽然这样会减轻服务端的负载,但容易产生作弊行为。所有通道信息都必须经过服务器。
这些抽象层不会暴露客户端或服务端的真实端点,这样Darkstar系统能将服务端的通信端点从一个服务器上移到另外一个机器上,同时又不改变客户对通信的感觉。底层的基础设施可以根据需要进行动态调整。
任务的可移动性
要实现负载的动态均衡能力,就应该实现任务的可移动性,从一台机器移到另外一台机器上。这是系统伸缩性的要求和目标。
技术要求:
1. 任务是由java写成,这样物理机器上运行JVM,任务就可以运行
2. 任务数据来源。任务的数据都来自数据服务。而数据服务是所有游戏实例和Darkstar栈所共享的。
3. 通信由"会话服务"或"通道"来实现中介。其抽象能力,使得特定的会话由一个服务器移向另外一个服务器。
4. 负载的监控。Darkstar元服务的工作,这是网络层面的服务,对程序员不可见,但对Darkstar栈中的服务是可见的。不会影响游戏的逻辑性,而能实现系统的伸缩性。
5. 利用伸缩机制来实现系统的高容错能力。由于任务和通信机制是与具体机器无关的,且任务本身也是持久对象,保存在"数据服务"中。若任务中断,等同于事务中断,系统会重新调度不同的机器运行。这样的后果是延迟有点长,但系统的正确性不变。
游戏架构的思考、改进
游戏与虚拟架构的特点:
1. 由于此行业的保密性,专用性, 其性能测试,数据指标比较难获取,另外很多偶然性的因素,使得无法对不同的基础设施做比较,对通用基础设施的测试更加困难。
2. 很多服务器的架构都是定制的,很少有可复用的基础设施假设,不注重这方面的积累。
Darkstar架构是围绕服务器性能做出了很多关键决定。
1. Darkstar拒绝在服务器中存放任何重要信息。
所有生存周期超过一个任务的数据都需要放入"数据服务"中统一管理。这是Darkstar的核心。为什么?因为它能检测数据的并发问题,对游戏程序员隐藏这些细节,让服务器能利用多核架构,并实现整体是伸缩性。
2. 延迟的分析。
以上对并行性的处理,会引发一些延迟。将数据放入内存,才能将延迟最小化,是主流观点。
而采用"数据服务"的方式会影响性能,访问数据会引入一定延迟。但比其他方法会更有竞争力:1)持久化存储可以利用数据库缓存和一致性,尽量减少数据访问延迟。2)将相关联的玩家放到一个服务器上去,也可以利用数据库的标准缓存技术,减少访问和保存持久数据的延迟。(与基于地理位置的技术不同,这部分工作无需放入游戏开发工作中,而是根据运行时进行优化,(类似于编译优化与运行优化))
3. 可靠性更高。
利用高并发来弥补持久化存储的延迟损失,方案总体上是更有优势,也更符合未来芯片基础架构发展的方向。另外,持久化"数据服务"将服务器失效而导致的数据丢失减少到了最小。
4. 简化游戏程序员工作。
当同时要求支持伸缩性和减少延迟的开发目标,那么开发者需要编写自己的分布式和多线程基础架构和代码,这对开发者的要求更高,且工作量更大。而Darkstar将所有任务封装到事务中,并在"数据服务"中检测数据冲突,开发者就能享受多线程的好处,又不必在他们的代码中引入锁协议、同步和信号量。 Darkstar提供透明的负载平衡。
但是开发者还是需要了解Darkstar的底层并发和分布式实质,需要遵循一定编程模型,尽量利用数据服务的并发性,提升游戏的整体性能。