你肯定你真的瞭解redis系列文章第一篇

面試官:你說說redis經常使用數據結構有哪些呢?面試

面試者:String(字符串),list(鏈表),hash(哈希),set(集合),zset(有序集合)redis

面試官:那你說說redis的string的底層具體實現呢?json

面試者:額?數組

首先咱們知道redis全部的數據結構都是以一個惟一的key來做爲名稱,而後經過這個惟一的key去獲取相應的value數據,不一樣的數據結構的差異就在於value的數據結構不同。緩存

今天咱們就先從最簡單的string來作一些深刻的瞭解。字符串的使用很是的普遍,好比咱們常常用來緩存用戶的信息,將用戶的信息經過JSON序列化成一個字符串,而後經過用戶的id或者code來將序列化之後的json字符串緩存到redis中,當要使用的時候將json字符串從redis中獲取到,再通過一次反序列化之後就能夠獲取到用戶的信息了。 網絡

一:string相關的經常使用命令數據結構

  1. set用來設置一個值,get用來獲取一個值,exists用來判斷一個key是否存在,del用來刪除key

  2. 咱們知道網絡是很珍貴的資源的,有時候咱們爲了節省網絡開銷,咱們能想到的最直接辦法就是批量操做,redis也爲咱們提供了相應的批量操做命令mset和mget,進行批量的設置設置和批量獲取
  3. 咱們還能夠對key設置過時時間,以及對set命令作一些擴展,例如setex命令在設置值的時候,順便設置一個過時時間,或者是setnx命令,只有當一個key不存在時才能設置成功,否者失敗,對於setnx命令咱們最多見的一個應用場景就是分佈式鎖了。這個咱們後面文章再專門討論這個。
  4. 計數,對於value是數字類型的,咱們還可使用incrby命令,對當前的值進行自增操做,固然這個自增操做是有範圍的,它的最大值是signed long的最大值,若是超過這個值,redis就會報錯。對於計數咱們最多見的一種用法就是用來生成分佈式主建,全部服務都使用同一個能夠而後進行自增,這樣就能得到一個全局惟一併且不重複的id了。

二:上面咱們對redis的string數據結構的使用和常見命令有了一些瞭解以後,咱們接下來就能夠對string的底層數據結果作一些深刻了解了。分佈式

  1. Redis的字符串名爲SDS,全稱爲simple dynamic String,結構是一個帶長度信息的字節數組。

    struct SDS<T> {  T capacity; // 數組分配的容量 T len; // 實際數據的長度 byte flags; byte[] content; // 真的的數據是存在這個字節數組裏面的。函數

    }spa

    capacity表示的是當前分配的字節數組的長度,而len表示的是當前字符串的真實長度,當初始化的時候,capacity和len是同樣大的,可是在後期進行分配的時候,就會出現下圖的狀況capacity大於len,具體緣由是當字符串的長度小於1M的時候,容量增長都是加倍現有的空間,當實際長度超過1M之後,實際擴容的時候,只會增長1M的空間,爲何這麼操做呢?是由於若是數組沒有多餘的冗餘空間,那麼當在追加內容的時候,就必需要分配新的數組,而且將舊數組的內容拷貝過去,再追加內容,這樣就分配內存和複製的開銷就會比較大,因此採起了一種空間換時間的作法。可是須要咱們注意的是,字符串的容量最大不能超過512M。

    上面咱們說到的是redis的string的數據結構,其實只是它的具體數據的存儲結構,對於redis來講,每種數據結構都有一個相同的結構頭,結構頭的具體內容以下:

    struct RedisObject {    int4 type; // 4bits,用來表示不一樣的對象,好比字符串,鏈表,集合,哈希,有序集合等 int4 encoding; // 4bits ,用來表示不一樣的存儲結構,好比一樣的都是字符串,

    可是若是字符串的大小不等,那麼結構也會有些差異 int24 lru; // 24bits  用來記錄當前對象的lru信息 int32 refcount; // 4bytes 對象的引用計數,

    當對象的引用計數爲0的時候,對象就會被銷燬,內存被回收 void *ptr; // 8bytes,64-bit system} robj; //指向實際存儲數據的指針,

    好比指向咱們上面提到的那個sds

    那麼爲何要設計一個對象頭呢?實際上是由於,咱們對於一樣的數據結構,

    若是它存儲的內容的大小不同,咱們須要有不一樣的存儲方式,存儲方式須要有個地方

    來保存,那麼咱們天然就想到設計一個公共頭,來保存這些信息,可能這麼說,

    你們不是很理解,不要緊我給你們舉個實際例子,一下就明白了。

    咱們看到上面,咱們都是string數據結構,可是當咱們的vlaue的大小不同的時候,conten的內容不同,表示存儲的結構不同。

  2. remstr和raw是string的兩種存儲格式,它們具體有什麼區別呢?仍是直接上圖。

    從上圖咱們能夠看到,embstr這種存儲結構,是將對象頭和數據部分直接分配在一個連續內存空間裏面的,而raw這種數據結構,是分配了兩個內存空間,它們在內存上是不連續的。那麼問題來了?何時使用embstr存儲格式,何時使用raw存儲格式呢?答案下面揭曉。

    首先咱們先計算一下即便不存儲任何內容的embstr字符串須要多少內存呢?RedisObject+SDS,首先RedisObject須要(4+4+24+32+64)/8=16個字節,而SDS即便不存儲任何內容也須要3個字節的內容,最少也須要16+3=19個字節.其次咱們知道操做系統使用jmalloc和tmalloc進行內存的分配,而內存分配的單位都是2的N次方,因此是2,4,8,16,32,64等字節,爲了可以容納一個embstr,因此操做系統最少也要分配32個字節的空間,可是這樣的話,可以存儲的內容也太少了,根本知足不了平常使用,因此默認直接分配64個字節,在redis中,若是一個總體佔用超過了64個字節空間的字符串,redis就認爲是一個大字符串,使用raw格式了,那麼在64個字節的狀況下,除去其餘部分佔用的19個字節,還剩下45個字節可以使用,可是redis方便glibc函數的使用,以及方便調試和打印,在字符串內容的末尾加上了一個\0的佔位符,因此實際可以使用的只有44個字節。因此咱們得出結論,embstr可以最多存儲4個字符串的內容。固然這些都是基於redis4.0版本獲得的,其餘版本由於結構不同,可能獲得的結論不同,可是上面的內容你們不用刻意去記憶,瞭解一下就好。

三:總結一下,redis的string底層數據結構使用的是sds,可是sds有兩種存儲方式,一種是embstr,一種是raw,它們的區別在於,是否和redisObject對象頭分配在一個連續的空間裏面。當總體佔用空間超過1M的時候,使用raw格式,當小於1M的時候使用embstr,而embstr最多可以存儲44個字節的內容。固然對於redis來講,string的value最大不能超過512M。

相關文章
相關標籤/搜索