天天看點

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼

一 字元串(string)

在Redis的世界裡,資料類型沒有什麼整數、浮點數、布爾值等,隻有一種類型來代替他們,那就是字元串。字元串無論是否加引号,redis可以自動識别它是否是整數或者浮點數,亦或是普通的字元串。如果是整數,那我們還可以進行自增自減等操作,如果是浮點數,則會被檢查出來不是整數,是以不能自增,隻能夠增加指定的浮點類型的資料。

#1 Redis的字元串是動态的可以修改的字元串,内部結構類似于Java的ArrayList,采用預配置設定記憶體來減少頻繁的記憶體配置設定,如果記憶體小于1M的,則成倍擴容;

#2 如果超過1M則每次隻增加1M。另外就是字元串最大隻允許512M。

1.1 設定

1.1.1 設定單個值

set key value [ex seconds] [px milliseconds] [nx|xx]

ex: 設定key的到期時間,以秒為機關 可以通過setex key second value代替

px: 設定key的到期時間,以毫秒為機關

nx: 如果key不存在,則執行操作,可以通過setnx key value代替

xx: 如果key存在,則執行操作

如果沒有設定到期時間,則就沒有到期時間

set shape round

set shape squre xx

set color red nx

set color blue ex 3600 xx

set gender women px 360000 nx

1.1.2 擷取單個字元串

get key

get shape

1.1.3 批量擷取和設定

1.1.3.1 批量設定

mset key value [ex seconds] [px milliseconds] [nx|xx] key value [ex seconds] [px milliseconds] [nx|xx] .......

mset a 15 ex 2000 xx b 20 ex 2000 nx

1.1.3.2批量擷取

mget a b

mget color shape gender

mget 和 get差別

批量操作指令可以有效提高效率,因為N次get時間 = N次網絡時間 + N次指令執行時間。是以mget隻需要一次網絡時間即可。但是也不能數量過多。

1.1.4 整數增加或者減少以及浮點數增加

1.1.4.1 incr 整數自增(可以用來實作計數的功能)

set count 100 xx

incr count

1.1.4.2 decr 正數自減

decr count

1.1.4.3 incrby 增加多少

incrby count 200

1.1.4.4 decrby 減少多少

decrby count 50

1.1.4.5 incrybyfloat 浮點數增加

set a 1.5 xx

incrbyfloat a 100

注意沒有decrbyfloat

1.1.5 getrange截取字元串

getrange key start end

start: 從什麼位置開始截取

end: 截取到神呢麼位置結束

從0開始截取5個字元串

getrange word 0 4

1.1.6 setrange替換字元串

setrange key offset value

offset: 從什麼位置開始替換

value: 要替換的值

setrange word 1

1.1.7設定完某一個key傳回之前的值

getset key value

getset a 15

1.1.8字元串長度

strlen key

strlen color

二 清單(list)

2.1 清單介紹

#1 清單相當于Java的LinkedList,是基于連結清單的資料結構,是以插入和删除很快,但是查詢較慢。

#2 當清單彈出最後一個元素的時候,該資料結構自動被删除,記憶體被回收。

#3 清單常用來做異步隊列隊列使用,将需要延後處理的任務結構序列化成字元串,然後塞進Redis的清單,另外一個線程從清單中輪詢資料。

#4 一個清單最多可以存儲2^32-1個元素

#5 清單可以從兩邊push和top,是以既可以充當棧,也可以充當隊列

2.2 存儲資料

我們可以從右邊或者左邊進行push

rpush key value [value ......]

rpush books "think in java" "spring in action" "design pattern" "hadoop in action"

lpush key value [value ......]

lpush movies "seventeen years" "ashes of time" "fream factory" "third sister liu" "titanlic"

2.3 彈出資料(删除) 和帶阻塞機制的彈出

2.3.1 非阻塞彈出

我們可以從右邊或者左邊進行pop

rpop books

lpop movies

2.3.2 阻塞式彈出

當清單list沒有資料的時候,就會阻塞等待

blpop key [key……] timeout

brpop key [key……] timeout

2.4 隊列

2.4.1 左進右出

lpush movies "seventeen years" "ashes of time" "fream factory" "third sister liu" "titanlic"

rpop movies

2.4.2 右進左出

