1. 程式人生 > >[原創]遊戲合服時如何避免主鍵沖突

[原創]遊戲合服時如何避免主鍵沖突

lock log you mongo mys 好的 lec database schema

目錄

  • 背景
  • 合服處理
    • 防止主鍵沖突
      • 合服時處理沖突
      • 開服時預分配防止後續沖突
    • 字段順序不一致

背景

滾服類型的遊戲常見於 手遊、網遊(包括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.COLUMNSWHERE TABLE_NAME=‘table‘ and TABLE_SCHEMA=‘database‘;

[原創]遊戲合服時如何避免主鍵沖突