搜尋
首頁資料庫Redisredis學習筆記-list原理

redis學習筆記-list原理

Aug 07, 2023 pm 04:36 PM
redis

list 基本功能

#命令 描述
#BLPOP key1,key2,… timeout 移除並取得清單的第一個元素,如果清單沒有元素會阻塞清單直到等待超時或彈出元素為止。
BRPOP key1 [key2 ] timeout ## 移出並取得清單的最後一個元素, 如果清單沒有元素會阻塞清單直到等待逾時或發現可彈出元素為止。
BRPOPLPUSH source destination timeout ##從清單中彈出一個值,將彈出的元素插入到另外一個列表中並返回它;如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。
LIndex key index 通过索引获取列表中的元素
Linsert key before/after pivot value 在列表的元素前或者后插入元素
LLEN key 获取列表长度
LPOP key 移出并获取列表的第一个元素
LPUSH key value1,value2,… ##將一個或多個值插入到列表頭
LPUSHX key value ##將一個值插入到已經存在的清單頭
#LRANGE key srart stop #取得清單指定範圍內的元素
LREM key count value 移除清單元素
LSET key index value 透過索引設定清單元素的值
#LTRIM key start stop 對一個清單進行修剪,就是說,讓清單只保留指定區間內的元素,不在指定區間之內的元素都被刪除。 index從0開始,區間均包含。
RPOP key 移除清單的最後一個元素,傳回值為移除的元素
RPOPPUSH source destination 移除清單的最後一個元素,並將這個元素加入到另一個清單並傳回
RPUSH key value1 值 … #新增一個或多個值到list的尾部
RPUSHX key value 為已經存在的;列表新增值
####################

圖表來自:https://www.cnblogs.com/amyzhu/p/13466311.html

單鍊錶

在學習redis 的list 實作之前,我們先來看看單鍊錶list 怎麼實作:

每一個節點都有一個向後的指標(引用)指向下一個節點,最後一個節點指向NULL表示結束,有一個Head(頭)指標指向第一個節點表示開始。

redis學習筆記-list原理

類似於這樣,新建和刪除雖然只需要O(1) ,但是查找需要O(n) 的時間;無法反向查找,一旦錯過需要從頭開始。

增加一個節點:

redis學習筆記-list原理

刪除一個節點:

redis學習筆記-list原理

雙向鍊錶

雙向鍊錶也叫雙鍊錶,是鍊錶的一種,它的每個資料結點中都有兩個指針,分別指向直接後繼和直接前驅。所以,從雙向鍊錶中的任一個結點開始,都可以很方便地存取它的前驅結點和後繼結點。

redis學習筆記-list原理

特點:

  1. #每次都在插入或刪除某個節點時, 需要處理四個節點的引用, 而不是兩個. 實現起來要困難一些

  2. #相對於單向鍊錶, 必然佔用內存空間更大一些.

  3. 既可以從頭到尾遍歷到尾, 又可以從尾遍歷到頭

這好像就解決redis 可以實現前後都可以遍歷的問題了。

那我們來看看redis 的鍊錶## 怎麼處理的:

redis學習筆記-list原理

再來看它的結構體定義原始碼

#ListNode:

typedef struct listNode
{
    // 前驱节点
    struct listNode *prev;
    // 后继节点
    struct listNode *next;
    // 节点的值
    void *value;
} listNode;

List:

typedef struct listNode
{
    // 表头节点
    listNode *head;
    // 表尾节点
    listNode *tail;
    // 链表所包含的节点数量
    unsigned long len;
    // 节点值复制函数
    void *(*dup)(void *ptr);
    // 节点值释放函数
    void *(*free)(void *ptr);
    // 节点值对比函数
    int (*match)(void *ptr,void *key)
} list;