rpush books "think in java" "spring in action" "design pattern" "hadoop in action"

lpop books

2.5 棧

2.5.1 左進左出

lpush movies "seventeen years" "ashes of time" "fream factory" "third sister liu" "titanlic"

lpop movies

2.5.2 右進右出

rpush books "think in java" "spring in action" "design pattern" "hadoop in action"

rpop books

2.6 查找(不會删除)

2.6.1 範圍查找

lrange key start end

lrange key 0 -1: 表示全部查詢(慎用)

lrange books 1 2   

2.6.2 索引查找

lindex key index

lindex movies 0

2.7 删除元素

2.7.1 擷取元素之後删除

lpop/rpop key 彈出元素之後,從清單中删除

2.7.2 直接删除指定數量的元素,不擷取元素

lrem key count value 從清單中查找出所有和指定value相同的值,删除count個。

count: 代表要删除的個數,可以是正數、負數和0

value: 代表要删除的元素

#1 如果是lpush

其中count > 0,表示從右邊到左邊删除count個;count < 0表示從左邊到右邊删除count個元素;count=0删除所有相同的值。

rpush counts 1 2 1 2 1 2 1 2 1 2

lrem nums 2 1 : 從右往左删除2個1, 則剩餘元素:1 2 1 2 1 2 2 2

lrem nums -2 1: 從左往右删除2個1,則剩餘元素: 2 2 1 2 1 2 1 2

lrem counts 0 1: 删除所有1元素,剩餘元素: 2 2 2 2 2

#2如果是rpush

其中count > 0,表示從左邊到右邊删除count個;count < 0表示從右邊到左邊删除count個元素;count=0删除所有相同的值。

lpush counts 1 2 1 2 1 2 1 2 1 2

lrem counts 2 1 : 從左往右删除2個1, 則剩餘元素:2 2 1 2 1 2 1 2

lrem counts -2 1: 從右往左删除2個1,則剩餘元素: 1 2 1 2 1 2 2 2

lrem counts 0 1: 删除所有1元素,剩餘元素: 2 2 2 2 2

2.7.3 按照指定索引範圍修剪清單,删除其他索引範圍之外的元素

ltrim key start stop

start:從哪個位置開始裁剪

stop: 裁剪到什麼位置結束

ltrim key 1 0 删除整個清單

ltrim counts 3 4

2.8 修改

lset key index new_value

index: 要修改哪一個位置元素

new_value: 要修改的值

lset counts 0 5

三 哈希(hash)

我們在Redis中要存儲使用者資訊,如果不考慮哈希的情況下有2種辦法,都是基于字元串的。

第一: 使用者ID為key,使用者對象序列化成JSON字元串作為value,比如 user:1={'name':'nicky','age':'20'}

優點:簡單

缺點:需要序列化和反序列化;有并發問題,影響性能

第二: 将使用者ID和使用者屬性作為key,屬性值作為value,比如user:1:name='nicky',user:1:age=20

優點:無需序列化

缺點:需要存儲很多key,對于記憶體來說也不是好事;另外也并未消除并發問題,隻是降低了并發的粒度

hash資料結構,類似于java中的HashMap資料結構,也是采用數組+連結清單的結構作為底層資料結構,當key的hash值發生沖突的時候就放傳入連結表中。但是他們的rehash的方式不一樣

HashMap的rehash是對所有的資料全部進行rehash,這是一個耗時的操作;但是redis采用漸進式rehash,即在rehash的時候,保留新舊2個hash結構,查詢的時候會同時查詢2個結構,然後在後續的任務中将舊hash裡面的資料一點一點遷移到新的哈希結構,當遷移完成了,就會使用新的hash結構取代它

哈希結構和清單一樣,當移除最後一個元素的時候,資料結構自動删除,記憶體被回收。

3.1 設定值 hset

hset key field value 成功傳回1,錯誤傳回0

field: 字段

value: 值

hset user:1 name admin

hset user:1 age 45

hset user:1 gender men

3.2 擷取

hget key field

hget user:1 name

3.3 删除

hdel key field

hdel user:1 e-mail

3.4 計算field個數和 value長度

3.4.1 hlen計算字典中field個數

hlen key

hlen user:1

3.4.2 hstrlen 計算value的長度

hstrlen user:2 name

