游戏配置二级缓存一致性问题解决方案

游戏服务器进程在启动的时候,一般会把所有策划配置数据加载到内存里,将主键以及对应的记录存放在一个HashMap容器里,这称为一级缓存。部分功能可能还需要缓存其他数据,这些称为二级缓存。举个例子,对于如下的玩家升级表记录

程序缓存level与ConfigPlayerLevel的对应关系,同时也缓存level与needExp的对应关系。

public class ConfigPlayerLevelStorage implements Reloadable {private Map<Integer, ConfigPlayerLevel> levels = new HashMap<>();private Map<Integer, Long> level2Exp = new HashMap<>();@Overridepublic void reload() {String sql = "SELECT * FROM ConfigPlayerLevel";try {List<ConfigPlayerLevel> datas = DbUtils.queryMany(DbUtils.DB_DATA, sql, ConfigPlayerLevel.class);levels = datas.stream().collect(Collectors.toMap(ConfigPlayerLevel::getLevel, Function.identity()));level2Exp = new HashMap<>();levels.forEach((x,y)->{level2Exp.put(x, y.getNeedExp());});} catch (Exception e) {LoggerUtils.error("", e);}}}

其中的reload()方法,代表程序在运行期间进行配置数据重载(这是运营项目是非常常见的)。这段代码初看起来没什么问题,但在程序运行期间遇到线程并发问题就会出现二级缓存状态不一致问题。假设热更新配置的线程在执行”level2Exp = new HashMap<>();“这行代码,level2Exp的数据已经被清空了,但同时业务线程访问level2Exp这个缓存的时候,就会出现level2Exp是完整数据,而level2Exp是初始数据的情况了。

这个问题的解决思路有两种(通过加锁来分离读写的方式不现实,即麻烦又低效)

1,先保证所有缓存的数据已经完整再切换引用,代码如下

