页面

2010年11月20日星期六

MMORPG通用服务端引擎分析方式

转自:http://www.cnblogs.com/Alexander-Lee/archive/2010/11/20/ViewOfHightPerformanceMMORPGGameServer.html

一般来说,当然是想一个游戏装的用户越多越好,当然这里有很多现实上的制约,第一是运营上,游戏里总是老玩家等级更高、实力更强,由于游戏的竞争性,玩家都喜欢进新服,所以一组服务器总是在开服的时候开始在线人数持续攀升,然后在某个点后就会下降并趋于稳定。其二是因为游戏中的场景不是无限大的,有固有的玩家密度限制,如果一个游戏屏幕上到处都塞满人,估计你也不会觉得好玩。并且根据这两个特点就决定了,MMORPG的服务引擎需要有高性能,这样才能在开服的时候承载尽可能多的用户进入,并且要具备高度的可伸缩性,在开服用户在线高峰的时候可以用比较多的服务器资源来拉动用户,在平稳运行后能够省出多余的服务器资源来挪给其他的服务器组。
从最简单的来说,MMORPG游戏基本上就是经典的C-S结构的系统,所以最简单的结构就是
Server  
    |  
    |

 Client

这个时候Server端什么都要处理,就是忒累点,所以承载能力也就不用指望了,扩展能力就更不用想了,不过胜在简单,如果好好设计性能还是有保障的。有个开源的C#开发的UO的服务引擎实现大家可以借鉴一下,http://www.runuo.com/。其实从本质上来说MMORPG的服务端和企业应用没有差别,所以我们解决问题的方式也是很相似的,如果性能有问题,一台服务器无法解决问题,就用两台,属于叫分而治之的策略,所以要提高性能,我们需要从系统的最慢的地方开始。如果有点经验的开发人员都知道,系统中最慢的就是数据库了。所以要提高系统的响应,我们一般是在用户的角色登录的时候直接将角色的数据读进内存来处理,但是内存的数据不怎么保险,万一服务器当掉或者掉电了怎么办呢,这个问题就需要定时将用户的数据变更发给数据库了,这样就不存在读写数据库的延迟了,当然掉电的时候在上一次保存后的数据还是会掉,这个时候就叫服务器回档,这种小几率事件一般我们不用特殊处理,一般游戏偶尔出现故障回档的话基本都是对在线的用户赔点经验装备了事。以后架构还有可能扩张,而为了让你不必将SQL写得满世界都是,我们将数据库操作抽象成数据库服务,为了多台服务器都可以一视同仁的使用,也也可以单独用一台服务器来作为数据库服务的主机,如下图:

DB-Service
       |
  Server  
       |
    CLient  

 

这张图看着很眼熟吧,恩,啥三层架构不就是这么画的嘛。� 这个时候系统的压力就不在数据存储上了,于是压力到了游戏的逻辑上,现在游戏既要同时处理大量的并发网络请求,且还要对游戏的逻辑进行运算,一边是高IO应用,一边是高CPU运算,都放在一块那就是一半火焰一半海水,不是烧死就是淹死了。高网络IO并不需要很强的CPU运算性能,比如路由器,一台城域网的核心路由器负担了整个城市的出口网络路由,IO高得吓人,路由器其实也就是一台特殊的电脑,但是这台核心路由器的CPU其实并不比我们的家用PC的CPU高,甚至有可能低一点。但是游戏的逻辑运算对CPU的消耗就是实打实的消耗了,假设游戏有1000个玩家在线,这个时候每处理一个事件需要1ms,那么假设每个玩家每秒有4个事件需要处理,那么1000个玩家就把一颗4核CPU或者2颗双核CPU的计算能力耗尽了,这里还是假设的理想情况,如果再加上NPC,怪物,物品之类的要处理,那就需要非常多的CPU资源,如果所有CPU资源都给了逻辑运算,网络消息处理所需的CPU能力就不足了,虽然消耗低,但是总是消耗的,所以我们继续采取分而治之的办法来处理,我们把前端服务器和逻辑服务器分开,前端服务器用来处理大量的用户链接和消息分发,而逻辑服务器用来处理游戏逻辑。由于游戏逻辑更消耗CPU,所以一个前端服务器可以带N台逻辑服务器,所以结构改成了下图:  

 

Logic-Service   Logic-Service    DB-Service
         |                     |                       |
         -------------------------------------
                               |
                       Front Server
                               |
                            Client                   

 我们可以继续将游戏逻辑进行分类,由于MMORPG的每一个玩家从经入游戏开始就是处于一个个的场景当中的,所以逻辑服务器也可以叫场景服务器(地图服务器),有的游戏即使没有场景切换,其实也是分了很多场景的,只是采用了无缝拼接的技术让你觉得是没有分开的。玩家的逻辑就可以分为连续事件逻辑和瞬时逻辑。连续事件逻辑是在场景中需要和其他用户发生反映的事件,也可以称之为场景事件,比如我攻击了一个怪,这一个事件需要通知场景里所有的玩家知道,并且会影响怪接下来的行动。所谓的瞬时事件,就是只会影响玩家自身的状态且不需要通知其他玩家或者说是对场景产生影响的(当然如果对场景产生了影响势必需要通知场景内其他玩家)。有的事件甚至会跨场景通知系统内所有用户(比如某玩家击杀了某著名BOSS,或者通知师父自己的徒弟在某处遭到了攻击[假如要实现师徒系统的话])。玩家大部分的逻辑都是在场景内完成的,所以场景逻辑的实现非常的重要。在这个部分的运算涉及到多个对象间的互动,如果想用多线程来提高并行度来提高性能其实反而会适得其反,因为要保证计算的数据安全避免脏读,在多线程的环境下就需要处理大量的锁,相信在游戏的业务逻辑里还需要处理锁,防止死锁这里的处理会宁所有的程序员抓狂,对于这种高CPU运算的场景来说,大量的线程也会将宝贵的CPU时间浪费不少在线程切换上,所以一般来说游戏的逻辑服务器都是单进程单线程的结构,通过一个大循环来驱动整个事件逻辑。那么你可能会问,如果有单进程单线程岂不是只能利用一颗CPU内核了么?当然一个游戏也不可能只有一个场景嘛,我们可以在一台服务器上跑N个场景服务,处理N个场景的逻辑(根据经验来说,N=CPU内核数量性能最好)。所以你可以看到为啥上图我不写Logic Server而是写的Logic Service了。
