Organizations

  • Akka Actor服务 这里按照官方说明直接采用多项目 Maven + JDK17 设置, 基础的父根目录框架名为 fusion-framework 需要注意其实还有热更新方案等考虑, 但是这里基于 websocket 对于服务热更要求不高所以直接跳过 另外还需要注意: akka 在 2.6.x 之后的版本转向闭源付费, 仅允许在开发和非生产系统中免费使用, 如果为了规避商业行为清尽量采用 2.6.x 版本二次开发. 或者采用后续开源版本: Pekko <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.meteorcat.fusion</groupId> <artifactId>fusion-framework</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <!-- 全局属性 --> <properties> <java.version>17</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>3.3.10</spring-boot.version> <maven-compiler.version>3.13.0</maven-compiler.version> <lombok.version>1.18.36</lombok.version> <!-- 2023.0.x 又名 Leyton SpringBoot 3.3.x, 3.2.x --> <spring-cloud.version>2023.0.2</spring-cloud.version> <!-- akka3 Actor --> <akka3.version>2.6.21</akka3.version> <scala.binary.version>2.13</scala.binary.version> <logback.version>1.5.18</logback.version> </properties> <!-- 子模块定义 --> <modules> <module>fusion-actor</module> <!-- Actor集群项目 --> </modules> <!
    Java 部署 Created Thu, 17 Apr 2025 22:29:39 +0800
  • Linux应用打包 这里的Linux打包其实总共分以下几类, 更多是方便内网一键部署的情况: Debian(.deb) 系列包系统: dpkg -i xxx.deb(安装)|dpkg -r xxx(卸载) RedHat(.rpm) 系列包系统: rpm -ivh xxx.rpm(安装)|rpm -e xxx.rpm(卸载) Docker 直接编写 Dockerfile 和 docker-compose 部署 一般来说如果是打包有分为 命令行 和 桌面应用, 这里主要是用于部署服务端所以采用命令行操作 这里需要先采用 debian 打包方式说明, 适用于 Debian|Ubuntu 系列的虚拟环境, 其他因为不常用所以后续再补充. deb打包 实际上需要按照以下命令来做打包: # 按照文件夹规则放置到其中 dpkg -b 文件夹名称 安装包名称 # 比如以下方式打包, fusion-gateway 是当前目录下的子目录 dpkg -b fusion-gateway fusion-gateway_1.0_amd64.deb 按照以下流程测试打包名为 fusion-gateway 的应用, 该应用基于 Java17-Jre 依赖启动: # 创建并进入目录之中, 注意 DEBIAN 目录是必须的 mkdir -p fusion-gateway/DEBIAN && cd fusion-gateway/DEBIAN # 之后就是包信息文件 control # 关键字首字母大写, 冒号后面必须有空格 # 必填字段: Package、Version、Architecture、Maintainer、Description,且内容不能为空 touch control # postinst(安装后脚本) | prerm(卸载前脚本) # 注意权限必须要 <= 775, 否则会出现包安装失败 touch postinst && chmod 755 postinst touch prerm && chmod 755 prerm 这里为了演示移动文件, 所以下载个 MIT 许可文件放入依赖目录安装之后自动创建目录和移动文件, 文件地址为: MIT License, 下载或者复制内容之后命名为 License.
    部署 Created Sat, 29 Mar 2025 18:37:18 +0800
  • 微服务API网关设计 当项目负载规模不大的时候, 基本上单个项目 http://域名/user 和 http://域名/pay 这样访问API就行了, 在之后如果项目规模开始上来最多用 nginx 负载均衡处理一下分流到不同服务器, 但是后续功能业务和流量大规模上来之后也会到达瓶颈. 按照业务程度分布起始就这以下阶段: 简单实现基础 api 功能, 业务功能比较少且接口简单 流量上来需要对请求限流和负载均衡, 与此同时业务接口还是在可控可维护范围 不止接口流量庞大, 同时业务也规模上去(用户模块,订单模块,统计模块,广告模块,….将近上千多个), 这时候单项目维护就很麻烦了 当超大流量的情况把功能集中在单个项目里面, 哪怕按照目录区分(/user,/pay,/activity,...) 也是很耗费精力的事, 所以需要做模块拆分. 拆分出来之后很可能内部不同地址需要统一的网关服务器, 网关负责接收外部请求统一入口并协调转发到内网服务. 对于内网来说就是编写模块下业务并且注册到网关提供服务, 这样的好处就是隔离不同业务服务并且支持热更, 如果某个服务请求过大卡住的时候不会直接影响到其他服务, 只要不是同个模块下的服务都互不影响从而方便开发针对业务调试. 这里也引申出微架构的主要结构: 网关 和 模块 网关负责模块的服务注册和转发, 利用 eureka 可以更加方便做服务注册中心来处理服务重连等情况 模块就是具体业务分层, 启动的时候注册到网关来暴露自己的业务功能, 利用 springCloud 可以直接无缝接入到 eureka 以下方案也是直接采用 eureka 和 springCloud, 同时最低Java版本为17 配置多模块 直接在项目目录下追加 pom.xml, 文件内容如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.meteorcat.fusion</groupId> <artifactId>micro</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <!-- 全局属性 --> <properties> <java.
    Java 部署 Created Fri, 28 Mar 2025 23:48:33 +0800
  • 事故起源其他游戏服务端开发程序开发的时候, 可能为了测试游戏功能临时屏蔽数据上报模块代码从而导致上报数据不完整, 但是在提交游戏功能的时候连带将这部分屏蔽上功能一起同步; 而负责 Code Review 的老司机在更新业务代码的时候没有留意到这部分异常的上报模块, 随即同步到正式服上导致代码同步正式服务器差不多两天的时候上报统计数据模块服务直接告警数据全部关联不上数据. 我们数据上报采用第三方私有部署的 kafka 服务, 主要上报数据问题集中在屏蔽的那部分上报代码缺失玩家 player_id, 体现在统计系统上面就是数据异常归因从而影响整个运营统计功能的正常运行. 不过不幸中的万幸就是上报虽然丢失 玩家ID(player id) 的唯一关联, 但是幸好内部还有 服务器ID(sid) + account(账号信息), 通过两个字段可以反查 玩家ID(player id) 将数据修改重新修正更新上报应该就没有太大问题了, 但是事情真的能够这么顺利吗? 当时计划采用的修复方案如下: 创建 kafka 结果保存临时表 通过匹配字段JSON关键字导出数据到临时表 最后本地导出这些临时表下载本地用脚本修复重新补充 player_id 上报 看起来很理想的方案但实际操作就是一场灾难, 从上报事件的设计到导出数据都充满了苦涩, 总结起来就是以下问题: 导出丢失的游戏单天道具流水记录将近快 100GB+, 甚至都快把服务器硬盘空间撑爆 字段内部采用 JSON 格式保存, 只能采用 LIKE '%event:item_change%' 方式模糊匹配筛选数据 数据散乱在 kafka 不同节点上联合查询, 没办法直接单体数据这样 UPDATE, 只能重新上报附带 uuid 让其更新数据, 上报数据量惊人 另外必须要说下我最讨厌的命名方式: change_item 和 change_items 类似事件单复数命名, 在文本和日志做匹配如 WHERE field LIKE '%change_item%' 和 file.log| grep change_item 简直就是大灾难,
    事故现场 Created Sat, 01 Mar 2025 00:24:07 +0800
  • 状态定义 这里其实主要对于数据库的状态字段定义, 基本上有以下几种定义: is_del(1|0): bit(1) 或者 tinyint(1) 方式做的 是|否 类似 bool 处理 delete_time(0|timestmap): 删除时间, 如果为0代表没删除, 非0代表删除时间 state(0|number): unsigned 的状态值支持多种状态递增变化 数据库表状态字段这几种方式各有各的好处, 但是这里首先不推荐 is_xxx 方式定义字段. 之所以不推荐是因为首先 is_xxx 这种定义单个状态作用太小且占用表字段列空间; 比如今天运营需要 is_del, 明天需要 is_check(审核删除), 后天需要 is_lock(锁定操作) 等字段, 这里字段随着业务不断扩展出来会导致表结构需要不停更改不断去 ADD Column. 在数据业务量少的情况每次变动的时候可能没什么, 但是后续单表数据量膨胀的时候需要追加动到表结构字段的情况就很恐怖. 并且对于 is_xxx 这种字段其实还有个问题就是 Kotlin 引入 JPA 的时候会出现解析错误, 需要自定义处理 @Column(name = "is_del") 重命名实体类字段名并让其不要以 is 开头的作为实体字段. 关于 kotlin 的 is_ 开头字段就是被坑过, 所以后续默认不采用这种方式 而 delete_time 其实主要问题就是状态类型单一已被定义成 delete, 只能表示单种状态处理没办法处理后续多种状态, 所以推荐的方式其实采用 state 单个字段处理: CREATE TABLE `test_1` ( `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', `state` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '状态码, 采用 TINYINT3 方便后续扩展', PRIMARY KEY (`id`) USING BTREE ) COMMENT ='测试表1' COLLATE = 'utf8mb4_unicode_ci' UNSIGNED TINYINT(3) 取值 0~255 基本上已经足够日常使用, 一般状态数据不会超过这么大的值且占用也不用那么高.
    架构 Created Sat, 15 Feb 2025 18:24:42 +0800
  • 初始项目搭建 这里实际上参照之前 Vue+ElementUI脚手架搭建 速成搭建项目, 但是需要对自身定制化处理时候就太过复杂, 裁剪之后发现太多没用的依赖并且业务依赖也比较复杂, 所以审视之后发现还是自己重新初始化再编写业务方便. 另外默认 ElementUI 是没有 CssBaseline 的 CSS 初始化组件, 并且找到模板也是依赖 iframe 渲染 基本上先引入以下这些已经足够编写基础框架, 后续就是按照自己方法扩展: # 构建个Vue项目, 这里构建 fusion-admin 基础库 npm create vue@latest # 正式依赖 npm install mitt vue-router pinia pinia-plugin-persistedstate axios echarts lodash-es --save # 开发依赖 npm install @tsconfig/node22 @types/node @vitejs/plugin-vue @vue/eslint-config-prettier @vue/eslint-config-typescript @vue/tsconfig eslint eslint-plugin-vue jiti npm-run-all2 prettier typescript vite vite-plugin-vue-devtools vue-tsc unplugin-auto-import unplugin-vue-components @types/lodash-es esbuild --save-dev 最后生成主要配置 package.json : { "name": "fusion-admin", "version": "0.
    vue Created Sun, 26 Jan 2025 20:52:24 +0800
  • Vue+ElementUI脚手架搭建 因为最近需要渠道商需要单独处理管理系统提供对账处理, 所以需要速成搭建项目且公司内部主要技术栈在 Vue+ElementUI 上, 所以最后项目选型就确定下来开始速成开发. 所以直接从头构建配置好项目应该没问题: # 安装 vue-cli 命令行工具进入项目初始化配置 npm create vue@latest # 构建项目 fusion-vue 就行 # ✔ Project name(项目目录): … fusion-vue # ✔ Add TypeScript?(是否采用typescript,看个人习惯) … No # ✔ Add JSX Support?(是否支持JSX, 一般不需要) … No # ✔ Add Vue Router for Single Page Application development?(VueRouter单页开发,后续手动追加) … No # ✔ Add Pinia for state management?(引入Pinia状态管理) … No # ✔ Add Vitest for Unit testing?(Vitest作为单元测试) … No # ✔ Add an End-to-End Testing Solution?
    vue Created Fri, 24 Jan 2025 21:18:05 +0800
  • 日志分表分页 因为公司数据日志打点十分频繁, 并且涵盖有多个条件筛选的情况, 并且采用了数据分表的出来数据写入. 更加主要问题是每日单表数据递增速度在最低 50G 以上, 并且如果用户量越高递增的数据流越高, 还有更大的问题就是数据格式不稳定. 内部上报数据是 JSON 格式数据, 高版本 MySQL 支持的 JSON 数据结构选用这种类型方便对后续数据过滤匹配筛选 而日志上报数据后台需要可以通过 年月日时分秒 这种条件精确筛选入库时间, 后续还要支持其他复杂条件精确检索. 这里以简单打点日志表 step_log 做说明, 后台进程会采用分表入库 step_log_20241201,step_log_20241202,.... 入库, 那么后续需要检索分页数据就需要费时处理下, 可以假设表结构字段如下: player_id: 玩家ID sid: 服务器ID time: 入库时间戳 action: 行为id val: 上报数据 JSON, 以 {} 数据格式为主 需要说明这里的 val 数据内部 JSON 内部数据会按照频繁度, 从里面升级为表字段; 比如表内部带有 { "channel":1001 } 字段是运营迫切需要的, 那么后续会将 JSON 内字段 晋升 为表字段, 追加上索引之后能够更快加速表数据检索的速度. 之后数据分页也需要利用 UNION ALL 来做表合并, 因为日志表一般数据结构是固定, 所以可以直接将所有表连接查询: # 假设用户需要查询 step_log_20241201 ~ step_log_20241207 的表数据 # 首选需要判断表是否存在, 去掉那些不存在的表 SHOW TABLES LIKE 'step_log_20241201' # 之后通过检索出存在合表数据总量 SELECT COUNT(1) AS `total` FROM step_log_20241201 UNION ALL SELECT COUNT(1) AS `total` FROM step_log_20241202; # 其他表追加 UNION ALL .
    架构 Created Thu, 23 Jan 2025 23:44:48 +0800
  • 强关联架构 之前说过强关联数据的复杂情况( 涵盖有交叉切库和多表关联的关系 ), 当时想着早晚会遇到这种需求, 没想到这么快就遇到了. 这里涉及到的有以下关联: 游戏服务器组(ServerGroup): 将大量服务器分组, 每个组关联多个服务器放置在同个数据库, 该数据库对应组ID 游戏服务器(Server): 游戏服务器, 放置设计游戏关联信息的 渠道来源(Channel): 来源渠道, 比如快手|抖音|微信或者商店对应来源渠道 后台管理员(User): 后台管理员, 不同管理员关联不同渠道|不同服务器|不同权限等 后台权限(Auth): 访问权限, 管理员的权限各有不同 这里强烈这种需求尽可能 不要用脚本语言去实现, 特别是弱类型语言维护坑太多了; 复杂关联数据本身就很绕了, 还要处理数据类型传递验证且没有常驻数据连接池访问的问题. 弱类型后续维护坑太大了, 主要是弱类型写着最后传递过来的类型分不清是 int|string|null 哪种, 不注意的时候传递类型直接被修改都不清楚情况. 这里关联数据的方式先说明, 这里授权采用传统 RBAC 建模, 单个用户关联多个角色, 角色有: 用户登录后台(User 检索数据) 判断用户是最高管理员或者普通管理员( 只有最高管理员默认拥有所有权限 ) 获取关联角色|分组( Group|Role 检索关联 ) 通过角色|分组关联获取权限( Auth 检索菜单和接口权限 ) 通过角色|分组关联获取渠道( Channel 检索关联渠道 ) 通过角色|分组关联获取服务器( Server 检索关联服务器 ) 后续所有数据都要去 ServerId IN (1,2,3) AND Channel(1,2,3,4) 这里追加条件, 单单要建立这里面关联就需要以下表: # 管理员表, 最基础简单的账户表 CREATE TABLE IF NOT EXISTS `user` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '账号名', `nickname` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '昵称', `salt` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '盐值', `password` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '哈希密码', `token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '授权Token', `created_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', `created_uid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建管理员', `created_ip` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建IP', `updated_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', `updated_uid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新管理员', `updated_ip` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '更新IP', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT ='管理员账户表'; # 管理员对应分组, 给后台划分角色类型, 比如策划|主管|客服等角色 CREATE TABLE IF NOT EXISTS `user_group` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色组名称', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT ='管理员角色组'; # 用户权限表 CREATE TABLE IF NOT EXISTS `user_auth` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `pid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '等于0标识父级节点, 大于0是关联的父节点', `ty` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '菜单类型, 0 默认为菜单页面, 1 为API隐藏接口类型', `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限名', `path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '权限路径', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT ='管理员权限表'; # 对应渠道来源, 比如快手|抖音|微信|H5等 CREATE TABLE IF NOT EXISTS `channel` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `channel_id` bigint(20) unsigned NOT NULL COMMENT '第三方渠道定义', `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '渠道名称', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT ='渠道分类'; # 服务器列表 CREATE TABLE IF NOT EXISTS `server` ( `id` bigint(20) unsigned NOT NULL COMMENT '服务器ID, 需要运维定义', `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '服务器名称', `created_at` int(10) unsigned NOT NULL COMMENT '创建时间', `created_uid` bigint(20) unsigned NOT NULL COMMENT '创建管理员', `created_ip` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '创建IP地址', `updated_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', `updated_uid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '更新管理员', `updated_ip` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '更新IP地址', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT ='服务器列表'; 这就是基础的信息表, 需要留意基础表数据千万 不要硬删除, 否则会出现关联数据关联错误, 而是采用字段软删除才能保证数据关联; 之后关联的表数据用于 role|group 这些分组ID来检索关系:
    架构 Created Thu, 16 Jan 2025 21:14:39 +0800
  • React 脚手架搭建 这里项目需要规范化所以采用前端目前常用的技术栈, 这里查看时候打算采用 material-ui 处理. 基本上了解基础的 html+css+jss 和 react 基础就可以了, 其他就是日常积累或者不常用的. 这里需要说明目前不推荐 React 19 直接搭建, 有的核心组件暂时不支持, 千万不要盲目更新到最新版本. 这里采用从零手动搭建 Login+Dashboard 并且过程会补充说明用于加深印象, 首先是创建项目根目录: # 首先创建目录作为项目根目录 mkdir fusion-devops cd fusion-devops # 进入之后初始化项目 npm init 命令按照自身需要选择就行了, 最后生成 package.json 文件如下: { "name": "fusion-devops", "version": "2025.1.10", "private": true, "description": "后台界面", "main": "index.js", "keywords": [ "fusion", "react", "material-ui" ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "MeteorCat", "license": "MIT" } name: 项目名称 version: 项目版本, 我喜欢采用 Y.
    React Created Fri, 10 Jan 2025 23:14:47 +0800