@Overridepublic void reload() {String sql = "SELECT * FROM ConfigPlayerLevel";try {List<ConfigPlayerLevel> datas = DbUtils.queryMany(DbUtils.DB_DATA, sql, ConfigPlayerLevel.class);Map<Integer, ConfigPlayerLevel> _levels = datas.stream().collect(Collectors.toMap(ConfigPlayerLevel::getLevel, Function.identity()));Map<Integer, Long> _level2Exp = new HashMap<>();_levels.forEach((x,y)->{_level2Exp.put(x, y.getNeedExp());});this.levels = _levels;       // 临时执行行数1this.level2Exp = _level2Exp; // 临时执行行数2} catch (Exception e) {LoggerUtils.error("", e);}}

如此修改,即使出现线程并发问题,也能保证一级二级缓存的数据是完整的。

但细心的朋友也发现了,假设热更新线程指定到临时执行代码1的时候,业务线程就开始访问level2Exp的数据,这个时候仍然存在状态不一致的问题。(业务线程访问了热更后的一级缓存和热更前的二级缓存,这种情况虽然不甚完美,但在业务上是容许的)

2,直接替换配置容器。说得高大上一点,就是clojure在处理并发所采用的方式,分离标识与状态(程序在获取一个标识的当前状态,无论将来对这个标识怎样修改,获取的那个状态将不再改变。)

当热更新的时候,我们先重新初始化一个新的配置容器,等所有缓存的数据填充完毕之后,再把原先旧的配置容器整个替换掉。参考代码如下:

public class ConfigDataPool {private static ConfigDataPool instance = new ConfigDataPool();public static ConfigDataPool getInstance() {return instance;}private ConcurrentMap<Class<?>, Reloadable> datas = new ConcurrentHashMap<>();/*** 单表重载* @param configTableName 配置表名称*/public boolean reload(String configTableName) {for (Map.Entry<Class<?>, Reloadable> entry : datas.entrySet()) {Class<?> c = entry.getKey();if (c.getSimpleName().toLowerCase().indexOf(configTableName.toLowerCase()) >= 0) {try {// 初始化新的数据容器Reloadable storage = (Reloadable) c.newInstance();// 读取新数据,并缓存storage.reload();// 替换旧容器datas.put(c, storage);return true;} catch (Exception e) {LoggerUtils.error(c.getName() + "配置数据重载异常", e);} break;}}return false;}}

这种方式能完美解决所有缓存的状态一致性问题,可以说无懈可击。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

电脑wifi丢失修复

当你打开电脑突然发现wifi功能不见了&#xff0c;可以先查看一下网卡的状态 在控制面板中找到设备管理器&#xff0c;打开就能找到网络适配器&#xff0c; 我这里是修复过的&#xff0c;wifi丢失后这里可能会显示WALN是丢失的&#xff0c;其他项显示黄色感叹号。 如何修复呢…

跳表是一种什么样的数据结构

跳表是有序集合的底层数据结构&#xff0c;它其实是链表的一种进化体。正常链表是一个接着一个用指针连起来的&#xff0c;但这样查找效率低只有O(n)&#xff0c;为了解决这个问题&#xff0c;提出了跳表&#xff0c;实际上就是增加了高级索引。朴素的跳表指针是单向的并且元素…

第十八章 Redis的使用

文章目录 1. Redis安装2. Redis的使用命令3. python使用redis 1. Redis安装 链接&#xff1a;https://pan.baidu.com/s/1EIGLFjDRxWyy1bU9Hwr_dw?pwdoloh 提取码&#xff1a;oloh 添加环境变量 Redis 启动 在命令行输入&#xff1a;redis-server 命令 设置redis数据库的…

【Vuforia+Unity】AR04-地面、桌面平面识别功能

不论你是否曾有过相关经验&#xff0c;只要跟随本文的步骤&#xff0c;你就可以成功地创建你自己的AR应用。 官方教程Ground Plane in Unity | Vuforia Library 这个功能很棒&#xff0c;但是要求也很不友好&#xff0c;只能支持部分移动设备&#xff0c;具体清单如下&#xf…

【PX4SimulinkGazebo联合仿真】在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化

在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化 系统架构Matlab官方例程Control a Simulated UAV Using ROS 2 and PX4 Bridge运行所需的环境配置PX4&Simulink&Gazebo联合仿真实现方法建立Simulink模型并完成基本配置整体框架各子系统实现…

4.网络游戏逆向分析与漏洞攻防-游戏启动流程漏洞-模拟游戏登陆器启动游戏并且完成注入

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;游戏启动流程的分析 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;bcf7559184863febdcad819e48aaacad9f25d633 代码下…

写一份简单的产品说明书:语言和表达技巧

在当今竞争激烈的市场环境中&#xff0c;一个好的产品说明书对于产品的销售和推广起着至关重要的作用。无论是软件、电子产品还是日用品&#xff0c;一份简洁明了、语言精准的产品说明书能够有效地传达产品的特点和使用方法&#xff0c;吸引消费者的注意力并建立信任感。接下来…

计网 - 域名解析的工作流程

文章目录 Pre引言1. DNS是什么2. 域名结构3. 域名解析的工作流程4. 常见的DNS记录类型5. DNS安全6. 未来的发展趋势 Pre 计网 - DNS 域名解析系统 引言 在我们日常使用互联网时&#xff0c;经常会输入各种域名来访问网站、发送电子邮件或连接其他网络服务。然而&#xff0c;我…

JS知识点学习

构造函数 构造函数语法: 大写字母开头的函数创建构造函数。 // 1.创建构造函数 function Pig(name) { this.name name } // 2. new关键字调用函数 // new Pig(佩奇) // 接受创建的对象 const peppa new Pig(佩奇) console.log(peppa) // {name:佩奇} …

微服务篇之监控

一、为什么要监控 1.问题定位 假设客户端查询一些东西的时候&#xff0c;需要经过网关&#xff0c;然后服务A调用服务H&#xff0c;服务H调用K&#xff0c;服务K调用MySQL&#xff0c;当查询不出来的时候&#xff0c;我们不能快速定位到底是哪个服务的问题&#xff0c;这就需要…

YOLOv5代码解读[02] models/yolov5l.yaml文件解析

文章目录 YOLOv5代码解读[02] models/yolov5l.yaml文件解析yolov5l.yaml文件检测头1--->耦合头检测头2--->解耦头检测头3--->ASFF检测头Model类解析parse_model函数 YOLOv5代码解读[02] models/yolov5l.yaml文件解析 yolov5l.yaml文件 # YOLOv5 &#x1f680; by Ult…

分组统计

目录 分组统计 根据部门编号分组&#xff0c;查询每个部门的编号、人数、平均工资 根据职位分组&#xff0c;统计出每个职位的人数、最低工资与最高工资 如果查询不使用 GROUP BY 子句&#xff0c;那么 SELECT 子句中只允许出现统计函数&#xff0c;其他任何字段不允许出现…

Global Gamers Challenge | 与 Flutter 一起保护地球

作者 / Kelvin Boateng 我们知道 Flutter 开发者热爱挑战&#xff0c;因此我们很高兴地宣布&#xff0c;新一轮的 Flutter 挑战赛来了&#xff01; 挑战https://flutter.cn/events/puzzle-hack Global Gamers Challenge 是一项为期 8 周的比赛&#xff0c;参赛者需要设计、构建…

【数据结构】单向循环链表

一、mian函数 #include <stdio.h> #include "./3.looplinklist.h" int main(int argc, const char *argv[]) {looplinklist* head create_looplinklist();insertHead_looplinklist(head,100);insertHead_looplinklist(head,200);insertHead_looplinklist(hea…

观察者模式和发布订阅模式的区别

从下图中可以看出&#xff0c;观察者模式中观察者和目标直接进行交互&#xff0c;而发布订阅模式中统一由调度中心进行处理&#xff0c;订阅者和发布者互不干扰。这样一方面实现了解耦&#xff0c;还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息&#xff0c;但…

《Linux C编程实战》笔记:消息队列

消息队列是一个存放在内核中的消息链表&#xff0c;每个消息队列由消息队列标识符标识。与管道不同的是消息队列存放在内核中&#xff0c;只有在内核重启&#xff08;即操作系统重启&#xff09;或显示地删除一个消息队列时&#xff0c;该消息队列才会被真正的删除。 操作消息…

使用python构建Android,探索跨平台应用开发Kivy框架

使用python构建Android&#xff0c;探索跨平台应用开发Kivy框架 1. 介绍Kivy框架 Kivy是什么&#xff1f; Kivy是一个开源的Python跨平台应用程序开发框架&#xff0c;旨在帮助开发者快速构建创新的、可扩展的移动应用和多点触控应用。Kivy采用MIT许可证&#xff0c;允许开发…

⭐北邮复试刷题106. 从中序与后序遍历序列构造二叉树__递归分治 (力扣每日一题)

106. 从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postor…

Git笔记——2

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、撤销修改__情况一 二、撤销修改__情况二 三、撤销修改__情况三 四、删除文件 五、理解分支 六、创建、切换和合并分支初体验 七、删除分支 八、合并冲突 总…

cms内容管理系统drupal简析

Drupal CMS是一个免费、开源的内容管理系统。 1、我们可以下载一个xampp客户端&#xff0c;方便打开apache 然后在drupal官网上下载一个版本的drupal代码&#xff0c;将其放在xampp\htdocs的目录下&#xff0c;这里我将下载的文件命名为drupal9。 2、在网页里输入localhost\…