如何設計一個排行榜?
前言
大家好,我是田螺。
最近有位星球粉絲問:田螺哥,如何設計一個排行榜?
日常開發中,我們經常需要涉及設計排行榜的需求,如禮物排行榜、微信運動排行、王者榮耀段位排行榜等等。今天我帶大家聊聊,排行榜如何設計。
數據庫的 order by
很多小夥伴,一提到排行榜,就想到數據庫的order by
。
其實這個思路是沒有錯的,我們假設用order by
來實現一個簡單的微信運動步數排行榜。
假設表結構如下:
CREATE TABLE `user_info` (
`id` int NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) DEFAULT NULL,
`age` int DEFAULT NULL,
`step` int DEFAULT 0 comment '步數',
`picture_url` varchar(255) DEFAULT NULL comment '頭像url',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3
這時候要實現一個微信步數排行版,不是有手就行嘛:
select *
from user_info
order by step desc
這個實現沒有問題的,如果表的數據量少的話,反而推薦這樣實現。如果數據量多呢?
我們先來造一千萬數據。
for (int i = 0; i < 10000000; i++) {
UserInfo record = new UserInfo();
record.setUserName("user" + i);
record.setAge(10);
record.setUserName("userName" + i);
record.setStep(new Random().nextInt(1000) * 1000);
record.setPictureUrl("https://tianluo/picture/"+i+".jpg");
userInfoDao.insertUserInfo(record);
}
我們現在查詢步數前 1000 的用戶,SQL 如下:
select *
from user_info
order by step desc limit 0,1000;
然後看下耗時,set @@profiling=1;
,show profiles;
可以發現耗時 14 秒多, 這個耗時太久了,這種情況,我們可以添加索引優化它。
ALTER TABLE user_info ADD INDEX idx_step (step);
添加完索引之後,發現再去查步數前 1000 的用戶,0.005 秒:
這時候感覺,order by 做分頁也挺好的。但是這個是你加了索引和限制排序條數的前提下啦。
實際上,當數據量較小且查詢不頻繁時,可以使用 MySQL 的 order by 來實現排行榜。而當數據量較大且需要實時更新並頻繁查詢時,使用 Redis 的有序集合更爲適合。
Redis 的 zset
有序集合zset
是 Redis
提供的一種數據結構,它類似於集合(set),但每個成員都關聯着一個分數(score
),Redis 使用這個分數來對集合中的成員進行排序。
大家可以看看官網對它的簡介哈:
https://redis.io/commands/zadd/#sorted-sets-101
我們繼續搞一個微信運動排行版。假設 redis 的 key 是 run:ranking
, 然後小明的步數是 1000,小華的步數是 3000,小紅的步數是 2000,小李的步數是 4000 步,我們可以用 zadd 添加到redis
,如下:
zadd run:ranking 1000 “小明”
zadd run:ranking 3000 “小華”
zadd run:ranking 2000 “小紅”
zadd run:ranking 4000 “小李”
我們用這個命令,就可以輸出排行榜啦:
ZREVRANGE run:ranking 0 -1 WITHSCORES
-
ZREVRANGE 表示從大到小排序
-
ZRANGE 表示從小到大排序
-
ZADD key score1 member1 score2 member2… 表示向集合中添加元素
它在 Redis 類似這樣保存的:
如果在 redis 中的 score 分數相同,它是如何排序的呢?
在 Redis 的有序集合(Sorted Set)中,如果多個成員具有相同的分數,則它們按照字典順序進行排序。具體來說,當有多個成員具有相同的分數時,Redis 會根據成員的字典順序(lexicographical order)對它們進行排序。
字典順序是一種字符串比較的方式,它按照字母表的順序進行排序。對於 ASCII 字符集,字母表的順序是 A-Z,a-z,數字的順序是 0-9。如果成員具有相同的分數,那麼它們將按照它們的字典順序進行排列。
例如,假設有一個名爲 myset 的有序集合,包含了以下成員和分數:
"a" - 1000
"b" - 2000
"c" - 2000
"d" - 3000
在這個示例中,成員 "b" 和 "c" 具有相同的分數 2000。當使用 ZRANGE 或 ZREVRANGE 命令來查看有序集合的成員時,它們將按照字典順序進行排列。因此,成員 "b" 將在成員 "c" 的前面,因爲 "b" 的字典順序小於 "c"。
如果每個人看到的排行榜,都是一樣的,類似上面的處理就可以啦。如果每個人看到的排行榜不一樣的,我們則可以在redis zset
的key
,加上用戶標誌,比如userId
即可。
使用 Redis 的有序集合作爲排行榜,也有一些問題需要注意哈:
-
內存消耗:有序集合中的數據會存儲在 Redis 的內存中。如果排行榜的數據量非常大,可能會佔用大量的內存資源,導致 Redis 實例的內存使用率增加。
-
數據更新頻繁:如果排行榜的數據需要頻繁更新,例如每秒鐘都有大量的用戶分數變化,這可能會導致 Redis 的寫入負載增加,影響系統的性能。
-
網絡開銷:如果客戶端需要頻繁地向 Redis 發送更新排行榜數據的請求,可能會增加網絡開銷,尤其是在高併發的情況下。
-
數據一致性:在多個客戶端同時更新排行榜數據時,可能會出現數據不一致的情況。雖然 Redis 提供了一些原子操作來確保數據的一致性,但在高併發的情況下,仍然需要注意數據一致性的問題。
-
持久化:默認情況下,Redis 將數據存儲在內存中,並且可以通過持久化功能將數據寫入磁盤。但是,使用持久化功能可能會影響 Redis 的性能,並增加系統的負載。
-
高可用性:如果 Redis 實例發生故障或者網絡中斷,可能會影響排行榜的可用性。爲了確保排行榜的高可用性,需要考慮使用 Redis 的主從複製、哨兵模式或者集羣模式來實現數據的備份和故障切換。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/SKm_xJvbKmCMtZNY9EvATg