hstrlen user:2 gender

3.5 批量設定和批量擷取

hmset key field value [field value......]

hmset user:2 name "nicky" age 32 gender "men"

hmget key field field .......

hmget user:2 name age gender

3.6 判斷key是否存在

hexists key

hexists user:1

3.7 擷取字典中所有的field和value

類似于java中擷取字典中所有的鍵和值

3.7.1 擷取所有的field(鍵)

hkeys key

hkeys user:1

1) "name"

2) "age"

3) "gender"

3.7.2 擷取所有的value(值)

hvals key

hvals user:1

1) "nicky"

2) "32"

3) "men"

3.7.3 擷取所有鍵值

hgetall key

hgetall user:1

1) "name"

2) "nicky"

3) "age"

4) "32"

5) "gender"

6) "men"

3.8 可以增加value的值

hincrby/hincrbyfloat key field increment

hincrbyfloat user:1 age 10

四 集合(set)

#1 和Java中HashSet相似,HashSet底層是基于HashMap實作的,所有的value字段都是null; Redis也一樣,基于hash實作的,所有的value都是nil

#2 集合是不能重複的,且沒有順序(是以有去重的功能)

#3 當集合中最後一個元素被移除,集合資料結構被删除,記憶體被回收

4.1 集合内部的操作

4.1.1 添加集合

sadd key member [member ......]

添加一個

sadd animals tiger

添加多個

sadd animals lions cat panda

4.1.2 擷取集合元素

4.1.2.1 随機擷取N個元素

srandmember key count

srandmember animals 2

4.1.2.2 随機擷取N個元素(删除)

spop key count

spop animals 2 會從集合傳回2個元素,然後删除

4.1.2.3 擷取所有元素

smember key

smembers animals

4.1.3 删除集合元素

4.1.3.1 删除指定元素

srem key member [member......]

srem animals buff cat

4.1.3.2 删除指定數量元素,并且傳回删除的元素

spop key count

spop animals 2 會從集合傳回2個元素,然後删除

4.1.4 是否存在某個元素

sismember key member 如果存在傳回1否則傳回0

sismember animals buff

4.1.5 計算集合元素個數

scard key

scard animals

4.2 集合之間的操作

sadd a 10 20 30 40 50

sadd b 40 50 60 70 80

4.2.1 交集

sinter key1 key2 ......

sinter a b

1) "40"

2) "50"

4.2.2 并集

sunion key1 key2 ......

sunion a b

1) "10"

2) "20"

3) "30"

4) "40"

5) "50"

6) "60"

7) "70"

8) "80"

4.2.3 差集

sdiff key1 key2 ......

sdiff a b

1) "10"

2) "20"

3) "30"

4.2.4 将交集儲存為新集合

sinterstore destination key [key......]

destination: 要儲存的新的集合名字

sinterstore abinter a b

4.2.5 将并集儲存為新集合

sunionstore destination key [key......]

destination: 要儲存的新的集合名字

sunionstore abunion a b

4.2.6 将差集儲存為新集合

sdiffstore destination key [key......]

destination: 要儲存的新的集合名字

sdiffstore abdiff a b

五 有序集合(zset)

#1 類似于Java中SortedSet和HashMap的結合體:一方面他是一個set,保證了元素value的唯一性;另一方面給每一個元素賦予了一個分數,代表這個value的排序權重。

#2 内部實作用的是跳躍清單的資料結構

#3 雖然集合不能重複,但是集合裡面元素的分數是可以重複的

#4 zset中資料全部被移除之後,資料結構會被删除,然後記憶體被回收

#5 比較适合存儲需要按照權重來排序的資料,比如考試成績等等

5.1 集合内部操作

5.1.1 添加元素

zadd key score member [score member ......]

zadd books_1 6.5 "apache kafka in action" 8.2 "apache kylin" 4.5 "hadoop in action" 9.0 "spark in action"

zadd books_2 9.0 "spark in action" 6.8 "think in java" 7.9 "Java Virtual Machine" 8.3 "spring in action"

5.1.2 擷取某一個成員分數

zscore key member

zscore books_1 "apache kylin"

5.1.3 傳回指定排名的成員

zrange books_1 start stop [withscores]

start: 從哪一個排名開始

stop: 到哪一個排名結束

