游戏服务端配置“热更”及“秒启动”终极方案(golang/ygluu/卢益贵)

游戏服务端配置“热更”及“秒启动”终极方案

ygluu  卢益贵

关键词:游戏微服务架构、游戏服务端热更、模块化解耦、golang

目录

一、前言

二、异步线程加载/重载方案

三、配置表碎片化方案

四、指针间接引用

五、重载通知

六、示例代码

七、相关连接


一、前言

众所周知,游戏服务端配置信息热更有几大问题(非lua架构):

1、因配置对象的指针被场景对象引用而导致热更复杂度提高

2、信息量大的配置表热更导致游戏卡顿、玩家闪断

3、一般重载后的配置信息仅影响重载后新创建的对应场景对象,不能影响已存在的场景对象

4、在高度解耦的模块化开发模式下导致热更复杂度提高

本示例代码将使用常用通用的方法来演示在“高度解耦、模块化、模板化”的开发模式下对上述问题的解决方案,并提出游戏服务器秒启动的辅助方案。同时给出了两种方案的完整示例代码下载连接(见后)。

二、异步线程加载/重载方案

异步加载可以完美解决主线程重载大配置文件时可能引起游戏卡顿的现象。见下载连接1

图1

三、配置表碎片化方案

由多行电子表格或者xml格式自动导出一行对应一个ini文件的ini格式,重载时可大大减少重载时间。另外此项方案可应用于游戏秒启动,游戏启动时仅加载必要配置,场景等玩家相关的配置,在玩家登录进入场景以后创建场景对象时再在主线程里同步加载碎片化的配置,碎片化的配置加载时所需时间极短,玩家几乎没有太明显的卡断感知。见下载连接2

当然秒启动还有一个重要方案:主从机方案,即采用主从机方式,在主机崩溃释放所占用端口号后,从机立即夺得相同端口的控制权,从机角色瞬间转变为主机角色

图2

四、指针间接引用

配置对象Cfg引用配置项Item(真正记载配置信息的地方),场景对象Map引用配置对象Cfg,每次Map使用配置信息时间接访问Cfg.Item。配置加载模块执行重载后,在主线程的回调中重置Cfg对象的Item字段,这样既不不影响主线程复杂的场景应用逻辑,又能让已存在的场景对象Map更新到最新的配置信息

图3

五、重载通知

针对部分需要复制配置信息的场景对象,通过主线程同步执行重载回调函数OnReload,这样每个场景对象实例Map1~N都能及时更新到最新的配置信息。

图4

六、示例代码

(一)示例代码下载连接及运行效果

1、示例代码(golang)下载链接:

线程异步加载方案示例代码(1):

游戏服务端配置“热更”终极方案icon-default.png?t=N7T8https://download.csdn.net/download/GuestCode/88977874配置表碎片化加载方案示例代码(2):

游戏服务端配置“热更”及“秒启动”终极方案icon-default.png?t=N7T8https://download.csdn.net/download/GuestCode/88979391

2、示例代码运行效果

重载后会通知所有场景实例更新最新的配置信息。

图5 线程异步加载效果

图6 配置表碎片化重载效果

(二)示例代码结构及源码文件

1、示例代码模块化目录结构

图7 

2、主线程队列代码(异步加载方案)

主线程队列采用了匿名函数队列

package queue/*******************************************************************************Author: Yigui Lu (卢益贵)Contact WX/QQ: 48092788Blog: https://blog.csdn.net/guestcodeCreation by: 2024-3-16*******************************************************************************/// 主线程队列(匿名函数),仅主线程调用,无需同步
var MainQ = make(chan func())// 主线程事件响应函数列表
var events = make(map[string]func())// 触发主线程事件
func TriggerEvent(name string) {MainQ <- func() {on := events[name]if on != nil {on()}}
}// 设置事件监听(在主线程中执行onEvent)
func ListenEvent(name string, onEvent func()) {events[name] = onEvent
}

3、主线程代码(异步加载方案)

package main/*******************************************************************************Author: Yigui Lu (卢益贵)Contact WX/QQ: 48092788Blog: https://blog.csdn.net/guestcodeCreation by: 2024-3-16*******************************************************************************/import (_ "cfgload/mapcfg"_ "cfgload/mapmgr""cfgload/queue"
)func main() {// 模拟游戏主线程for {// 匿名函数队列proc := <-queue.MainQ// 出列并执行proc()}
}

4、地图配置模块示例代码(异步加载方案)

加载和重载共一份逻辑代码。

