redis核心與實戰(一)數據結構篇

1.《redis數據結構概覽》
1.數據結構概覽
數據模型:一共5種,String(字符串)、List(列表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合)

數據結構:一共有 6 種,分別是簡單動態字符串、雙向鏈表、壓縮列表、哈希表、跳表和整數數組。

對應關系圖:

2.全局哈希表
Redis 使用了一個全局哈希表來保存所有鍵值對

哈希桶中的元素保存的并不是值本身,而是指向具體值的指針。這也就是說,不管值是 String,還是集合類型,哈希桶中的元素都是指向它們的指針。

3.列表(list)
對應兩種實現方法,一種是壓縮列表(ziplist),另一種是雙向循環鏈表。

當列表中存儲的數據量比較小的時候,列表就可以采用壓縮列表的方式實現。具體需要同時滿足下面兩個條件:

列表中保存的單個數據(有可能是字符串類型的)小于 64 字節;(cpu緩存行一行最多64字節)
列表中數據個數少于 512 個。
壓縮列表:

特點:

可以存儲不同字節大小的數據
基于數組實現,但不支持隨機訪問
獲取首尾元素時間復雜度O(1),其他情況O(n)
壓縮列表在表頭有三個字段 zlbytes、zltail 和 zllen,分別表示列表長度(所占空間大小)、列表尾的偏移量(列尾到起始位置的偏移量)和列表中的 entry 個數;

其中每個entry,分為四個部分previous_entry_length,entry_length,encoding,content

分別表示 上一個元素的長度,本元素長度 ,編碼方式,文本內容

4.字典(hash)
有兩種實現方式,一種是壓縮列表,另一種是散列表

有當存儲的數據量比較小的情況下,Redis 才使用壓縮列表來實現字典類型。具體需要滿足兩個條件:

字典中保存的鍵和值的大小都要小于 64 字節;
字典中鍵值對的個數要小于 512 個。
5.集合(set)
有兩種實現方法,一種是基于有序數組,另一種是基于散列表。

當要存儲的數據,同時滿足下面這樣兩個條件的時候,Redis 就采用有序數組,來實現集合這種數據類型。

存儲的數據都是整數;
存儲的數據元素個數不超過 512 個。
6.有序集合(sortedset)
當數據量比較小的時候,Redis 會用壓縮列表來實現有序集合。具體點說就是,使用壓縮列表來實現有序集合的前提,有這樣兩個:

所有數據的大小都要小于 64 字節;
元素個數要小于 128 個。
7.為什么 sortedset,hash,list都會用壓縮列表呢 ?
當數據量小的時候通過數組下標訪問最快、占用內存最小,而壓縮列表只是數組的升級版;
因為數組需要占用連續的內存空間,所以當數據量大的時候,就需要使用鏈表了,同時為了保證速度又需要和數組結合,也就有了散列表。

1、內存利用率,數組和壓縮列表都是非常緊湊的數據結構,它比鏈表占用的內存要更少。Redis是內存數據庫,大量數據存到內存中,此時需要做盡可能的優化,提高內存的利用率。

2、數組對CPU高速緩存支持更友好,所以Redis在設計時,集合數據元素較少情況下,默認采用內存緊湊排列的方式存儲,同時利用CPU高速緩存不會降低訪問速度。當數據元素超過設定閾值后,避免查詢時間復雜度太高,轉為哈希和跳表數據結構存儲,保證查詢效率。

2.《redis 動態字符串詳解》
1.案例:實現一個圖片存儲系統,并快速定位文件位置
案例描述:開發一個圖片存儲系統,要求這個系統能快速地記錄圖片 ID 和圖片在存儲系統中保存時的 ID(可以直接叫作圖片存儲對象 ID)。同時,還要能夠根據圖片 ID 快速查找到圖片存儲對象 ID。因為圖片數量巨大,所以我們就用 10 位數來表示圖片 ID 和圖片存儲對象 ID,

例如,圖片 ID 為 1101000051,它在存儲系統中對應的 ID 號是 3301000051。

photo_id: 1101000051
photo_obj_id: 3301000051
1
2
圖片 ID 和圖片存儲對象 ID 正好一一對應,是典型的“鍵 - 單值”模式,這和 String 類型提供的“一個鍵對應一個值的數據”的保存形式剛好契合。

當我們儲存一億張圖片時,用6.4G內存,但是,隨著圖片數據量的不斷增加,我們的 Redis 內存使用量也在增加,結果就遇到了大內存 Redis 實例因為生成 RDB 而響應變慢的問題。

我們保存了 1 億張圖片的信息,用了約 6.4GB 的內存,一個圖片 ID 和圖片存儲對象 ID 的記錄平均用了 64 字節。但問題是,一組圖片 ID 及其存儲對象 ID 的記錄,實際只需要 16 字節就可以了。

