access ole 對象 最大長度_Redis 數據結構和對象系統,有這 12 張圖就夠了!

34d9ef614c7771fdac33211d578b17c5.png

作者 | 程序員歷小冰

責編 | 林瑟

Redis 是一個開源的 key-value 存儲系統,它使用六種底層數據結構構建了包含字符串對象、列表對象、哈希對象、集合對象和有序集合對象的對象系統。?

今天我們就通過 12 張圖來全面了解一下它的數據結構對象系統的實現原理

01

數據結構

簡單動態字符串

Redis 使用動態字符串 SDS 來表示字符串值。下圖展示了一個值為 Redis 的 SDS結構 :

  • len: 表示字符串的真正長度(不包含 NULL 結束符在內)。

  • alloc: 表示字符串的最大容量(不包含最后多余的那個字節)。

  • flags: 總是占用一個字節。其中的最低 3 個 bit 用來表示 header 的類型。

  • buf: 字符數組

6df54e269d5015869dc8be21328cf7fd.png

SDS 的結構可以減少修改字符串時帶來的內存重分配的次數,這依賴于內存預分配和惰性空間釋放兩大機制。

當 SDS 需要被修改,并且要對 SDS 進行空間擴展時,Redis 不僅會為 SDS 分配修改所必須要的空間,還會為 SDS 分配額外的未使用的空間。

  • 如果修改后, SDS 的長度(也就是len屬性的值)將小于 1MB ,那么 Redis 預分配和 len 屬性相同大小的未使用空間。

  • 如果修改后, SDS 的長度將大于 1MB ,那么 Redis 會分配 1MB 的未使用空間。

比如說,進行修改后 SDS 的 len 長度為 20 字節,小于 1MB,那么 Redis 會預先再分配 20 字節的空間, SDS 的 buf 數組的實際長度(除去最后一字節)變為 20 + 20 = 40 字節。

當 SDS 的 len 長度大于 1MB 時,則只會再多分配 1MB 的空間。

類似的,當 SDS 縮短其保存的字符串長度時,并不會立即釋放多出來的字節,而是等待之后使用。

鏈表

鏈表在 Redis 中的應用非常廣泛,比如列表對象的底層實現之一就是鏈表。除了鏈表對象外,發布和訂閱、慢查詢、監視器等功能也用到了鏈表。

d7014bfd59213f7585af13aeedbb75d3.png

Redis 的鏈表是雙向鏈表,示意圖如上圖所示。鏈表是最為常見的數據結構,這里就不在細說。

Redis 的鏈表結構的 dup 、 free 和 match 成員屬性是用于實現多態鏈表所需的類型特定函數:

  • dup 函數用于復制鏈表節點所保存的值,用于深度拷貝。

  • free 函數用于釋放鏈表節點所保存的值。

  • match 函數則用于對比鏈表節點所保存的值和另一個輸入值是否相等。

字典

字典被廣泛用于實現 Redis 的各種功能,包括鍵空間和哈希對象。其示意圖如下所示。

ec2e06c830a7770b7c9519d2d39ca8ea.png

Redis 使用 MurmurHash2 算法來計算鍵的哈希值,并且使用鏈地址法來解決鍵沖突,被分配到同一個索引的多個鍵值對會連接成一個單向鏈表。

跳躍表

Redis 使用跳躍表作為有序集合對象的底層實現之一。它以有序的方式在層次化的鏈表中保存元素, 效率和平衡樹媲美 —— 查找、刪除、添加等操作都可以在對數期望時間下完成, 并且比起平衡樹來說, 跳躍表的實現要簡單直觀得多。

a3fe7135cb78cc68b95646ab8a82bd19.png

跳表的示意圖如上圖所示,這里只簡單說一下它的核心思想,并不進行詳細的解釋。

如示意圖所示,zskiplistNode 是跳躍表的節點,其 ele 是保持的元素值,score 是分值,節點按照其 score 值進行有序排列,而 level 數組就是其所謂的層次化鏈表的體現。