withscores: 是否傳回分數

zrange books_1 1 2  withscores

1) "apache kafka in action"

2) "6.5"

3) "apache kylin"

4) "8.2"

5.1.4 傳回指定分數範圍的排序成員

zrangebyscore key min max [withscores] [limit offset cout]

#1 擷取指定分數範圍的有成員

zrangebyscore books_1 7 9

1) "apache kylin"

2) "spark in action"

#2 擷取指定分數範圍的有成員,并且攜帶分數

zrangebyscore books_1 7 9 withscores

1) "apache kylin"

2) "8.2"

3) "spark in action"

4) "9"

#3 擷取指定分數範圍的有成員,但是隻從結果某位置(offset)開始,連續取N(count)個

zrangebyscore books_1 4 9 limit 1 2

1) "apache kafka in action"

2) "apache kylin"

5.1.5 擷取成員個數分區區間成員個數

zcard key 傳回成員個數

zcard books_1

zcount key min max 分數區區間[min,max]成員個數

zcount books_1 7 9

5.1.5 擷取成員排名

5.1.5.1 由低到高排序

zrank key member

zrank books_2 "spark in action"

5.1.5.2 由高到低排序

zrevrank key member

zrevrank books_2 "spark in action"

5.1.7 删除成員

5.1.7.1 删除指定成員

zrem key member

zrem books_1 "spark in action"

5.1.7.2 删除指定排名内的升序成員

zremrangebyrank key start end

zrmrangebyrank animals 0 2 删除正序排序為0 1 2的成員

5.1.7.3 删除制指定分數範圍的成員

zremrangebyscore key min max

zremrangebyscore animals 5 7 删除5-7分的成員

5.1.7 元素自增

zincrby key increment member

zincrby A 10 1 擷取A集合中元素1,如果存在在1的基礎上添加10

5.2 集合之間的操作

5.2.1 将交集儲存為新集合

zinterstore destination key [key......] [weights weight ] [aggregate sum|min|max]

destination: 要儲存的新的集合名字

zinterstore abinter a b

5.2.2 将并集儲存為新集合

zunionstore destination key [key......] [weights weight ] [aggregate sum|min|max]

destination: 要儲存的新的集合名字

zunionstore abunion a b

六 geo

Geo是基于zset實作的

6.1 添加

geoadd 集合 經度 緯度 名稱

geoadd geo:city 118.8921 31.32751 nanjing

6.2 擷取地理位置的坐标

geppos 集合 名稱

geopos geo:city Nanjing

6.3傳回兩個給定位置之間的距離

Geodist 集合 member1 member2 距離機關

geodist geo:city nanjing hangzhou km

6.4以給定的經緯度為中心,傳回鍵包含的位置元素當中,與中心的距離不超過

過給定最大距離的所有位置元素

georadius 集合 經度 緯度 範圍 機關

georadius geo:city 120 30 100 km withcoord

6.5 删除

zrem 集合 名稱

zrem geo:city suzhou

六 redis對key的管理

6.1 檢視所有key(慎用),支援正則

keys *

keys an*

6.2 删除某個key

del key

3.6.3 擷取key的資料類型

type key

type user:1

6.3 是否存在某個key

exists key [key .....]

隻傳回存在個數

6.4 設定key的到期時間和檢查到期時間

設定到期時間

expire key second(秒級)

pexpire key millionseconds(毫秒級)

ttl key 檢查到期時間(秒級)

pttl key 檢查到期時間(毫秒級)

6.6 key重命名

rename key newkey

重命名期間,會删除舊的鍵,如果鍵對應的值比較大,會存在阻塞

6.7 key遷移

從一個Redis 資料庫遷移到另一個Redis 資料庫

move animals 1

migrate 也适用于Redis執行個體間進行資料遷移

migrate host port key| destination-db timeout [COPY] [REPLACE] [KEYS key]

host:目标資料庫的IP位址

port: 目标資料庫端口

key|"": 如果遷移一個key,則用key,如果遷移多個則使用“”

destination-db: 目标資料庫

timeout: 遷移的逾時時間

copy: 如果添加此選項,并不删除原來key

replace: 不管是否目标資料庫存在這個key,給替換掉

keys: 表示遷移多個key

migrate 192.168.3.201 6379 "" 1 5 copy KEYS fruits plants