question:那么redis 的動態字符串類型為什么會使用這么多內存呢?另外48Byte 的內存用到哪里了呢?

2.動態字符串結構體 (Simple Dynamic String,SDS)
當我們用String類型鍵值對存儲數據時,

當你保存 64 位有符號整數時,String 類型會把它保存為一個 8 字節的 Long 類型整數,這種保存方式通常也叫作 int 編碼方式。

當你保存的數據中包含字符時,String 類型就會用簡單動態字符串(Simple Dynamic String,SDS)結構體來保存,如下圖所示:

len: 4字節 ,表示buf 使用長度

alloc:4字節,表示 內存分配器分配空間 大小

buf:字節數組,保存實際數據,Redis 會自動在數組最后加一個“\0”,這就會額外占用 1 個字節的開銷。

3.RedisObject
對于 String 類型來說,除了 SDS 的額外開銷,還有一個來自于 RedisObject 結構體的開銷。

因為 Redis 的數據類型有很多,而且,不同數據類型都有些相同的元數據要記錄(比如最后一次訪問的時間、被引用的次數等),所以,Redis 會用一個 RedisObject 結構體來統一記錄這些元數據,同時指向實際數據。

Long類型時: ptr引用指針直接填充賦值為整數數據這樣就不用額外的指針再指向整數了,節省了指針的空間開銷。這種布局方式也被稱為 。
小于等于44字節的字符串:RedisObject 中的元數據、指針和 SDS 是一塊連續的內存區域,這樣就可以避免內存碎片。這種布局方式也被稱為
大于44字節的字符串時,SDS 的數據量就開始變多了,Redis 就不再把 SDS 和 RedisObject 布局在一起了,而是會給 SDS 分配獨立的空間,并用指針指向 SDS 結構。這種布局方式被稱為
下面是 三種編碼方式的結構圖:

所以,例子中,保存“1101000051:3301000051”只使用 了 32個字節,另外32字節消耗在哪里了?

這就要講一下 redis 中的 dicEntry結構了

4.全局哈希表的 dicEntry結構
Redis 會使用一個全局哈希表保存所有鍵值對,哈希表的每一項是一個 dictEntry 的結構體,用來指向一個鍵值對。

dictEntry 結構中有三個 8 字節的指針,分別指向 key、value 以及下一個 dictEntry,三個指針共 24 字節,如下圖所示:

但是,加上 這三個指針也才 24個字節 + 32字節 ,不夠64字節啊,為什么?

應為redis 內存分配器 jemalloc,分配內存 按頁分配,且分配 2的冪次方大小的內存頁

所以,申請 了 56個字節,但是 內存分配器分配了 64字節

至此,保存“1101000051:3301000051”為什么分配了64字節就 真相大白了

那么,用什么來存儲海量 的String類型,單向鍵值對呢?

3.壓縮列表
1. ziplist結構
壓縮列表(ziplist):表頭有 zlbyts(4字節,列表所占字節大小),zltail(4字節,列尾到 列表其實位置的偏移量offset),zllen(2字節,列表entry元素個數); 最后在列表尾部有一個 zlend(1字節)表示列表結束

prev_len: 上一個元素 長度,prev_len 有兩種取值情況:1 字節或 5 字節。

取值 1 字節時,表示上一個 entry 的長度小于 254 字節。雖然 1 字節的值能表示的數值范圍是 0 到 255,但是壓縮列表中 zlend 的取值默認是 255,因此,就默認用 255 表示整個壓縮列表的結束,其他表示長度的地方就不能再用 255 這個值了。所以,當上一個 entry 長度小于 254 字節時,prev_len 取值為 1 字節;

否則,就取值為 5 字節。

len:當前元素長度,4字節

encoding: 編碼方式,1字節

content:保存實際數據

如上圖,展示了一個總長為80字節,包含3個節點的壓縮列表。如果我們有一個指向壓縮列表起始地址的指針p,那么表尾節點的地址就是P+60。

回到 存儲海量圖片并要求快速定位圖片的例子:

每個 entry 保存一個圖片存儲對象 ID(8 字節),此時,每個 entry 的 prev_len 只需要 1 個字節就行,因為每個 entry 的前一個 entry 長度都只有 8 字節,小于 254 字節。這樣一來,一個圖片的存儲對象 ID 所占用的內存大小是 14 字節(1+4+1+8=14),實際分配 16 字節。

