Dai Chong's blog

介绍

 说起排行榜大家肯定不陌生,在项目开发中排行榜的运用非常的多。如王者农药的天梯排行榜、商城的销量排行榜、热卖榜、用户积分排行榜等等。不管他是什么类型的排行榜,其业务逻辑、实现方法几乎是完全相同的。
 如这样的一个手机和笔记本的销量排行榜是如何实现的呢?

例子

榜单实现

 假设做一个如上图一样的排行榜,规定榜单必须是实时更新的,你会使用什么技术来实现呢?

方案一

 在数据库新建一张数据表,通过下单事件钩子来更新这张表的数据,记录销量情况。

1
select id,name,image from goods left join goods_top on goods.id=goods_top.goods_id where sales > 0 order by sales desc limit 10;

 最常见、最简单的写法莫过于此,但这个写法有一个非常严重的问题。假设goods中数据规模极大,记录表goods_top表中数据规模也不小,在每次查询的性能消耗无疑是很大的,在极端的情况下甚至可能会出现请求超时的情况。

 这个方案在实际的项目运用中也很少出现,除非你能确定数据表规模一定会非常的小,或者你根本不考虑什么性能的情况下才会使用。

方案二

 使用redis有序集合(zset)来实现实时排行榜。关于redis有序集合的相关介绍、原理什么的大家自行百度

 (1)Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
 (2)不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
 (3)有序集合的成员是唯一的,但分数(score)却可以重复。
 (4)集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

 也就是说,我们可以通过下单事件钩子创建一个zset集合,我们通过value来区分是哪个商品,通过score来排名。

具体代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

use Illuminate\Support\Facades\Redis;

/**
* 商品销量排行榜
*/
class Rank
{
// key
private static function key()
{
$activeActivityId = Main::info();
return "goods_top";
}

// 增加销量
public static function add($id, $number)
{
return Redis::zIncrBy(self::key(), $number, $id);
}

// 获取销量
static function get($id)
{
return Redis::zScore(self::key(), $id);
}

// 获取成员排名-正序
static function getRank($id)
{
return Redis::zrevrank(self::key(), $id);
}

// 通过排名获取成员
static function getRevRange($start, $stop): array
{
return Redis::zRevRange(self::key(), $start, $stop);
}
}
如何使用
1
2
3
4
Rank::add(1008,10); // 商品ID为1008的销量增加10
Rank::get(1008); // 获取商品ID为1008的销量
Rank::getRank(1008); // 获取商品ID为1008的排名
Rank::getRevRange(0,9); // 获取排名前十的商品ID,返回数组

总结

 实现排行榜的方式有很多,大家根据具体需求和现有技术来确定实现方案。不要过分的追求性能或者是开发速度,欲速则不达。


 评论