国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數據庫技術|

服務器之家 - 數據庫 - Redis - Redis為什么要存兩次數據

Redis為什么要存兩次數據

2021-07-26 16:28雙子孤狼 Redis

這篇文章主要介紹了Redis為什么要存兩次數據的相關資料,需要的朋友可以參考下

redis 中,有一種數據類型,當在存儲的時候會同時采用兩種數據結構來進行分別存儲,那么 redis 為什么要這么做呢?這么做會造成同一份數據占用兩倍空間嗎?

五種基本類型之集合對象

 

redis 中的集合對象是一個包含字符串類型元素的無序集合,集合中元素唯一不可重復。

集合對象的底層數據結構有兩種:intsethashtable。內部通過編碼來進行區分:

 

 

編碼屬性 描述 object encoding命令返回值
obj_encoding_intset 使用整數集合實現的集合對象 intset
obj_encoding_ht 使用字典實現的集合對象 hashtable

 

intset 編碼

intset(整數集合)可以保存類型為 int16_tint32_tint64_t 的整數值,并且保證集合中沒有重復元素。
intset 數據結構定義如下(源碼 inset.h 內):

?
1
2
3
4
5
typedef struct intset {
  uint32_t encoding;//編碼方式
  uint32_t length;//當前集合中的元素數量
  int8_t contents[];//集合中具體的元素
} intset;

下圖就是一個 intset 的集合對象存儲簡圖:

Redis為什么要存兩次數據

encoding

intset 內部的 encoding 記錄了當前整數集合的數據存儲類型,主要有三種:

intset_enc_int16
此時 contents[] 內的每個元素都是一個 int16_t 類型的整數值,范圍是:-32768 ~ 32767(-2 的 15 次方 ~ 2 的 15 次方 - 1)。
intset_enc_int32
此時 contents[] 內的每個元素都是一個 int32_t 類型的整數值,范圍是:-2147483648 ~ 2147483647(-2 的 31 次方 ~ 2 的 31 次方 - 1)。
intset_enc_int64
此時 contents[] 內的每個元素都是一個 int64_t 類型的整數值,范圍是:-9223372036854775808 ~ 9223372036854775807(-2 的 63 次方 ~ 2 的 63 次方 - 1)。

 contents[]

contents[] 雖然結構的定義上寫的是 int8_t 類型,但是實際存儲類型是由上面的 encoding 來決定的。

整數集合的升級

 

假如一開始整數集合中的元素都是 16 位的,采用 int16_t 類型來存儲,此時需要再存儲一個 32 位的整數,那么就需要對原先的整數集合進行升級,升級之后才能將 32 位的整數存儲到整數集合內。這就涉及到了整數集合的類型升級,升級過程主要有 4 個步驟:

  1. 根據新添加元素的類型來擴展底層數組空間的大小,按照升級后現有元素的位數來分配新的空間。
  2. 將現有的元素進行類型轉換,并將轉換類型后的元素從后到前逐個重新放回到數組內。
  3. 將新元素放到數組的頭部或者尾部(因為觸發升級的條件就是當前數組的整數類型無法存儲新元素,所以新元素要么比現有元素都大,要么就比現有元素都小)。
  4. encoding 屬性修改為最新的編碼,并且同步修改 length 屬性。

ps:和字符串對象的編碼一樣,整數集合的類型一旦發生升級,將會保持編碼,無法降級。

升級示例

假如我們有一個集合存儲的 encodingint16_t,內部存儲了 3 個元素:

Redis為什么要存兩次數據

這時候需要插入一個整數 50000,發現存儲不下去,而 50000 是一個 int32_t 類型整數,所以需要申請新空間,申請空間大小為 4 * 32 - 48=80

Redis為什么要存兩次數據

現在新的數組內要放置 4 個元素,原來的數組排在第 3,所以需要將升級后的 3 移動到 64-95 位。

Redis為什么要存兩次數據

繼續將升級后的 2 移動到 32-63 位。

Redis為什么要存兩次數據

繼續將升級后的 1 移動到 0-31 位。

Redis為什么要存兩次數據

然后會將 50000 放到 96-127 位。

Redis為什么要存兩次數據

最后會修改 encodinglength 屬性,修改之后就完成了本次的升級。

hashtable 編碼

 

hashtable 結構在前面講述哈希對象的時候進行過詳細分析,想詳細了解的可以。

intset 和 hashtable 編碼轉換

 

當一個集合滿足以下兩個條件時,redis 會選擇使用 intset 編碼:

集合對象保存的所有元素都是整數值。集合對象保存的元素數量小于等于 512 個(這個閾值可以通過配置文件 set-max-intset-entries 來控制)。
一旦集合中的元素不滿足上面兩個條件,則會選擇使用 hashtable 編碼。

