net.sz.framework 框架 ORM 消消乐超过亿条数据名次榜分析 天王盖地虎

序言

 

帝王盖地虎,

 

妻子霎时生孩子了,在家待产,内人喜欢玩消消乐类似的休闲游戏,闲置状态,无聊的解析一下消消乐游戏的片段技术难点;

由于自己重假如服务器研发,客户端属于半吊子,所以就分析一下消消乐排名榜难点;

第一章

消消乐排名榜大致分成好友排名榜和全国名次榜;

好友排名榜和全国排名榜的实在是重合的只是索要从全国名次榜中领到出来而已;

那么就须求记录所有玩家的通关记录已开展查询;

想必你说全国名次榜只突显前xxx名就好;可是你的莫逆之交记录必要求的啊?你的好友不容许所有进来全国排名榜吧;

而好友排名榜基本都是要去全体体现出来排行;

负有那么难题来了:

俺们插手400万用户,那么每一关卡都会有400万记下;

此时此刻消消乐关卡开始1200关,那么就是400万 x 1200 = 48亿条数据;那他妈的吓死人啊;

消消乐游戏,最大的技术紧假使名次榜查询难点,反而写入速度,和频率却不高;

再有首要的一点是每一关卡的玩家流失率大致:0.xx%;

出于自身在家休息中,家里开发条件限制所以设定数据存在是sqlite、mysql数据库,其余数据库有待研讨;借使redis
牵涉排序难题,搜索难题,么有想到好的方案;

 

第二章

自我第一设计通关记录存储表结构模型;

内需,玩家id,通关关卡,通关星级,通关积分,通关时间

图片 1图片 2

 1 class TopList extends DataBaseModel implements Serializable, Cloneable {
 2 
 3     /*玩家id*/
 4     private long pid;
 5     /*关卡*/
 6     private int point;
 7     /*星级*/
 8     private int star;
 9     /*通关时间*/
10     private long time;
11     /*积分*/
12     private int integral;
13 
14     public long getPid() {
15         return pid;
16     }
17 
18     public void setPid(long pid) {
19         this.pid = pid;
20     }
21 
22     public int getPoint() {
23         return point;
24     }
25 
26     public void setPoint(int point) {
27         this.point = point;
28     }
29 
30     public int getStar() {
31         return star;
32     }
33 
34     public void setStar(int star) {
35         this.star = star;
36     }
37 
38     public long getTime() {
39         return time;
40     }
41 
42     public void setTime(long time) {
43         this.time = time;
44     }
45 
46     public int getIntegral() {
47         return integral;
48     }
49 
50     public void setIntegral(int integral) {
51         this.integral = integral;
52     }
53 
54     @Override
55     public Object clone() {
56         try {
57             return super.clone(); //To change body of generated methods, choose Tools | Templates.
58         } catch (CloneNotSupportedException ex) {
59         }
60         return null;
61     }
62 
63 }

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         /*创建表*/
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

 

咱俩查询一下数据库;

 图片 3

总数量是560多万行数据;

询问一下关卡数据

 图片 4

询问关卡数据火速;

不过我急需排序

图片 5

 那几个就看出相比较了吧,那仅仅唯有不到600万条数据吧;而且我们只是是查询了举国上下总名次,还么有牵累好友名次榜,多规格搜索;

 

第三章

数据库可以扩张索引的,参加索引后,查询会快很多;

那就是说接下去我们修改模型,测试

图片 6图片 7

 1 class TopList extends DataBaseModel implements Serializable, Cloneable {
 2 
 3     /*玩家id*/
 4     @AttColumn(index = true)
 5     private long pid;
 6     /*关卡*/
 7     @AttColumn(index = true)
 8     private int point;
 9     /*积分*/
10     @AttColumn(index = true)
11     private int integral;
12     /*星级*/
13     private int star;
14     /*通关时间*/
15     private long time;
16 
17     public long getPid() {
18         return pid;
19     }
20 
21     public void setPid(long pid) {
22         this.pid = pid;
23     }
24 
25     public int getPoint() {
26         return point;
27     }
28 
29     public void setPoint(int point) {
30         this.point = point;
31     }
32 
33     public int getStar() {
34         return star;
35     }
36 
37     public void setStar(int star) {
38         this.star = star;
39     }
40 
41     public long getTime() {
42         return time;
43     }
44 
45     public void setTime(long time) {
46         this.time = time;
47     }
48 
49     public int getIntegral() {
50         return integral;
51     }
52 
53     public void setIntegral(int integral) {
54         this.integral = integral;
55     }
56 
57     @Override
58     public Object clone() {
59         try {
60             return super.clone(); //To change body of generated methods, choose Tools | Templates.
61         } catch (CloneNotSupportedException ex) {
62         }
63         return null;
64     }
65 
66 }

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 耗时:823
10 [07-25 13:11:08:027:DEBUG:CUDThread.run():246] 当前待处理剩余数量:27449
11 [07-25 13:11:08:966:DEBUG:CUDThread.run():219] 新增数据插入影响行数:5000 耗时:936
12 [07-25 13:11:08:967:DEBUG:CUDThread.run():246] 当前待处理剩余数量:27900
13 [07-25 13:11:09:945:DEBUG:CUDThread.run():219] 新增数据插入影响行数:5000 耗时:977

 

 数据库数据进一步多的时候,插入速度就会越来越慢;看下图,是还是不是很可怕?

