exZset是阿里雲自研的資料結構,可實現256維度double類型的分值排序。
背景資訊
原生Zset痛點:原生Redis的Sorted Set(也稱Zset)排序結構只支援1個double類型的分值排序,這在實現多維度排序時非常困難。例如通過IEEE 754結合拼接的方式實現多維度排序,此類方式存在實現複雜、精度下降、ZINCRBY命令無法使用等局限性。
exZset特點
藉助阿里雲自研的exZset資料結構,可協助您輕鬆實現多維度排序能力,相較於傳統方案具有如下優勢:
最大支援256維的double類型的分值排序(排序優先順序為從左往右)。
對於多維score而言,左邊的score優先順序大於右邊的score,以一個三維score為例:score1#score2#score3,exZset在比較時,會先比較score1,只有score1相等時才會比較score2,否則就以score1的比較結果作為整個score的比較結果。同樣,只有當score2相等時才會比較score3。若所有維度分數都相同,則會按照元素順序(ascii順序)進行排序。
為了方便理解,可以把#想象成小數點(.),例如0#99、99#90和99#99大小關係可以理解為0.99 < 99.90 < 99.99,即0#99 < 99#90 < 99#99。
支援EXZINCRBY命令,不再需要取回當前資料,在本地增加值後再拼接寫回Tair。
支援和原生Zset相似的API。
提供 普通熱門排行榜 和 分布式架構熱門排行榜 的能力。
提供開源TairJedis用戶端,無需任何編解碼封裝,您也可以參考開源自行實現封裝其他語言版本。
關於本文中使用的exZset相關命令,詳細解釋,請參見exZset。
應用情境
排序需求常見於各類遊戲、應用、獎牌等熱門排行榜中,通常業務對排序的需求如下:
支援增刪改查和反向排序,可根據分數範圍擷取相應使用者。
快速返回排序請求的結果。
具備擴充能力(即 分布式架構熱門排行榜 ),在資料分區容量或計算能力不足時,可以將其擴充到其他資料分區。
實現獎牌榜
在獎牌榜中,從金、銀、銅牌的維度對參與方進行排名,先按照金牌數量排序;如果金牌數量一致,再以銀牌數量排序;如果銀牌數量也一致,再按照銅牌數量排序。在本樣本中,參與方E和F的金牌數相等,但是銀牌數參與方E大於F,因此參與方E排名靠前,通過exZset的多維排序能力,您只需要使用簡單的API即可完成該需求。
排名 | 參與方 |
|
|
|
1 | A | 32 | 21 | 16 |
2 | B | 25 | 29 | 21 |
3 | C | 20 | 7 | 12 |
4 | D | 14 | 4 | 16 |
5 | E | 13 | 21 | 18 |
6 | F | 13 | 17 | 14 |
程式碼範例
本方案需使用TairJedis(Tair自研)用戶端。
添加pom.xml配置。
<dependency> <groupId>com.aliyun.tair</groupId> <artifactId>alibabacloud-tairjedis-sdk</artifactId> <version>5.3.1</version> </dependency>範例程式碼。
import io.valkey.JedisPool; import io.valkey.JedisPoolConfig; import com.aliyun.tair.tairzset.LeaderBoard; public class LeaderBoardExample { // 配置執行個體串連地址、連接埠號碼、帳號密碼等資訊。 private static final int DEFAULT_CONNECTION_TIMEOUT = 5000; private static final int DEFAULT_SO_TIMEOUT = 2000; private static final String HOST = "<r-bp1mx0ydsivrbp****.redis.rds.aliyuncs.com>"; private static final int PORT = 6379; private static final String PASSWORD = "<Pass****word>"; private static final JedisPoolConfig config = new JedisPoolConfig(); public static void main(String[] args) { JedisPool jedisPool = new JedisPool(config, HOST, PORT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_SO_TIMEOUT, PASSWORD, 0, null); // 建立熱門排行榜。 LeaderBoard lb = new LeaderBoard("leaderboard", jedisPool, 10, true, false); // 如果金牌數相同,按照銀牌數排序,否則繼續按照銅牌。 // 金牌 銀牌 銅牌 lb.addMember("A", 32, 21, 16); lb.addMember("D", 14, 4, 16); lb.addMember("C", 20, 7, 12); lb.addMember("B", 25, 29, 21); lb.addMember("E", 13, 21, 18); lb.addMember("F", 13, 17, 14); // 擷取A的排名。 lb.rankFor("A"); // 1 System.out.println(lb.rankFor("A")); // 擷取Top3。 lb.top(3); System.out.println(lb.top(3)); // [{"member":"A","score":"32#21#16","rank":1}, // {"member":"B","score":"25#29#21","rank":2}, // {"member":"C","score":"20#7#12","rank":3}] // 擷取整個熱門排行榜。 lb.allLeaders(); System.out.println(lb.allLeaders()); // [{"member":"A","score":"32#21#16","rank":1}, // {"member":"B","score":"25#29#21","rank":2}, // {"member":"C","score":"20#7#12","rank":3}, // {"member":"D","score":"14#4#16","rank":4}, // {"member":"E","score":"13#21#18","rank":5}, // {"member":"F","score":"13#17#14","rank":6}] } }更多操作請參見alibabacloud-tairjedis-sdk中的com.aliyun.tair.tairzset.LeaderBoard介紹。
實現即時、小時、日、周和月維度熱門排行榜
該情境下的需求是實現月榜,那麼這個Key就從月的維度進行索引。
利用TairZset的多級索引能力可以輕鬆實現不同時間範圍的熱門排行榜。本案例中,月度的所有資料存放區在1個Key中(名稱為julyZset),寫入示範資料如下:
EXZINCRBY julyZset 7#2#6#16#22#100 7#2#6#16#22_user1
EXZINCRBY julyZset 7#2#6#16#22#50 7#2#6#16#22_user2
EXZINCRBY julyZset 7#2#6#16#23#70 7#2#6#16#23_user1
EXZINCRBY julyZset 7#2#6#16#23#80 7#2#6#16#23_user17#2#6#16#22#100表示7月第2周6號16點22分,更新其分數為100。7#2#6#16#22_user1表示此時間點更新的使用者,使用者名稱加入了具體時間首碼。
熱門排行榜類型 | 具體實現的命令和返回結果 |
小時層級即時熱門排行榜,即從目前時間往前推算一個小時(例如16:23~15:23)。 說明 如果訪問非常頻繁,可以將結果進行緩衝。 | 查詢命令: 返回結果: |
固定1小時熱門排行榜,例如查詢16:00~17:00時間段的熱門排行榜。 | 查詢命令: 返回結果: |
日熱門排行榜,例如查詢7月5號的日熱門排行榜。 | 在查詢前,插入一條7月5號的資料: 返回結果: 查詢命令: 返回結果: |
周熱門排行榜,例如查詢7月第2周的熱門排行榜。 | 查詢命令: 返回結果: |
月熱門排行榜,例如查詢7月的月熱門排行榜。 | 在查詢前,插入一條7月20號的資料: 返回結果: 查詢命令: 返回結果 |
金牌
銀牌
銅牌