集合對象常用命令

 

  • sadd key member1 member2:將一個或多個元素 member 加入到集合 key 當中,并返回添加成功的數目,如果元素已存在則被忽略。
  • sismember key member:判斷元素 member 是否存在集合 key 中。
  • srem key member1 member2:移除集合 key 中的元素,不存在的元素會被忽略。
  • smove source dest member:將元素 member 從集合 source 中移動到 dest 中,如果 member 不存在,則不執行任何操作。
  • smembers key:返回集合 key 中所有元素。

了解了操作集合對象的常用命令,我們就可以來驗證下前面提到的哈希對象的類型和編碼了,在測試之前為了防止其他 key 值的干擾,我們先執行 flushall 命令清空 redis 數據庫。

依次執行如下命令:

?
1
2
3
4
5
6
7
sadd num 1 2 3 //設置 3 個整數的集合,會使用 intset 編碼
type num //查看類型
object encoding num  //查看編碼
 
sadd name 1 2 3 test //設置 3 個整數和 1 個字符串的集合,會使用 hashtable 編碼
type name //查看類型
object encoding name //查看編碼

得到如下效果:

Redis為什么要存兩次數據

可以看到,當設置的元素里面只有整數時,集合使用的就是 intset 編碼,當設置的元素中含有非整數時,使用的就是 hashtable 編碼。

五種基本類型之有序集合對象

 

redis 中的有序集合和集合的區別是有序集合中的每個元素都會關聯一個 double 類型的分數,然后按照分數從小到大的順序進行排列。換句話說,有序集合的順序是由我們自己設值的時候通過分數來確定的。

有序集合對象的底層數據結構有兩種:skiplistziplist。內部同樣是通過編碼來進行區分:

 

 

編碼屬性 描述 object encoding命令返回值
obj_encoding_skiplist 使用跳躍表實現的有序集合對象 skiplist
obj_encoding_ziplist 使用壓縮列表實現的有序集合對象 ziplist

 

skiplist 編碼

skiplist 即跳躍表,有時候也簡稱為跳表。使用 skiplist 編碼的有序集合對象使用了 zset 結構來作為底層實現,而zset 中同時包含了一個字典和一個跳躍表。

跳躍表

跳躍表是一種有序的數據結構,其主要特點是通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的。

大部分情況下,跳躍表的效率可以等同于平衡樹,但是跳躍表的實現卻遠遠比平衡樹的實現簡單,所以 redis 選擇了使用跳躍表來實現有序集合。

下圖是一個普通的有序鏈表,我們如果想要找到 35 這個元素,只能從頭開始遍歷到尾(鏈表中元素不支持隨機訪問,所以不能用二分查找,而數組中可以通過下標隨機訪問,所以二分查找一般適用于有序數組),時間復雜度是 o(n)

Redis為什么要存兩次數據

那么假如我們可以直接跳到鏈表的中間,那就可以節省很多資源了,這就是跳表的原理,如下圖所示就是一個跳表的數據結構示例:

Redis為什么要存兩次數據

上圖中 level1level2level3 就是跳表的層級,每一個 level 層級都有一個指向下一個相同 level 層級元素的指針,比如上圖我們遍歷尋找元素 35 的時候就有三種方案:

1 種就是執行 level1 層級的指針,需要遍歷 7 次(1->8->9->12->15->20->35)才能找到元素 35。第 2 種就是執行 level2 層級的指針,只需要遍歷 5 次(1->9->12->15->35)就能找到元素 35。第 3 種就是執行 level3 層級的元素,這時候只需要遍歷 3 次(1->12->35)就能找到元素 35 了,大大提升了效率。

skiplist 的存儲結構

跳躍表中的每個節點是一個 zskiplistnode 節點(源碼 server.h 內):

?
1
2
3
4
5
6
7
8
9
typedef struct zskiplistnode {
  sds ele;//元素
  double score;//分值
  struct zskiplistnode *backward;//后退指針
  struct zskiplistlevel {//層
    struct zskiplistnode *forward;//前進指針
    unsigned long span;//當前節點到下一個節點的跨度(跨越的節點數)
  } level[];
} zskiplistnode;

level(層)

level 即跳躍表中的層,其是一個數組,也就是說一個節點的元素可以擁有多個層,即多個指向其他節點的指針,程序可以通過不同層級的指針來選擇最快捷的路徑提升訪問速度。

level 是在每次創建新節點的時候根據冪次定律(power law)隨機生成的一個介于 1~32 之間的數字。

forward(前進指針)

每個層都會有一個指向鏈表尾部方向元素的指針,遍歷元素的時候需要使用到前進指針。

