排行榜设计逐字稿

在之前呢,我们设计了一个积分功能,积分很重要的一个作用就是来参与排行,那今天我们就来设计一下排行榜的功能。

1.业务分析

        一般在项目中,排行榜都会有两种

• 实时榜单:也就是本赛季的榜单(学霸天梯榜)

• 历史榜单:也就是历史赛季的榜单

1.1实时榜单

        实时榜单通常指的是我们当前的榜单数据,要想形成榜单,我们在查询数据库时,需要先对用户分组,再对积分求和,最终按照积分和排序

        要知道,每个用户都可能会有数十甚至上百条积分记录,当用户规模达到百万规模,可能产生的积分记录就是数以亿计。要在每次查询排行榜时,在内存中对这么多数据做分组、求和、排序,对内存和CPU的占用会非常恐怖,不太靠谱。此时我们又可以考虑使用我们的Redis来保存榜单数据了。

        Redis中有一种类型是带有排序功能的也就是ZSet,所以我们选择这个类型进行存储。由于内存是十分珍贵的,我们在存储的时候选择可以只选择存储用户id,每当用户得到积分后,将对应用户的score+1,这样就可以进行一个排序的作用。

1.2历史榜单

        历史榜单是曾经的某个月最后一天的排名结果,积分和排名已经确定,没有实时更新排序的需求。

        我们可以在每个月第一天的凌晨,使用定时任务,将上个月最终的排行榜数据写入到数据库,然后就可以清空redis中上个月的积分和榜单数据了。

2.功能实现

2.1实时积分榜

2.1.1生成积分榜

        此功能很简单,只需要将保存到数据库的内容同时保存给Redis即可,具体可参考下图:

      

2.1.2查询积分榜

        一般在我们用户的个人中心,可以查看指定赛季的排行榜,还可以查看自己的排名,并且排行榜还分为实时榜单和历史榜单,我们需要在这一个接口中实现这两类的查询。

        需要注意的是:

无论是历史榜单还是当前榜单,结构都一样,分为两部分:

  • 榜单数据,就是N个用户的积分、排名形成的集合

  • 当前用户的积分和排名,当前用户不一定上榜,因此需要单独查询

2.2历史积分榜

        由于历史榜单不会再变动,所以我们可以选择将redis中的榜单再写回数据库,我们需要完成三个步骤:

  1. 创建历史赛季表(一个赛季一张表)

  2. 从redis中将数据保存到mysql中对应的赛季表中

  3. 删除redis中历史赛季的数据

上面三个定时任务应该使用xxl-job实现,而且要规定好执行顺序(任务链)

2.2.1生成积分榜

        在天机学堂项目中,积分排行榜是分赛季的,每一个月是一个赛季。因此每到每个月的月初,就会进入一个新的赛季。所有用户的积分应该清零,重新累积。

        但是,我们能把Redis中的榜单数据直接清空吗?显然不行!Redis中的榜单数据是上个月的数据,属于历史榜单了,直接清空就丢失了一个赛季的数据。

        因此,我们必须将Redis中的历史数据持久化到数据库中,然后再清零。如图:

        不过,这里就有一个问题需要解决:

        假如有数百万用户,这就意味着每个赛季榜单都有数百万数据。随着时间推移,历史赛季越来越多,如果全部保存到一张表中,数据量会非常恐怖!

        由于我们要解决的是数据过多问题,因此分表的方式选择水平分表。具体来说,就是按照赛季拆分,每一个赛季是一个独立的表,如图:

不过这里我们可以做一些简化:

  • 我们可以将id采用自增id,那么id就是排名,排名字段就不需要了。

  • 不同赛季用不同表,那么赛季字段就不需要了。

        由于每个赛季都有一张表,显然我们应该在月初进行创建表,因此我们可以使用定时任务创建。

2.2.2清理缓存    

        添加清理Redis缓存任务,这里不使用del ,否则可能因为大数据量删除阻塞主线程,而是使用unlink命令开启异步线程删除。

2.2.3任务链

        现在,所有任务都已经定义完毕。接下来就给配置任务调度了。我们最终期望的任务执行顺序是这样的:

XXL-JOB提供了子任务功能,可以直接完成此需求。

3.优化方案

3.1海量数据存储

        对于海量数据存储的方案有很多常见的有:

3.1.1分区

        表分区(Partition)是一种数据存储方案,可以解决单表数据较多的问题,MySQL5.1开始支持表分区功能。

        分区就是按照某种规则,把表数据对应的ibd文件拆分成多个文件进行存储来存储。

        从物理上来看,一张表的数据被拆到多个表文件存储了;从逻辑上来看,他们对外表现是一张表。

        增删改查的方式不会有什么变化,只不过底层MySQL底层的处理上会有变更,例如检索时可以只检索某个文件,而不是全部。

优点:

• 可以存储更多的数据,突破单表上限,甚至可以存储到不同磁盘,突破磁盘上限

• 查询时可以根据规则只检索某一个文件,提高查询效率

• 数据统计时,可以多文件并行统计,最后汇总结果,提高统计效率

• 对于一些历史数据,如果不需要时,可以直接删除分区文件,提高删除效率

缺点:

  • 分区字段必须是索引字段

  • 分区方式不够灵活

  • 只支持水平分区

3.1.2分表  

        分表是一种表设计方案,由开发者在创建表时按照自己的业务需求拆分表。一旦做了分表,无论是逻辑上,还是物理上,就从一张表变成了多张表,增删改查的方式就发生了变化,必须自己考虑要去哪张表做数据处理。

水平分表

        例如,对于赛季榜单,我们可以按照赛季拆分为多张表,例如每一个赛季一张新的表,水平分表仅仅是每张表数据不同。但结构一样,查询赛季1,就找第一张表。查询赛季2,就找第二张表。

垂直分表

        如果一张表的字段非常多,比如达到30个以上,这样的表我们称为宽表。

        宽表由于字段太多,单行数据体积就会非常大,虽然数据不多,但可能表体积也会非常大!从而影响查询效率。

        这个时候一张表就变成了两张表。而且两张表的结构不同,数据也不同。这种按照字段拆分表的方式,称为垂直拆分。

优点

• 拆分方式更加灵活

• 可以解决单表字段过多和数据过多的问题

缺点:

• 增删改查时,需要自己判断访问哪张表

• 垂直拆分还会导致事务问题及数据关联问题:原本一张表的操作,变为多张表操作。

3.3.3分库和集群

        无论是分库还是分区,都是在单独的对一个数据库的操作,但是单个数据库也存在一些问题:

  • 单点故障问题:数据库发生故障,整个系统就会瘫痪

  • 单库的性能瓶颈问题:单库受服务器限制,其网络带宽、CPU、连接数都有瓶颈

  • 单库的存储瓶颈问题:单库的磁盘空间有上限,如果磁盘过大,数据检索的速度又会变慢

        综上,在大型系统中,我们除了要做分表、还需要对数据做分库,建立综合集群。

        首先,在微服务项目中,我们会按照项目模块,每个微服务使用独立的数据库,因此每个库的表是不同的,这种分库模式称为垂直分库。

        而为了保证单节点的高可用性,我们会给数据库建立主从集群,主节点向从节点同步数据。两者结构一样,可以看做是水平扩展。

        这个时候就会出现垂直分库、水平扩展的综合集群,如图:

优点:

• 解决了海量数据存储问题,突破了单机存储瓶颈

• 提高了并发能力,突破了单机性能瓶颈

• 避免了单点故障

缺点:

• 成本非常高

• 数据聚合统计比较麻烦

• 主从同步的一致性问题

• 分布式事务问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值