• 排行榜的简单设计思路


    TOC

    前言

    排行榜几乎已经成为互联网应用中的必备模块,特别是游戏领域,它是对某一相关同类事物的客观实力的反映,带有相互之间的比较性质,带有竞争意义。
    对于平台来说,可以带来一定的权威性,提高平台影响力。
    对于商家来说,可以带来更多的曝光,并对比自身的不足加以改进。
    对于用户来说,可以为行动决策做参考,降低相关风险成本。
    那么排行榜如何实现?我将结合自身的经验提供一些简单设计思路。

    基于mysql

    SELECT ORDER BY

    对于小型的低频业务系统,mysql已经可以支撑所有的排序需求,类似班级排名,成绩排名都比较好实现。
    直接SELECT ORDER BY即可。

    SELECT name, score, @rank := @rank + 1 AS rank
    FROM test, ( SELECT @rank := 0 )
    ORDER BY score DESC

    其中@rank := 0是为了在生成查询结果表的时候生成一组递增的序列号

    mysql中:==的区别
    =
    只有在set和update时才是和:=一样,用于赋值,其它都情况用作等于判断,1表示真,0表示假。
    :=
    用于变量赋值

    如果想要计算排第几名就找出他分数高的有几个,再加上自身个数就好了。

    SELECT count(1)+1 as '排名'
      FROM test
      WHERE score>(SELECT score FROM test WHERE name='李四')

    需要注意的是,该sql语句会进行全表的扫描,所以对于大表来说就不适用了。

    加索引

    一般来说,在数据比较多的时候,排行榜中我们一般不会进行全表排名,否则成本开销会很大,所以会按前100、500、1000这样来排名。此时我们只要在需排序字段加个索引,然后limit即可。

    SELECT name, score, @rank := @rank + 1 AS rank
    FROM test, ( SELECT @rank := 0 )
    ORDER BY score DESC
    LIMIT 100

    这样mysql就会优先走索引,避免了全表扫描。

    加缓存

    对于mysql来说,索引的增删查改也是需要维护的,所以如果对于需要频繁修改的排序字段,并且是非实时的排序需求(例如按小时、按天、按月等),我们可以考虑在写入前加缓存,避免频繁操作数据库,影响其性能。
    例如:一分钟可能需要增减score字段值50次,那么我们可以由缓存先接手请求,等一分钟后,再统一写入数据库,那么这一分钟数据库操作的次数就少了50次,另外读取排行的时候也可以加缓存,效果显著。
    当然缓存期越长,提升越多,但是也要考虑到缓存失效导致数据丢失等情况,来保证数据的一致性。

    借助redis

    对于非实时的排行来说,mysql+缓存是可以支撑业务,但是如果需要实时的排行,mysql就力不从心了,此时我们需要基于高性能的内存来进行操作。
    没错,redis大兄弟又登场了,借助redis的高性能、原子性、以及丰富的数据结构,能够帮助我们快速实现许多传统数据库很难完成的事。
    这次我们需要借助到的是redis的有序集合(sorted set)

    有序集合是通过包含跳表和哈希表的双端口数据结构实现的,因此,每次添加元素时,Redis的复杂度都是O(log(N))。当我们要求排序的元素时,Redis根本不需要做任何工作,它已经全部排序了。

    这里我们主要用到以下几个命令

    ZADD key score1 member1 [score2 member2]
    向有序集合添加一个或多个成员,或者更新已存在成员的分数
    
    
    ZINCRBY key increment member
    有序集合中对指定成员的分数加上增量 increment
    
    
    ZRANK key member
    返回有序集合中指定成员的索引
    
    
    ZREVRANK key member
    返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
    
    
    ZRANGE key start stop [WITHSCORES]
    通过索引区间返回有序集合指定区间内的成员
    
    
    ZREMRANGEBYRANK key start stop
    移除有序集合中给定的排名区间的所有成员

    实时排行榜

    由于用户的score数据还是需要记录保存的,所以上述的mysql方案依旧需要执行,只不过实时部分由redis接手。
    以前100名实时数据为例。
    1、若集合未初始化,读取mysql中排名前100,写入redis有序集合中。
    2、当数据发生变动

    • 判断score是否低于最后一名,是则直接忽略。
    • 写入redis有序集合中。

    3、实时排名直接从redis读
    4、定期移除排名外的元素。

    实现原理

    redis有序集合实现的核心数据结构是【跳表】,大概长这样
    在这里插入图片描述

    对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高,是 O(n)。
    如果像图中那样,对链表建立一级“索引”,查找起来是不是就会更快一些呢?每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作索引或索引层。
    当链表的长度 n 比较大时,比如 1000、10000 的时候,在构建索引之后,查找效率的提升就会非常明显。
    这种链表加多级索引的结构,就是跳表

    熟悉JAVA的同学肯定知道,hashmap是基于数组+链表的方式,当链表大小超过阀值之后,会变为红黑树来提高效率。

    为什么 Redis 要用跳表来实现有序集合,而不是红黑树?

    Redis 中的有序集合支持的核心操作主要有下面这几个:
    插入一个数据;
    删除一个数据;
    查找一个数据;
    按照区间查找数据(比如查找值在 [100, 356] 之间的数据);
    迭代输出有序序列。
    其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。
    对于按照区间查找数据这个操作,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。
    Redis 之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
    不过,跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。

    超过全国**%的用户

    最早看到这个提示是在腾讯管家和360卫士上的开机助手提示,有点意思,那如何简单实现呢?
    1、可以肯定的是我们不会去进行实时检索,而是采用离线计算。
    2、我们可以基于正态分布,定期去计算一次均值与方差,然后通过标准化公式(X-μ)/σ转换成标准正太分布查表得出结果。

    3、或者更简单的方法是,直接用当前值除以最大值得出百分比。

    参考

    https://redis.io/topics/data-types-intro
    https://redis.io/commands#sorted_set
    https://time.geekbang.org/column/article/42896
    https://blog.csdn.net/weixin_41140174/article/details/99696028

  • 相关阅读:
    C# 事务之SqlTransaction
    java获取字符串格式日期向前或向后n天的日期
    java中保留几位小数
    第一个androidAPP项目总结—ListView的上拉和下拉
    java中static作用详解
    第一个androidAPP项目总结—数据请求
    获取控件的高和宽
    Android学习笔记:如何设置ImageView中图片的显示方式
    TextView过长显示省略号, TextView文字中间加横线
    android中控件的使用
  • 原文地址:https://www.cnblogs.com/leestar54/p/13200549.html
Copyright © 2020-2023  润新知