redis 鍊錶的特性:

  • 雙向無環:鍊錶節點帶有前驅、後繼指標取得某個節點的前驅、後繼節點的時間複雜度為O(1),表頭節點的前驅指標和表尾節點的後繼指標都指向NULL,對鍊錶的存取以NULL為終點。

  • 長度計數器:透過List結構的len屬性取得節點數量的時間複雜度為O(1)。

由於 list 還存在一個記憶體分配不連續,並引發的記憶體碎片的問題,是否有辦法讓他們的記憶體優化一下?

redis 壓縮列表

ZipList不是基礎資料結構,是Redis自己設計的一種資料儲存結構。它有點類似數組,透過一片連續的記憶體空間來儲存資料。

與陣列不同的是,它允許所儲存的清單元素所佔據的記憶體空間不同。說到壓縮這兩個字,大家第一時間可能想到的就是節省記憶體。之所以說這種儲存結構節省內存,是相對數組而言的。

我們都知道,陣列要求每個元素儲存空間的大小相同,如果我們要儲存不同長度的字串,就要以最大長度的字串所佔的儲存空間作為字串陣列每個元素儲存空間的大小(假如是50位元組)。

因此在字元數數值中儲存小於50位元組長度的字串時就會浪費一部分儲存空間。

陣列 的優點就是佔用一片連續的空間,可以善用CPU快取快速存取資料。

如果既想保留陣列的這種優勢又想節省儲存空間,那麼我們可以壓縮陣列:

redis學習筆記-list原理

不過,有一個問題,在遍歷壓縮列表的時候我們並不知道每個元素佔用的記憶體大小是多少,因此無法計算出下一個元素的特定起始位置。

但是轉念一想,如果每個元素訪問之前我們能有它的長度,那麼不就可以解決這個問題了嘛。

redis學習筆記-list原理

接下來我們來看看Redis如何透過實作ZipList使之結合既保留了陣列的優點又節省了記憶體。

redis學習筆記-list原理

  • zlbytes:压缩列表的字节长度,是uint32_t类型,占4字节,因此压缩列表最多有232-1字节,可以通过该值计算zlend的位置,也可以在内存重新分配的时候使用。

  • zltail:压缩列表尾元素相对于压缩列表起始地址的偏移量,是uint32_t类型,占4字节,可以通过该值快速定位到列表尾元素的位置。

  • zllen:压缩列表元素的个数,是uint16_t类型,占2字节,表示最多存储的元素个数为216-1=65 535,如果需要计算总数量,则需要遍历整个压缩列表。

  • entryx:压缩列表存储的元素,既可以是字节数组,也可以是整数,长度不限。

  • zlend:压缩列表的结尾,是uint8_t类型,占1字节,恒为0xFF。

  • previous_entry_length:表示前一个元素的字节长度,占1字节或者5字节,当前元素的长度小于254时用1字节,大于等于254时用5字节,previous_entry_length 字段的第一个字节固定是0xFE,后面的4字节才是真正的前一个元素的长度。

  • encoding:表示元素当前的编码,有整数或者字节数。为了节省内存,encoding字段长度可变。

  • content:表示当前元素的内容。

ZipList变量的读取和赋值都是通过宏来实现的,代码片段如下:

//返回整个压缩列表的总字节
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))

//返回压缩列表的tail_offset变量,方便获取最后一个节点的位置
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))

//返回压缩列表的节点数量
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))

//返回压缩列表的表头的字节数
//(内存字节数zlbytes,最后一个节点地址ztail_offset,节点总数量zllength)
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))

//返回压缩列表最后结尾的字节数
#define ZIPLIST_END_SIZE        (sizeof(uint8_t))

//返回压缩列表首节点地址
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)

//返回压缩列表尾节点地址
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))

//返回压缩列表最后结尾的地址
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)


对此,可以通过定义的结构体和对应的操作定义知道大概 redis 设计 压缩list 的思路;

这种方式虽然节约了空间,但是会存在每次插入和创建存在内存重新分配的问题。

quicklist

