天王蓋地虎,
老婆馬上生孩子了,在家待產,老婆喜歡玩消消樂類似的休閑游戲,閑置狀態(tài),無聊的分析一下消消樂游戲的一些技術問題;
由于我主要是服務器研發(fā),客戶端屬于半吊子,所以就分析一下消消樂排行榜問題;
第一章
消消樂排行榜大致分為好友排行榜和全國排行榜;
好友排行榜和全國排行榜的其實是重合的只是需要從全國排行榜中提取出來而已;
那么就需要記錄所有玩家的通關記錄已進行查詢;
也許你說全國排行榜只顯示前xxx名就好;但是你的好友記錄必須要的吧?你的好友不可能全部進入全國排行榜吧;
而好友排行榜基本都是要去全部顯示出來排名;
所有那么問題來了:
我們加入400萬用戶,那么每一關卡都會有400萬記錄;
目前消消樂關卡開始1200關,那么就是400萬 x 1200 = 48億條數據;這他媽的嚇死人?。?/h3>
消消樂游戲,最大的技術關鍵是排行榜查詢問題,反而寫入速度,和頻率卻不高;
還有重要的一點是每一關卡的玩家流失率大約:0.xx%;
由于我在家休息中,家里開發(fā)環(huán)境限制所以設定數據存在是sqlite、mysql數據庫,其他數據庫有待研究;如果redis 牽涉排序問題,搜索問題,么有想到好的方案;
第二章
我首先設計通關記錄存儲表結構模型;
需要,玩家id,通關關卡,通關星級,通關積分,通關時間
View Code
測試代碼
1 public static void main(String[] args) throws Exception { 2 3 SqliteDaoImpl sdi = new SqliteDaoImpl("/home/toplist.db"); 4 TopList topList = new TopList(); 5 6 CUDThread cudt = new CUDThread(sdi, "top-list-thread"); 7 /*設置異步操作的緩沖容量*/ 8 cudt.setMaxTaskCount(500000); 9 /*設置單次寫入的數據量*/10 cudt.setGetTaskMax(5000);11 /*創(chuàng)建表*/12 sdi.createTable(topList);13 14 /*id生成器*/15 LongId0 longId0 = new LongId0();16 17 /*模擬5萬個玩家*/18 for (int i = 0; i < 50000; i++) {19 long id = longId0.getId();20 /*模擬500關卡*/21 for (int j = 1; j <= 500; j++) {22 23 TopList clone = (TopList) topList.clone();24 clone.setPid(id);25 clone.setTime(System.currentTimeMillis());26 clone.setPoint(j);27 clone.setStar(3);28 /*隨機積分*/29 clone.setIntegral(RandomUtils.random(20000, 400000));30 31 cudt.insert_Sync(clone);32 }33 }34 35 }
sqlite插入速度非???,
[07-25 11:28:20:368:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:115[07-25 11:28:20:368:DEBUG:CUDThread.run():246] 當前待處理剩余數量:7257[07-25 11:28:20:524:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:155[07-25 11:28:20:524:DEBUG:CUDThread.run():246] 當前待處理剩余數量:8342[07-25 11:28:20:696:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:172[07-25 11:28:20:696:DEBUG:CUDThread.run():246] 當前待處理剩余數量:9129[07-25 11:28:20:818:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:122[07-25 11:28:20:818:DEBUG:CUDThread.run():246] 當前待處理剩余數量:8188[07-25 11:28:20:973:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:154[07-25 11:28:20:973:DEBUG:CUDThread.run():246] 當前待處理剩余數量:8424
我們查詢一下數據庫;
總數據是560多萬行數據;
查詢一下關卡數據
查詢關卡數據很快;
但是我需要排序
這些就看出對比了吧,這僅僅只有不到600萬條數據呢;而且我們僅僅是查詢了全國總排行,還么有牽涉好友排行榜,多條件搜索;
第三章
數據庫可以增加索引的,加入索引后,查詢會快很多;
那么接下來我們修改模型,測試
View Code
修改模型,在玩家id,分數,關卡,這三個地方加入索引;
1 [07-25 13:11:04:759:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:662 2 [07-25 13:11:04:760:DEBUG:CUDThread.run():246] 當前待處理剩余數量:22525 3 [07-25 13:11:05:549:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:787 4 [07-25 13:11:05:550:DEBUG:CUDThread.run():246] 當前待處理剩余數量:24975 5 [07-25 13:11:06:437:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:885 6 [07-25 13:11:06:438:DEBUG:CUDThread.run():246] 當前待處理剩余數量:27030 7 [07-25 13:11:07:198:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:759 8 [07-25 13:11:07:199:DEBUG:CUDThread.run():246] 當前待處理剩余數量:27454 9 [07-25 13:11:08:023:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:82310 [07-25 13:11:08:027:DEBUG:CUDThread.run():246] 當前待處理剩余數量:2744911 [07-25 13:11:08:966:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:93612 [07-25 13:11:08:967:DEBUG:CUDThread.run():246] 當前待處理剩余數量:2790013 [07-25 13:11:09:945:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:977
數據庫數據越來越多的時候,插入速度就會越來越慢;看下圖,是不是很嚇人?
1 [07-25 13:15:06:511:DEBUG:CUDThread.run():246] 當前待處理剩余數量:234732 [07-25 13:15:10:948:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:44343 [07-25 13:15:10:950:DEBUG:CUDThread.run():246] 當前待處理剩余數量:420284 [07-25 13:15:14:666:DEBUG:CUDThread.run():219] 新增數據插入影響行數:5000 耗時:37155 [07-25 13:15:14:668:DEBUG:CUDThread.run():246] 當前待處理剩余數量:49338
也許與我當前筆記本硬盤有關系;電腦性能占用問題;
插入速度是慢了,我們來看看查詢速度吧,
我們可以看到插入速度快很多;
當然此時的數據量確實么有之前的多,因為測試問題,插入的數據的確實很慢,
如果是mysql 數據庫,還需要加入聯合索引查詢才會翻倍提升性能;mysql插入速度會比sqlite好一點,因為sqlie本身就不適用于大型數據集合;
第四章
其實以上工作只是做到了索引優(yōu)化問題;但是也抵不住越來越多的數據;對不對?
所以還是的尋求其他方式來解決問題;
我們先來分析情況
可以建立分區(qū)表形式;但是建立分區(qū)表,數據也只是在一個表內大hash集合,然后區(qū)分的小hash集合,然后就算mysql這種官方建議的分區(qū)表都是200萬條數據;
所以沒有嘗試過;有興趣的同學可以研究研究;
我想到的第二種方式,分表,之前在做游戲運營后臺,接受來之多個游戲的運營日志數據,就采用了這種方式,分表,每一天一張表,來劃分;
所以我很快又想到了這種方式,我以每一個關卡劃分通關數據記錄,
可是仔細一想肯定不行啊,因為按照現在消消樂這種游戲來說1200關,1000多張表,維護都得忙死;而且越往后越更新越多,依然不可行!
任何游戲只要在生存周期內,就有有人加入,有人流失;
那么消消樂這種關卡類的游戲,那么數據量永遠都在前面關卡,越往后越少,就跟我前面說的一樣,每一關卡都會流失0.xxx%;
那么我們可以設計30張表,30張總能接受了吧?
起碼我是能接受了;
可是如何優(yōu)雅的利用30張表數據的呢?
我們分析可得越靠前的關卡,數據越多,
那么我們設計前800關卡的數據存入20張表;
后面的所以表記錄到剩余的10張表中;
創(chuàng)建表,獲取表名的簡易算法;
1 /** 2 * 獲取表名 3 * 4 * @param point 5 * @return 6 */ 7 static String getTableName(int point) { 8 int tableId = 0; 9 if (point < 300) {10 /*第一段前300關存入15張表*/11 tableId = 1000 + point % 15;12 } else if (point < 800) {13 /*第二段后500關卡存入5張表*/14 tableId = 2000 + point % 5;15 } else {16 /*800關卡以后的數據存入剩余10張表*/17 tableId = 3000 + point % 10;18 }19 20 return TopList.class.getSimpleName().toLowerCase() + tableId;21 }
創(chuàng)建表
1 int points = 1200;2 3 TopList topList = new TopList();4 for (int i = 1; i <= points; i++) {5 topList.setDataTableName(getTableName(i));6 /*創(chuàng)建表*/7 sdi.createTable(topList);8 }
表自動創(chuàng)建完成;
1 /*模擬5萬個玩家*/ 2 for (int i = 1; i <= 50000; i++) { 3 long id = longId0.getId(); 4 /*模擬關卡*/ 5 int j = 1; 6 for (; j <= points; j++) { 7 8 TopList clone = (TopList) topList.clone(); 9 clone.setDataTableName(getTableName(j));10 clone.setPid(id);11 clone.setTime(System.currentTimeMillis());12 clone.setPoint(j);13 clone.setStar(3);14 /*隨機積分*/15 clone.setIntegral(RandomUtils.random(20000, 400000));16 17 cudt.insert_Sync(clone);18 }19 log.info("總共寫入數據量:" + (i * (j - 1)));20 Thread.sleep(1000);21 }
寫入速度,已經得到提升了,
1 [07-25 14:24:12:293:DEBUG:CUDThread.run():219] 新增數據插入影響行數:3000 耗時:2750 2 [07-25 14:24:12:294:DEBUG:CUDThread.run():246] 當前待處理剩余數量:3600 3 [07-25 14:24:12:605:INFO :TopTest.main():68] 總共寫入數據量:222185 4 [07-25 14:24:13:619:INFO :TopTest.main():68] 總共寫入數據量:223386 5 [07-25 14:24:14:647:INFO :TopTest.main():68] 總共寫入數據量:224587 6 [07-25 14:24:14:757:DEBUG:CUDThread.run():219] 新增數據插入影響行數:3000 耗時:2463 7 [07-25 14:24:14:758:DEBUG:CUDThread.run():246] 當前待處理剩余數量:4200 8 [07-25 14:24:15:690:INFO :TopTest.main():68] 總共寫入數據量:225788 9 [07-25 14:24:16:709:INFO :TopTest.main():68] 總共寫入數據量:22698910 [07-25 14:24:17:502:DEBUG:CUDThread.run():219] 新增數據插入影響行數:3000 耗時:274411 [07-25 14:24:17:502:DEBUG:CUDThread.run():246] 當前待處理剩余數量:360012 [07-25 14:24:17:743:INFO :TopTest.main():68] 總共寫入數據量:22819013 [07-25 14:24:18:755:INFO :TopTest.main():68] 總共寫入數據量:22939114 [07-25 14:24:19:783:INFO :TopTest.main():68] 總共寫入數據量:23059215 [07-25 14:24:20:250:DEBUG:CUDThread.run():219] 新增數據插入影響行數:3000 耗時:274816 [07-25 14:24:20:250:DEBUG:CUDThread.run():246] 當前待處理剩余數量:420017 [07-25 14:24:20:824:INFO :TopTest.main():68] 總共寫入數據量:23179318 [07-25 14:24:21:843:INFO :TopTest.main():68] 總共寫入數據量:232994
查詢速度在第三章已經驗證了,就不在驗證;
總結
像消消樂這類型游戲重點就在于排行榜數據存儲和讀取,然后寫入和讀取相比,寫入的需求遠遠小于讀取的需求;
我做的是實時數據排行榜區(qū)分;
當然我們還可以利用mysql的主從關系,提供讀寫分離情況,做非實時排行榜數據;
也可以利用非滑動緩存來做非實時排行榜,解決寫入和讀取的性能平衡問題,緩存可以設置比如每5分鐘或者每10分鐘更新一次排行榜數據來完成;
以上分析就不再做代碼測試;
這是我分析和我的解決方案,不知道屌大的園友們還要更好的解決方案嗎?
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓源碼地址↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ /** * * @author 失足程序員 * @Blog http://www.cnblogs.com/ty408/ * @mail 492794628@qq.com * @phone 13882122019 * */ C#版本代碼 vs2010及以上工具可以 java版本代碼推薦 NetBeans 工具,eclipse 打開編譯不通過,工具對maven的支持不同 跪求保留標示符 提供免費倉儲。 svn地址,svn里面代碼保證最新, http://code.taobao.org/svn/my_maven/ git地址,git相對不是那么新,因為網速問題更新比較遲緩 https://github.com/qq313796269
http://www.cnblogs.com/ty408/p/7233202.html