ZSet數據結構相似於Set結構,只是ZSet結構中,每一個元素都會有一個分值,而後全部元素按照分值的大小進行排列,至關因而一個進行了排序的鏈表。git
若是ZSet是一個鏈表,並且內部元素是有序的,在進行元素插入和刪除,以及查詢的時候,就必需要遍歷鏈表才行,時間複雜度就達到了O(n),這個在以單線程處理的Redis中是不能接受的。因此ZSet採用了一種跳躍表的實現。這個實現有點相似於Kafka存儲消息是使用的稀疏索引,kafka這個相對較簡單,能夠用來介紹類比學習。github
若是熟悉Kafka,就知道Kafka在進行持久化的時候,生成了兩個文件,一個是xxxxxxx.log,一個是xxxxxxx.index,這其中log文件中以鏈表的形式保存着消息的詳細信息,而index文件中,則是保存着這些消息的索引,或者說偏移量,但又不是每一條消息的索引都在index文件中存在,而是稀疏的,好比log文件中的消息的索引從0-10000,那麼index文件中存儲的索引多是100, 500, 700, 1000, 5000, 6500,每個索引中都保存着對應的log文件中的消息的具體位置,如圖:redis
當要訪問偏移量爲899的這條消息時,先去index文件中查找,找到了700和1000這個區間,根據700這個索引中的信息,找到log文件中700這條消息的具體位置,而後順序往下查找,直到找到索引爲899的這條消息爲止。從這個實現中咱們能夠看到,Kafka並無進行log文件的整個遍歷,而是經過index中的稀疏索引,找到消息在log中的大概位置,而後順序遍歷找到消息,這樣就大大提升了查找的效率,如圖:數組
Redis的跳躍表和上面相似,只是更加複雜一些,Kafka的稀疏索引只有一層,而Redis的索引被提取爲多層。如圖:數據結構
全部的元素都會在L0層的鏈表中,根據分數進行排序,同時會有一部分節點有機會被抽取到L1層中,做爲一個稀疏索引,一樣L1層中的索引也有必定機會被抽取到L2層中,組成一個更稀疏的索引列表。tcp
下面用圖來演示一下在對快速鏈表進行插入、刪除、查詢時,是如何定位到L0層中的具體位置的。學習
首先,假定有這麼一個鏈表,注意這裏只展現分數,而不展現具體的值了:spa
若是要查找分數爲66的元素,首先在L2層的索引找。很明顯,66位於25和85中間,這時就縮小了查找區間:線程
而後根據得到的區間,去L1對應的區間中查找,獲得一個更精確的區間:blog
最終,根據這個更精確的區間,去L0層順序遍歷,便可獲得要查找的元素:
上述便是對Redis的跳躍表的原理的一個簡述。
這種跳躍表的實現,其實和二分查找的思路有點接近,只是一方面由於二分查找只能適用於數組,而沒法適用於鏈表,因此爲了讓鏈表有二分查找相似的效率,就以空間換時間來達到目的。
跳躍表由於是一個根據分數權重進行排序的列表,能夠再不少場景中進行應用,好比排行榜,搜索排序等等。
添加元素,zadd zsetName score1 value1 score2 value2 score3 value3 .....
查看全部元素,zrange zsetName 0 -1
查看全部元素,按score逆序排列, zrevrange zsetName 0 -1
元素數量,zcard zsetName
獲取指定value的分數, zscore zsetName value
獲取指定value的排名,zrank zsetName value(從0開始)
獲取指定分值區間中的元素, zrangebyscore zsetName scoreStart scoreEnd(包含上下區間)(注意inf表示無窮大,-inf表示服務券大)
獲取指定分值區間中的元素,而且返回分數, zrangebyscore zsetName scoreStart scoreEnd withscores
刪除元素,zrem zsetName value
package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main(){ // 鏈接redis conn,err := redis.Dial("tcp", "localhost:6379") if err != nil { fmt.Errorf("connection redis failed. error info: ", err) return } // zadd _,err = conn.Do("zadd", "phones", "100", "Nokia", "80", "tianyu", "60", "xiaomifeng", "50", "shangshai") if err != nil { fmt.Errorf("sadd failed, error info: ", err) return } // zrange result,err := redis.Strings(conn.Do("zrange", "phones", "0", "-1")) if err != nil { fmt.Errorf("zrange failed, error info: ", err) return } fmt.Println(result) // zrevrange result,err = redis.Strings(conn.Do("zrevrange", "phones", "0", "-1")) if err != nil { fmt.Errorf("zrange failed, error info: ", err) return } fmt.Println(result) // zcard size,err := conn.Do("zcard", "phones") if err != nil { fmt.Errorf("zrange failed, error info: ", err) return } fmt.Println(size) // zscore score,err := redis.Int(conn.Do("zscore", "phones", "shangshai")) if err != nil { fmt.Errorf("zrange failed, error info: ", err) return } fmt.Println(score) // zrem _,err = conn.Do("zrem", "phones", "shangshai") if err != nil { fmt.Errorf("zrange failed, error info: ", err) return } fmt.Println("delete shangshai success.") // 關閉鏈接 defer conn.Close() }
執行效果: