大流量、业务效率?从一个榜单开始

原创不易,求分享、求一键三连

业务场景

之前在一家直播团队做过一段时间的营收部门负责人,榜单是直播平台最通用的一种玩法,可以彰显用户的身份,刺激用户之间的pk,从而增加平台的营收,下面介绍几种榜单常见的玩法。

限时热门榜

玩法规则大致是每30分钟,对主播收到打赏值进行排行,其中有2类排行榜,限时热门总榜和限时热门分区榜,这里使用自然30分钟代表每个周期,每天有48个30分钟,分别有1、2、3代表每天第1、2、3个30分钟。

大流量、业务效率?从一个榜单开始

欧皇主播榜

玩法规则大致是主播房间内用户抽到的冰晶城堡数量的排行,页面上有3个榜单,昨日榜、今日榜、总榜。

大流量、业务效率?从一个榜单开始

直播重营收,营收看活动,活动看打榜,所以这种榜单每个月都会以各种形式出现,我们需要设计一套通用的榜单系统,减轻后续工作量,这是背景。

榜单分析

首先我们对业务进行抽象:

大流量、业务效率?从一个榜单开始

我们抽象出一些关键词:

  • 用户id(user_id)
  • 主播id(master_id)
  • 投喂(coin)
  • 时间
  • 分区

时间有今日、昨日、自然30分钟。从这些榜单中我们可以抽象出统一的一套规则,榜单类型、榜单维度、榜单对象、榜单积分。

榜单规则

  • *榜单类型

同一种榜单类型代表的是一类榜单,这一类榜单具备同一套逻辑规则,例如限时热门榜,虽然每30分钟会有一个榜单,但是这些榜单数据的规则是一致的。

限时热门分区榜和限时热门榜的规则是不一样的,热门分区限时榜统计的是分区的主播,限时热门分区榜统计的是全区的主播。

需要注意的是,限时热门分区榜和限时热门榜也可抽象成一类榜单。

  • *榜单维度

同一类榜单可能会有多个榜单,例如限时热门榜,每个自然30分钟内都会有一个榜单,每个的榜单都是不同的,或者说是互不影响的。

限时热门分区榜,每个自然30分钟内都会有一个榜单,这里自然30分钟就是一个维度。

限时热门分区榜,每个自然30分钟内*所有分区都会有一个榜单,这里自然30分钟和分区就是一个维度。

欧皇主播日榜,活动时间内主播房间内每日用户抽到的冰晶城堡数量的排行,这里日就是一个维度。

欧皇主播日榜,活动时间内主播房间内用户抽到的冰晶城堡数量的排行,这里只有一个榜单数据,维度为空。

  • *榜单对象

榜单对象指的是我们给谁进行排行,这个谁可以是用户,也可以是主播,也可以是其他,例如限时热门榜,这个榜单对象就是主播,我们需要给主播进行排行。

  • *榜单对象积分

榜单对象积分比较简单,就是一个进行排序的值,例如限时热门榜,用户消费就是积分。

榜单实现

  • *榜单配置

配置可以放在配置文件里面,或者可以通过后台管理系统进行管理,配置如下:

[[rank]]<br>rankname&#xA0;=&#xA0;"master_luck_day"&#xA0;&#xA0;//&#xA0;&#x699C;&#x5355;&#x7C7B;&#x578B;<br>title&#xA0;=&#xA0;"&#x6B27;&#x7687;&#x4E3B;&#x64AD;&#x65E5;&#x699C;"&#xA0;//&#xA0;&#x699C;&#x5355;&#x540D;&#x79F0;&#xFF0C;&#x5B9E;&#x9645;&#x4E1A;&#x52A1;&#x4E2D;&#x6CA1;&#x6709;&#x4F7F;&#x7528;&#x5230;&#xFF0C;&#x8FD9;&#x91CC;&#x53EA;&#x505A;&#x4E00;&#x4E2A;&#x540D;&#x79F0;&#x533A;&#x5206;<br>top&#xA0;=&#xA0;100&#xA0;//&#xA0;&#x699C;&#x5355;&#x6700;&#x591A;&#x5C55;&#x793A;n&#x6761;&#xFF0C;&#x548C;&#x4E1A;&#x52A1;&#x6709;&#x5173;<br>set&#xA0;=&#xA0;86400&#xA0;*&#xA0;2&#xA0;//&#xA0;redis&#xA0;set&#x7684;&#x8FC7;&#x671F;&#x65F6;&#x95F4;&#xFF0C;&#x89C1;&#x4E0B;&#x65B9;&#x8BF4;&#x660E;<br>string_expire&#xA0;=&#xA0;86400&#xA0;//&#xA0;redis&#xA0;item&#x7684;&#x8FC7;&#x671F;&#x65F6;&#x95F4;&#xFF0C;&#x89C1;&#x4E0B;&#x65B9;&#x8BF4;&#x660E;<br>customsort&#xA0;=&#xA0;1&#xA0;//&#xA0;&#x81EA;&#x5B9A;&#x4E49;&#x6392;&#x5E8F;&#x89C4;&#x5219;&#xFF0C;&#x4EE3;&#x8868;&#x76F8;&#x540C;&#x79EF;&#x5206;&#xFF0C;&#x5148;&#x5230;&#x7684;&#x5728;&#x524D;&#xFF0C;&#x89C1;&#x4E0B;&#x65B9;&#x8BF4;&#x660E;<br>[[rank]]<br>rankname&#xA0;=&#xA0;"master_luck_total"<br>title&#xA0;=&#xA0;"&#x6B27;&#x7687;&#x4E3B;&#x64AD;&#x603B;&#x699C;"&#xA0;<br>top&#xA0;=&#xA0;100&#xA0;&#xA0;<br>set&#xA0;=&#xA0;86400&#xA0;*&#xA0;30&#xA0;//&#xA0;&#x5047;&#x8BBE;&#x6D3B;&#x52A8;&#x8FC7;&#x671F;&#x65F6;&#x95F4;&#x662F;30&#x5929;<br>string_expire&#xA0;=&#xA0;86400&#xA0;<br>customsort&#xA0;=&#xA0;2
  • *榜单接口

这里只展示最常见的3个接口,其它接口请在具体业务场景中添加。

incrScore:增加榜单积分,类似于redis的incr;

请求参数

大流量、业务效率?从一个榜单开始

返回结果

