--合服逻辑 --[=[ 0.一般在跨服上进行合服, 所以需要把merge目录下所有文件更新到跨服上 1.更新MergeServerDefine.MERGE_DB_TB中要合并的数据库, 并备份数据库, 方法见 MergeServerDefine 2.[可选] 先执行一次start.sh, 再杀掉所有游戏服的进程。因为重启游戏服时会根据条件删除一些数据, 这样合服时要处理的数据就少了一些。 比较前面的游戏服的邮件数据可能有异常, 可先执行/server/cleanExpireMail.sh清理下异常的邮件(需要修改脚本中数据库范围) 3.将跨服的bin*文件 的/script 目录下的启动文件Main.lua暂时改为其他名字, MainMerger改为Main.lua,合服完毕后再改回来。 4.检查MergeServerDefine中的MERGE_DB_TB数据库名是否正确, 如果正确则和正常启动游戏一样启动游戏服, 开始进行合服 5.查看日志oss_merge, 是否有报错, 如果有报错则还原数据, 恢复见方法见 MergeServerDefine 6.修改mysql区服列表的 port, dbName, megre_server字段, 被合服的 port 和 dbname 要改为目标服一样, 被合服的 megre_server字段更新为1。 sql见sdk数据库下查询中的updateMergeServerData(需要修改数据库范围) 7.修改linux上被合服的bin*文件名, 防止 start.sh 启动时会把被合服也启动起来。脚本见/server/changebinName.sh (需要修改脚本中范围) 8.合服完毕, 将步骤3修过的文件名改回原来的名字, 执行start.sh 启动游戏服。 ]=] local LuaMongo = _G.lua_mongo local Config = require("Config") local Log = require("common.Log") local MergeServerDefine = require("merge.MergeServerDefine") local MoZhuExcel = require("excel.mozhu") local MailExcel = require("excel.mail") local TowerConfig = require("excel.huanjingTower").huanjingTower local Util = require("common.Util") local ItemDefine = require("bag.ItemDefine") local HuanJingTowerLogic = require("huanjingTower.HuanjingTowerLogic") local GodsAreaConfig = require("excel.godsArea") local primaryKeyTable = {} --重复_id缓存表 local repeatIdTb = {} --目标服和被合服所有玩家的名字缓存表 local allNameTb = {} --同名数据 local repeatName2Data = {} local num = 0 --次元魔珠数据缓存表 local mozhuDbCache = { roleList = {}, unionList = {}} --烟花加成时间 local firesTimeCache = 0 --恶魔之塔最高层数 local towerMaxLevel = 0 local towerHeadCache = {} local TOWER_LV_HEAD_MAX = HuanJingTowerLogic.TOWER_LV_HEAD_MAX --创建一个mongo连接实例 local function clientMongoDb(addr) addr = addr or Config.DB_IP LuaMongo.client(addr) end --切换数据库 local function chooseMongoDb(dbName) if not dbName or dbName == "" then print(string.format("数据库名错误, dbName = %s\n", dbName)) return end LuaMongo.auth(dbName, Config.DB_USER, Config.DB_PASS) end --生成新的 _id local function generateNewId() local newId = LuaMongo.id() return newId end --清理部分缓存数据 local function cleanCache() primaryKeyTable = {} repeatIdTb = {} allNameTb = {} repeatName2Data = {} mozhuDbCache = { roleList = {}, unionList = {}} firesTimeCache = 0 towerHeadCache = {} end --写日志 local function writeLog(tag, targetDb, sourceDb, sourceColl, info1, info2, docId, errInfo) local logInfo = string.format("tag: %s, targetDb: %s, sourceDb: %s, sourceColl: %s, info1: %s, info2: %s, docId: %s, errInfo: %s", tag, targetDb, sourceDb, sourceColl, info1, info2, docId, errInfo) Log.write(Log.LOGID_OSS_MERGE, logInfo) end local function writeLog2(logStr) Log.write(Log.LOGID_OSS_MERGE, logStr) end -------------------------------------------------------------需要单独处理的部分数据--------------------------------------------------------------------- ---------------------------------次元魔蛛/梼杌------------------------------------ local MozhuQueryFiles = { [MergeServerDefine.KEY_CIYUAN_MOZHU] = 1} --读取 local function loadMoZhuDB(sourceDb, collectionName) local targetCollection = sourceDb .. collectionName LuaMongo.find(targetCollection, nil, MozhuQueryFiles) local data = {} if not LuaMongo.next(data) then return nil end local targetMoZhuData = data.ciyuanMoZhu for uuid, roleData in pairs(targetMoZhuData.role or {}) do mozhuDbCache.roleList[uuid] = roleData end for uuid, unionData in pairs(targetMoZhuData.union or {}) do mozhuDbCache.unionList[uuid] = unionData num = num + 1 end end --随机词条 local function randomCiTiao(data) local weekTime = Util.getWeekStartTime(os.time()) -- if data.citiaoTime and data.citiaoTime == weekTime then -- return -- end data.citiao = {} local citiao_rare_max if not citiao_rare_max then -- 防止策划删减 词条 citiao_rare_max = 0 for k, v in pairs(MoZhuExcel.citiao) do if v then citiao_rare_max = citiao_rare_max + v.quanzhong end end end local getRare = 0 for i=1, 1 do local random = math.random(1, citiao_rare_max - getRare) for k, v in pairs(MoZhuExcel.citiao) do if not data.citiao[k] and random <= v.quanzhong then data.citiao[k] = i getRare = getRare + v.quanzhong break elseif not data.citiao[k] then -- 防止循环最后一个是 已经随机过的 导致 随机不出来 random = random - v.quanzhong end end end data.citiaoTime = weekTime end --初始化魔珠db local function initMoZhuDB(data) data.time = os.time() data.unionRank = {} data.roleRank = {} data.role = {} data.union= {} --词条 randomCiTiao(data) end --插入 local function updateTargetMoZhuDB(targetDb) local collectionName = MergeServerDefine.COLLECTIONS[3] local targetCollection = targetDb .. collectionName LuaMongo.find(targetCollection, nil, MozhuQueryFiles) local data = {} if not LuaMongo.next(data) then return end if not data.ciyuanMoZhu then data.ciyuanMoZhu = {} initMoZhuDB(data.ciyuanMoZhu) end local targetMoZhuData = data.ciyuanMoZhu local roleRankLen = #targetMoZhuData.roleRank local unionRankLen = #targetMoZhuData.unionRank --玩家 for uuid, roleData in pairs(mozhuDbCache.roleList) do targetMoZhuData.role[uuid] = roleData roleRankLen = roleRankLen + 1 targetMoZhuData.roleRank[roleRankLen] = uuid end --公会 for uuid, unionData in pairs(mozhuDbCache.unionList) do targetMoZhuData.union[uuid] = unionData unionRankLen = unionRankLen + 1 targetMoZhuData.unionRank[unionRankLen] = uuid end --排序 table.sort(targetMoZhuData.roleRank, function(a, b) local roleA = targetMoZhuData.role[a] local roleB = targetMoZhuData.role[b] return roleA.hurt > roleB.hurt end) table.sort(targetMoZhuData.unionRank, function(a, b) local unionA = targetMoZhuData.union[a] local unionB = targetMoZhuData.union[b] if unionA.hurt > unionB.hurt then return true elseif unionA.hurt == unionB.hurt then return unionA.time < unionB.time end return false end) --更新 LuaMongo.update(targetCollection, {}, { ["$set"] = { [MergeServerDefine.KEY_CIYUAN_MOZHU] = targetMoZhuData }, }) mozhuDbCache = nil end ----------------------------------烟花加成时间------------------------------------ local FireQueryFiles = { [MergeServerDefine.KEY_FIREWORKBONUS_TIME] = 1} --读取 local function loadFireTimeData(sourceDb, collectionName) local targetCollection = sourceDb .. collectionName LuaMongo.find(targetCollection, nil, FireQueryFiles) local data = {} if not LuaMongo.next(data) then return nil end local now = os.time() local ti = data.fireWorksBonusTime if ti and ti > now then firesTimeCache = firesTimeCache + (ti - now) end end --更新目标库 local function updateTargetFireTimeDB(targetDb) if firesTimeCache > 0 then local collectionName = MergeServerDefine.COLLECTIONS[3] local targetCollection = targetDb .. collectionName LuaMongo.find(targetCollection, nil, FireQueryFiles) local data = {} if not LuaMongo.next(data) then return end local now = os.time() local ti = data.fireWorksBonusTime if not ti or ti < now then ti = now + firesTimeCache else ti = ti + firesTimeCache end local maxTime = now + MergeServerDefine.FIRE_MAX_TIME ti = math.min(ti, maxTime) --更新 LuaMongo.update(targetCollection, {}, { ["$set"] = { [MergeServerDefine.KEY_FIREWORKBONUS_TIME] = ti }, }) end end ----------------------------------恶魔之塔------------------------------------ local queryTowerByLv = { lv = nil } local queryTowerFiles = { headList = 1 } local function calcTblLen(tb) local len = 0 for _,_ in pairs(tb) do len = len + 1 end return len end --加载目标库中tower集合中所有文档的headList local function loadTowerHeadData(targetDb) local collectionName = MergeServerDefine.COLLECTIONS[16] local targetCollection = targetDb .. collectionName towerMaxLevel = #TowerConfig for i=1,towerMaxLevel do queryTowerByLv.lv = i LuaMongo.find(targetCollection, queryTowerByLv, queryTowerFiles) -- 后续优化: 一次全部读取或者批量读取 local data = {} if LuaMongo.next(data) then towerHeadCache[i] = { subNum = TOWER_LV_HEAD_MAX, headList = data.headList } if data.headList and next(data.headList) then local len = calcTblLen(data.headList) if len < TOWER_LV_HEAD_MAX then towerHeadCache[i].subNum = TOWER_LV_HEAD_MAX - len end end end end end -- 把源库tower集合中的headList插入到目标库的headList中 local function insertTargetTowerHeadCache(sourceDb, coll) local targetCollection = sourceDb .. coll for i=1,towerMaxLevel do towerHeadCache[i] = towerHeadCache[i] or {subNum = TOWER_LV_HEAD_MAX, headList = {}} local subNum = towerHeadCache[i] and towerHeadCache[i].subNum or 0 if subNum > 0 then queryTowerByLv.lv = i LuaMongo.find(targetCollection, queryTowerByLv, queryTowerFiles) local data = {} if LuaMongo.next(data) then local headData = data.headList if headData and next(headData) then towerHeadCache[i].headList = towerHeadCache[i].headList or {} local targetHeadList = towerHeadCache[i].headList for k,v in pairs(headData) do targetHeadList[k] = v subNum = subNum - 1 if subNum <= 0 then break end end towerHeadCache[i].subNum = subNum towerHeadCache[i].isChange = true end end end end end --更新目标库的headList local function updateTargetTowerHeadDB(targetDb) local collectionName = MergeServerDefine.COLLECTIONS[16] local targetCollection = targetDb .. collectionName for i=1,#towerHeadCache do local data = towerHeadCache[i] if data.isChange then --更新 queryTowerByLv.lv = i LuaMongo.update(targetCollection, queryTowerByLv, { ["$set"] = { headList = data.headList }}) end end end ---------------------------------------玩家同名------------------------------ local NameQueryFiles = { ["name"] = 1 } --加载目标库所有玩家的名字 local function loadTargetCharName(targetDb) local collectionName = MergeServerDefine.COLLECTIONS[1] local targetCollection = targetDb .. collectionName LuaMongo.find(targetCollection, nil, NameQueryFiles) while true do local data = {} if not LuaMongo.next(data) then break end allNameTb[data.name] = '1' end end --得到一个邮件数据 local mailConfig = MailExcel.mail[MergeServerDefine.MAIL_ID] local function frontMail(receiverUuid) local mail = {} mail.type = 1 mail.receiverUuid = receiverUuid mail.title = mailConfig.title mail.content = mailConfig.content mail.read = nil mail.senderName = mailConfig.title mail.head = 0 mail.fbTime = nil mail.fbContent = nil mail.time = os.time() mail.get = nil mail.flag = 1 mail.items = { { ItemDefine.ITEM_ZUANSHI_ID, MergeServerDefine.MAIL_NUM } } return mail end --给因同名而修改名字的玩家发送补偿邮件 local function sendRepeatNameMail(targetDb) local coll = MergeServerDefine.COLLECTIONS[2] local targetCollection = targetDb .. coll for oldName, v in pairs(repeatName2Data) do local mail = frontMail(v.uuid) local _, err = pcall(LuaMongo.insert, targetCollection, mail) writeLog(MergeServerDefine.CHANGENAME_TAG, targetDb, v.sourceDb, coll, oldName, v.newName, v.uuid, err or "") end end --------------------------------更新合服时间------------------------------------ local function updateMergeTime(targetDb) local targetCollection = targetDb .. MergeServerDefine.COLLECTIONS[3] local now = os.time() LuaMongo.update(targetCollection, {}, { ["$set"] = { [MergeServerDefine.KEY_MERGE_TIME] = now }, }) end --------------------------------诸神圣域----------------------------------------- -- 删掉诸神圣域称号 local function deleteGodsAreaChenghao(db) local chenghaoId = 0 for _, rankCfg in ipairs(GodsAreaConfig.rankList) do if db.chenghaoList and db.chenghaoList[rankCfg.chenghaoID] then chenghaoId = rankCfg.chenghaoID break end end if chenghaoId ~= 0 then db.chenghaoList[chenghaoId] = nil if db.chenghao and db.chenghao == chenghaoId then db.chenghao = nil end local logStr = string.format("删除诸神圣域称号, 玩家newUniqueTag: %s, chenghaoId: %d", db.newUniqueTag, chenghaoId) writeLog2(logStr) end end ------------------------------------------------------------------------------------- -----------------------------------------------------char集合中_id的重复检测和处理------------------------------------------------- --用于检测不同数据库的char集合中的_id是否有重复, 一般来说通过mongo生成的_id基本不会重复(暂时不用) local function primaryKeyRepeatCheck() print("================================检查char集合_id的重复性开始=======================================\n") local collectionName = MergeServerDefine.COLLECTIONS[1] for k, dbName in ipairs(MergeServerDefine.MERGE_DB_TB) do local targetCollection = dbName .. collectionName LuaMongo.find(targetCollection, nil, nil, {_id = 1}) while true do local data = {} if not LuaMongo.next(data) then break end if k ~= 1 then if primaryKeyTable[data._id] then repeatIdTb[data._id] = generateNewId() Log.write(Log.LOGID_OSS_MERGE, string.format("result: %s, targetColl: %s, repeatId: %s, newId: %s", "Repeat", targetCollection, data._id, repeatIdTb[data._id])) else primaryKeyTable[data._id] = '1' end else primaryKeyTable[data._id] = '1' end end end print("================================检查char集合_id的重复性结束=======================================\n") end --好友集合 local function handleFriend(data) if repeatIdTb[data.uuid1] then data.uuid1 = repeatIdTb[data.uuid1] end if repeatIdTb[data.uuid2] then data.uuid2 = repeatIdTb[data.uuid2] end end --邮件集合 local function handleMail(data) if repeatIdTb[data.receiverUuid] then data.receiverUuid = repeatIdTb[data.receiverUuid] end end --公会集合 local function handleUnion(data) local tb = {} for id, v in pairs(data.member) do if repeatIdTb[id] then tb[id] = v end end for id, v in pairs(tb) do data.member[id] = nil data.member[repeatIdTb[id]] = v end tb = {} for id, v in pairs(data.apply) do if repeatIdTb[id] then tb[id] = v end end for id, v in pairs(tb) do data.apply[id] = nil data.apply[repeatIdTb[id]] = v end if data.presidentUuid and repeatIdTb[data.presidentUuid] then data.presidentUuid = repeatIdTb[data.presidentUuid] end end --星空争霸集合 local function handleStar(data) if repeatIdTb[data.uuid] then data.uuid = repeatIdTb[data.uuid] end end --单人竞技场集合 local function handleJJC(data) if not data.monsterOutID and repeatIdTb[data._id] then data._id = repeatIdTb[data._id] end end --战斗视频集合 local function handleVideo(data) local uuid = data.combatInfo.attacker.uuid if repeatIdTb[uuid] then data.combatInfo.attacker.uuid = repeatIdTb[uuid] end end ----------------------------------------------------合并数据---------------------------------------------------------- local function dbCheck() if #MergeServerDefine.MERGE_DB_TB ~= #MergeServerDefine.MERGE_CHECK_TB then print("=================数据异常=============") return end for idx, dbArray in ipairs(MergeServerDefine.MERGE_DB_TB) do local checkArray = MergeServerDefine.MERGE_CHECK_TB[idx] if not checkArray or #checkArray ~= #dbArray then print(string.format("数据库数量与检查数量不相同")) return end for i, dbName in ipairs(dbArray) do local chackTb = checkArray[i] if not chackTb then print(string.format("检查表中缺失数据, idx1: %d, idx2: %d", idx, i)) return end local channelId = chackTb[1] local serverId = chackTb[2] local opType = chackTb[3] if i == 1 and opType ~= 0 then print(string.format("检查表错误, idx1: %d, idx2: %d", idx, i)) return end if i > 1 and opType == 0 then print(string.format("检查表错误, idx1: %d, idx2: %d", idx, i)) return end local dbNameNum = (MergeServerDefine.CHANNEL_2_DBNUMBER[channelId] or 0) + serverId local correctDBName = MergeServerDefine.DB_NAME_STR .. dbNameNum if dbName ~= correctDBName then print(string.format("数据库名错误, idx1: %d, idx2: %d", idx, i)) return end end end return true end local writeQueue = {} local function flushWriteQueue() for _, task in ipairs(writeQueue) do local ok, err = pcall(LuaMongo.insert, task.collection, task.data) if not ok then Log.write(Log.LOGID_OSS_MERGE, task.collection, err) end end writeQueue = {} end -- 每积累100条数据批量写入 local function bufferedInsert(collection, data) table.insert(writeQueue, {collection=collection, data=data}) if #writeQueue >= 100 then flushWriteQueue() end end local function processCollection(sourceDb, targetDb, coll) local sourceCollection = sourceDb .. coll local targetCollection = targetDb .. coll local errNum = 0 local allNum = 0 LuaMongo.find(sourceCollection, nil, {}) while true do local data = {} if not LuaMongo.next(data) then break end --直接插入目标库 local ok, err = pcall(LuaMongo.insert, targetCollection, data) if not ok then errNum = errNum + 1 writeLog(MergeServerDefine.ERR_TAG, targetDb, sourceDb, coll, 1, 1, data._id, err) end allNum = allNum + 1 end writeLog(MergeServerDefine.SUCC_TAG, targetDb, sourceDb, coll, allNum, errNum, "", "") end --合并char集合 local function processCharColl(sourceDb, targetDb, coll, sourceDbTp) local sourceCollection = sourceDb .. coll local targetCollection = targetDb .. coll local errNum = 0 local allNum = 0 LuaMongo.find(sourceCollection, nil, {}) while true do local data = {} if not LuaMongo.next(data) then break end --同名检测 if allNameTb[data.name] then num = num + 1 local newName = MergeServerDefine.NEW_NAME .. num --统计数据 repeatName2Data[data.name] = { uuid = data._id, newName = newName, sourceDb = sourceDb, } --改名 data.name = newName end allNameTb[data.name] = '1' -- 诸神圣域称号处理 if sourceDbTp == 2 then deleteGodsAreaChenghao(data) end --插入目标库 local ok, err = pcall(LuaMongo.insert, targetCollection, data) if not ok then errNum = errNum + 1 writeLog(MergeServerDefine.ERR_TAG, targetDb, sourceDb, coll, 1, 1, data._id, err) end allNum = allNum + 1 end writeLog(MergeServerDefine.SUCC_TAG, targetDb, sourceDb, coll, allNum, errNum, "", "") end local function startMergeServer(dbArray) assert(dbArray and next(dbArray), "====数据错误====") cleanCache() --目标集合数据库名 local targetDb = dbArray[1][1] loadTargetCharName(targetDb) loadTowerHeadData(targetDb) for i = 2, #dbArray do -- local moveOhtherCS = false -- local checkTb = MergeServerDefine.MERGE_CHECK_TB[idx][i] -- if checkTb[3] == 2 then -- moveOhtherCS = true -- end local sourceDb = dbArray[i][1] local opType = dbArray[i][2] LuaMongo.auth(sourceDb, Config.DB_USER, Config.DB_PASS) for _, coll in ipairs(MergeServerDefine.COLLECTIONS) do if not MergeServerDefine.NO_INSERT_COLLECTIONS[coll] then print(string.format("=============开始合并集合, 目标数据库: %s, 源数据库:%s, 集合:%s\n", targetDb, sourceDb, coll)) if coll == MergeServerDefine.COLLECTIONS[1] then processCharColl(sourceDb, targetDb, coll, opType) elseif coll == MergeServerDefine.COLLECTIONS[3] then loadMoZhuDB(sourceDb, coll) loadFireTimeData(sourceDb, coll) elseif coll == MergeServerDefine.COLLECTIONS[16] then insertTargetTowerHeadCache(sourceDb, coll) else processCollection(sourceDb, targetDb, coll) end -- _G.collectgarbage("collect") end end end --处理那些需要单独处理的数据 updateTargetMoZhuDB(targetDb) updateTargetFireTimeDB(targetDb) sendRepeatNameMail(targetDb) updateTargetTowerHeadDB(targetDb) updateMergeTime(targetDb) end local function getDBInfo() for _, chanelId in ipairs(Config.SVR_CHANEL) do if MergeServerDefine.MERGE_CHECK_TB[chanelId] then return MergeServerDefine.MERGE_CHECK_TB[chanelId] end end end local concatTb = { [1] = MergeServerDefine.DB_NAME_STR } local function generateDBName(dbInfo) local channelId = dbInfo[1] local serverId = dbInfo[2] local opType = dbInfo[3] local dbName_Number = MergeServerDefine.CHANNEL_2_DBNUMBER[channelId] + serverId concatTb[2] = dbName_Number local finalName = table.concat(concatTb) print("======generateDBName:", finalName, opType) return finalName, opType end local function generateDBArray() local dbBaseInfo = getDBInfo() if not dbBaseInfo or not next(dbBaseInfo) then return end local dbNameTb = {} for k, infoArray in ipairs(dbBaseInfo) do dbNameTb[k] = dbNameTb[k] or {} local dbTb = dbNameTb[k] for _, dbInfo in ipairs(infoArray) do local dbName, opType = generateDBName(dbInfo) if not dbName or not opType then return end dbTb[#dbTb+1] = {dbName, opType} end end return dbNameTb end --合服开始函数 function Start() -- if not dbCheck() then -- return -- end local dbArray = generateDBArray() if not dbArray or not next(dbArray) then print("生成合服数据库列表失败") return end --创建mongo连接实例 clientMongoDb() --检查char集合_id的重复性 --primaryKeyRepeatCheck() print("========================合服开始=====================") -- for i, dbList in ipairs(MergeServerDefine.MERGE_DB_TB) do -- startMergeServer(dbList, i) -- end for _, dbList in ipairs(dbArray) do startMergeServer(dbList) end print("========================合服结束=====================") end