| | |
| | | #"""Version = 2025-05-09 12:20"""
|
| | | #-------------------------------------------------------------------------------
|
| | |
|
| | | import CommFunc
|
| | | import DBStruct
|
| | | import ChConfig
|
| | | import GameWorld
|
| | | import PyGameData
|
| | | import DBEventTrig
|
| | | import PyMongoMain
|
| | | import DBPlayerViewCache
|
| | | import DBEventTrig
|
| | | import DBBillboard
|
| | | import DBFuncTeam
|
| | | import DBGameRec
|
| | | import DBFamily
|
| | | import DBMail
|
| | |
|
| | | import binascii
|
| | | import time
|
| | | import zlib
|
| | | import os
|
| | |
|
| | | BakRoot = "C:\ServerRealTimeBackup"
|
| | | BakFileType = ".bak"
|
| | | BakFileType = ".rtb"
|
| | | BackupCopyMax = 5 # 保留历史备档文件数
|
| | | BackupInterval = 30 # 备档频率,分钟,暂定每30分钟
|
| | |
|
| | | #临时变量,之后优化
|
| | | g_loadErr = False
|
| | | Map2ServerID = {
|
| | | 10010:87,
|
| | | 10090:89,
|
| | | }
|
| | | |
| | | def OnServerStart():
|
| | | ##启动服务器数据处理
|
| | | GameWorld.DebugLog("地图服务器启动")
|
| | | LoadServerDataBackup()
|
| | | if not LoadGameWorldDataFromBackup():
|
| | | LoadGameWorldDataFromDB()
|
| | | return
|
| | |
|
| | | def OnServerClose():
|
| | | ##关服服务器数据处理
|
| | | SaveGameWorldDataToDB()
|
| | | return
|
| | |
|
| | | def OnMinute(curTime):
|
| | | curMinute = curTime.minute
|
| | | ServerDataBackup()
|
| | | DBFamily.OnMinute(curMinute)
|
| | | if PyGameData.g_serverClosing:
|
| | | return
|
| | | DBFamily.OnMinute()
|
| | | DBBillboard.OnMinute()
|
| | | |
| | | #备档、入库放最后,等数据都处理完后再保存,每天 [[时,分], ...],尽量岔开整点、5分
|
| | | if [curTime.hour, curTime.minute] in [[3, 6]]:
|
| | | BackupGameWorldData(True)
|
| | | else:
|
| | | BackupGameWorldData(False)
|
| | | return
|
| | |
|
| | | #------------------------------------------- 备档 ---------------------------------------------------
|
| | | def GetBakFileSortList(bakPath):
|
| | | ## 按备档时间倒序排序返回 [[bakTime, filePath], ...]
|
| | | ## 按备档时间倒序排序返回 [[bakTime, 数据版本号, filePath], ...]
|
| | | bakFileList = []
|
| | | for parent, _, filenames in os.walk(bakPath):
|
| | | for filename in filenames:
|
| | | if not filename.endswith(BakFileType):
|
| | | continue
|
| | | fullPath = os.path.join(parent, filename)
|
| | | bakTime = GameWorld.ToIntDef(filename[:filename.index(".")].split("_")[1])
|
| | | bakFileList.append([bakTime, fullPath])
|
| | | bakInfo = filename[:filename.index(".")].split("_")
|
| | | if len(bakInfo) != 3:
|
| | | continue
|
| | | dataVersionNO = GameWorld.ToIntDef(bakInfo[1]) # 数据版本号
|
| | | bakTime = GameWorld.ToIntDef(bakInfo[2]) # yyyyMMddhhmmss
|
| | | bakFileList.append([bakTime, dataVersionNO, fullPath])
|
| | | bakFileList.sort(reverse=True)
|
| | | return bakFileList
|
| | |
|
| | | def LoadServerDataBackup():
|
| | | ## 服务器公共数据备档加载
|
| | | global g_loadErr
|
| | | mapID = GameWorld.GetMap().GetMapID()
|
| | | if mapID not in Map2ServerID:
|
| | | return
|
| | | serverName = "S%s" % Map2ServerID[mapID]
|
| | | def LoadGameWorldDataFromBackup():
|
| | | ## 备档加载服务器公共数据
|
| | | # @return: 是否存在备档成功加载
|
| | | serverID = GameWorld.GetGameWorld().GetServerID()
|
| | | serverName = "S%s" % serverID
|
| | | BakDir = os.path.join(BakRoot, serverName)
|
| | | GameWorld.Log("加载备档: %s" % BakDir)
|
| | | bakFileList = GetBakFileSortList(BakDir)
|
| | | if not bakFileList:
|
| | | GameWorld.Log("不存在备档!")
|
| | | return
|
| | | bakFilePath = bakFileList[0][1] # 取第一个为最近的一次备档
|
| | | finalBakInfo = bakFileList[0] # 排序后的第一个为最新备档数据
|
| | | bakVersionNO = finalBakInfo[1]
|
| | | bakFilePath = finalBakInfo[2]
|
| | | if bakVersionNO != DBStruct.GAMEWORLD_DATA_VERSION_NO:
|
| | | GameWorld.ErrLog("备档数据版本号校验失败,请使用正确版本服务器启动重新导入数据! bakVersionNO=%s, GAMEWORLD_DATA_VERSION_NO=%s" |
| | | % (bakVersionNO, DBStruct.GAMEWORLD_DATA_VERSION_NO))
|
| | | raise
|
| | | |
| | | GameWorld.Log("读取备档: %s" % bakFilePath)
|
| | |
|
| | | f = open(bakFilePath, 'rb')
|
| | | compressed_data = f.read().strip()
|
| | | compressed_data = f.read()
|
| | | f.close()
|
| | |
|
| | | try:
|
| | | decompressed_data = zlib.decompress(compressed_data)
|
| | | bakData = binascii.a2b_hex(decompressed_data)
|
| | | except:
|
| | | g_loadErr = True
|
| | | raise |
| | | saveData = CommFunc.Decompress(compressed_data)
|
| | | if not saveData:
|
| | | raise
|
| | |
|
| | | LoadPyGameData(bakData, 0)
|
| | | return
|
| | | LoadPyGameData(saveData, 0)
|
| | | SaveGameWorldDataToDB(saveData) # 如果是从备档读取的则直接入库
|
| | | return True
|
| | |
|
| | | def ServerDataBackup():
|
| | | def BackupGameWorldData(saveToDB=False):
|
| | | ## 服务器公共数据定时备档,暂时直接存盘
|
| | | if g_loadErr:
|
| | | GameWorld.ErrLog("加载备档已异常,暂时不在存储备档")
|
| | | # @param saveToDB: 是否附带入库,为确保备档数据存在时一定优于db库数据,故常规入库时必须附带先备档一次
|
| | | if PyGameData.g_serverClosing:
|
| | | return
|
| | | mapID = GameWorld.GetMap().GetMapID()
|
| | | if mapID not in Map2ServerID:
|
| | | if not PyGameData.g_loadDataOK:
|
| | | GameWorld.Log("加载数据未完成,不存储备档")
|
| | | return
|
| | | serverName = "S%s" % Map2ServerID[mapID]
|
| | | curTime = int(time.time())
|
| | | if saveToDB:
|
| | | if curTime - PyGameData.g_lastRTBTime < BackupInterval * 60:
|
| | | GameWorld.Log("备档冷却中!")
|
| | | return
|
| | | |
| | | serverID = GameWorld.GetGameWorld().GetServerID()
|
| | | serverName = "S%s" % serverID
|
| | | BakDir = os.path.join(BakRoot, serverName)
|
| | | BackupCopyMax = 3
|
| | | GameWorld.Log("服务器备档: %s" % serverName)
|
| | |
|
| | | if not os.path.exists(BakDir):
|
| | | os.makedirs(BakDir)
|
| | |
|
| | | curTime = int(time.time())
|
| | | bakFilePath = os.path.join(BakDir, "%s_%s%s" % (serverName, curTime, BakFileType))
|
| | | bakData = GetSavePyData()
|
| | | GameWorld.Log("Bak:%s, len=%s, %s" % (serverName, len(bakData), bakFilePath))
|
| | | dataVersionNO = DBStruct.GAMEWORLD_DATA_VERSION_NO
|
| | | bakTiemStr = GameWorld.ChangeTimeNumToStr(curTime, ChConfig.TYPE_Time_Format_YmdHMS)
|
| | | bakFilePath = os.path.join(BakDir, "%s_%s_%s%s" % (serverName, dataVersionNO, bakTiemStr, BakFileType))
|
| | | saveData = GetSavePyData()
|
| | | GameWorld.Log("Bak:%s, len=%s, %s" % (serverName, len(saveData), bakFilePath))
|
| | | compressed_data = CommFunc.Compress(saveData) # 备档数据才进行压缩
|
| | | if not compressed_data:
|
| | | GameWorld.SendGameError("ServerDataBackupError")
|
| | | return
|
| | | GameWorld.Log("compress len=%s" % len(compressed_data))
|
| | | |
| | | try:
|
| | | compressed_data = zlib.compress(bakData, 9) #最大压缩
|
| | | GameWorld.Log("compress len=%s" % len(compressed_data))
|
| | | fp = open(bakFilePath, "wb")
|
| | | fp.write(compressed_data)
|
| | | fp.close()
|
| | | except:
|
| | | return
|
| | |
|
| | | PyGameData.g_lastRTBTime = curTime
|
| | | bakFileList = GetBakFileSortList(BakDir)
|
| | | # 删除多余备档
|
| | | for _, filePath in bakFileList[BackupCopyMax:]:
|
| | | for bakInfo in bakFileList[BackupCopyMax:]:
|
| | | filePath = bakInfo[-1]
|
| | | os.remove(filePath)
|
| | | GameWorld.Log("删除多余备档文件: %s" % filePath)
|
| | |
|
| | | if saveToDB:
|
| | | SaveGameWorldDataToDB(saveData)
|
| | | return
|
| | |
|
| | | def ClearBackupFile():
|
| | | ## 清空备档文件
|
| | | serverID = GameWorld.GetGameWorld().GetServerID()
|
| | | serverName = "S%s" % serverID
|
| | | BakDir = os.path.join(BakRoot, serverName)
|
| | | GameWorld.Log("清空备档: %s" % BakDir)
|
| | | CommFunc.DelFolder(BakDir, True)
|
| | | return
|
| | |
|
| | | #--------------------------------------------------------------------------------------------------
|
| | |
|
| | | def LoadGameWorldDataFromDB():
|
| | | ## 直接从db读取公共数据
|
| | | GameWorld.Log("没有备档数据,直接从db加载数据!")
|
| | | serverData = PyMongoMain.GetUserCtrlDB().readGameWorldData()
|
| | | LoadPyGameData(serverData, 0)
|
| | | return
|
| | |
|
| | | def SaveGameWorldDataToDB(saveData=""):
|
| | | ## 公共数据入库
|
| | | if not saveData:
|
| | | saveData = GetSavePyData()
|
| | | PyMongoMain.GetUserCtrlDB().saveGameWorldData(saveData)
|
| | | return
|
| | |
|
| | | def GetSavePyData():
|
| | |
|
| | |
| | | pyGameDataMgr = GetDBDataMgr()
|
| | | result = pyGameDataMgr.GetSaveData()
|
| | | GameWorld.Log("GetSavePyData!! id = %s-%s"%(id(pyGameDataMgr), len(result)))
|
| | | result = binascii.b2a_hex(result)
|
| | | #GameWorld.DebugLog("GetSavePyData!! result = %s-%s"%(result, len(result)))
|
| | | # 字节码在C++转化会发生错误must be string without null bytes, not str,但是可以正常保存,错误会在下次调用便宜接口才会触发
|
| | | # 暂时改成字符串返回,暂无解决方案
|
| | | return result
|
| | | # return str(len(result)) + "|" + result
|
| | |
|
| | | def LoadPyGameData(gameBuffer, pos):
|
| | | pyGameDataMgr = GetDBDataMgr()
|
| | |
| | | #加载数据后,一些功能转化为功能业务数据
|
| | | #...
|
| | |
|
| | | |
| | | #放最后
|
| | | PyGameData.g_loadDataOK = True
|
| | | return pos
|
| | |
|
| | | class PyGameDataManager(object):
|
| | | def __init__(self):
|
| | | self.EventTrigMgr = DBEventTrig.EventTrigMgr()
|
| | | self.PlayerViewCacheMgr = DBPlayerViewCache.PlayerViewCacheMgr()
|
| | | self.FamilyMgr = DBFamily.FamilyMgr()
|
| | | self.BillboardMgr = DBBillboard.BillboardMgr()
|
| | | self.MailMgr = DBMail.MailMgr()
|
| | | self.FamilyMgr = DBFamily.FamilyMgr()
|
| | | self.GameRecMgr = DBGameRec.GameRecMgr()
|
| | | self.FuncTeamMgr = DBFuncTeam.FuncTeamMgr()
|
| | | return
|
| | |
|
| | | def GetSaveData(self):
|
| | | buff = ""
|
| | | buff += self.EventTrigMgr.GetSaveData()
|
| | | buff += self.PlayerViewCacheMgr.GetSaveData()
|
| | | buff += self.FamilyMgr.GetSaveData()
|
| | | buff += self.BillboardMgr.GetSaveData()
|
| | | buff += self.MailMgr.GetSaveData()
|
| | | buff += self.FamilyMgr.GetSaveData()
|
| | | buff += self.GameRecMgr.GetSaveData()
|
| | | buff += self.FuncTeamMgr.GetSaveData()
|
| | | return buff
|
| | |
|
| | | def LoadGameData(self, gameBuffer, pos):
|
| | | dataslen = len(gameBuffer)
|
| | | pos = self.EventTrigMgr.LoadPyGameData(gameBuffer, pos, dataslen)
|
| | | pos = self.PlayerViewCacheMgr.LoadPyGameData(gameBuffer, pos, dataslen)
|
| | | pos = self.FamilyMgr.LoadPyGameData(gameBuffer, pos, dataslen)
|
| | | pos = self.BillboardMgr.LoadPyGameData(gameBuffer, pos, dataslen)
|
| | | pos = self.MailMgr.LoadPyGameData(gameBuffer, pos, dataslen)
|
| | | pos = self.FamilyMgr.LoadPyGameData(gameBuffer, pos, dataslen)
|
| | | pos = self.GameRecMgr.LoadPyGameData(gameBuffer, pos, dataslen)
|
| | | pos = self.FuncTeamMgr.LoadPyGameData(gameBuffer, pos, dataslen)
|
| | | return pos
|
| | |
|
| | | def GetDBDataMgr():
|
| | |
| | | dbDataMgr = GetDBDataMgr()
|
| | | return dbDataMgr.MailMgr
|
| | |
|
| | | def GetBillboardMgr():
|
| | | ## 排行榜数据管理器
|
| | | dbDataMgr = GetDBDataMgr()
|
| | | return dbDataMgr.BillboardMgr
|
| | |
|
| | | def GetGameRecMgr():
|
| | | ## 通用记录数据管理器
|
| | | dbDataMgr = GetDBDataMgr()
|
| | | return dbDataMgr.GameRecMgr
|
| | |
|
| | | def GetFuncTeamMgr():
|
| | | ## 功能队伍数据管理器
|
| | | dbDataMgr = GetDBDataMgr()
|
| | | return dbDataMgr.FuncTeamMgr
|