1 [07-25 13:15:06:511:DEBUG:CUDThread.run():246] 当前待处理剩余数量:23473
2 [07-25 13:15:10:948:DEBUG:CUDThread.run():219] 新增数据插入影响行数:5000 耗时:4434
3 [07-25 13:15:10:950:DEBUG:CUDThread.run():246] 当前待处理剩余数量:42028
4 [07-25 13:15:14:666:DEBUG:CUDThread.run():219] 新增数据插入影响行数:5000 耗时:3715
5 [07-25 13:15:14:668:DEBUG:CUDThread.run():246] 当前待处理剩余数量:49338

 

恐怕与我当下台式机硬盘有关联;电脑品质占用难点;

图片 8

 插入速度是慢了,大家来探望查询速度吗,

 图片 9图片 10

咱俩得以见见插入速度快很多;

本来此时的数据量确实么有从前的多,因为测试难点,插入的多寡的着实很慢,

借使是mysql 数据库,还必要进入联合索引查询才会翻倍升高质量;mysql插入速度会比sqlite好一点,因为sqlie本身就不适用于大型数据集合;

第四章

其实以上工作只是完结了目录优化难题;然则也抵不住越多的多寡;对不对?

故而依旧的寻求其余办法来化解难点;

大家先来分析气象

 

可以建立分区表方式;不过建立分区表,数据也只是在一个表内大hash集合,然后分其余小hash集合,然后就是mysql那种法定提出的分区表都是200万条数据;

为此并未尝试过;有趣味的校友可以探讨切磋;

 

自家想开的第二种方法,分表,从前在做游戏运营后台,接受来之多少个游戏的运营日志数据,就应用了这种措施,分表,每一日一张表,来划分;

由此自己很快又想到了这种艺术,我以每一个关卡划分通关数据记录,

唯独仔细一想一定越发啊,因为依据现行消消乐那种游戏的话1200关,1000多张表,维护都得忙死;而且越以后越更新更多,如故不可行!

 

其他游戏要是在生活周期内,就有有人进入,有人流失;

那就是说消消乐那种关卡类的嬉戏,那么数据量永远都在前方关卡,越将来越少,就跟自家面前说的等同,每一关卡都会收敛0.xxx%;

那么我们可以陈设30张表,30张总能接受了啊?

最少我是能经受了;

不过怎么优雅的行使30张表数据的啊?

俺们分析可得越靠前的关卡,数据越多,

那就是说大家安顿前800关卡的数量存入20张表;

前边的之所以表记录到剩余的10张表中;

开创表,获取表名的大致算法;

 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     }

 

创建表

1         int points = 1200;
2 
3         TopList topList = new TopList();
4         for (int i = 1; i <= points; i++) {
5             topList.setDataTableName(getTableName(i));
6             /*创建表*/
7             sdi.createTable(topList);
8         }

图片 11

表自动成立落成;

 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] 总共写入数据量:226989
10 [07-25 14:24:17:502:DEBUG:CUDThread.run():219] 新增数据插入影响行数:3000 耗时:2744
11 [07-25 14:24:17:502:DEBUG:CUDThread.run():246] 当前待处理剩余数量:3600
12 [07-25 14:24:17:743:INFO :TopTest.main():68] 总共写入数据量:228190
13 [07-25 14:24:18:755:INFO :TopTest.main():68] 总共写入数据量:229391
14 [07-25 14:24:19:783:INFO :TopTest.main():68] 总共写入数据量:230592
15 [07-25 14:24:20:250:DEBUG:CUDThread.run():219] 新增数据插入影响行数:3000 耗时:2748
16 [07-25 14:24:20:250:DEBUG:CUDThread.run():246] 当前待处理剩余数量:4200
17 [07-25 14:24:20:824:INFO :TopTest.main():68] 总共写入数据量:231793
18 [07-25 14:24:21:843:INFO :TopTest.main():68] 总共写入数据量:232994

 

查询速度在第三章早已表明了,就不在验证;

 

总结

像消消乐这类型游戏紧要就在于名次榜数据存储和读取,然后写入和读取比较,写入的急需远远低于读取的需求;

我做的是实时数据名次榜区分;

 

当然大家还足以应用mysql的主从关系,提供读写分离情况,做非实时排名榜数据;

也足以应用非滑动缓存来做非实时名次榜,解决写入和读取的属性平衡难点,缓存可以安装比如每5分钟或者每10分钟更新三遍排名榜数据来形成;

以上剖析就不再做代码测试;

那是本人分析和我的化解方案,不掌握屌大的园友们还要更好的解决方案吧?

 

图片 12图片 13图片 14

 

网站地图xml地图