6.8 key周遊

3.6.8.1 keys 全量周遊

3.6.8.2 scan漸進式周遊

SCAN 指令及其相關的 SSCAN 指令、 HSCAN 指令和 ZSCAN 指令都用于增量地疊代(incrementally iterate)一集元素(a collection of elements):

SCAN 指令用于疊代目前資料庫中的資料庫鍵。

如果不指定遊标,預設傳回10個,然後傳回下一次擷取需要從哪一個遊标開始。他疊代的是整個資料庫的key.

 scan 0

1) "5" // 下一次擷取key的遊标位置

2)  1) "AB"

    2) "ab"

    3) "b"

    4) "books_2"

    5) "user:2"

    6) "A"

    7) "abunion"

    8) "user:4"

    9) "user:3"

   10) "a"

   11) "animals"

   12) "abinter"

scan 5

1) "0" // 遊标位置為0,說明已經周遊完了

2) 1) "B"

   2) "animal"

   3) "user:1"

   4) "books_1"

SSCAN 指令用于疊代集合鍵中的元素。

HSCAN 指令用于疊代哈希鍵中的鍵值對。

ZSCAN 指令用于疊代有序集合中的元素(包括元素成員和元素分值)。

6.9 鍵值序列化

dump key 會将鍵值序列化,格式采用rdb

restore key ttl value # 将序列化的值複原,其中ttl參數代表過期時間,ttl=0表示沒有過期時間

七 基礎資料結構

7.1 字元串

字元串是redis中基礎的資料結構, 在C語言中字元串用char[]表示,并且末尾用\0結尾,表示字元串結束。比如 char[] data ="content",  C語言表示為"content\0"。

如果字元串内有多個\0的字元, 那麼有可能字元串就會被截斷,比如"content\0123545\0Bee\0"。是以為了解決這個問題, Redis引入了SDS(simple dynamic string)結構,簡單動态對象,核心思想,就是字元串長度不再因\0表示結尾,而是取決于資料本身的長度,是以每一個字元串有一個int類型的len屬性表示字元串長度,然後char[] 字元數組表示字元串。其實這樣也基本解決問題了,但是一個int最多可以表示8位元組,普通int也是4位元組,可以表示20多億的長度,一般情況下應該沒有這麼長的字元串,是以為了避免不必要的空間浪費,redis又進行優化,根據字元串長度分為sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64。

7.1.1 sdshdr5

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼
struct __attribute__ ((__packed__)) sdshdr5 {

    unsigned char flags; // 前3位表示類型,後5位表示資料長度

    char buf[]; // 字元串

};
           

flags: 字元串類型,占用1位元組,其中前3位表示字元串類型,後5位表示字元串長度

buf: 表示字元串

當字元串長度小于2^5-1,也就是1 <= len <= 31,則使用這個類型

7.1.2 sdshdr8

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; // 字元串已占用多少位元組
    uint8_t alloc; // 剩餘多少可用位元組
    unsigned char flags; // 前3位表示類型,後5位沒有使用
    char buf[];// 字元串
};
           

len: 字元串占用多少位元組

alloc: 剩餘多少可用位元組

flags: 字元串類型,占用1位元組,其中前3位表示字元串類型,後5位表示字元串長度

buf: 表示字元串

7.1.3 sdshdr16

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; // 字元串已占用多少位元組
    uint16_t alloc; // 剩餘多少可用位元組
    unsigned char flags; // 前3位表示類型,後5位沒有使用
    char buf[];// 字元串
};
           

len: 字元串占用多少位元組

alloc: 剩餘多少可用位元組

flags: 字元串類型,占用1位元組,其中前3位表示字元串類型,後5位表示字元串長度

buf: 表示字元串

7.1.4 sdshdr32

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; // 字元串已占用多少位元組
    uint32_t alloc; // 剩餘多少可用位元組
    unsigned char flags;// 前3位表示類型,後5位沒有使用
    char buf[];// 字元串
};
           

len: 字元串占用多少位元組

alloc: 剩餘多少可用位元組

flags: 字元串類型,占用1位元組,其中前3位表示字元串類型,後5位表示字元串長度

buf: 表示字元串

7.1.5 sdshdr64

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼
struct __attribute__ ((__packed__)) sdshdr64 {