package mapcfg/*******************************************************************************Author: Yigui Lu (卢益贵)Contact WX/QQ: 48092788Blog: https://blog.csdn.net/guestcodeCreation by: 2024-3-16*******************************************************************************/// 本配置单元可以作为配置模板加载所有配置import ("cfgload/queue""io/ioutil""log""time"
)const ModuleName = "地图配置模块"// 地图配置项,一个对应一个
type Item struct {Name    string // 地图名ResName string // 地址资源名ResData []byte // 资源数据Attrs   []int  // 地图属性
}// 外部类型:地图配置类
type Cfg struct {Item      *Item    // 真正的配置对象是ItemLoadCount int      // 加载次数,演示用onReloads []func() // 触发重载回调,部分场景需求用
}func (c *Cfg) AddOnReload(onReload func()) {c.onReloads = append(c.onReloads, onReload)
}// 配置表
type cfgs = map[string]*Cfg// 配置表(全局变量/外部接口,仅主线程调用无需同步)
var Cfgs cfgs// 配置异步加载协程
var loadcount int// 异步加载/重载通用函数,无需两者区分而额外编写代码
func loadFunc(ch chan bool) {for {// 等待加载通知<-ch// 以下在加载线程中执行 -->isReload := Cfgs != nilloadcount++if isReload {log.Println("地图配置模块(加载线程) => 正在重载......", loadcount)} else {log.Println("地图配置模块(加载线程) => 正在加载......")}var items []*Itemfor {// 模拟配置文件加载过程item := &Item{Name:    "南天门",ResName: "res001.map",}item.ResData, _ = ioutil.ReadFile(item.ResName)items = append(items, item)break // 仅加载一行}// <-- 以上在加载线程中执行// 匿名函数将在主线程执行,达到线程同步目的onModuleLoadedInMain := func() {if Cfgs == nil {// 首次加载Cfgs = make(cfgs)}for _, item := range items {cfg := Cfgs[item.Name]if cfg == nil {// 新增配置cfg = &Cfg{}cfg.Item = itemcfg.LoadCount = 1Cfgs[item.Name] = cfg} else {// 替换旧的配置cfg.Item = itemcfg.LoadCount++// 在主线程调用重载通知回调(只有部分场景需求才需要通知)for _, onReload := range cfg.onReloads {onReload()}}}}if isReload {log.Println("地图配置模块(加载线程) => 重载完毕", loadcount)} else {log.Println("地图配置模块(加载线程) => 加载完毕")}// 给主消息队列压栈// 主线程会调用onModuleLoadedInMain执行,达到线程同步目的queue.MainQ <- onModuleLoadedInMain//  首次加载,触发事件告诉主线程模块加载完毕if !isReload {queue.TriggerEvent(ModuleName)}}
}func reloadTrigger(ch chan bool) {// 模拟5秒钟触发一次重载请求for {ch <- truetime.Sleep(time.Second * 5)}
}func init() {ch := make(chan bool)go loadFunc(ch)go reloadTrigger(ch)
}

5、地图管理模块示例代码(异步加载方案)

package mapmgr/*******************************************************************************Author: Yigui Lu (卢益贵)Contact WX/QQ: 48092788Blog: https://blog.csdn.net/guestcodeCreation by: 2024-3-16*******************************************************************************/import ("cfgload/mapcfg""cfgload/queue""fmt""log"
)type Map struct {cfg   *mapcfg.CfgAttrs []int
}// 间接引用转为直接引用:Map.CfgItem
func (m *Map) CfgItem() *mapcfg.Item {return m.cfg.Item
}// 直接引用配置对象字段示例
func (m *Map) Name() string {return m.cfg.Item.Name
}// 复制配置信息示例(模拟部分需求场景,无需求则可以省略)
func (m *Map) onReloadCopyInfo() {const txt = "地图管理模块(主线程) => 复制配置信息,地图名称:%s,加载次数:%d"log.Println(fmt.Sprintf(txt, m.Name(), m.cfg.LoadCount))// csdn资源未修复此项bugm.Attrs = nilfor _, attr := range m.CfgItem().Attrs {m.Attrs = append(m.Attrs, attr)}
}var Maps = make(map[string]*Map)func createMap() {log.Println("地图管理模块(主线程) => 正在创建地图......")for _, cfg := range mapcfg.Cfgs {mp := &Map{cfg: cfg,}// 无复制配置信息需求时可以省略此步骤cfg.AddOnReload(mp.onReloadCopyInfo)// 为避免代码冗余,首次加载自己调用onReloadCopyInfo复制配置信息mp.onReloadCopyInfo()}log.Println("地图管理模块(主线程) => 创建地图完毕")
}func init() {// 待地图配置首次加载完毕后创建地图queue.ListenEvent(mapcfg.ModuleName, createMap)
}

七、相关连接

对比脚本型和编译型游戏服务器的热更新方案 - codedump的网络日志icon-default.png?t=N7T8https://www.codedump.info/post/20191206-gameserver-hot-refresh/

聊聊Golang游戏服务器的热更 | wudaijun's blogicon-default.png?t=N7T8https://wudaijun.com/2022/08/golang-gameserver-hotfix/

一种基于so的C/C++服务热更新方案_mob604756fea1c5的技术博客_51CTO博客icon-default.png?t=N7T8https://blog.51cto.com/u_15127648/4542189

游戏开发(九) 之 纯 lua 版 热更新 方案_纯lua的热更新方案-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zyxjx1314/article/details/106045843

lua游戏服务器热更新_lua热更函数但不修改变量-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/peter_teng/article/details/52751231

游戏开发中的热更新:一_热更新从服务器上下载的是什么文件-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_40695640/article/details/129463767

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/752682.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Samtec科普 | 一文了解患者护理应用连接器

【摘要/前言】 通过医疗专业人士为患者提供护理的种种需求&#xff0c;已经不限于手术室与医院的各种安全状况。当今许多患者的护理都是在其他环境进行&#xff0c;例如医生办公室、健康中心&#xff0c;还有越来越普遍的住家。尤其是需要长期看护的患者&#xff0c;所需的科技…

202006A卷青少年软件编程(Scratch)等级考试试卷(三级)

第1题:【 单选题】 执行以下脚本后舞台上的角色将 ?( ) A:先克隆自身,克隆体出现后被删除 B:先克隆自身,克隆体出现后删除本体 C:克隆出自身后本体与克隆体同时被删除 D:克隆出自身后本体与克隆体被不会被删除 【正确答案】: A 【试题解析】 : 第2题:【 单选题】…

MATLAB环境下基于决策树和随机森林的心力衰竭患者生存情况预测

近年来&#xff0c;随着医学数据的不断积累和计算机技术的快速发展&#xff0c;许多机器学习技术已经被用在医学领域&#xff0c;并取得了不错的效果。与传统的基于医学知识经验的心衰预后评估模型相比&#xff0c;机器学习方法可以快速、高效地从繁杂的、海量的心衰病人数据中…

SSH 批量免密登录服务器

思路&#xff1a;创建密钥&#xff0c;并通过分发公钥文件到其他服务器&#xff0c;从而实现批量免密无交互登录服务器 拓扑结构&#xff1a;主服务器 |----》从服务器1 |----》从服务器2 |----》..... 参考链接&#xff1a;SSH命令批量操作服务器_ssh批量登录远程执行脚本…

【Liunx-后端开发软件安装】Liunx安装nginx

【Liunx-后端开发软件安装】Liunx安装nginx 使用安装包安装 一、简介 nginx&#xff0c;这个家伙可不是你厨房里的那位大厨&#xff0c;它可是互联网世界的“煎饼果子摊主”。想象一下&#xff0c;在熙熙攘攘的网络大街上&#xff0c;nginx挥舞着它的锅铲——哦不&#xff0c;是…

flask 继续学习

group_by group_by是一种在数据库查询或数据处理中常用的操作&#xff0c;它用于将数据按照指定的列进行分组。通过group_by操作&#xff0c;可以将数据集按照某个列的值进行分类&#xff0c;然后对每个分类进行聚合计算或其他操作。 在SQL语言中&#xff0c;group_by通常与聚…

校招:饿了么一面八股

文章目录 1.new String&#xff08;“abc”&#xff09;存在哪里2.序列化和反序列化3.数据结构中堆和栈的区别4面向对象编程三大特征&#xff0c;分别说明5.子类继承类的方法&#xff0c;使用super调用父类的方法&#xff0c;输出的顺序6.多台7Mysql的存储引擎有什么8mysql中有…

你会怎样爱别人

传统经济学中的纯粹利己模型假定人们试图最大化的效用函数不受其他人效用的影响&#xff0c;其他人的行为都是外生的&#xff0c;不用考虑&#xff0c;因此&#xff0c;是一种极为简化的模型&#xff0c;可以便于建模和运用&#xff0c;其功能与完全竞争模型有点像。 与传统经济…

消息队列面试题

目录 1. 为什么使用消息队列 2. 消息队列的缺点 3. 消息队列如何选型&#xff1f; 4. 如何保证消息队列是高可用的 5. 如何保证消息不被重复消费&#xff08;见第二条&#xff09; 6. 如何保证消息的可靠性传输&#xff1f; 7. 如何保证消息的顺序性&#xff08;即消息幂…

总说上下文切换耗性能,那他到底耗了多少性能?

大家好&#xff0c;我是「云舒编程」&#xff0c;今天我们来聊聊上下文切换性能消耗。 文章首发于微信公众号&#xff1a;云舒编程 关注公众号获取&#xff1a; 1、大厂项目分享 2、各种技术原理分享 3、部门内推 一、前言 众所周知&#xff0c;操作系统是一个分时复用系统&…

.NET高级面试指南专题十九【 数据库设计-4范式】

数据库范式设计是关系数据库设计中的重要概念&#xff0c;旨在减少数据冗余和提高数据的一致性。 范式设计的目的是提高数据库的数据质量、一致性和可维护性。通过将数据结构化为不同的范式&#xff0c;可以降低数据冗余&#xff0c;减少数据更新异常&#xff0c;提高数据的可靠…

Java八股文(MyBatis Plus)

Java八股文のMyBatis Plus MyBatis Plus MyBatis Plus MyBatis Plus 是什么&#xff1f;它与 MyBatis 有什么区别&#xff1f; MyBatis Plus 是基于 MyBatis 进行扩展的一款持久层框架&#xff0c;它提供了一系列增强功能&#xff0c;简化了 MyBatis 的使用。 与 MyBatis 相比…

C++学习基础版(一)

目录 一、C入门 1、C和C的区别 2、解读C程序 3、命名空间 4、输入输出 &#xff08;1&#xff09;cout输出流 &#xff08;2&#xff09;endl操纵符 &#xff08;3&#xff09;cin输入流 二、C表达式和控制语句 1、数据机构 特别&#xff1a;布尔类型bool 2、算数运…

处理Centos 7 中buff/cache高的问题

在CentOS 7中,如果发现 buff/cache 栏目的值过高占用了大量内存,可以尝试以下方法来释放部分缓存: 清理页面缓存 Linux内核会缓存最近使用过的内存页面,以提高访问速度。你可以使用以下命令清理页面缓存: sudo sync && sudo echo 1 > /proc/sys/vm/drop_caches …

Leetcode 3082. Find the Sum of the Power of All Subsequences

Leetcode 3082. Find the Sum of the Power of All Subsequences 1. 解题思路2. 代码实现 题目链接&#xff1a;3082. Find the Sum of the Power of All Subsequences 1. 解题思路 这一题的话其实反而还好&#xff0c;就是一个比较常规的动态规划的题目。 我们首先需要想明…

数据结构的基本框架以及泛型

目录 集合框架复杂度大O的渐进表示法 装包(箱)或者拆包(箱)装包拆包 泛型泛型的上界泛型方法求最大值 集合框架 Java的集合框架,Java Collection Framework 又被称为容器container, 定义在java.util包下的一组 interfaces 和其实现类 classes interface: 接口 abstracb class…

【LeetCode热题100】24. 两两交换链表中的节点(链表)

一.题目要求 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 二.题目难度 中等 三.输入样例 示例 1&#xff1a; 输入&am…

Docker 哲学 - 容器操作 (二)

命令行启动 参数键值之间可以使 " " 或者 空格 卷的挂载是在容器创建时指定的&#xff0c;不能在容器运行时再添加 当加上 --network-alias 设置同一网络下别名参数后 &#xff0c;inspect 该容器发现 会同步到 容器信息中 2、给容器打日志 docker logs 【-…

深度学习_ResNet_5

ResNet学习目标 什么是ResNet为什么要引入ResNet&#xff1f;ResNet网络结构的特点利用ResNet完成图像分类 什么是ResNet&#xff1f; ResNet&#xff08;Residual Network&#xff09;是一种深度残差网络&#xff0c;由何凯明等人在2015年提出&#xff0c;是深度学习领域中一…

Leetcode 31. 删除无效的括号

心路历程&#xff1a; 一开始看到有点懵&#xff0c;后来发现有点像按照一定规则穷举所有可能情况&#xff0c;想到了排列组合问题&#xff0c;再结合问题长度不固定&#xff0c;无法用已知个for循环表示&#xff0c;从而想到了回溯。这个题相当于需要在一定规则下枚举。 按照…