深刻學習Redis( 六),基本類型【ZSet】剖析

更多精彩文章,關注公衆號【ToBeTopJavaer】,更有數萬元精品vip資源免費等你來拿!!!

本文咱們要剖析的基本類型是ZSet,下面咱們將深刻源碼剖析Redis中ZSet的實現。php

ZSet 有序集合

存儲類型

一、sorted set,有序的 set,每一個元素有個 score。java

二、score 相同時,按照 key 的 ASCII 碼排序。python

數據結構對比

操做命令

添加元素redis

zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python

獲取所有元素算法

zrange myzset 0 -1 withscoreszrevrange myzset 0 -1 withscores

根據分值區間獲取元素數組

zrangebyscore myzset 20 30

移除元素,也能夠根據 score rank 刪除ruby

zrem myzset php cpp

統計元素個數數據結構

zcard myzset

分值遞增dom

zincrby myzset 5 python

根據分值統計個數函數

zcount myzset 20 60

獲取元素 rank

zrank myzset java

獲取元素 score

zsocre myzset java

也有倒序的 rev 操做(reverse)

存儲( 實現) 原理

同時知足如下條件時使用 ziplist 編碼:

一、元素數量小於 128 個

二、全部 member 的長度都小於 64 字節

在 ziplist 的內部,按照 score 排序遞增來存儲。插入的時候要移動以後的數據。

對應 redis.conf 參數:

超過閾值以後,使用 skiplist+dict 存儲。

問題一、什麼是 skiplist?

咱們先來看一下有序鏈表:

在這樣一個鏈表中,若是咱們要查找某個數據,那麼須要從頭開始逐個進行比較,直到找到包含數據的那個節點,或者找到第一個比給定數據大的節點爲止(沒找到)。

也就是說,時間複雜度爲 O(n)。一樣,當咱們要插入新數據的時候,也要經歷一樣的查找過程,從而肯定插入位置。

而二分查找法只適用於有序數組,不適用於鏈表。

假如咱們每相鄰兩個節點增長一個指針(或者理解爲有三個元素進入了第二層),讓指針指向下下個節點。

這樣全部新增長的指針連成了一個新的鏈表,但它包含的節點個數只有原來的一半(上圖中是 7, 19, 26)。在插入一個數據的時候,決定要放到那一層,取決於一個算法(在 redis 中 t_zset.c 有一個 zslRandomLevel 這個方法)。

如今當咱們想查找數據的時候,能夠先沿着這個新鏈表進行查找。當碰到比待查數據大的節點時,再回到原來的鏈表中的下一層進行查找。好比,咱們想查找 23,查找的路徑是沿着下圖中標紅的指針所指向的方向進行的:

1. 23 首先和 7 比較,再和 19 比較,比它們都大,繼續向後比較。

2. 但 23 和 26 比較的時候,比 26 要小,所以回到下面的鏈表(原鏈表),與 22比較。

3. 23 比 22 要大,沿下面的指針繼續向後和 26 比較。23 比 26 小,說明待查數據 23 在原鏈表中不存在。

在這個查找過程當中,因爲新增長的指針,咱們再也不須要與鏈表中每一個節點逐個進行比較了。須要比較的節點數大概只有原來的一半。這就是跳躍表。

爲何不用 AVL 樹或者紅黑樹?由於 skiplist 更加簡潔。

源碼展現以下:

typedef struct zskiplistNode {
sds ele; /* zset 的元素 */
double score; /* 分值 */
struct zskiplistNode *backward; /* 後退指針 */
struct zskiplistLevel {
struct zskiplistNode *forward; /* 前進指針, 對應 level 的下一個節點 */
unsigned long span; /* 從當前節點到下一個節點的跨度(跨越的節點數) */
} level[]; /* 層 */
} zskiplistNode;
​
typedef struct zskiplist {
struct zskiplistNode *header, *tail; /* 指向跳躍表的頭結點和尾節點 */
unsigned long length; /* 跳躍表的節點數 */
int level; /* 最大的層數 */
} zskiplist;
​
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;

複製代碼

隨機獲取層數的函數(源碼):

int zslRandomLevel(void) {
  int level = 1;
  while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
  level += 1;
  return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}複製代碼

應用場景

排行榜

id 爲 6001 的新聞點擊數加 **1:zincrby hotNews:20190926 1 n6001

獲取今天點擊最多的 15 條:zrevrange hotNews:20190926 0 15 withscores**

更多精彩文章,關注公衆號【ToBeTopJavaer】,更有數萬元精品vip資源免費等你來拿!!!``

                         

相關文章
相關標籤/搜索