Go中提供了一种初始化数据和逻辑的方式:func init() {}。当我们要初始化配置文件,或者是要启动一个goroutine,都可以在对应模块的init方法中进行。
而init方法执行的时机在于该package被第一次访问时。在程序中,我们有两种方式来触发指定package内的init方法的执行:
- 显示地import package来,如:import github.com/Jordanzuo/goutil/baseUtil
- 使用_的方式来import package,如:import _github.com/Jordanzuo/goutil/baseUtil
在第一种方式中,需要在import package的文件中显示地调用baseUtil的方法,否则会编译失败;而在第二种方式中,则没有这个限制。因为import _ package的设计目的就是为了方便在不使用package内容的情况下,让其内部的init方法得到执行。
看起来很美好。只要各个package之间互相之间没有依赖关系,则各个模块可以独自地进行数据和方法和初始化。但是在一个规模稍微大一些的系统中,模块和模块之间或多或少地存在着一些依赖关系,比如模块A依赖于系统配置模块。那么在系统配置模块初始化完成前进行模块A的初始化,则会出现空引用的异常。
这个问题如何解决呢?我们以游戏为例来说明此问题的解决方案。
在游戏中,需要进行初始化的内容包括两大类:数据和goroutine。其中:
- 数据:游戏中的各类数据;数据内部有依赖关系。
- goroutine:用于控制系统流程的一些独立goroutine;大多数时间都依赖于数据的初始化完成。
而数据又可以分为4类:
- 静态的配置数据:在整个程序的生命周期内保持不变的配置数据。
- 动态的配置数据:在整个程序的生命周期内会改变的配置数据。
- 玩家相关数据:只由玩家自己改变的数据。如玩家的装备、随从等数据。
- 全局数据:任何玩家都可以改变的数据。如boss、竞技场等数据。
为了保证数据的一致性和程序运行的正确性,我们对于以上的4类数据施加了一些限制:
- 对于静态的配置数据,我们必须在系统启动阶段,验证数据的准确性;如果发现有错误数据,则不允许程序启动。而数据准确性的保证,则依赖于模块之间数据的彼此验证。比如:装备升级表中包含了装备的模型ID,那么我们就需要保证该模型ID一定存在于装备的模型表中。
- 对于动态的配置数据,除了在系统启动阶段,验证的数据准确性之外;在系统运行期间,每当数据发生变化,我们在将数据应用到系统前,都需要验证数据的准确性。
- 对于玩家相关数据,我们一般只会将最核心的玩家数据加载到内存中,而剩余的数据则进行延迟加载。
- 对于全局数据,需要将全部数据都加载到内存中,并进行适当的验证。
针对以上的需求,我设计了以下的初始化管理系统。
- 将系统的初始化分解成8个步骤:
- Load: 从数据库中加载数据到内存中的临时变量中。
- Verify: 验证临时变量中的数据的正确性;比如装备等级表中的模型ID必须存在于装备模型配置表中。
- Convert: 对临时变量中的数据进行必要的转换;比如将,分隔的字符串转换为[]int。
- Apply: 将验证和转换后的临时数据赋值给正式数据变量。
- Print: 打印数据。
- Clean: 清理临时数据。到此数据加载完成。
- Setup: 设置系统运行环境。例如:启动一个持续运行的goroutine。
- Complete: 系统初始化完成后执行的一些操作。例如:注册小红点处理逻辑。
- 在系统中提供LoadMgr, VerifyMgr, ConvertMgr, ApplyMgr, PrintMgr, CleanMgr, SetupMgr, CompleteMgr等8个对象。
- 在各个package的init方法中,根据实际需要,将自己内部的方法分别注册到初始化管理系统对应的对象中。如:
bll.go
func init() {
systemInitMgr.LoadMgr.RegisterFunc("load", load)
systemInitMgr.VerifyMgr.RegisterFunc("verify", verify)
systemInitMgr.ConvertMgr.RegisterFunc("convert", convert)
systemInitMgr.ApplyMgr.RegisterFunc("apply", apply)
systemInitMgr.PrintMgr.RegisterFunc("print", print)
systemInitMgr.CleanMgr.RegisterFunc("clean", clean)
systemInitMgr.SetupMgr.RegisterFunc("setup", setup)
systemInitMgr.CompleteMgr.RegisterFunc("complete", complete)
}
func load() []error {}
func verify() []error {}
func convert() []error {}
func apply() []error {}
func print() []error {}
func clean() []error {}
func setup() []error {}
func complete() []error {}
核心代码如下:
model.go
package systemInitMgr
// 方法对象
type FuncItem struct {
// 模块名称
moduleName string
// 方法定义
funcDefinition func() []error
}
func newFuncItem(moduleName string, funcDefinition func() []error) *FuncItem {
return &FuncItem{
moduleName: moduleName,
funcDefinition: funcDefinition,
}
}
load.go
package systemInitMgr
import (
"strings"
"sync"
)
var (
LoadMgr = newLoadMgr()
)
type loadMgr struct {
funcList []*FuncItem
mutex sync.Mutex
}
func newLoadMgr() *loadMgr {
return &loadMgr{
funcList: make([]*FuncItem, 0, 64),
}
}
func (this *loadMgr) RegisterFunc(moduleName string, funcDefinition func() []error) {
this.mutex.Lock()
defer this.mutex.Unlock()
funcItemObj := newFuncItem(moduleName, funcDefinition)
this.funcList = append(this.funcList, funcItemObj)
}
func (this *loadMgr) CallByModuleName(moduleName string) (errList []error) {
this.mutex.Lock()
defer this.mutex.Unlock()
for _, item := range this.funcList {
if item.moduleName != moduleName {
continue
}
errorList := item.funcDefinition()
if errorList != nil && len(errorList) > 0 {
errList = append(errList, errorList...)
}
}
return
}
func (this *loadMgr) CallByModuleNamePrefix(moduleNamePrefix string) (errList []error) {
this.mutex.Lock()
defer this.mutex.Unlock()
for _, item := range this.funcList {
if strings.HasPrefix(item.moduleName, moduleNamePrefix) == false {
continue
}
errorList := item.funcDefinition()
if errorList != nil && len(errorList) > 0 {
errList = append(errList, errorList...)
}
}
return
}
func (this *loadMgr) CallAll() (errList []error) {
this.mutex.Lock()
defer this.mutex.Unlock()
for _, item := range this.funcList {
errorList := item.funcDefinition()
if errorList != nil && len(errorList) > 0 {
errList = append(errList, errorList...)
}
}
return
}
systemInitMgr.go
package systemInitMgr
import (
"fmt"
)
var (
serverModuleName = "systemInitMgr"
)
func StartProcess() {
totalErrList := make([]error, 0, 16)
// 从数据库中加载所有的配置数据
loadErrList := LoadMgr.CallAll()
if len(loadErrList) > 0 {
totalErrList = append(totalErrList, loadErrList...)
}
// 验证所有的数据是否合法
verifyErrList := VerifyMgr.CallAll()
if len(verifyErrList) > 0 {
totalErrList = append(totalErrList, verifyErrList...)
}
// 将所有的配置数据进行转换(如果需要)
convertErrList := ConvertMgr.CallAll()
if len(convertErrList) > 0 {
totalErrList = append(totalErrList, convertErrList...)
}
// 将验证后的数据应用到正式数据中
applyErrList := ApplyMgr.CallAll()
if len(applyErrList) > 0 {
totalErrList = append(totalErrList, applyErrList...)
}
// 打印数据
printErrList := PrintMgr.CallAll()
if len(printErrList) > 0 {
totalErrList = append(totalErrList, printErrList...)
}
// 清理数据(临时变量)
cleanErrList := CleanMgr.CallAll()
if len(cleanErrList) > 0 {
totalErrList = append(totalErrList, cleanErrList...)
}
// 设置环境
setupErrList := SetupMgr.CallAll()
if len(setupErrList) > 0 {
totalErrList = append(totalErrList, setupErrList...)
}
// 初始化完成
completeErrList := CompleteMgr.CallAll()
if len(completeErrList) > 0 {
totalErrList = append(totalErrList, completeErrList...)
}
// 检查全部的错误信息
if len(totalErrList) > 0 {
fmt.Println("-------------------------------------------------------------------------------------")
fmt.Printf("There are %d error(s):\n", len(totalErrList))
for i, v := range totalErrList {
fmt.Printf("No.%d: %v\n", i+1, v)
}
panic("Please fix them first.")
}
}
网友评论