网络游戏物品校验系统的设计
网络游戏若要有支持一个稳固的经济系统,服务器底层必须有一个可靠的数据服务。要设计出精简的数据协议可不容易。它需要保证在发生异常(可能是硬件异常,也可能是软件异常)时,不会出现物品/货币丢失,复制的问题。
使用带事务的数据库是一个不错的选择,但对程序员的要求较高。程序员需要仔细考虑每个数据操作如何编写事务才是安全的,还需要考虑数据操作的异步性。这对需求变化迅速,量又比较大的游戏,做的好真的是很困难。
我思考了很久,几经易稿,大约得到了这么一个东西:
数据存储和合法性校验应该分开,独立为不同的服务,这样才容易做的稳固。也就是说,数据服务不必做到完全的完备,简单的去读写修改数据即可。这部分,我倾向于用简单的 key/value 方式储存数据到数据库,可以自己实现,也可以用 redis 这样的现成产品。不必使用事务机制。
但是我们应该提供一个强的校验系统,所有的虚拟物品发放、转移,都应该经过这个校验系统。一切操作都需要经过事后的核对。由此系统来修正数据异常。
简单来说,我们需要保证的是游戏世界中的每件物品都有唯一的拥有者,如果不被玩家拥有,则被系统所有。虚拟货币也是如此。应该避免物品的蒸发(平白无故丢失)或是被复制。
物品和所有者的关系是简单的,单层的,不必实现多层次拥有的树状关系。每个玩家以及可以拥有货币和物品的 Entity 都有独立的帐号(用一个 64bit ID 表示)。而每件物品 Goods ,都有其唯一的 ID (64bit 整数),以及唯一的拥有者。
Entity 和 Goods 是独立正交的两个概念,一个 Entity 不可能是一个 Goods ,反之亦然。Goods 对 Entity 是一个 n:1 的关系。
这样看来,这个数据校验系统的 API 设计就可以比较简洁了。
本来,Entity 和 Goods 可以有相同的 id ,因为它们相互不影响(类型不同)。但为了实现方便,我们让 Entity 和 Goods 公用一个 id 空间,不会使用相同的 id 。
ApplyID(number)
申请一段 ID 备用。这是一个有返回值的 API 。多在服务启动时调用。数据服务返回 number 个空闲 ID 。这样,请求者之后,可以任意使用这段 ID 中的某一个,而不用担心和其他人冲突。一旦申请的 ID 接近枯竭,则可以提前申请下一批。0~1023 为保留 id ,通常 0 表示系统。
CreateEntity(id)
创建一个 entity ,赋予它一个空闲 id 。如果 id 是正确的通过 ApplyID 得到,这个 API 通常不会调用失败。
CreateGoods(id)
创建一个 Goods ,赋予它一个空闲 id 。它的所有者,默认为 0 ,即系统。
ExchangeGoods( { entity1 , funds , goods[] } , ... )
这个 API 接收若干组数据,每组数据包含 entity 的 id ,货币 funds 数量,以及它可以获得的 goods
这个 API 用于在几组物品以及货币的所有者间做交换。所有的 goods 的所有者必须存在于传入的列表内。 所有的 funds 总数必须为 0 。例如:
玩家 player1 用 1000 块钱,交换 player2 的编号为 12345 的物品,系统抽取 player1 的 10 块钱税。则可以表达为 ExcahngeGoods ( { player1 , -1010 , [12345] } , { player2 , 1000 , [] } , { 0 , 10 } ) 即,每组数据表达了某个 Entity 在这次交易中将获得什么。
系统发放(凋落)物品则可以先用 CreateGoods 创建出来,再用 ExchangeGoods 来发放。
VerifyGoods( entity , goods [] )
这个 API 用于校验 entity 所拥有的物品的合法性。API 传入他所拥有物品 id 列表 goods[]。校验服务校验完毕后,将告诉调用者,entity 拥有的物品是否有缺失,或是否有冗余。一般说来,再服务正常的情况下,这个校验是多余的。所以可以在服务维护时,离线跑一次。也可以在玩家上线时(或定期)做一次校对。
QueryGoods( entity )
可以获取 entity 所拥有的所有物品列表以及货币数量。这个 API 仅供调试使用。功能上和 VerifyGoods 有所重复。
如果一个玩家拥有的东西过多,可以考虑把仓库和背包分离成两个 Entity ,这样可以减轻 VerifyGoods 的负担(如果需要定期去做的话)
数据校验服务和数据存储服务是独立的,所以数据储存服务中还是记录有玩家对物品的拥有关系。游戏逻辑不应该依赖数据校验服务提供的物品列表,它只是用来保证游戏内的交易系统、怪物凋落系统都是正常工作的。并可以在异常发生时(硬件异常或软件异常),提供一份数据来修复。
1 月 13 日 补充:
关于一个玩家拥有多件相同物品的优化。
其实大多数物品并不具备唯一性,比如血瓶,材料等等。每个玩家都可能拥有多件。这种东西界于货币和特有物品之间。如果每个都为其分配一个 uuid ,可能会浪费大量的储存空间。
解决方法有三:
对于这个数据校验服务,不记录这些无关紧要的物品。
当同一件物品达到一定数量,以一定数量和系统兑换大面额 ID 。比如 10 个 id 兑换 1 个表示 10 个数量的 id 。这个方法不用为校验服务增加新协议,只需要在使用方约定即可。需要特殊处理的只是把系统 id 0 特殊对待,因其只换出不换入,就不需要把换掉的 id 进入数据库,写一下 log 即可。
保留的 1~1023 号 id 做特殊用途(当然从协议上说,不限于某一段 id ,每个 id 都可以允许多份,但不利于做一个简洁健壮的实现),每个表示特定物品。每个 entity 可以拥有这些小 id 多件。在校验服务的实现里,可以优化储存,保存 id 和数量即可。这个方法需要在 ExchangeGoods 里增加 Entity 失去的 id 列表,另外需要为实现做一定的优化。
Comments
Posted by: Haitao | (22) January 22, 2011 10:40 PM
Posted by: stephantan | (21) January 21, 2011 09:00 AM
Posted by: stephantan | (20) January 20, 2011 09:26 PM
Posted by: Cloud | (19) January 20, 2011 08:05 PM
Posted by: stephantan | (18) January 20, 2011 01:21 PM
Posted by: spray gun | (17) January 18, 2011 07:08 PM
Posted by: zuhd | (16) January 18, 2011 10:12 AM
Posted by: 宁波网站推广 | (15) January 18, 2011 09:59 AM
Posted by: 郑州第二中医院儿科 | (14) January 17, 2011 12:49 PM
Posted by: mos | (13) January 15, 2011 01:25 PM
Posted by: xuruoji | (12) January 14, 2011 06:07 PM
Posted by: Cloud | (11) January 14, 2011 03:17 PM
Posted by: Dante | (10) January 14, 2011 01:33 PM
Posted by: Eye Nuts | (9) January 14, 2011 11:06 AM
Posted by: lin_style | (8) January 13, 2011 08:46 PM
Posted by: goodorc | (7) January 13, 2011 08:15 PM
Posted by: kmplayer | (6) January 13, 2011 07:22 PM
Posted by: Cloud | (5) January 13, 2011 12:55 PM
Posted by: Hex Bolts | (4) January 13, 2011 12:47 PM
Posted by: Kevin Lynx | (3) January 13, 2011 09:05 AM
Posted by: leesoft | (2) January 13, 2011 07:40 AM
Posted by: Liu Liu | (1) January 13, 2011 06:30 AM