• [原创]游戏合服时如何避免主键冲突


    背景

    滚服类型的游戏常见于 手游、网游(包括H5), 滚服类型游戏的特点(与传统大服架构区别):

    • 单服同时在线游戏人数少(eg. 3000人), 达到上限就开新服

    以下这部分内容来自: https://www.cnblogs.com/youjiaxing/articles/10491357.html

    滚服模式是游戏类型,技术架构和急功近利的坑钱策略等因素共同决定的,大服游戏包括绝大部分端游,以及类COC这样类型的游戏。

    另外,虽然像英雄联盟,王者荣耀这样的游戏也分服架构,但是这个并不是我理解中的“滚服游戏“,首先他们虽然分服,但是每个服的人数上限也是可以高达几十万,他们并不会发生频繁的合服情况。

    而滚服游戏更多是通过游戏策略设计,鼓励玩家花钱走捷径透支游戏生命周期,甚至几天即可独霸一个服务器。从而导致其他玩家望尘莫及,即使是花钱追也性价比极低,还不如进入一个新服重新开始。

    这就导致了新服一开,玩家即蜂拥而至,争先恐后练级升装备,以求最快速进入排行榜前列,如果努力一番发现落后了,可能就只能坐等下一个新服。这也导致了新服人数火爆,老服慢慢变成人烟凋零的村服,甚至没人的死服。

    为了能够节约服务器带宽资源,同时让少数剩余的玩家能够玩得起来,就必须要要进行频繁的合服,把若干个互不相干的服务器玩家,合并到一个服里面;这样又开启一波玩家竞争和收割。

    合服处理

    合服时要特别注意:

    1. 防止主键冲突
    2. 防止唯一(unique)键不冲突 (eg. 用户昵称)
    3. 清空僵尸数据/无效玩家数据(小心数据残留, 避免数据不一致)
    4. insert into 时注意字段顺序不一致问题

    处理主键冲突的办法主要有2种:

    1. 合服前预处理冲突键
    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';

  • 相关阅读:
    汇编语言LAHF和SAHF指令
    JSONHelper
    【CSS】利用宽高比例的媒体查询
    swiftmailer时没有设置https的选项,才可以发送成功。在linux下面
    DevExpress
    IntelliJ IDEA2017 激活方法 最新的激活注册方式方法,破解,密钥
    vue.js 列表追加项写法
    RedisCache 缓存
    时间通用类 datetime
    LogHelper 日志
  • 原文地址:https://www.cnblogs.com/youjiaxing/p/10495803.html
Copyright © 2020-2023  润新知