背景
滚服类型的游戏常见于 手游、网游(包括H5), 滚服类型游戏的特点(与传统大服架构区别):
- 单服同时在线游戏人数少(eg. 3000人), 达到上限就开新服
以下这部分内容来自: https://www.cnblogs.com/youjiaxing/articles/10491357.html
滚服模式是游戏类型,技术架构和急功近利的坑钱策略等因素共同决定的,大服游戏包括绝大部分端游,以及类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
, 同时若使用了外键还得额外处理外键的删除和重做.
实际上这种处理很繁琐,因为通常需要修改主键的不只一张表,表的关系越复杂,修改一个表的主键牵涉到的相关表越多,实际操作时层层嵌套会很恶心...
开服时预分配防止后续冲突
一种更为推荐的办法是在一开始设计好主键,提前规避冲突,这样在合服操作时就可以无脑数据合并。
1.使用自增值, 服编号在前
每个服都有一个唯一的服编号, 可以利用这一点, 提前规划好每个服的 主键区域。
比如 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/p/5531167.html
牺牲一点硬盘空间来规避后续合服的恶心事项,我是觉得很划算。
2.不完全使用自增值, 服编号在后
还有另外一种预分配方案, 即一开始预估服编号的范围(比如 1~9999),将服编号作为 users.id
后N位,这样可以避免玩家看到自己一大长串的uid觉得恶心。比如1服玩家原id
为23的玩家, 按照这种方案, 其id
就是 230001, 111服的id
为23的玩家则是 230111.
这种方案就是在生成主键id
时会绕一点, 但对于 MongoDB这一类的倒无所谓了.
3.使用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';