    uint64_t len; // 字元串已占用多少位元組

    uint64_t alloc; // 剩餘多少可用位元組

    unsigned char flags; // 前3位表示類型,後5位沒有使用

    char buf[];// 字元串

};
           

len: 字元串占用多少位元組

alloc: 剩餘多少可用位元組

flags: 字元串類型,占用1位元組,其中前3位表示字元串類型,後5位表示字元串長度

buf: 表示字元串

7.2 字典(dict)

dict 字典是redis中基礎的資料結構,RedisDb的key和value,以及hash或者zset都是基于dict字典實作的。

7.2.1 字典的資料結構

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼
typedef struct dictht {

    dictEntry **table; // 資料

    unsigned long size; // 表示這個dictht已經配置設定空間的大小,大小總是2^n

    unsigned long sizemask;  //sizemask = size - 1; 是用來求hash值的掩碼,為2^n-1

    unsigned long used; //目前已有的元素數量

} dictht;


typedef struct dictEntry {

    void *key; // 指向key的指針

    union {

        void *val; // 指向value的指針

        uint64_t u64;

        int64_t s64;

        double d;

    } v;

    struct dictEntry *next; // 後繼節點指針

} dictEntry;
           

7.2.2 漸進式哈希

一般字典擴容,需要重新申請新的數組,然後将老字典中資料拷貝過去,這是一個O(N)級别的事情,有的可能是阻塞然後rehash,有的可能是多線程rehash。如果是大字典的話,作為單線程的Redis,肯定是不可能多線程來進行rehash的。Redis采用是一種漸進式rehash。

7.2.2.1 字典中字段

typedef struct dict {

    dictType *type; // 字典類型

    void *privdata;

    dictht ht[2]; // 哈希表,ht[0]對外提供通路,ht[1]用于rehash

    long rehashidx; // rehash索引,沒有發生rehash,這個字段就是-1,否則代表dict[0]哪一個索引正在進行rehash

    int iterators; // 目前正在運作的疊代器數量

} dict;
           

哈希字典有兩個哈希表ht[0]和ht[1], ht[0]用于對外通路,ht[1]用于rehash。并且有一個rehashidx用于表示目前哪一個索引正在進行rehash, 如果沒有發生rehash,那麼rehashidx=-1。

7.2.2.2 rehash擴容和縮容時機

第一:哈希表擴容和縮容都會觸發rehash

第二: 每次插入鍵值對的時候會檢查哈希表中元素數量或者redis資料庫定時任務會自動檢測目前哈希表元素數量

第三: 負載因子 = 哈希表已儲存節點數量 / 哈希表大小

load_factor = ht[0].used / ht[0].size

第四: 目前沒有子程序執行bgsave或者bgwriteaof指令,即沒有進行任何持久化的動作,并且哈希表負載因子大于1,即大于目前哈希表的size,則觸發擴容操作

第五: 目前有子程序執行bgsave或者bgwriteaof指令,即正在進行持久化操作,并且哈希表負載因子大于5,則也會觸發擴容操作

第六:當資料庫定時任務檢測到哈希表元素或者節點數量隻有哈希表size的1/10,也就是加載因子小于0.1的時候,這時候會觸發縮容操作

7.2.2.3 rehash是如何擴容

Redis擴容是按照2的倍數來擴容

7.2.2.4 redis是如何漸進式rehash的?

第一:檢查到哈希表需要擴容,則會将ht[1]配置設定空間,, 讓字典同時持有 ht[0] 和 ht[1] 兩個哈希表。

第二:在字典中維持的rehashidx字段, 将它的值設定為 0 , 表示 rehash已經開始

第三:用戶端每次對字典執行添加、删除、查找或者更新操作時, 程式除了執行指定的操作以外, 還會順帶将 ht[0] 哈希表在 rehashidx 索引上的所有鍵值對 rehash 到 ht[1] , 當 rehash 工作完成之後, 程式将 rehashidx 屬性的值增一。這個過程也叫操作輔助rehash。

第四:可能用戶端長時間沒有操作,此時不會有增删改查,伺服器比較空閑,redis資料庫将很長時間内都一直使用兩個哈希表。這樣的話,長時間長時間兩個哈希表都占用記憶體,肯定不好。是以,Redis資料庫有定時任務,如果發現有字典正在進行漸進式rehash操作,則會花費1毫秒的時間,幫助一起進行漸進式rehash操作。這個操作也叫定時輔助rehash。

