序列化存储与反序列化加载¶
介绍¶
TFRobot的序列化与反序列化是一个难题,因为整个TFRobot不仅仅存在基础数据类型的属性,还会存在复杂类型的属性,比如BaseModel甚至是函数或者自定义类。这些复杂类型,往往很难序列化到数据库中存储。
如果只是单纯的存储对象的一些元数据信息,通过序列化与反序列化方法来存储与加载,会带来繁重的开发工作,因为每扩展一个自定义类或者自定义属性,均需要重新定义或者扩展序列化与反序列化方法。
但目前业界也没有特别统一的方式来统一解决自定义类的序列化与反序列化问题,因此TFRobot在吸收了一些解决方案之后,初步形成了自己的一套「序列化与反序列化」的标准办法,但实话实说,目前还达不到协议的标准,只能称之为一种「约定」或者「私有协议」。
这个「私有协议」有以下只个核心点:
- 使用文件系统存储,而不使用数据库。
- 基于版本号进行管理,如果「序列化与反序列化」的规则发生变化,需要更新版本号。
- 尽可能地使用「元数据+Json」的方式进行序列化,但对于特殊对象,也可以使用pickle进行序列化。
- 辅助使用注册表系统,将自定义类注册到注册表中,以便在反序列化时进行查找,进一步加强「元数据+Json」的应用范围,减小pickle的使用,毕竟其可阅读性远远不如Json结构。
基于文件系统的存储¶
TFRobot的序列化与反序列化是基于文件系统的存储,而不是数据库。这是因为数据库的存储方式是基于表结构的,而TFRobot的数据结构是非常复杂的,不适合使用表结构进行存储。比如TFRobot是一个树状的数据结构,如果使用表结构进行存储,会导致表结构的设计非常复杂,而且不利于扩展。
另一种解决思路是基于MongoDB这种文档型数据库进行存储,因为MongoDB是基于文档的存储方式,可以存储复杂的数据结构。但是,MongoDB的存储方式也有一些问题
- 因为如果树结构的深度比较大的时候,MongoDB会比较难处理与阅读。
- 如果叶子节点上的对象有复用的情况,MongoDB的存储方式会导致数据冗余,同时不便维护。
而使用文件系统存储的方式,可以很好地解决这些问题。
- 文件系统本来也是树结构,可以很好地映射到树状数据结构。
- 可以通过软链(或者其它方式)等方式,实现数据的复用,减小数据冗余。
- 容易配合pickle等模块,实现复杂对象的存储。
版本号管理¶
因为序列化与反序列化的规则是会发生变化的,因此需要对序列化与反序列化的规则进行版本号管理。如果规则发生变化,需要更新版本号。同时版本号规则按如下设计:
检查版本是否兼容
- Major(第1位),高低均不兼容。如果第1位发生变化,报错出来
- Minor(第2位),高兼容低。如果框架代码版本比文件内容版本高,可以兼容,但如果框架低,则不读高版本内容
- Patch(第3位),Bug修复,相互兼容
对于当前的版本号管理,大致上采用Semantic Versioning 2.0.0规范,即MAJOR.MINOR.PATCH的版本号规范,但目前并未严格遵守其规范,比如没有rc、alpha、beta等标识。
存储规则¶
- 对于符合
SavableProtocol协议的对象,会直接调用其存储方法在指定路径进行存储。 - 对于不符合
SavableProtocol协议的对象,判断如果是Sequence(除str外)/Set的对象,会在当前目录建立以index为文件名的文件,递归进行对象存储。 - 对于不符合
SavableProtocol协议的对象,判断如果是Mapping的对象,会在当前目录建立以key为文件名的文件,递归进行对象存储。 - 对于其它对象,则调用dill库进行pickle存储。
按如上存储规则,最终形成的目录结构是一个树状结构,每个节点是一个文件夹,文件夹下存在一个.metadata文件。示例结构如下:
/root/test
├── .metadata
├── action
│ ├── .metadata
│ └── function.pkl
├── main_model.json
└── sub_model
├── .metadata
└── subscmodel.json
加载规则¶
- 读取加载目录下的
.metadata文件,获取对象的类型信息。 - 如果是pickle模式,直接反序列化
- 如果是json模式,则根据类型加以区分。
- 对于
list | dict类型,需要读取子目录递归处理。 - 对于
single类型,则根据主类名称,配合注册表进行查找,并依据配置进行加载。