通用背包设计范式以及背包数据持久化思路
笔者这几天在开发背包系统,调研了一些背包系统常规的设计思路,这里做下总结和思考。
分布式思路
背包系统是能够被抽象成单独的服务、为多个游戏提供服务的,因为背包系统所需要的功能对于大部分游戏来说是差不多的,无非是对道具的增删查改,过期检查,装备卸下等功能。如果能够抽象成单独的服务,对外开放接口,这样就能提高游戏系统的复用率,降低维护成本。所以后面的设计思路都将围绕这个主题进行设计展开。
通用道具设计以及配置表实现自定义化
站在背包的视角,所有的道具理论上都可以使用四个字段来描述:
1
2
3
4
5
6
type Prop struct {
PropId string
ConfigurationId string
KeyValuesInt map[key]int64
KeyValuesStr map[key]string
}
为什么说这个结构能够表示道具应该具备的所有状态呢?下面我们来一个字段一个字段的分析。
- PropId:道具Id这个不必多说,作为数据库的主键。但是这里我要提醒一下,PropId本身是个抽象度极高的字段,不能简单得认为同一种的道具的id是一样的。PropId最重要的特性就是证明这个道具的唯一性,用户背包内只能存在这一份数据。
- ConfigurationId:配置Id,这个字段非常重要,是实现通用道具的结构的关键。不同的游戏下,道具被赋予的业务是不同的,但是这并不是背包系统所关心的,但是后端又需要记录道具具备的业务方便客户端使用。配置表的作用就体现了,在游戏业务系统内实现一张配置表,存储着道具的业务信息,背包内的道具只需要记录配置表的key即可。到时候业务层自己去根据key去查表得到道具的业务信息。这就是配置设计模式的作用,将配置信息与道具结构体本身解耦开来。
- KeyValuesInt:int类型的key-value对,存储道具的属性信息。比如道具的耐久度、道具的等级等。这个字段是一个map,key是属性的名称,value是属性的值。这个字段可以存储道具的所有属性信息。因为是map,所以是可扩展的。也不一定要用map,但是要注意选取的数据结构要具备可扩展性。
- KeyValuesStr:string类型的key-value对,存储道具的属性信息。道具有些属性可以被序列化成字符串,比如属性是个结构体,能够被json序列化。这样道具能够存储的信息就更多了,不在局限于int类型。
这样这样的道具结构理论上可以表示所有的道具。
用户背包设计 / UserBag
话不多说,直接上伪代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type BagType int
type BagInfo struct {
userid string
bagid string
...
}
type BagConfig struct {
bagType BagType
capacity int
...
}
type UserBag struct {
info *BagInfo
config *BagConfig
slots []*Slot // 背包道具槽位
wearRef map[int, int] // 装备道具Id -> 存放槽位
...
}
背包结构的增删查改比较简单,这里就不过多的赘述。
背包管理器 / BagManager
这里来到本篇文章的重点,背包管理器的实现。背包管理器管理着一个游戏的所有用户的背包,系统访问具体的用户背包的入口就在这个模块,同时负责重要的背包系统的功能,比如加载用户背包数据,持久化用户背包数据,背包道具过期检查,高并发高性能查询背包数据等。
加载和持久化的艺术
我们都知道内存是存在风险的,把数据长时间存放在游戏服务器内存中是危险的。但是与Web不同,游戏又对实时响应性有着要求,在一些玩法上与背包有高强度关联的时候,内存的高响应性有至关重要。
有句话说的好,软件设计是Balance的过程。内存和硬盘间数据存在位置的平衡一直都是服务开发的难题,没有通用的方法论,必须结合具体的业务场景来进行设计。所以虽然背包是通用的,但是背包管理器其实不尽然能适配全部的游戏,需要根据游戏的玩法风格的不同来进行调整。
分阶段做法
游戏玩法要求背包内的道具会频繁变动,此时对响应性的要求很高,对于每一次变动都要求持久化保证数据安全诚然是不合理的。通常的做法是,在游戏进入需要高响应性阶段的时候,向管理器申请背包数据进入游戏内存,之后的所有修改都在内存中,等到游戏进入低响应性阶段的时候,再想管理器申请持久化背包数据。或者设计存档点,在存档点时,顺便进行一次背包数据持久化。在低响应阶段,所有的数据修改都可以向管理器申请持久化。
持久化权威
持久化权威要求所有的数据以硬盘数据为权威,向它同步。这样的场景在于存在第三方发放道具,且道具的安全性能极高,需要即刻入盘。比如充值道具,活动道具等稀有道具。游戏业务层不再具备向管理器申请持久化的权限,只能通过加载刷新数据的方式将数据加载到内存中。也就是游戏业务需要背包信息的时候都向管理器申请加载最新的数据。
过期检查处理 / expire
道具具备有效期是非常常见的需求,背包系统也就承担起对道具过期检查的责任。过期检查是个非常令人头痛的问题,其问题在于实时性。过期的发生和过期发生的检测完成几乎不可能同时达成。
所以何时进行过期检查是问题的关键!
在持久化权威的方案中,过期检查发生的时机通常在申请 / load 数据的过程中,check when load;
在高响应下的方案中,过期检查发生的时机通常在use,即check before use。
了解完这些,相信读者能够设计出一个完善的背包系统哩!