2.使用ziplist好處以及 ziplist應用
Redis 基于壓縮列表實現了 List、Hash 和 Sorted Set 這樣的集合類型,這樣做的最大好處就是節省了 dictEntry 的開銷。當你用 String 類型時,一個鍵值對就有一個 dictEntry,要用 32 字節空間。但采用集合類型時,一個 key 就對應一個集合的數據,能保存的數據多了很多,但也只用了一個 dictEntry,這樣就節省了內存。

3.如何用集合類型保存單值的鍵值對?
在保存單值的鍵值對時,可以采用基于 Hash 類型的二級編碼方法。這里說的二級編碼,就是把一個單值的數據拆分成兩部分,前一部分作為 Hash 集合的 key,后一部分作為 Hash 集合的 value,這樣一來,我們就可以把單值數據保存到 Hash 集合中了。

以圖片 ID 1101000060 和圖片存儲對象 ID 3302000080 為例,我們可以把圖片 ID 的前 7 位(1101000)作為 Hash 類型的鍵,把圖片 ID 的最后 3 位(060)和圖片存儲對象 ID 分別作為 Hash 類型值中的 key 和 value。

增加一條記錄后,內存占用只增加了 16 字節:

127.0.0.1:6379> hset 1101000 060 3302000080
(integer) 1
1
2
4.什么時候使用ziplist?
Redis Hash 類型的兩種底層實現結構,分別是壓縮列表和哈希表。

Hash 類型底層結構什么時候使用壓縮列表,什么時候使用哈希表呢?其實,Hash 類型設置了用壓縮列表保存數據時的兩個閾值,一旦超過了閾值,Hash 類型就會用哈希表來保存數據了。

hash-max-ziplist-entries:表示用壓縮列表保存時哈希集合中的最大元素個數。
hash-max-ziplist-value:表示用壓縮列表保存時哈希集合中單個元素的最大長度。
為了能充分使用壓縮列表的精簡內存布局,我們一般要控制保存在 Hash 集合中的元素個數。所以,在剛才的二級編碼中,我們只用圖片 ID 最后 3 位作為 Hash 集合的 key,也就保證了 Hash 集合的元素個數不超過 1000,同時,我們把 hash-max-ziplist-entries 設置為 1000,這樣一來,Hash 集合就可以一直使用壓縮列表來節省內存空間了。

4.集合類型:一億個key要統計怎么辦?
總結


幾種類型使用:

HyperLogLog:基數統計,集合元素量達到億級別而且不需要精確統計

Bitmap:記錄的數據只有 0 和 1 兩個值的狀態

Set 和 Sorted Set:都支持多種聚合統計(交并差集),不過,對于差集計算來說,只有 Set 支持

List和Sorted Set:

排序統計時,List 中的元素雖然有序,但是一旦有新元素插入,原來的元素在 List 中的位置就會移動,那么,按位置讀取的排序結果可能就不準確了。而 Sorted Set 本身是按照集合元素的權重排序,可以準確地按序獲取結果,所以建議你優先使用它。
?

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

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

相關文章

redis核心技術與實戰(四)高可用高擴展篇

1.《redis架構組成》 1.redis學習維度 2.一個基本的鍵值型數據庫包括什么? 1.訪問框架 redis通過網絡框架進行訪問,使得 Redis 可以作為一個基礎性的網絡服務進行訪問,擴大了redis應用范圍; 過程:如果客戶端發送“pu…

tomcat監控腳本

#!/bin/sh# func:自動監控tomcat腳本并且執行重啟操作# 獲取tomcat進程ID(其中[grep -w .....]中的.....需要替換為實際部署的tomcat文件夾名,如下) TomcatID$(ps -ef |grep tomcat |grep -w /usr/local/tomcat/apache-tomcat-8.5.31|grep -v…

weblogic命令行操作

啟動和停止子節點: [rootoud bin]# cd /sotware/oracle_ldap/Middleware/user_projects/domains/base_domain/bin/ [rootoud bin]# ./startManagedWebLogic.sh Server-0 http://192.168.63.129:7001 -Dweblogic.management.usernameweblogic -Dweblogic.management…

Ansible系列--Copy模塊

copy模塊 copy模塊在ansible里的角色就是把ansible執行機器上的文件拷貝到遠程節點上。 與fetch模塊相反的操作 常用參數 參數名是否必須默認值選項說明srcno 用于定位ansible執行的機器上的文件,需要絕對路徑。如果拷貝的是文件夾,那么文件夾會整體…

ANSIBLE--handlers的概念

