Last-Modified: 2019 年 5 月 10 日 15:23:31
背景
滚服类型的游戏常见于 手游、网游(包括 H5), 滚服类型游戏的特点(与传统大服架构区别):
- 单服同时在线游戏人数少(eg. 3000 人), 达到上限就开新服
以下这部分内容来自: https://www.cnblogs.com/youji…
滚服模式是游戏类型,技术架构和急功近利的坑钱策略等因素共同决定的,大服游戏包括绝大部分端游,以及类 COC 这样类型的游戏。
另外,虽然像英雄联盟,王者荣耀这样的游戏也分服架构,但是这个并不是我理解中的“滚服游戏“,首先他们虽然分服,但是每个服的人数上限也是可以高达几十万,他们并不会发生频繁的合服情况。
而滚服游戏更多是通过游戏策略设计,鼓励玩家花钱走捷径透支游戏生命周期,甚至几天即可独霸一个服务器。从而导致其他玩家望尘莫及,即使是花钱追也性价比极低,还不如进入一个新服重新开始。
这就导致了新服一开,玩家即蜂拥而至,争先恐后练级升装备,以求最快速进入排行榜前列,如果努力一番发现落后了,可能就只能坐等下一个新服。这也导致了新服人数火爆,老服慢慢变成人烟凋零的村服,甚至没人的死服。
为了能够节约服务器带宽资源,同时让少数剩余的玩家能够玩得起来,就必须要要进行频繁的合服,把若干个互不相干的服务器玩家,合并到一个服里面;这样又开启一波玩家竞争和收割。
合服处理
合服时要特别注意:
- 防止主键冲突
- 防止唯一 (unique) 键不冲突 (eg. 用户昵称)
- 清空僵尸数据 / 无效玩家数据(小心数据残留, 避免数据不一致)
-
insert into
时注意字段顺序不一致问题
处理主键冲突的办法主要有 2 种:
- 合服前预处理冲突键
- 开服时预分配好可能的冲突键, 合服时则无需额外处理(推荐)
防止主键冲突
合服时处理冲突
如果在一开始没有设计好数据库的话, 合服时很容易遇到的普遍情况就是: 主键冲突
游戏通常有角色表, 道具表, 一般都是用数据库的自增长 (AUTO_INCREMENT) 特定来创建其主键, 以此保证主键的唯一,以如下表结构为例,id
只能保证在本服中唯一,A 服中有个玩家 id
是 1, B 服中也有个玩家 id
是 1, 合服前必须解决这个冲突.
-- 玩家表
CREATE TABLE `users`(`id` int(11) unsigned not null,
`name` varchar(50) default null,
primary key (`id`)
) Engine=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-- 玩家道具表
CREATE TABLE `props`(`id` int(11) unsigned not null,
`user_id` int(11) unsigned not null,
primary key (`id`)
) Engine=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
通常做法是给 B 表中的所有 users.id
字段值加上一个基数(max('A.users.id')
, 同时还要修改涉及到的其他表, 比如 props.user_id
字段必须相应修改, 否则无法关联到对应玩家,因为修改了 users.id
后一般要修改相应的数十张表中的 user_id
, 同时若使用了外键还得额外处理外键的删除和重做.
实际上这种处理很繁琐,因为通常需要修改主键的不只一张表,表的关系越复杂,修改一个表的主键牵涉到的相关表越多,实际操作时层层嵌套会很恶心 …
开服时预分配防止后续冲突
一种更为推荐的办法是在一开始设计好主键,提前规避冲突,这样在合服操作时就可以无脑数据合并。
- 使用自增值, 服编号在前
每个服都有一个唯一的服编号,可以利用这一点,提前规划好每个服的 主键区域。
比如 users
表主键 id
, 当存在 1 万个玩家时, id
取值范围为 1~10000。
先定个小目标, 我们预估单个服玩家数量不会超过 1 一个亿, 因此用服编号乘以这个量级, 因此 1 服的主键 id
范围 100000000 ~ 199999999
, 111 服的是 11100000000 ~ 11199999999
,
注意这已经超出 int
表示范围, 因此必须使用 bigint
, 若使用 unsigned bigint
可以完整表示 19 位数。如果你觉得可能会超出小目标,那可以把这个量级再调大一点,比如 100 个亿。
关于 mysql 数字的数据类型可以看一下这边: https://www.cnblogs.com/yiwd/…
牺牲一点硬盘空间来规避后续合服的恶心事项,我是觉得很划算。
- 不完全使用自增值, 服编号在后
还有另外一种预分配方案,即一开始预估服编号的范围(比如 1~9999),将服编号作为 users.id
后 N 位,这样可以避免玩家看到自己一大长串的 uid 觉得恶心。比如 1 服玩家原 id
为 23 的玩家, 按照这种方案, 其 id
就是 230001, 111 服的 id
为 23 的玩家则是 230111.
这种方案就是在生成主键 id
时会绕一点, 但对于 MongoDB 这一类的倒无所谓了.
- 使用 uuid 作为主键
优点:确保全局唯一,不仅是表唯一,而且是库唯一,很方便不同数据库间迁移
这种方案不好的地方在于 UUID 的无序性, 会导致 InnoDB 引擎产生巨大的 IO 压力,这根 InnoDB 主键索引与数据存储位置相关。
字段顺序不一致
游戏版本迭代更新容易导致不同数据库结构有所差异, 因此在合服时, 一个是确保数据库版本一致.
如果是简单地使用 insert into db2.table2 select * from db1.table1
时要确保字段是一致的.
如果不一致就乖乖指定字段 INSERT INTO new_db.table_name(column1, column2,...) (SELECT column1, column2,... FROM old_db.table_name);
具体有哪些字段可以通过如下语句查出:
select COLUMN_NAME FROM information_schema.
COLUMNS WHERE TABLE_NAME='table' and TABLE_SCHEMA='database';