每個 node 的 level 數組大小都不同, level 數組中的值是指向下一個 node 的指針和 跨度值 (span),跨度值是兩個節點的 score 的差值。越高層的 level 數組值的跨度值就越大,底層的 level 數組值的跨度值越小。

level 數組就像是不同刻度的尺子。度量長度時,先用大刻度估計范圍,再不斷地用縮小刻度,進行精確逼近。

當在跳躍表中查詢一個元素值時,都先從第一個節點的最頂層的 level 開始。比如說,在上圖的跳表中查詢 o2 元素時,先從 o1 的節點開始,因為 zskiplist 的 header 指針指向它。

先從其 level[3] 開始查詢,發現其跨度是 2,o1 節點的 score 是1.0,所以加起來為 3.0,大于 o2 的 score 值2.0。所以,我們可以知道 o2 節點在 o1 和 o3 節點之間。這時,就改用小刻度的尺子了。就用level[1]的指針,順利找到 o2 節點。

整數集合

整數集合 intset 是集合對象的底層實現之一,當一個集合只包含整數值元素,并且這個集合的元素數量不多時, Redis 就會使用整數集合作為集合對象的底層實現。

0af7e9eaaa90238af957410dcaf27276.png

如上圖所示,整數集合的 encoding 表示它的類型,有 int16t,int32t 或者 int64_t。其每個元素都是 contents 數組的一個數組項,各個項在數組中按值的大小從小到大有序的排列,并且數組中不包含任何重復項。length 屬性就是整數集合包含的元素數量。

壓縮列表

壓縮隊列 ziplist 是列表對象和哈希對象的底層實現之一。當滿足一定條件時,列表對象和哈希對象都會以壓縮隊列為底層實現。

468a2484f8e81230ee783f1eecdd1ff9.png

壓縮隊列是 Redis 為了節約內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序型數據結構。它的屬性值有:

  • zlbytes : 長度為 4 字節,記錄整個壓縮數組的內存字節數。

  • zltail : 長度為 4 字節,記錄壓縮隊列表尾節點距離壓縮隊列的起始地址有多少字節,通過該屬性可以直接確定尾節點的地址。

  • zllen : 長度為 2 字節,包含的節點數。當屬性值小于 INT16_MAX時,該值就是節點總數,否則需要遍歷整個隊列才能確定總數。

  • zlend : 長度為 1 字節,特殊值,用于標記壓縮隊列的末端。

中間每個節點 entry 由三部分組成:

  • previous_entry_length : 壓縮列表中前一個節點的長度,和當前的地址進行指針運算,計算出前一個節點的起始地址。

  • encoding:節點保存數據的類型和長度

  • content :節點值,可以為一個字節數組或者整數。

02

對象系統的實現原理

上面介紹了 6 種底層數據結構,Redis 并沒有直接使用這些數據結構來實現鍵值數據庫,而是基于這些數據結構創建了一個對象系統.

這個系統包含字符串對象、列表對象、哈希對象、集合對象和有序集合這五種類型的對象,每個對象都使用到了至少一種前邊講的底層數據結構。

Redis 根據不同的使用場景和內容大小來判斷對象使用哪種數據結構,從而優化對象在不同場景下的使用效率和內存占用。

Redis 的 redisObject 結構的定義如下所示。

typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; int refcount; void *ptr;} robj;

其中 type 是對象類型,包括 REDISSTRING, REDISLIST, REDISHASH, REDISSET 和 REDIS_ZSET。

encoding 是指對象使用的數據結構,全集如下。8bb882ac1205372b3542c709f2535f12.png

字符串對象

我們首先來看字符串對象的實現,如下圖所示。

a5bfddcbbc6ad141e5db6222852151a8.png

如果一個字符串對象保存的是一個字符串值,并且長度大于 32 字節,那么該字符串對象將使用 SDS 進行保存,并將對象的編碼設置為 raw,如圖的上半部分所示。

如果字符串的長度小于 32 字節,那么字符串對象將使用 embstr 編碼方式來保存。

embstr 編碼是專門用于保存短字符串的一種優化編碼方式,這個編碼的組成和 raw 編碼一致,都使用 redisObject 結構和 sdshdr 結構來保存字符串,如上圖的下半部所示。