handlers可以理解成另一種tasks,handlers是另一種’任務列表’,handlers中的任務會被tasks中的任務進行”調用”,但是,被”調用”并不意味著一定會執行,只有當tasks中的任務”真正執行”以后(真正的進行實際…

ansible--- tags

tags可以幫助我們對任務進行’打標簽’的操作,當任務存在標簽以后,我們就可以在執行playbook時,借助標簽,指定執行哪些任務,或者指定不執行哪些任務。在實際的使用中,我們應該讓tags的值能夠見名知義。 當…

ANSIBLE---變量

注冊變量 ansible的模塊在運行之后,其實都會返回一些”返回值”,只是默認情況下,這些”返回值”并不會顯示而已,我們可以把這些返回值寫入到某個變量中,這樣我們就能夠通過引用對應的變量從而獲取到這些返回值了&…

inux中限制用戶進程CPU和內存占用率

#!/bin/sh PIDStop -bn 1 | grep "^ *[1-9]" | awk { if($9 > 50 || $10 > 25 && id -u $2 > 500) print $1} echo $PIDS for PID in $PIDS dorenice 10 $PIDecho "renice 10 $PID" done

按月拆分數據庫表--oracle

生產有一張日志表,數據量很大,需要按月進行存儲,存儲過程如下: CREATE OR REPLACE PROCEDURE NEWLOG4_SUB_TABLE IStable_name1 VARCHAR2(50);create_table_sql VARCHAR2(4000);insert_data_sql VARC…

plsql定時器

Oralce中的任務有2種:Job和Dbms_job,兩者的區別有: 1. jobs是oracle數據庫的對象, dbms_jobs只是jobs對象的一個實例, 就像對于tables, emp和dept都是表的實例。 2. 創建方式也有…

PL/SQL批處理語句:BULK COLLECT 和 FORALL

PL/SQL程序中運行SQL語句是存在開銷的,因為SQL語句是要提交給SQL引擎處理,這種在PL/SQL引擎和SQL引擎之間的控制轉移叫做上下文卻換,每次卻換時,都有額外的開銷 請看下圖: 但是,FORALL和BULK COLLEC…

oracle 中DATETIME與TIMESTAMP區別

1.DATETIME的日期范圍是1001——9999年,TIMESTAMP的時間范圍是1970——2038年。 2.DATETIME存儲時間與時區無關,TIMESTAMP存儲時間與時區有關,顯示的值也依賴于時區。在mysql服務器,操作系統以及客戶端連接都有時區的設置。 3.DAT…

PARALLEL(并行)

在Oracle中,PARALLEL(并行)方式最大化調用計算機資源來成倍提高數據分析效率。 1. 用途 強行啟用并行度來執行當前SQL。這個在Oracle 9i之后的版本可以使用,之前的版本現在沒有環境進行測試。也就是說,加…

Oracle數據庫查詢優化

1.對查詢進行優化,應盡量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。 2.應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: select id from t w…

redis-full-check

https://github.com/alibaba/RedisFullCheck/releases redis-full-check是阿里云Redis&MongoDB團隊開源的用于校驗2個redis數據是否一致的工具。 ??redis-full-check通過全量對比源端和目的端的redis中的數據的方式來進行數據校驗,其比較方式通過多輪次比較&a…

2021-06-22

服務器信息 [rootiZs7z01dz0z12dyttz9zn5Z cluster]# /app/redis/redis-3.2.1/src/redis-cli -c -h 10.252.120.9 -p 8003 10.252.120.9:8003> cluster nodes b1f543d646c5c97a70b0635439a44a72f8a143b1 10.252.120.10:8004 master - 0 1624349601417 7 connected 0-5460 1…

Docker目錄掛載

Docker容器啟動的時候,如果要掛載宿主機的一個目錄,可以用-v參數指定。 譬如我要啟動一個centos容器,宿主機的/test目錄掛載到容器的/soft目錄,可通過以下方式指定: # docker run -it -v /test:/soft centos /bin/ba…

Redis主從復制原理學習

Redis主從復制原理學習總結 - 運維筆記 和Mysql主從復制的原因一樣,Redis雖然讀取寫入的速度都特別快,但是也會產生讀壓力特別大的情況。為了分擔讀壓力,Redis支持主從復制,Redis的主從結構可以采用一主多從或者級聯結構&#xff…

redis數據恢復

公司線上一個項目數據存儲采用MySQL,共分為10個庫,分布在4臺機器上,每個庫數據量約為10G,各機器均采用RAID5加速磁盤訪問; 當同時在線人數達高峰期(10w),DB磁盤IO壓力巨大&#xff0…

Redis哨兵模式(sentinel)學習總結及部署記錄(主從復制、讀寫分離、主從切換)

Redis的集群方案大致有三種:1)redis cluster集群方案;2)master/slave主從方案;3)哨兵模式來進行主從替換以及故障恢復。 一、sentinel哨兵模式介紹 Sentinel(哨兵)是用于監控redis集群中Master狀態的工具&…