quicklist是Redis 3.2中新引入的数据结构,能够在时间效率和空间效率间实现较好的折中。Redis中对quciklist的注释为A doubly linked list of ziplists。顾名思义,quicklist是一个双向链表,链表中的每个节点是一个ziplist结构。quicklist可以看成是用双向链表将若干小型的ziplist连接到一起组成的一种数据结构。

当ziplist节点个数过多,quicklist退化为双向链表,一个极端的情况就是每个ziplist节点只包含一个entry,即只有一个元素。当ziplist元素个数过少时,quicklist可退化为ziplist,一种极端的情况就是quicklist中只有一个ziplist节点。

redis學習筆記-list原理

redis學習筆記-list原理


quicklist 结构定义:

typedef struct quicklist {
    // 指向quicklist的首节点
    quicklistNode *head;
    // 指向quicklist的尾节点
    quicklistNode *tail;
    // quicklist中元素总数
    unsigned long count;        /* total count of all entries in all ziplists */
    // quicklistNode节点个数
    unsigned long len;          /* number of quicklistNodes */
    // ziplist大小设置,存放list-max-ziplist-size参数的值
    int fill : 16;              /* fill factor for individual nodes */
    // 节点压缩深度设置,存放list-compress-depth参数的值
    unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
    unsigned int bookmark_count: 4;
    quicklistBookmark bookmarks[];
} quicklist;

typedef struct quicklistBookmark {
    quicklistNode *node;
    char *name;
} quicklistBookmark;

quicklistNode定义如下:

typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;
    unsigned int sz;             /* ziplist size in bytes */
    unsigned int count : 16;     /* count of items in ziplist */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

redis为了节省内存空间,会将quicklist的节点用LZF压缩后存储,但这里不是全部压缩,可以配置compress的值,compress为0表示所有节点都不压缩,否则就表示从两端开始有多少个节点不压缩;compress如果是1,表示从两端开始,有1个节点不做LZF压缩。compress默认是0(不压缩),具体可以根据你们业务实际使用场景去配置。

redis 的配置文件可以配置该参数

list-compress-depth 0

#值 意思
#0 #特殊值,表示都不會壓縮
1 #quicklist兩端各有1個節點不壓縮,中間的節點壓縮
#2######## #quicklist兩端各有2個節點不壓縮,中間的節點壓縮#########################n###### quicklist兩端各有n個節點不壓縮,中間的節點壓縮


還有個fill字段,它的意義是每個quicknode的節點最大容量 ,不同的數值有不同的意義,預設是-2,當然也可以配置為其他數值;

list-max-ziplist-size -2

  • 當值為正數時,表示quicklistNode節點上的ziplist的長度。例如當這個值為5時,每個quicklistNode節點的ziplist最多包含5個資料項目

  • #當值為負數時,表示依照位元組數來限制quicklistNode節點上的ziplist的長度,可選值為-1 到-5。

-1ziplist節點最大為4kb-2
##ziplist節點最大為8kb
-3 ziplist節點最大為16kb ########################-4###################ziplist節點最大為32kb# #####
-5 #ziplist節點最大為64kb

為什麼會有設定提供出來?

  • ziplist越短,記憶體碎片增多,影響儲存效率。當一個ziplist只存一個元素時,quicklist又退化成雙向鍊錶了

  • ziplist越長,為ziplist分配大的連續的記憶體空間難度也就越大,會造成很多小塊的記憶體空間被浪費,當quicklist只有一個節點,元素都存在一個ziplist上時,quicklist又退化成ziplist了

#結束語

雖然沒有完全去理解它的原始碼,但我們也可以透過這篇文章來熟悉redis 的一個設計想法。並知道它是怎麼一步一步優化過來的。讓我們有一個大概的表現認知。

#

以上是redis學習筆記-list原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:Golang菜鸟。如有侵權,請聯絡admin@php.cn刪除
REDIS:流行數據結構指南REDIS:流行數據結構指南Apr 11, 2025 am 12:04 AM

