前言:
遊戲領域, 特別是移動端的社交類遊戲, 排行榜成爲了一種加強體驗交互, 提升用戶粘性的大法寶. 這邊講述在不一樣用戶規模下, 遊戲服務化/遊戲平臺化趨勢下, 如何去設計和實現遊戲排名榜. 本文側重於傳統關係型Mysql的方案實現, 後續會講解Nosql的做用. 小編(mumuxinfei)對這塊認識較淺, 所述觀點不表明主流(工業界)作法, 望能拋磚引玉. mysql
需求分析
曾幾什麼時候, 微信版飛機大戰紅極一時. 各路英雄刷排名, 曬成績. 不過該排名限制在本身的好友圈中, 而每一個用戶的好友圈各不同, 所以每一個用戶有本身的排名. 且排名按周重置清零. 一些簡單的移動端遊戲(好比2048, 沒有好友概念), 則採用簡單的全局排名的方式, 且排名採用歷史最高.sql
綜上的列子, 對於遊戲排行榜, 咱們能夠依據屬性來進行劃分.
1). 按是否屬於好友圈劃分
* 遊戲全局的Top N模式.
* 以自身好友圈爲界的Top N模式.
2). 按時間週期來劃分
* 按時間週期重置, 好比按周清零
* 歷史最高, 沒有重置清零機制數據庫
基礎篇:
社交類遊戲, 在小規模用戶的前提下, 藉助關係型數據庫(mysql)來實現, 採用單庫單表.
定義好友表tb_friend微信
CREATE TABLE IF NOT EXISTS tb_friend ( id INT PRIMARY KEY AUTO_INCREMENT, user_id varchar(64), friend_id varchar(64), UNIQUE KEY `idx_tb_friend_user_id_friend_id` (`user_id`, `friend_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
用戶得分表tb_score併發
CREATE TABLE IF NOT EXISTS tb_score ( id INT PRIMARY KEY AUTO_INCREMENT, user_id varchar(64), score int, UNIQUE KEY `idx_tb_score_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
評註: tb_friend表中user_id/friend_id構成複合索引, 用於維護user_id的好友列表, tb_score用於記錄每一個用戶的得分狀況
在該兩張表的前提之下, 如何獲取該好友的排行榜呢?
利用兩表join來實現:分佈式
SELECT tf.friend_id AS friend_id, ts.score AS score FROM tb_friend tf JOIN tb_score ts ON tf.friend_id = ts.user_id WHERE tf.user_id = ? ORDER BY ts.score DESC
相似的結果以下:
評註: 在sql的join中, 須要注意left join/equal join/right join的區別. 這邊選用等值join.性能
性能評估和執行分析:
1). 小表+大表模式: 在tb_friend單表9801條(100個小夥伴, 互爲好友)/tb_score單表53條(53個小夥伴有得分)記錄下, 進行join分析
執行規劃優化
EXPLAIN SELECT tf.friend_id AS friend_id, ts.score AS score FROM tb_friend tf JOIN tb_score ts ON tf.friend_id = ts.user_id WHERE tf.user_id = ? ORDER BY ts.score DESC
評註: 這邊sql優化器很是的智能, 藉助了小表驅動大表的join優化方式(小表tb_score驅動大表tb_friend進行join), 小表用到了file sort(總共53行記錄), 大表用了index(等值join對應一行大表記錄).
2). 等表模式: 在tb_friend單表19602條/tb_score單表5092條記錄下, 進行join分析設計
評註: 這邊tb_friend表驅動tb_score做join, tb_friend藉助複合索引(user_id,friend_id)來加速優化. Mysql的sql優化器仍是至關的智能和強大. 3d
進階篇:
隨着數據規模愈來愈大, 併發訪問量的增長, mysql的訪問逐漸變成瓶頸. 同時該join的sql語句涉及的filesort很是耗CPU的. 如何破解這種情況?
*) 引入分佈式mysql集羣, 進行分庫分表.
分庫分表做爲互聯網的一大神器, 做用立竿見影. 可是有所得,就會有所失, 分庫分表後, 會失去不少特性. 好比事務性, 外鍵約束關聯. 在業務這層, 會致使涉及用戶的tb_score, tb_friend表數據不在同一庫中, 進而致使join失效. 最終致使, 在應用層作merge, 使得排名操做演變成 1+N sql操做(1 sql 用於獲取好友列表, N sql 用於獲取每一個好友的得分). 這須要注意.
1+N的SQL演化, 應用層作得分排序, 性能會演變成一場災難.
(1) 獲取用戶好友列表 SELECT friend_id FROM tb_friend_{N} WHERE user_id = ? (2) 遍歷獲取每一個好友的得分 foreach friend_id in friend_list(?) SELECT score FROM tb_score_{M} WEHRE user_id = ? (3) 應用層作得分排序
評註: {N},{M}是指具體的分表數, 固然在同一庫同一表, 能夠藉助SELECT * IN (...)來優化,這個得看具體的數據分佈. 不過是種很好的思路.
小編觀點: 因爲tb_friend是大表, 而tb_score是小表, 所以tb_friend採用分庫分表(以user_id做爲依據)的方式去實現, 而tb_score採用單庫單表(便於批量查詢)的方式實現.
固然在工業界, Mysql的優化方案很是的成熟, 不光是分庫分表,還有主從分離(Master/Slave機制, Master用於寫服務, 多Slave節點提供讀服務).
能夠參見以下的圖示:
總結&後續: 這邊主要講述基於傳統關係型數據庫mysql來實現基於好友的遊戲排行榜, 我的的戰績須要實時的去獲取, 而好友列表的戰績能容許有必定的延遲. 而好友戰績的排序實現,就成爲了本文的中心議題. Mysql的實現方案在數據量/併發數增長的前提下,仍是顯示了必定的疲態. 下文將講解, 如何引入Nosql系統, 在遊戲rank中,扮演重要的角色. 期待你的關注.