{<br>&#xA0;&#xA0;&#xA0;&#xA0;"code":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;"errcode":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;"message":&#xA0;"ok",<br>&#xA0;&#xA0;&#xA0;&#xA0;"errmsg":&#xA0;"ok",<br>&#xA0;&#xA0;&#xA0;&#xA0;"data":&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#xA0;&#x6210;&#x529F;&#x6216;&#x5931;&#x8D25;&#xFF0C;&#x5931;&#x8D25;&#x53EF;&#x4EE5;&#x91CD;&#x8BD5;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"status":&#xA0;true<br>&#xA0;&#xA0;&#xA0;&#xA0;}<br>}

getScore:获取榜单分数及榜单排名;

请求参数

大流量、业务效率?从一个榜单开始

返回结果

{<br>&#xA0;&#xA0;&#xA0;&#xA0;"code":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;"errcode":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;"message":&#xA0;"ok",<br>&#xA0;&#xA0;&#xA0;&#xA0;"errmsg":&#xA0;"ok",<br>&#xA0;&#xA0;&#xA0;&#xA0;"data":&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#xA0;&#x5206;&#x6570;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"score":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#xA0;&#x6392;&#x540D;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"rank":&#xA0;0<br>&#xA0;&#xA0;&#xA0;&#xA0;}<br>}

topScore:获取榜单排名

请求参数

大流量、业务效率?从一个榜单开始

返回结果

{<br>&#xA0;&#xA0;&#xA0;&#xA0;"code":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;"errcode":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;"message":&#xA0;"ok",<br>&#xA0;&#xA0;&#xA0;&#xA0;"errmsg":&#xA0;"ok",<br>&#xA0;&#xA0;&#xA0;&#xA0;"data":&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#xA0;&#x6392;&#x540D;&#x6570;&#x636E;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"data":&#xA0;[<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#xA0;rank_item<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"rank_item":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#xA0;&#x6392;&#x540D;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"rank":&#xA0;0,<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#xA0;&#x79EF;&#x5206;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"score":&#xA0;0<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;}<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;]<br>&#xA0;&#xA0;&#xA0;&#xA0;}<br>}

榜单表设计

表设计如下,在实际使用中,需要注意分库分表,索引也根据实际使用到的场景进行添加,这里只展示唯一索引:

CREATE&#xA0;TABLE&#xA0;rank&#xA0;(<br>&#xA0;&#xA0;id&#xA0;bigint(20)&#xA0;unsigned&#xA0;NOT&#xA0;NULL&#xA0;AUTO_INCREMENT,<br>&#xA0;&#xA0;rank_name&#xA0;varchar(30)&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'0'&#xA0;COMMENT&#xA0;'&#x699C;&#x5355;&#x7C7B;&#x578B;',<br>&#xA0;&#xA0;rank_type&#xA0;varchar(50)&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;''&#xA0;COMMENT&#xA0;'&#x699C;&#x5355;&#x7EF4;&#x5EA6;',<br>&#xA0;&#xA0;rank_item&#xA0;bigint(20)&#xA0;unsigned&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'0'&#xA0;COMMENT&#xA0;'&#x699C;&#x5355;&#x5BF9;&#x8C61;',<br>&#xA0;&#xA0;score&#xA0;bigint(20)&#xA0;unsigned&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'0'&#xA0;COMMENT&#xA0;'&#x79EF;&#x5206;',<br>&#xA0;&#xA0;extra_data&#xA0;varchar(50)&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'&#x6269;&#x5C55;&#x6570;&#x636E;',<br>&#xA0;&#xA0;rank&#xA0;int(11)&#xA0;unsigned&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'0'&#xA0;COMMENT&#xA0;'&#x6392;&#x540D;',<br>&#xA0;&#xA0;custom_sort&#xA0;varchar(200)&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;''&#xA0;COMMENT&#xA0;'&#x81EA;&#x5B9A;&#x4E49;&#x6392;&#x5E8F;',<br>&#xA0;&#xA0;PRIMARY&#xA0;KEY&#xA0;(id),<br>&#xA0;&#xA0;UNIQUE&#xA0;KEY&#xA0;uk_rank_id_rank_type_rank_item&#xA0;(rank_id,rank_type,rank_item)<br>)&#xA0;ENGINE=InnoDB&#xA0;DEFAULT&#xA0;CHARSET=utf8mb4&#xA0;COMMENT='&#x901A;&#x7528;&#x699C;&#x5355;&#x8868;'<br>CREATE&#xA0;TABLE&#xA0;rank_log&#xA0;(<br>&#xA0;&#xA0;id&#xA0;bigint(20)&#xA0;unsigned&#xA0;NOT&#xA0;NULL&#xA0;AUTO_INCREMENT,<br>&#xA0;&#xA0;rank_name&#xA0;varchar(30)&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'0'&#xA0;COMMENT&#xA0;'&#x699C;&#x5355;id',<br>&#xA0;&#xA0;rank_type&#xA0;varchar(50)&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;''&#xA0;COMMENT&#xA0;'&#x5B50;&#x699C;id',<br>&#xA0;&#xA0;rank_item&#xA0;bigint(20)&#xA0;unsigned&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'0'&#xA0;COMMENT&#xA0;'&#x5BF9;&#x8C61;id',<br>&#xA0;&#xA0;msg_id&#xA0;varchar(150)&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;''&#xA0;COMMENT&#xA0;'&#x6D88;&#x606F;',<br>&#xA0;&#xA0;change_score&#xA0;bigint(20)&#xA0;unsigned&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'0'&#xA0;COMMENT&#xA0;'&#x53D8;&#x5316;&#x7684;&#x79EF;&#x5206;',<br>&#xA0;&#xA0;after_score&#xA0;bigint(20)&#xA0;unsigned&#xA0;NOT&#xA0;NULL&#xA0;DEFAULT&#xA0;'0'&#xA0;COMMENT&#xA0;'&#x53D8;&#x5316;&#x540E;&#x7684;&#x79EF;&#x5206;',<br>&#xA0;&#xA0;PRIMARY&#xA0;KEY&#xA0;(id),<br>&#xA0;&#xA0;UNIQUE&#xA0;KEY&#xA0;uk_rank_name_rank_type_rank_item_msg_id&#xA0;(rank_name,rank_type,rank_item,msg_id),<br>)&#xA0;ENGINE=InnoDB&#xA0;DEFAULT&#xA0;CHARSET=utf8mb4&#xA0;COMMENT='&#x699C;&#x5355;&#x66F4;&#x65B0;&#x65E5;&#x5FD7;&#x8868;'

更新榜单积分时,同时会更新榜单日志表,通过事务更新,保持数据一致性,通过msg_id保证幂等,接口如果调用失败,可以重试,类似于用户花钱时,会更新钱包数据同时会记录流水数据。调用incr接口时,会执行下面的sql,这2条sql在同一事务中执行。

insert&#xA0;into&#xA0;rank(rank_name,rank_type,rank_item,score)&#xA0;values(params.rank_name,params.rank_type,params.rank_item,params.score)&#xA0;insert&#xA0;on&#xA0;dumplicate&#xA0;update&#xA0;score&#xA0;=&#xA0;params.score;<br>insert&#xA0;into&#xA0;rank_log(rank_name,rank_type,rank_item,score,msg_id)&#xA0;values(params.rank_name,params.rank_type,params.rank_item,params.score,params.msg_id);

需要注意的是数据库会保存全量排行榜数据。

事务说明

使用事务更新是否有必要?能否直接通过缓存做幂等?

确实在一般情况下使用缓存做幂等(set key … nx px),然后辅以日志查询就足够了,使用流水日志对一致性更好,同时查询问题更加方便,但是对 数据库的压力更大,可以根据实际业务场景选用合适的技术方案。

榜单缓存设计

在一般业务中,榜单只需要展示topn的排名数据,例如top10,top100等,并且在有一定体量的公司中,数据库都不能直接对外,必须在数据库上层加一层缓存。

  • *榜单排名数据

榜单排名数据使用的是zset实现,zset的key为榜单名称+子榜id, zset的member为对象id,score为榜单积分。更新榜单时,做如下操作:

_rankListKey&#xA0;=&#xA0;"rank:list:%d:%s"<br>rankListKey&#xA0;:=&#xA0;fmt.Sprintf(_rankListKey,&#xA0;params.RankName,&#xA0;params.RankType)<br>//&#xA0;&#x4E0B;&#x9762;&#x7684;redis&#x64CD;&#x4F5C;&#x53EF;&#x4EE5;&#x4F7F;&#x7528;&#x4E00;&#x4E9B;&#x4F18;&#x5316;&#x624B;&#x6BB5;&#xFF0C;&#x4F8B;&#x5982;pipline&#xFF0C;&#x6B64;&#x5904;&#x4E3A;&#x793A;&#x4F8B;<br>redis.zAdd(_rankListKey,&#xA0;score,&#xA0;rank_item)&#xA0;//&#xA0;score&#x4EE3;&#x8868;&#x7684;&#x662F;&#x8BE5;&#x699C;&#x5355;&#x5BF9;&#x8C61;&#x5F53;&#x524D;&#x7684;&#x79EF;&#x5206;<br>redis.Expire(_rankListKey,&#xA0;&#xA0;config.set_expire)&#xA0;//&#xA0;config.set_expire&#x4E3A;&#x914D;&#x7F6E;set&#x7684;&#x7684;&#x8FC7;&#x671F;&#x65F6;&#x95F4;<br>redis.zrembyscore(_rankListKey,0,last_rank_score&#xA0;-&#xA0;1)&#xA0;//last_rank_score&#x4EE3;&#x8868;&#x7684;&#x662F;&#x7B2C;top&#x540D;&#x7684;&#x79EF;&#x5206;&#xFF0C;&#x5220;&#x9664;0&#x5230;&#x6700;&#x540E;&#x4E00;&#x540D;&#x4E4B;&#x95F4;&#x7684;&#x6570;&#x636E;&#xFF0C;&#x4FDD;&#x8BC1;&#x6570;&#x636E;&#x53EA;&#x6709;top&#x4E2A;

zset的过期时间大于榜单更新最大时间,如下所示:

大流量、业务效率?从一个榜单开始

需要注意的是,zset的member数量是需要限制的,不然可能会有大key和热key的问题。

  • *榜单积分数据

业务场景中需要展示某个主播具体的有多少积分。榜单排名数据使用的是string实现, key为榜单类型+榜单维度+榜单对象,value为榜单积分。

此处可能会有人会有疑惑,为啥会需要需要榜单积分缓存?

  1. zset限制member数量大小;
  2. 业务场景需要展示超过topn的积分,如上第2张图;
_rankItemKey&#xA0;=&#xA0;"rank:item:%d:%s:%d"<br>rankItemKey&#xA0;:=&#xA0;fmt.Sprintf(_rankItemKey,&#xA0;params.RankName,&#xA0;params.RankType,&#xA0;params.RankItem)<br>score,&#xA0;err&#xA0;:=&#xA0;redis.get(rankItemKey)<br>if&#xA0;err&#xA0;==&#xA0;redis.ErrNil&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#x56DE;&#x6E90;&#x6570;&#x636E;&#x5E93;&#xFF0C;&#x67E5;&#x8BE2;&#x79EF;&#x5206;&#xFF0C;&#x5F97;&#x5230;rscore<br>&#xA0;&#xA0;&#xA0;&#xA0;redis.set(rankItemKey,&#xA0;rank_item,&#xA0;rscore&#xA0;+&#xA0;params.score,&#xA0;config.string_expire)&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;config.string_expire&#x4E3A;&#x914D;&#x7F6E;&#x7684;&#x7684;&#x8FC7;&#x671F;&#x65F6;&#x95F4;<br>&#xA0;&#xA0;&#xA0;&#xA0;err&#xA0;=&#xA0;nil<br>&#xA0;&#xA0;&#xA0;&#xA0;return&#xA0;nil<br>}&#xA0;else&#xA0;if&#xA0;err&#xA0;!=&#xA0;nil&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;//&#xA0;&#x8FD4;&#x56DE;&#x9519;&#x8BEF;&#xFF0C;&#x4E1A;&#x52A1;&#x53EF;&#x4EE5;&#x91CD;&#x8BD5;<br>&#xA0;&#xA0;&#xA0;&#xA0;return&#xA0;err<br>}<br>redis.incr(rankItemKey,&#xA0;params.rank_item,params.score)&#xA0;

榜单积分缓存数据量会比榜单排名缓存多很多,过期时间可以根据redis服务容量进行配置,可以在榜单更新时间内失效。

最后给一个流程图:

榜单更新流程

大流量、业务效率?从一个榜单开始

榜单实现案例

  1. 限时热门榜/限时热门分区榜实现

当用户在直播间消费时,增加榜单数据,参数入下:

大流量、业务效率?从一个榜单开始
  1. 欧皇主播日榜/欧皇主播总榜实现

当用户在直播间抽奖抽到指定道具时,增加榜单数据,参数如下:

大流量、业务效率?从一个榜单开始

进阶场景

近7日榜的实现

主播近七日收到用户打赏之和的排行,这里近七日是一个滑动窗口概念,例如20200420代表的是20200414 ~20200420这7日。

  • *业务分析

榜单维度,可以用日期来标识,例如20200420代表的是20200414 ~20200420这7日 榜单对象,主播 榜单积分,主播近7日收到的积分之和

  • *方案1

存在两种榜单数据,一个是七日的榜单数据(实际使用),一个是每日的榜单数据(辅助使用)。

每日凌晨启动定时任务将前6日的日榜数据加到近7日的榜单数据中,数据是从数据库中获取,获取的是全量数据,当凌晨用户投喂时,会实时更新七日榜单的数据,也就是说脚本积分数据和实时积分数据是同时在跑的,理论上,当脚本跑完时,数据会是正确的。

这种方案好处是简单,可以快速实现,坏处需要定时任务,且数据不是平滑更新的,定时任务执行期间数据不准确。

  • *方案2

方案2没有使用每日的辅助榜单数据,每次更新数据时会同步更新今日的七日榜和后6天的七日榜,例如今天是2022-04-20,如果增加1积分,会同时更新20220420七日榜、20220422七日榜、20220423七日榜、20220424七日榜、20220425七日榜、20220426七日榜。

大流量、业务效率?从一个榜单开始

当到了26日时,主播1的20220426七日榜的积分会为3;当到了27日时,主播1的20220427七日榜的积分会为2;当到了28日时,主播1的20220428七日榜的积分会为1;当到了29日时,主播1的20220429七日榜的积分会为0。

这种方案好处是没有定时任务,数据是平滑更新的,坏处是接口请求会放大,同时会更新很多条数据,基本无法支持近30天的场景,且业务调用较为复杂。

  • *方案3

更新数据时更新今日的七日榜数据,同时更新明天的七日榜数据(如果没有脚本相当于是今日的日榜数据),并且记录每日的数据,每日中午会将前5日每日的数据加到明日的7日榜中。

大流量、业务效率?从一个榜单开始

我们一起看一下20220423七日榜的数据的正确性,20220423七日榜在2022-04-22增加积分1,在2022-04-22中午,将2022-04-17 ~ 2022-04-21这5天日榜的数据共2分加到了20220423七日榜中,在2022-04-23主播1增加1积分增加了积分1,主播积分为4。

这个方案的好处是数据是平滑更新的,可以实现任意时间阶段的连续榜单,且调用简单,连续榜的逻辑已是在服务内部实现,坏处是实现较为复杂。

榜单积分相同如何排序?

zset存在一个问题,就是相同积分时,zset会按照member的字典序进行排序,有些业务场景,可能会对相同积分的也需要进行排序,例如相同积分,先到在前。榜单配置中增加有customsort字段, 1代表按时间正序排序, 2代表按时间倒序排序。

数据库存在custom_sort字段,如果按照时间正序排序,为负数的时间戳,如果按照时间倒序排序,为正数的时间戳。

每次更新积分数据后,搜索数据库与该对象积分相同的数据(最多top条,根据配置,下面用1000来说明),sql语句为:

select&#xA0;item_id&#xA0;from&#xA0;rank&#xA0;where&#xA0;rank_name&#xA0;=&#xA0;params.rank_name&#xA0;and&#xA0;rank_type&#xA0;=&#xA0;params.rank_type&#xA0;and&#xA0;score&#xA0;=&#xA0;cur_score&#xA0;order&#xA0;by&#xA0;custom_sort&#xA0;desc&#xA0;limit&#xA0;1000&#xA0;

然后将score积分加上一个小数,从0.999至0,将相同的数据添加至zset之中,从而实现相同积分排序。

如何实现排名变化趋势?

有些榜单场景会有主播今日的排名会和逐日昨日的排名进行比较,看是上升、下降还是不变?

大流量、业务效率?从一个榜单开始

例如主播今日投喂榜需要实现排名变化趋势,可以每天零点执行脚本,获取榜单上一个周期的排行数据,也就是昨日的topn排名的主播排行信息,写到今日的榜单数据中,并且将昨日排名数据,写到今日的排行数据中,字段使用extra_data,当获取榜单排行时,可以获取到extra_data数据,当前排名和昨日排名数据进行比较即可得到变化趋势,若没有获取到extra_data数据,即昨日没有上排行榜,变化趋势为向上。

这个方案有个小问题,就是不够平滑,但该功能实时性要求较小,可以忽略。extra_data怎么使用缓存、怎么平滑展示数据留个大家去思考。

以上就是一个实际业务场景,以及面对这个业务场景时候如何提升开发效率的case。

好了,今天的分享就到这,喜欢的同学可以四连支持:

大流量、业务效率?从一个榜单开始

想要更多交流可以加群讨论:

大流量、业务效率?从一个榜单开始

Original: https://www.cnblogs.com/yexiaochai/p/16221926.html
Author: 叶小钗
Title: 大流量、业务效率?从一个榜单开始

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/554274/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球