但是 raw 編碼會調用兩次內存分配來分別創建上述兩個結構,而 embstr 則通過一次內存分配來分配一塊連續的空間,空間中一次包含兩個結構。

embstr 只需一次內存分配,而且在同一塊連續的內存中,更好的利用緩存帶來的優勢,但是 embstr 是只讀的,不能進行修改,當一個 embstr 編碼的字符串對象進行 append 操作時,redis 會現將其轉變為 raw 編碼再進行操作。

列表對象

列表對象的編碼可以是 ziplist 或 linkedlist。其示意圖如下所示。

a8dff5d2e054ae73d659cf9629465e5a.png

當列表對象可以同時滿足以下兩個條件時,列表對象使用 ziplist 編碼:

  • 列表對象保存的所有字符串元素的長度都小于 64 字節。

  • 列表對象保存的元素數量數量小于 512 個。

不能滿足這兩個條件的列表對象需要使用 linkedlist 編碼或者轉換為 linkedlist 編碼。

哈希對象

哈希對象的編碼可以使用 ziplist 或 dict。其示意圖如下所示。

當哈希對象使用壓縮隊列作為底層實現時,程序將鍵值對緊挨著插入到壓縮隊列中,保存鍵的節點在前,保存值的節點在后。如下圖的上半部分所示,該哈希有兩個鍵值對,分別是 name:Tom 和 age:25。

ca58271e885f26706342c13c24094213.png

當哈希對象可以同時滿足以下兩個條件時,哈希對象使用 ziplist 編碼:

  • 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于64字節。

  • 哈希對象保存的鍵值對數量小于512個。

不能滿足這兩個條件的哈希對象需要使用 dict 編碼或者轉換為 dict 編碼。

集合對象

集合對象的編碼可以使用 intset 或者 dict。

intset 編碼的集合對象使用整數集合最為底層實現,所有元素都被保存在整數集合里邊。

而使用 dict 進行編碼時,字典的每一個鍵都是一個字符串對象,每個字符串對象就是一個集合元素,而字典的值全部都被設置為NULL。如下圖所示。

fdcd506aeb9f5ef656d24f5d23847b3b.png

當集合對象可以同時滿足以下兩個條件時,對象使用 intset 編碼:

  • 集合對象保存的所有元素都是整數值。

  • 集合對象保存的元素數量不超過 512 個。

否則使用 dict 進行編碼。

有序集合對象

有序集合的編碼可以為 ziplist 或者 skiplist。

有序集合使用 ziplist 編碼時,每個集合元素使用兩個緊挨在一起的壓縮列表節點表示,前一個節點是元素的值,第二個節點是元素的分值,也就是排序比較的數值。

壓縮列表內的集合元素按照分值從小到大進行排序,如下圖上半部分所示。

有序集合使用 skiplist 編碼時使用 zset 結構作為底層實現,一個 zet 結構同時包含一個字典和一個跳躍表。

其中,跳躍表按照分值從小到大保存所有元素,每個跳躍表節點保存一個元素,其score值是元素的分值。而字典則創建一個一個從成員到分值的映射,字典的鍵是集合成員的值,字典的值是集合成員的分值。通過字典可以在O(1)復雜度查找給定成員的分值。如下圖所示。

跳躍表和字典中的集合元素值對象都是共享的,所以不會額外消耗內存。

34aa8c45deb0c61948df2fb3de6b863a.png

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

  • 有序集合保存的元素數量少于128個;

  • 有序集合保存的所有元素的長度都小于64字節。

否則使用 skiplist 編碼。

數據庫鍵空間

Redis 服務器都有多個 Redis 數據庫,每個Redis 數據都有自己獨立的鍵值空間。每個 Redis 數據庫使用 dict 保存數據庫中所有的鍵值對。

0ed01afed1000d2868d3606e1750ff9f.png

鍵空間的鍵也就是數據庫的鍵,每個鍵都是一個字符串對象,而值對象可能為字符串對象、列表對象、哈希表對象、集合對象和有序集合對象中的一種對象。