第五:當ht[0]哈希表中所有桶内的資料已經rehash到ht[1]中,此時會将ht[1]重新賦給ht[0], 然後ht[1]置為null,并且rehashidx重置為-1,此時漸進式rehash結束。

7.3 ziplist(壓縮清單)

ziplist是一塊連續的記憶體空間,元素緊湊的存儲,沒有任何備援空隙。如圖所示:

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼

zlbytes: ziplist總的位元組數

zltail_offset: 最後一個元素距離ziplist起始位置的偏移量

zllen: ziplist中元素個數

entry數組: 存儲的内容

zlend: 表示壓縮清單ziplist的結束,值恒為0XFF

Entry字段資訊:

prevlen: 前一個entry的長度

encoding: 目前entry使用的編碼

data: entry中的資料

優點:元素存儲緊湊,節約空間

缺點:每次增加元素需要重新配置設定記憶體,然後将資料拷貝到新的配置設定區域上,如果資料量較大,而且redis又是一個單線程,是以将會影響性能。

7.4 quicklist(快速清單)

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼

因為ziplist每次增加元素需要重新配置設定記憶體,然後将資料拷貝到新的配置設定區域上,如果資料量較大,而且redis又是一個單線程,是以将會影響性能。是以Redis又又有了quicklist, quicklist是一個雙向連結清單,每一個節點維護一部分元素到一個ziplist,當ziplist達到門檻值則quicklist開始進行分裂,這樣可以将以前存在一個ziplist中元素分成多個段,那麼再進行增加元素的時候,拷貝的ziplist也隻是某一個段上資料,而不是全部ziplist中全部資料,這樣提升了性能。

7.5 skiplist(跳躍連結清單)

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼
struct zslnode {

    string value; // 資料

    double score; // 分數

    zslnode*[] forwards;  // 多層連接配接指針

    zslnode* backward;  // 回溯指針

}



struct zsl {

    zslnode* header; // 跳躍清單頭指針

    int maxLevel; // 跳躍清單目前的最高層

    map<string, zslnode*> ht; // hash 結構的所有鍵值對

}
           

八  資料結構的編碼

資料結構的編碼指的是各種資料結構在底層是如何存儲資料的, 我們可以通過過object encoding + key來檢視key的編碼。對于不同的資料結構,底層可能有不同的編碼,即不同的底層存儲實作。

8.1 RedisObject是什麼

Redis的資料最後會通過RedisObject将資料進行一個封裝,說白了,RedisObject就是存儲對象,除了包括存儲的資料(指向資料的位址),RedisObject還有資料的類型、資料的編碼、引用計數次數等中繼資料或者頭資訊,如下所示:

typedef struct redisObject {
    unsigned type:4; /* 對象類型: string?list?zset? hash?等*/
    unsigned encoding:4; // 編碼:int?row ?embstr?ziplist?
    unsigned lru:LRU_BITS; /* 記憶體淘汰相關的LRU資訊*/
    int refcount;// 引用次數,引用計數法,管理記憶體
    void *ptr; // 指向資料存儲的記憶體位址
} robj;
           

8.2 string類型的編碼

8.2.1 int

8.2.1.1 int的滿足條件

如果string類型的編碼是int,需要滿足什麼條件?

第一:該字元串必須是數字

如果是包含有其他字母或者特殊符号的,那麼編碼一定不是int

第二:該字元串長度必須小于20

如果字元串長度大于或者等于20,那麼編碼一定不是int。 因為int類型最大長度一般就是8位元組,即64位,可以表示最大資料長度不會超過20. 是以即便字元串全是數字,但是長度超過了20,那麼底層也不是int。

8.2.1.2 為什麼設計int這樣的編碼

我們知道RedisObject中的ptr指針會指向資料存儲的記憶體位址,也就是說ptr存儲的并不是資料,而是資料的位址,這樣的話,擷取資料的時候,每次是先要拿到ptr儲存的資料位址,然後再根據記憶體位址從記憶體擷取資料。