Redis支持多種數據結構,具體包括:1.字符串(String),適合存儲單一值數據;2.列表(List),適用於隊列和棧;3.集合(Set),用於存儲不重複數據;4.有序集合(SortedSet),適用於排行榜和優先級隊列;5.哈希表(Hash),適合存儲對像或結構化數據。

redis計數器怎麼實現redis計數器怎麼實現Apr 10, 2025 pm 10:21 PM

Redis計數器是一種使用Redis鍵值對存儲來實現計數操作的機制,包含以下步驟:創建計數器鍵、增加計數、減少計數、重置計數和獲取計數。 Redis計數器的優勢包括速度快、高並發、持久性和簡單易用。它可用於用戶訪問計數、實時指標跟踪、遊戲分數和排名以及訂單處理計數等場景。

redis命令行怎麼用redis命令行怎麼用Apr 10, 2025 pm 10:18 PM

使用 Redis 命令行工具 (redis-cli) 可通過以下步驟管理和操作 Redis:連接到服務器,指定地址和端口。使用命令名稱和參數向服務器發送命令。使用 HELP 命令查看特定命令的幫助信息。使用 QUIT 命令退出命令行工具。

redis集群模式怎麼搭建redis集群模式怎麼搭建Apr 10, 2025 pm 10:15 PM

Redis集群模式通過分片將Redis實例部署到多個服務器,提高可擴展性和可用性。搭建步驟如下:創建奇數個Redis實例,端口不同;創建3個sentinel實例,監控Redis實例並進行故障轉移;配置sentinel配置文件,添加監控Redis實例信息和故障轉移設置;配置Redis實例配置文件,啟用集群模式並指定集群信息文件路徑;創建nodes.conf文件,包含各Redis實例的信息;啟動集群,執行create命令創建集群並指定副本數量;登錄集群執行CLUSTER INFO命令驗證集群狀態;使

redis怎麼讀取隊列redis怎麼讀取隊列Apr 10, 2025 pm 10:12 PM

要從 Redis 讀取隊列,需要獲取隊列名稱、使用 LPOP 命令讀取元素,並處理空隊列。具體步驟如下:獲取隊列名稱:以 "queue:" 前綴命名,如 "queue:my-queue"。使用 LPOP 命令:從隊列頭部彈出元素並返回其值,如 LPOP queue:my-queue。處理空隊列:如果隊列為空,LPOP 返回 nil,可先檢查隊列是否存在再讀取元素。

redis集群zset怎麼使用redis集群zset怎麼使用Apr 10, 2025 pm 10:09 PM

Redis 集群中使用 zset:zset 是一種有序集合,將元素與評分關聯。分片策略: a. 哈希分片:根據 zset 鍵的哈希值分佈。 b. 範圍分片:根據元素評分劃分為範圍,並將每個範圍分配給不同的節點。讀寫操作: a. 讀操作:如果 zset 鍵屬於當前節點的分片,則在本地處理;否則,路由到相應的分片。 b. 寫入操作:始終路由到持有 zset 鍵的分片。

redis數據怎麼清空redis數據怎麼清空Apr 10, 2025 pm 10:06 PM

如何清空 Redis 數據:使用 FLUSHALL 命令清除所有鍵值。使用 FLUSHDB 命令清除當前選定數據庫的鍵值。使用 SELECT 切換數據庫,再使用 FLUSHDB 清除多個數據庫。使用 DEL 命令刪除特定鍵。使用 redis-cli 工具清空數據。

redis過期策略怎麼設置redis過期策略怎麼設置Apr 10, 2025 pm 10:03 PM

Redis數據過期策略有兩種:定期刪除:定期掃描刪除過期鍵,可通過 expired-time-cap-remove-count、expired-time-cap-remove-delay 參數設置。惰性刪除:僅在讀取或寫入鍵時檢查刪除過期鍵,可通過 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-user-del 參數設置。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器