span(跨度)

跨度記錄了兩個節點之間的距離,需要注意的是,如果指向了 null 的話,則跨度為 0

backward(后退指針)

和前進指針不一樣的是后退指針只有一個,所以每次只能后退至前一個節點(上圖中沒有畫出后退指針)。

ele(元素)

跳躍表中元素是一個 sds 對象(早期版本使用的是 redisobject 對象),元素必須唯一不能重復。

score(分值)

節點的分值是一個 double 類型的浮點數,跳躍表中會將節點按照分值按照從小到大的順序排列,不同節點的分值可以重復。

上面介紹的只是跳躍表中的一個節點,多個 zskiplistnode 節點組成了一個 zskiplist 對象:

?
1
2
3
4
5
typedef struct zskiplist {
  struct zskiplistnode *header, *tail;//跳躍表的頭節點和尾結點指針
  unsigned long length;//跳躍表的節點數
  int level;//所有節點中最大的層數
} zskiplist;

到這里你可能以為有序集合就是用這個 zskiplist 來實現的,然而實際上 redis 并沒有直接使用 zskiplist 來實現,而是用 zset 對象再次進行了一層包裝。

?
1
2
3
4
typedef struct zset {
  dict *dict;//字典對象
  zskiplist *zsl;//跳躍表對象
} zset;

所以最終,一個有序集合如果使用了 skiplist 編碼,其數據結構如下圖所示:

Redis為什么要存兩次數據

上圖中上面一部分中的字典中的 key 就是對應了有序集合中的元素(member),value 就對應了分值(score)。上圖中下面一部分中跳躍表整數 1,8,9,12 也是對應了元素(member),最后一排的 double 型數字就是分值(score)。
也就是說字典和跳躍表中的數據都指向了我們存儲的元素(兩種數據結構最終指向的是同一個地址,所以數據并不會出現冗余存儲),redis 為什么要這么做呢?

為什么同時選擇使用字典和跳躍表

 

有序集合直接使用跳躍表或者單獨使用字典完全可以獨自實現,但是我們想一下,如果單獨使用跳躍表來實現,那么雖然可以使用跨度大的指針去遍歷元素來找到我們需要的數據,但是其復雜度仍然達到了 o(logn),而字典中獲取一個元素的復雜度是 o(1),而如果單獨使用字典雖然獲取元素很快,但是字典是無序的,所以如果要范圍查找就需要對其進行排序,這又是一個耗時的操作,所以 redis 綜合了兩種數據結構來最大程度的提升性能,這也是 redis 設計的精妙之處。

ziplist 編碼

壓縮列表在列表對象和哈希對象都有使用到,想詳細了解的可以點擊這里

ziplist 和 skiplist 編碼轉換

當有序集合對象同時滿足以下兩個條件時,會使用 ziplist 編碼進行存儲:

有序集合對象中保存的元素個數小于 128 個(可以通過配置 zset-max-ziplist-entries 修改)。
有序集合對象中保存的所有元素的總長度小于 64 字節(可以通過配置 zset-max-ziplist-value 修改)。

