前一篇文章对WebGame服务器的物理结构做了一个简要说明,下面我们对各个组成元素进行详细说明。
首先来看一下数据库设计。
游戏的数据库设计是项目基础设计中很重要的一个环节,下面将说明以下几个要点:
- 为什么选用SqlServer
- 基本原则
- 表关系的设置
- 数据的冗余设计
- 什么时候使用存储过程
- 什么时候使用EntityFramework什么时候使用ADO.NET
1. 为什么先用SQL Server
首先,不要对SQL Server的性能表示怀疑。作为WebGame应用来讲,它的吞吐能力,承载能力完全够用。
第二,如果服务代码是使用C#来编写的,那么SQL Server2005/2008能得到最好的C#/Visual
Studio兼容性。最重要的是C#中很多组件都是为SQL Server设计的,并且做了很多优化,例如:EntityFramework,SQL
CLR等。
第三,SQL Server中支持SQL CLR功能,即可以使用C#/VB.NET来编写自定义函数,存储过程等,对于熟悉C#的开发人员来讲是一个福音,不仅仅是代码编写规则熟悉,而且可以使用一部分.NET类库来实现想要的功能,比如说基本.NET类型,数学运算,WCF等功能。
第四,可能很多人都觉得使用SQL CLR是因为T-SQL用的不精,其实不然,SQL CLR实际上是利用C#写好DLL文件,然后被SQL
Server调用,本质上利用了.NET来提高SQL Server的性能,在某些情况下SQL CLR的效率要高于T-SQL。
2. 基本原则
在设计游戏数据库时千万别怕表多,在一个常规的网页游戏中,数据表的数量应该大于150。因为一个游戏项目,细分起来,是由少则10几个,多则几十个子系统组织起来的,每个系统都有若干张表来辅助存储。
另外,游戏系统的数据库可以按状态分为:配置数据存储表(如基本数值配置),状态数据存储表(如计时),以及数据极不稳定的用来表示某个物件所属的中间关系数据表(如某个用户招募了某一个军官)。总之,要事无巨细,将90%的数据以数据库的形式存储起来。这么作的原因在于,游戏中很多的数据都是敏感数据,如金币,某个物件的数量等,如果将过多的数据存储于内存中,一旦服务掉电,丢失的数据将无法挽回,客服的电话会被打爆的。
当然,如果将大多数数据存储在数据库,反复读写所带来速度问题,也是不可以忽略的,解决方法如下:
- 客户端在加载的时候,就把常用的只读配置数据存储到客户端。
- 客户端要修改数据时,先修改客户端的数据,然后立即显示出来,最后再异步调用服务器的接口,去修改数据。
- 如果客户端一定要等待服务器的处理结果,那么就直接利用异步方式调用服务器接口,但是一定要给用户提示,防止误操作的产生。
- 服务器一定要将常用的数值计算的操作数和结果储存在内存中,以提高响应速度,例如:游戏中常常用到Pow函数,那就在内存中建立一个Pow的函数值表,每次调用时,就直接从表中取出来。
3. 表关系的设计
一个游戏中会存在很多个子系统,如:用户系统、道具系统、商城系统、社交系统、邮件系统、武将系统、建筑系统、资源系统、计时系统等,除了计时系统比较独立,其他系统大多数都要用户系统以及用户相关的家系统(基地,城池等)相关联,这样复杂的结构如何设计呢?
首先,一定要把握住ER的思想,哪些是实体表,哪些是关系表,实体和实体之间一定要通过关系相联,最好不出现直接的主表-子表关系。同时,在各个关系中一定不要使用级联操作(级联更新,级联删除),当然,这个约束在表关联比较多的时候就会自动加入。之所以要不存在“主表-子表”关系以及级联操作,是因为整个游戏系统就是一个小型的社会,一旦存在级联,很容易产生“雪崩”效应,修改或删除顶级表的某一条数据中,就有可能影响到N多个子表,这种影响是的复杂程度是程序员根本无法控制的。比较合理的作法是如果要修改或删除某一个数据,就要从关联关系中的最底层表作起,手动的逐级向上进行修改或删除。表面上看起来会比较麻烦,但是这种作法带来的好处也是显而易见的,手动控制,可以明确到底哪些数据该删除,哪些数据不该删除(有很多数据是要用来做统计的,是不能删除的),同时,在这些数据表之间进行导航,可以让程序更清楚数据的存储结构,更清楚自己在做什么。
4. 数据的冗余设计
在设计数据库时千万别想着什么精简字段,减少冗余,通过关系查找等所谓的优化手段,经过失败经验的证明,数据库设计的冗余越少,精简度越高,在数据查找时的速度就会越慢,代码的复杂度就会越高。
下面以常见的武将系统为例说明如何通过冗余设计来提高系统性能。
首先,我们来明确一下基本功能。武将系统中一定会存在三张有关系的表,一,是可选武将的列表,这个表是一张配置表;二,用户表;三,是用户所招募到的武将表。比较精简表结构如下所示:
对于关系表UserOwnedMilitaryOfficers来讲,只需要存储对应的武将ID即可,如果想知道这个武将的详细信息,可以通过关系进行想找,此种情况的EntityFramework查询如下所示:
testModel.testEntities2
context =
new
testModel.testEntities2();
var
myuid="warensoft";
var
myMilitaryOfficers = context.UserOwnedMilitaryOfficers.
Where(mo => mo.Users.uid == myuid).
Select(mo =>
new
{
Name=mo.MilitaryOfficers .Name ,
Life=mo.MilitaryOfficers .Life ,
Exp=mo.MilitaryOfficers .Exp
});
//do something else...
不难发现,上面代码中的查询是由两个函数组成的,首先要根据用户的ID找到所有该用户所拥有的武将ID(使用Where函数),然后,根据所找到的武将ID,再反过来到武将表中找到对应的名字(Select函数),生命以及初始经验。特点:两个函数查询,访问了两张表。
如果我们在UserOwnedMilitaryOfficers表中添加一些冗余数据的话,虽然存储量会增加,但是控制难度会大大下降,对该表进行修改,如下所示:
修改后的关系表,是将一些冗余数据加了进来,对于这样的关系结构来讲,查询语句如下所示:
testModel.testEntities2
context =
new
testModel.testEntities2();
var
myuid="warensoft";
var
myMilitaryOfficers = context.UserOwnedMilitaryOfficers.
Where(mo => mo.Users.uid == myuid).
Select(mo =>
new
{
Name=mo .Name ,
Life=mo .Life ,
Exp=mo .Exp
});
//do something else...
对于新版本的查询代码来讲,首先,从关系表中找到该用户所拥有的武将,这一点是不变的,但是在执行Select函数的时候确不用再通过关系查找武将到,而直接对已经选出的数据进行组织,取出想到的字段而已。特点:两个函数,一次查询。这种修改仅仅是添加了一些冗余,但是查询次数却减少了50%。
5. 什么时候使用存储过程
存储过程的使用是需要仔细考虑的,我们不应该走极端(要么所有数据访问逻辑都使用存储过程,要么所有数据访问逻辑都不使用存储过程),原则上来讲,是否使用存储过程,取决于这个功能的调用密集程度和数据访问的密集程度。
如果某一个功能的非数据访问逻辑很多又会被很频繁地调用,将这个功能写成存储过程的话,会导致SQL Server占用较多的资源,这样作并没有真正的利用好SQL
Server,同时还会影响其他数据访问操作的执行。在遇到这种情况时,应该考虑使用EntityFramework和ADO.NET,可以在应用程序中通过SQL语句对数据库进行外部调用。
如果一个操作中,大部分的功能是访问各个表,并添加修改删除数据,其他的非数据访问逻辑很少,那么将这个功能写成存储过程,就是比较合理的,因为这样会大大减少SQL语句在应用程序和数据库之间的传递次数。
6. 什么时候使用EntityFramework,什么时候使用ADO.NET
首先,要说明的是绝对不要对EntityFramework的性能有所怀疑,经验证明,在大多数情况下EntityFramework的性能都与ADO.NET不相上下。
使用EntityFramework的优点:
EntityFramework使用类之间的关系进行导航,这样可以不去编写大量的级联SQL,以操作类的形式代替了字符串形式的SQL操作,我们的开发效率以及编码的正确率都会有大幅度的提高。
使用EntityFramework的缺点:
当然使用EF也有不少问题,比如说,在指定的数据表中作大量记录的相同修改,或者指删除,如果使用EF就要先遍历一次所有的记录,然后再保存,最后SQL
Server再执行操作,数据里一大,数据的同步就存在问题。同时EF对存储过程的调用也不方便。
使用ADO.NET的优点:
万能的操作!没有什么数据库操作功能是ADO.NET作不了的,而且操作起来更为灵活,速度也非常快。EF的底层实现也大量的使用ADO.NET。
使用ADO.NET的缺点:
显而易见,使用ADO.NET,是要直接编写SQL语句的,SQL语句是通过字符串体现的,没有智能感知,没有语法检测,只有在运行时才能知道正确与否,这样的开发效率比较低。另外,ADO.NET是较为底层的数据访问技术,在业务逻辑比较复杂的情况下,开发难度(事务控制,同步控制)比较高。
组合比例:
通过已有经验,我们发现在60%的逻辑中使用EntityFramework,另外20%的逻辑使用ADO.NET,还有20%的逻辑是利用ADO.NET编写的SQL
CLR代码,这样的比例也许会比较合适(当然这样的比例也要和具体的需要相匹配)。
|