除了鍵空間,Redis 也使用 dict 結構來保存鍵的過期時間,其鍵是鍵空間中的鍵值,而值是過期時間,如上圖所示。

通過過期字典,Redis 可以直接判斷一個鍵是否過期,首先查看該鍵是否存在于過期字典,如果存在,則比較該鍵的過期時間和當前服務器時間戳,如果大于,則該鍵過期,否則未過期。

今日話題

#聊聊 Redis 的疑難雜癥#

—??—

程序員社群 | 連接更優秀的人

4002483075f697369cbb00dc92dc8616.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/385977.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/385977.shtml
英文地址,請注明出處:http://en.pswp.cn/news/385977.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python煙花表白_python炫酷煙花表白源代碼

詳細內容天天敲代碼的朋友,有沒有想過代碼也可以變得很酷炫又浪漫?今天就教大家用Python模擬出綻放的煙花,工作之余也可以隨時讓程序為自己放一場煙花秀。python炫酷煙花表白源代碼這個有趣的小項目并不復雜,只需一點可視化技巧&a…

【面試總結】2021Java春招面試經歷

三、堆空間 基本描述 JVM啟動時創建堆區,是內存管理的核心區,通常情況下也是最大的內存空間,是被所有線程共享的,幾乎所有的對象實例都要在堆中分配內存,所以這里也是垃圾回收的重點空間。 堆棧關系 棧是JVM運行時的…

tableau地圖城市數據_Tableau 地圖 | 無法識別的城市

Tableau自帶的地圖功能很強大,也很簡單只要雙擊具有地理位置角色的字段,即可生成地圖不過有的時候在你部署地圖的時候總會發現有些城市或地名無法識別,提示如下:這篇post就來簡單聊聊為啥今天直說處理方法,不談后臺原理…

【高級Java架構師系統學習】最新Java高級面試題匯

性能調優 影響MySQLServer 性能的相關因素 商業需求對性能的影響系統架構及實現對性能的影響Query語句對系統性能的影響Schema設計對系統的性能影響硬件環境對系統性能的影響 MySQL 數據庫鎖定機制 MySQL鎖定機制簡介各種鎖定機制分析合理利用鎖機制優化MySQL MySQL數據庫Qu…

vue 安裝指定版本swiper_Vue中的runtime-only和runtime-compiler

在我們使用vue-cli的時候,會提示你安裝的版本可以看到有兩種版本:Routime Only和Runtime Compiler版本1.Runtime Only - 代碼中不可以有任何template 性能更高在該版本下,通常需要借助如webpack的vue-loader發工具把.vue文件編譯成js因為是在…

一文搞懂JVM架構:入職3個月的Java程序員面臨轉正

Java基礎 1.JAVA 中的幾種數據類型是什么,各自占用多少字節。 2.String 類能被繼承嗎,為什么。 3. 兩個對象的 hashCode() 相同,則 equals() 也一定為 true,對嗎? 4. String 屬于基礎的數據類型嗎? 5.…

不顯示調用super_讓不懂編程的人愛上iPhone開發(2017秋iOS11+Swift4+Xcode9版)-第11篇

歡迎回到我們的iPhone開發教程系列,讓我們繼續前進吧。重新來過別害怕,哥不是讓你拋棄之前所有的源代碼,從零開始重新構建這個項目!這里說的是游戲界面里面的“Start over”按鈕。在我們的to-do清單里面曾經提到過,這個…

一文搞懂JVM架構:跳槽面試大廠被拒

正文 在實際的工作項目中, 緩存成為高并發、高性能架構的關鍵組件 ,那么Redis為什么可以作為緩存使用呢?首先可以作為緩存的兩個主要特征: 在分層系統中處于內存/CPU具有訪問性能良好,緩存數據飽和,有良好…

全局變量_Python函數中的全局變量與局部變量

# a,b變量是全局變量,在整個py文件中都可以訪問a 11b 12# 定義一個函數def first():# 這個變量是函數內部定義的變量,屬于局部變量,只能在函數中使用c "Hello"# 大括號{} 是format()函數的用法,格式化print("c {}".format(c))# 如果局部變量定義的名稱…

一文詳解:字節面試官必問的Mysql鎖機制

