[转载]ASP.NET Web Game 构架设计3--业务逻辑服务器之计时器

[转载]ASP.NET Web Game 构架设计3–业务逻辑服务器之计时器 – warensoft专注于.NET – 博客园.

ASP.NET Web Game 构架设计3业务逻辑服务器之计时器

业务逻辑服务器里主要包括以下四个模块

u 计时服务器

u 资源服务器

u 其他逻辑服务

u 对外的WCF接口模块/Socket接口模块

1. 计时服务器

计 时服务器的作用是给需要长耗时的功能提供一个延时管理模块,比较典型的应用如“种 菜的计时, 武将升级的计时,科技升级的计时,建筑升级的计时等。计时服务器主要由四个元素组成:

u 用于保存计时队列的数据表

u 添加计时的函数接口

u 删除计时的函数接口

u 用于加速的函数接口

u 定时机制

u 可以动态扩展的计时过期处理程序

用于保存计时队列的数据表

先 来说一下存储结构,计时的存储大体上有两种方案:1.基 于内存;2.基于数据库。首 先要说明的,从经验角度出发,本人更青睐于第二个方案。下面对这两个存储方案进行评比:

基于内存的优点:

操 作速度快

基于内存的缺点:

如 果服务器停电,所有用户的队列数据将全部消失!(客服的灾难)

基于数据库的优点:

不 怕服务器掉电,只要服务程序启动,就可以处理过期的队列

基于数据库的缺点:

读 取数据的速度较慢,

基 于上面的比较,以及我的基 本原则不让客服的电话被打爆!, 得到的结论就是:使用数据库来存储计时队列。计时队列的表结构如下图所示:

height=434

我 们可以看到,计时队列的存储,是由两张表来实现的,一张主表(PrimaryQueue), 一张辅表(SecondaryQueue)。

每 一次添加一个新计时队列的时候,我们要先在PrimaryQueue中 添加一条记录,在该记录里要包括,这个队列什么时候开始,多长时间结束,以及非常重要的队列类型名。读者会对两件事儿产生疑问:1.为 什么记录的是时长,而不是结束时间。答案是:为了方便计时的加速操作,为了方便界面上的倒计时显示。2.队 列类型名(QueueTypeName) 字段是干什么的。答案是:每一个队列都要有一个标记,该标记通过字符串形式告诉应用程序,当该队列过期后,应该找哪个函数,或者哪个类来处理相关数据。

仅 仅在PrimaryQueue中 添加一条记录是不够的,因为我们还不知道当这个队列过期后,有哪些要处理的数 据是相关的,为此我们还要在Secondary表 中添加对应的记录,PrimaryQueueID是 外部键,当然在SecondaryQueue中 最重要的是ForeignID字 段,该字段存储的是相关逻辑表中,和本队列有关的那条记录的ID, 根据此ForeignID, 我们可以在对应的计时过期处理程序中对相关记录做处理(这儿说的很别扭,当然也不太好表达,请读者注意理解!)。

呵 呵,看到这里,很多读者又会产生一个疑问,为什么要两张表,上面说的那点儿事儿,一个表不就搞定了吗?答案是:一个表是搞不定的,因为在很多情况下,在一 个队列里,可能处理的是多个元素的计时,这个时候可能会出现的情况是,在PrimaryQueue中 有一个记录,表示是一条队列,并且这个队列中的数据要作的事儿都一样,开始时间也一样,过期时间也一样, 在SecondaryQueue里 对应的是本次队列的多个计时元素。举例说明,在 用户的一次操作中要为该基地建造5只 船,因为每只船都有自己独立的状态,所以每只船的数据都存储在基地表和船只列表的关系表中(每只船一条记录),此时, 我们的队列表中的数据是:有一条记录在PrimaryQueue中, 记录着这些操作的开始时间以及时长,另外,对应主表中这个新的队列,在SecondaryQueue表 中会存在5条子记录,每个记录的ForeignID字 段的值就是每一只船的ID(基地表和船只列表的关系表的ID)。 现在应该明白使用两个表的原因了吧!

添加计时的函数接口以 及删除计时函数接口

这 两个功能相对来讲是比较简单的,无非就是将数据添加到数据表中,以及从数据表中删除。

用于加速的函数接口

加 速的函数接口,主要实现两点功能:1.直 接将加速的时间从TimeLengthSeconds字 段上减下去;2.计费作用,因为一般的游戏中,加速是要收费 的,这时候,在加速之前就要检测用户费用是否充足,在加速完毕后,将相应的费用扣除到。

定时机制

我 们如何才能知道哪些数据已经过期了呢?这时我们需要利用Thread编 写一个Timer,这个Timer每 间隔一定时长就Sleep一次(比如60秒), 每一次Timer启动时,就去找出已经过期的队列(now-StartTime>TimeLength,并 将其中的记录按QueueTypeName字 段的值进行分类(即:功能相同的计时放到一组),然后将每一类记录的集合都提交到ThreadPool中 进行异步处理,基本代码如下所示:

var queueTable = Monitor.GetExpiredQueuesTable();

var groups = queueTable.AsEnumerable().GroupBy(row => row[QueueTypeName]);

foreach (var item in groups)

{

ThreadPool.QueueUserWorkItem(obj =>

{

IGrouping<object, DataRow> items = (IGrouping<object, DataRow>)obj;

foreach (var row in items)

{

//deal with the row

}

},item);

}

可以动态扩展的计时过期处理程序

前 面我们已经将过期的队列取出来,并对其进行了分类,那么我们如何对每一类队列中的记录进行处理呢?另外,随着应用程序规模的不断扩大,处理程序的数量也不 会断增加,那么,我们又应当如何保证处理程序的可扩展性呢?

其 实,我们可以很容易发现,不同的处理程序中的函数的名称和参数列表应 该是一样的,只不过是该函数所处的文件或者类不同,并且逻辑不同而 已。大家会问题,为什么不同的处理程序的函数名和参数列表也要相同呢?其实想法也很简单,因为我 们需要通过一个统一的调用接口实现函数调用过程

为 了达到可扩展并且方便修改的的目,我的处理程序可以利用IronPython脚 本来实现,使用脚本语言来实现一些逻辑是游戏编程中常常用到的(当然用C#写 成DLL,并动态加载也可以)。我们要做的 是,在应用程序所在的目录中添加一个处理程序的脚本目录,在这个脚本目录中添加若干个IronPython脚 本,每个脚本的文件名应该对应着每一类队列的分类名(QueueTypeName, 在这些脚本中都提供一个名称相同,而逻辑不同的函数,在队列服务程序加载的时候,可以建立一个Dictionary, 应用程序可以去扫描脚本目录,并将脚本名以及脚本中的函数以键值对的形式添加到Dictionary中。 发现已过期的队列后,就可以根据队列所处的分类名,在Dictionary中 查找相应的函数并调用即可。

使 用脚本的好处:语法简单;可以直接修改脚本文件并保存,不用重新编译;处理程序经常会出现Bug, 并且处理程序的需求会经常发生改变,写成脚本方便修改;当添加了新的处理程序时,也不用重新编译服务器代码,只要重新Load一 次脚本就可以实现计时服务的可扩展性了。

使 用脚本语言的速度如何?速度肯定会慢一点点,但是你绝对感觉不出来!

总 结,根据上面的描述,我们可以得到一个流程图,如下所示:

今 天先聊到这儿,下次我们要谈一下资源服务的实现,有问题或需要技术支援,请给我发Emailwarensoft@foxmail.com

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