有序集合對象常用命令

 

  • zadd key score1 member1 score2 member2:將一個或多個元素(member)及其 score 添加到有序集合 key 中。 zscore key member:返回有序集合 keymember 成員的 score
  • zincrby key num member:將有序集合 key 中的 member 加上 numnum 可以為負數。
  • zcount key min max:返回有序集合 keyscore 值在 [min,max] 區間的 member 數量。
  • zrange key start stop:返回有序集合 keyscore 從小到大排列后在 [start,stop] 區間的所有 member
  • zrevrange key start stop:返回有序集合 keyscore 從大到小排列后在 [start,stop] 區間的所有 member。 zrangebyscore key min max:返回有序集合中按 score 從小到大排列后在 [min,max] 區間的所有元素。注意這里默認是閉區間,但是可以在 maxmin 的數值前面加上 ( 或者 [ 來控制開閉區間。
  • zrevrangebyscore key max min:返回有序集合中按 score 從大到小排列后在 [min,max] 區間的所有元素。注意這里默認是閉區間,但是可以在 maxmin 的數值前面加上 ( 或者 [ 來控制開閉區間。
  • zrank key member:返回有序集合中 member 中元素排名(從小到大),返回的結果從 0 開始計算。
  • zrevrank key member:返回有序集合中 member 中元素排名(從大到小),返回的結果從 0 開始計算。
  • zlexcount key min max:返回有序集合中 minmax 之間的 member 數量。注意這個命令中的 minmax 前面必須加 ( 或者 [ 來控制開閉區間,特殊值 -+ 分別表示負無窮和正無窮。

了解了操作有序集合對象的常用命令,我們就可以來驗證下前面提到的哈希對象的類型和編碼了,在測試之前為了防止其他 key 值的干擾,我們先執行 flushall 命令清空 redis 數據庫。

在執行命令之前,我們先把配置文件中的參數 zset-max-ziplist-entries 修改為 2,然后重啟 redis 服務。

重啟完成之后依次執行如下命令:

?
1
2
3
4
5
6
7
zadd name 1 zs 2 lisi //設置 2 個元素會使用 ziplist
type name //查看類型
object encoding name //查看編碼
  
zadd address 1 beijing 2 shanghai 3 guangzhou 4 shenzhen //設置4個元素則會使用 skiplist編碼
type address //查看類型
object encoding address //查看編碼

得到如下效果:

Redis為什么要存兩次數據

總結

 

本文主要分析了集合對象和有序集合對象的底層存儲結構 intsetskiplist 的實現原理,并且重點分析了有序集合如何實現排序以及為何同時使用兩種數據結構(字典和跳表)同時進行進行存儲數據的原因。

到此這篇關于redis為什么要存兩次數據的文章就介紹到這了,更多相關redis存兩次數據內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

延伸 · 閱讀

精彩推薦
  • Redisredis 交集、并集、差集的具體使用

    redis 交集、并集、差集的具體使用

    這篇文章主要介紹了redis 交集、并集、差集的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友...

    xiaojin21cen10152021-07-27
  • Redis詳解Redis復制原理

    詳解Redis復制原理

    與大多數db一樣,Redis也提供了復制機制,以滿足故障恢復和負載均衡等需求。復制也是Redis高可用的基礎,哨兵和集群都是建立在復制基礎上實現高可用的...

    李留廣10222021-08-09
  • RedisRedis 事務知識點相關總結

    Redis 事務知識點相關總結

    這篇文章主要介紹了Redis 事務相關總結,幫助大家更好的理解和學習使用Redis,感興趣的朋友可以了解下...

    AsiaYe8232021-07-28
  • RedisRedis如何實現數據庫讀寫分離詳解

    Redis如何實現數據庫讀寫分離詳解

    Redis的主從架構,能幫助我們實現讀多,寫少的情況,下面這篇文章主要給大家介紹了關于Redis如何實現數據庫讀寫分離的相關資料,文中通過示例代碼介紹...

    羅兵漂流記6092019-11-11
  • Redisredis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    redis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    這篇文章主要給大家介紹了關于redis中如何使用lua腳本讓你的靈活性提高5個逼格的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具...

    一線碼農5812019-11-18
  • RedisRedis的配置、啟動、操作和關閉方法

    Redis的配置、啟動、操作和關閉方法

    今天小編就為大家分享一篇Redis的配置、啟動、操作和關閉方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    大道化簡5312019-11-14
  • Redisredis實現排行榜功能

    redis實現排行榜功能

    排行榜在很多地方都能使用到,redis的zset可以很方便地用來實現排行榜功能,本文就來簡單的介紹一下如何使用,具有一定的參考價值,感興趣的小伙伴們...

    乘月歸5022021-08-05
  • RedisRedis全量復制與部分復制示例詳解

    Redis全量復制與部分復制示例詳解

    這篇文章主要給大家介紹了關于Redis全量復制與部分復制的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Redis爬蟲具有一定的參考學習...

    豆子先生5052019-11-27
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 欧美伦理一区二区 | 国产一区二区三区免费 | 依人久久久 | 国产成人精品电影 | 免费成人高清在线视频 | 国产成人精品一区二区三区 | а天堂中文最新一区二区三区 | 亚洲国产成人精品女人久久 | www.欧美.com| 亚洲国产精品一二三区 | 日韩福利 | av网站在线免费观看 | 日韩有码一区二区三区 | 欧美精品一区二 | 深夜免费网站 | 午夜精品久久久久久久星辰影院 | 久久久久久久一区 | 国产三级一区二区三区 | 国产探花在线精品一区二区 | 一区二区三区在线免费观看 | 久久精品无码一区二区三区 | 北条麻妃一区二区三区中文字幕 | 久久水蜜桃 | 亚洲午夜在线 | 久久精品国产一区二区三区 | 羞羞的网站 | 亚洲国产精品成人 | 91亚洲一区| 成人欧美一区二区三区在线播放 | 欧美黄色一区 | 少妇黄色一级片 | 毛片区 | 一区二区三区免费观看视频 | 成人精品国产 | 国产精品国色综合久久 | 91看视频 | 爱爱视频网站 | 国产视频黄在线观看 | 精品视频一区二区三区四区 | 久久69精品久久久久久久电影好 | 最近免费中文字幕在线视频2 |