一面 1 自我介紹和項目 2 Java的內存分區 3 Java對象的回收方式,回收算法。 4 CMS和G1了解么,CMS解決什么問題,說一下回收的過程。 5 CMS回收停頓了幾次,為什么要停頓兩次。 6 Java棧什么時候會發生內存溢出,Jav…

install npm 到某個文件下執行_你可能不知道的 npm 依賴管理那些事

點擊上方藍字關注我們npm 是 Node.js 默認的、以 JavaScript 編寫的包管理工具,如今,它已經成為世界上最大的包管理工具,是每個前端開發者必備的工具。不知你是否遇到過下面問題:哎?我本地明明是好的,線上的…

萬字總結!騰訊、字節跳動面經已發

二、常見的并發問題 1、臟讀 一個事務讀取了另一個事務未提交的數據 2、不可重復讀 一個事務對同一數據的讀取結果前后不一致。兩次讀取中間被其他事務修改了 3、幻讀 幻讀是指事務讀取某個范圍的數據時,因為其他事務的操作導致前后兩次讀取的結果不一致。幻讀…

ncbi查找目的基因序列_NCBI大搜索之目的基因尋蹤

NCBI大搜索之目的基因尋蹤最近經常碰到查找目的基因的問題,那今天就講一下如何利用NCBI數據庫查找目的基因!NCBI(National Center For Biotechnology Information),美國國家生物技術信息中心,分子生物學,生物化學及遺傳學領域常用…

萬字長文!2020-2021京東Java面試真題解析

我整理的spring學習筆記: 像spring這種知識點我們不能盲目的學習,首先我們得有一套學習路線,我總結了一套spring的學習思維導圖,今天通過我整理的Spring學習路線.xmind給大家分析spring需要掌握的一些核心知識點。 spring的特點&…

echarts label固定位置_ECharts+百度地圖網絡拓撲應用

前一篇談及到了ECharts整合HT for Web的網絡拓撲圖應用,后來在ECharts的Demo中看到了有關空氣質量的相關報表應用,就想將百度地圖、ECharts和HT for Web三者結合起來也做一個類似空氣質量報告的報表拓撲圖應用,于是有了下面的Demo&#xff1a…

三年Java開發,你連基礎的JVM運行時內存布局都忘了

面:為什么要使用雙親委派機制去加載類? 答:避免多份同樣字節碼的加載,浪費內存。 類的加載方式 隱式加載:new顯示加載:loadClass、forName等 類的裝載過程如下圖: 面:loadClass和…

vue實現可編輯的文字_蘋果還自帶文字轉語音,只要一鍵按下便可實現,今天分享給大家...

如果想將文字轉成語音,那大家平時都是怎么操作?下面小編就為大家介紹手機,電腦上都可以使用的方法,讓我們一起來看看吧!一、手機端操作1、蘋果手機其實蘋果手機就自帶了文字轉語音功能,只要打開手機&#x…

三面美團Java崗,面試竟然被這31道Java基礎題難倒了

01 分布式限流:NginxZooKeeper 1.1 分布式限流之Nginx 請解釋一下什么是 Nginx? 請列舉 x Nginx 的一些特性。 請列舉 x Nginx 和 和 Apache 之間的不同點 請解釋 x Nginx 如何處理 P HTTP 請求。 在 x Nginx 中,如何使用未定義的服務器名稱來阻止…

海龜繪圖小動物_震驚!被塑料繩勒成兩半的海龜

海洋,其實離人類很近,我們在追逐沙灘和日落,享受美味的海鮮的時候,可曾想到我們平時的一些很隨意的行為,會給一些海洋生物帶來無法恢復的傷害,甚至奪取它們的生命。或許人們的冷漠無知尚未得到懲罰&#xf…

上海大廠Java面試經歷:初步理解類加載運行機制和類加載過程

volatile相關經典面試題 談談volatile的特性volatile的內存語義說說并發編程的3大特性什么是內存可見性,什么是指令重排序?volatile是如何解決java并發中可見性的問題volatile如何防止指令重排volatile可以解決原子性嘛?為什么?v…