ptr是一個指針,指針會占用8位元組的記憶體空間,而如果存儲的值如果很明确知道不會超過8位元組,那我們可以直接用指針存儲資料,而不是位址,這樣用戶端擷取資料的時候,直接讀取RedisObject的ptr值就可以,無需再拿着ptr指向的位址去記憶體再讀一次資料。

而int類型的資料,我們知道最大也就8位元組,是肯定不會超過8位元組的,是以用8位元組的ptr指針直接存儲8位元組的int資料,節省了記憶體開銷和Redis的性能

8.2.2.embstr

8.2.2.1 滿足embstr條件

如果是數字還需要長度大于20且字元串長度小于或者等于44;如果不是數字,則隻需要字元串長度小于或者等于44的字元串則是使用的embstr編碼

8.2.2.2 為什麼設計embstr這樣的編碼

如果不這樣設計,那Redis可能就會為RedisObject和SDS單獨配置設定記憶體空間,而且在記憶體上可能不是連續的。這樣的話每次都需要先從RedisObject擷取對應ptr指向的位址,然後再将資料讀取出來。

我們知道,CPU從記憶體讀取資料,是根據緩存行讀取,預設緩存行大小是64位元組,是以如果要讀取的資料小于64,那麼讀取緩存行的時候就會把連續存儲的64位元組的資料讀取出來到CPU的緩存中,這樣提升了讀取效率。

是以Redis它将 RedisObject 對象頭和 SDS 對象連續存在一起,這樣的好處就是避免在建立的時候多配置設定1次空間,在删除的時候少釋放1次空間,而且連續存儲還可以提升查找效率。

8.2.2.3 為什麼少于44位元組的字元串才會使用embstr?

第一:緩存行是64位元組

第二:RedisObject占用16位元組(type: 4bit, encoding: 4bit, LRU: 24bit, refcount: 4byte, ptr: 8byte)

typedef struct redisObject {
    unsigned type:4; /* 對象類型: string?list?zset? hash?等*/
    unsigned encoding:4; // 編碼:int?row ?embstr?ziplist?
    unsigned lru:LRU_BITS; /* 記憶體淘汰相關的LRU資訊*/
    int refcount;// 引用次數,引用計數法,管理記憶體
    void *ptr; // 指向資料存儲的記憶體位址
} robj;
           

第三:SDS對象頭占用3位元組(len: 1byte, alloc: 1byte,flags:1byte),如下所示:

struct __attribute__ ((__packed__)) sdshdr8 {

    uint8_t len; // 1byte,表示已使用多少位元組
uint8_t alloc; // 1byte,表示有多少剩餘位元組數可以配置設定,如果為0,表示沒有位元組可以配置設定了
unsigned char flags; //1byte,前3位元組表示類型,後5位元組沒用
char buf[];

};
           

第四:字元串末尾還有一個\0,需占用一個位元組

是以:剩餘的位元組數量=64-16-3-1 = 44

8.2.3 raw

隻要大于44長度的字元串,無論是純數字還是其他字母或者特殊字元都使用這種編碼存儲資料。存儲特點,就是RedisObject和SDS是分開存儲的,在記憶體上不是連續的,需要進行兩次2配置設定記憶體,釋放也需要兩次。

8.2.4 int、embstr和raw的比較

Redis之資料結構和底層編碼一 字元串(string)二 清單(list)三 哈希(hash)四 集合(set)五 有序集合(zset)六 redis對key的管理七 基礎資料結構八  資料結構的編碼

8.3 hash的編碼

Hash資料結構預設使用ziplist,當ziplist中元素個數超過512個或者最大元素超過64位元組則切換為hashtable資料結構。

8.4 list的編碼

清單采用quicklist編碼來存儲資料,quicklist是将ziplist分成多個段,每一個段中ziplist元素數量是有限制的,采用quicklist的好處就是再大資料量的時候對性能友好

8.5 set的編碼

對于set集合,當元素全是數字且小于門檻值,預設512 則使用intset(數組)作為底層資料存儲結構;如果某一個元素不是純數字或者數字或者清單中全是數字,但是元素個數超過門檻值512,則會使用value等于null的hashtable

8.6 zset的編碼

當資料元素較少的時候,使用ziplist來存儲,當資料元素較多的時候使用skiplist來存儲。

繼續閱讀