当玩家连接上Front Server后怎么知道要把数据发到那一个Logic Service呢?这里就需要场景管理服务了,根据我的想法,我想在场景管理服务里提供一个用户代理,用户代理知道用户在那个场景中,代理了用户登录场景,离开场景的行为,以及帮助用户将数据转发到正确的场景服务上,所以结构图继续进化成下图:


 Logic-Service   Logic-Service    DB-Service
           |                     |                      |
           ------------------------------------
                                 |
                       Scene Manager
                                  |

                          Front Server
                                  |
                               Client
由于Front Server的数据都由场景管理服务器转发到场景上,所以我们在前端可以部署多个Front Server,因为一个Front Server只有100M的带宽,当Front Server性能不足或者带宽不足的时候我们可以通过两个来搞定,而Scene Manager只通过用户代理来做包转发,所以承载能力相当的高。再次增强后系统如下图:
 Logic-Service   Logic-Service    DB-Service
          |                      |                     |
          ------------------------------------
                                 |
                      Scene Manager
                                 |
                        ----------------
                        |                   |
                Front Server   Front Server
                        |                   |
                       Client         Client

考虑到Front Server有可能会存在CPU剩余的情况,所以也可以将一部分瞬时事件交给Front Server来处理,当然有一个公共的很耗时的瞬时逻辑需要被剥离出来,那就是登录的逻辑,登录的逻辑每一组服务器都一样,所以我们可以将N组服务器的登录交给一个统一的登录服务器来处理,所以我们再次进化

 Logic-Service   Logic-Service    DB-Service
           |                     |                     |
           -----------------------------------
                                 |
                        Scene Manager
                                 |
               ------------------------------------- 
              |                       |                      |
          Front Server   Front Server     Login Server
              |                      |
             Client             Client                

 

 现在看起来大体上已经有点模样了,但是其实还可以根据不同的需求做一些细节上的调整,比如怪物的AI运算,如果仅仅是很简单的逻辑运算,其实就可以直接在场景服务器的逻辑中处理了(其实有点像把怪物当作了场景的一部分)。如果需要很复杂的AI,就需要将怪物的AI拿到单独的进程里运算了,其实就是把怪物当成一个特殊的玩家来对待,这种模式比较特殊,由于比较耗资源,一般都对大BOSS采用这种方式。

2010年11月14日星期日

关于js中的webWorker(另一种多线程)


//worker.js
onmessage = function (evt){
  var d = evt.data;//通过evt.data获得发送来的数据
  postMessage(d+"双核时代来啦");//再发送回去,礼尚往来
}
//main thread
var w = new Worker("worker.js");
w.postMessage("javascript");
w.onmessage = function(evt){
  alert(evt.data);//获取新线程的js发送来的数据
}

2010年11月2日星期二

flash中的安全沙箱

  前几天看了下《深入java虚拟机》中的安全性一章,借次再回头来分析温习一下flash中的安全模型
  这里主要记录一下单纯网络沙箱中的安全。
  在flash中有一个安全域的概念,每一个加载域中的内容都被加载者加载到一个单独的安全域中,比如对于A.swf,加载本域的B.swf则直接加载到A.swf所在的安全域中,加载exmple2.com中的c.swf,则会把c.swf放到一个单独的安全域中。
  每个安全域中可以定义多个应用程序域,应用程序域只是用来保存独立的引用。
  按照java中的安全模型,安全水箱同 类加载器----……-----安全管理器组成,在一个虚拟机实例启动时会同时实例一个Sys.setSecurityManager(securityManager);在每一个执行线程中,对于特殊的操作,如文件操作等需要安全验证的操作,虚拟机都会调用securityManager.checkPermission(java.security.permission)---->AccessController.checkPermission(perm)来执行验证,验证不通过则抛出异常,其中perm为根据策略文件生成的Permission子类实例
  同样,在flash中应该也采取类似的验证方式,不同的是flash中把外部资料分为了两类:
        加载内容:为可视化媒体、音频、视频等媒体资源;
        提取或访问数据:数据为媒体资源的像素或者音频波形数据,或者其它文本数据
  在加载内容和访问数据两方面采取的安全策略不尽相同,
    对于加载内容方面的策略只需要遵从那些本地沙箱、本地可信任沙箱、网络沙箱等规则;
    对于数据方面的策略则要更加严格一些,需要使用策略文件或者Security.allowDomin()
  
未完待续