Redis-基本數據類型與內部存儲結構

https://www.jianshu.com/p/f09480c05e42

1-概覽

Redis是典型的Key-Value類型數據庫,Key爲字符類型,Value的類型經常使用的爲五種類型:String、Hash 、List 、 Set 、 Ordered Sethtml

2- Redis內部內存管理

 
 
redisObject 核心對象

Redis 內部使用一個 redisObject 對象來表示全部的 key 和 value。redis

  1. type :表明一個 value 對象具體是何種數據類型。數據庫

  2. encoding :是不一樣數據類型在 redis 內部的存儲方式,好比:type=string 表明 value 存儲的是一個普通字符串,那麼對應的 encoding 能夠是 raw 或者是 int,若是是 int 則表明實際 redis 內部是按數值型類存儲和表示這個字符串的,固然前提是這個字符串自己能夠用數值表示,好比:"123" "456"這樣的字符串。json

  3. vm 字段:只有打開了 Redis 的虛擬內存功能,此字段纔會真正的分配內存,該功能默認是關閉狀態的。 Redis 使用 redisObject 來表示全部的 key/value 數據是比較浪費內存的,固然這些內存管理成本的付出主要也是爲了給 Redis 不一樣數據類型提供一個統一的管理接口,實際做者也提供了多種方法幫助咱們儘可能節省內存使用。數組

3- Key(鍵值)

官網Key連接:https://redis.io/commands#generic緩存

過時刪除

過時數據的清除歷來不容易,爲每一條key設置一個timer,到點馬上刪除的消耗太大,每秒遍歷全部數據消耗也大,Redis使用了一種相對務實的作法: 當client主動訪問key會先對key進行超時判斷,過期的key會馬上刪除。 若是clien永遠都再也不get那條key呢? 它會在Master的後臺,每秒10次的執行以下操做: 隨機選取100個key校驗是否過時,若是有25個以上的key過時了,馬上額外隨機選取下100個key(不計算在10次以內)。可見,若是過時的key很少,它最多每秒回收200條左右,若是有超過25%的key過時了,它就會作得更多,但只要key不被主動get,它佔用的內存何時最終被清理掉只有天知道。安全

經常使用操做
  1. Key的長度限制:Key的最大長度不能超過1024字節,在實際開發時不要超過這個長度,可是Key的命名不能過短,要能惟一標識緩存的對,做者建議按照在關係型數據庫中的庫表惟一標識字段的方式來命令Key的值,用分號分割不一樣數據域,用點號做爲單詞鏈接。數據結構

  2. Key的查詢:Keys,返回匹配的key,支持通配符如 「keys a*」 、 「keys a?c」,但不建議在生產環境大數據量下使用。併發

  3. 對Key對應的Value進行的排序:Sort命令對集合按數字或字母順序排序後返回或另存爲list,還能夠關聯到外部key等。由於複雜度是最高的O(N+Mlog(M))*(N是集合大小,M 爲返回元素的數量),有時會安排到slave上執行。官網連接https://redis.io/commands/sortdom

  4. Key的超時操做:Expire(指定失效的秒數)/ExpireAt(指定失效的時間戳)/Persist(持久化)/TTL(返回還可存活的秒數),關於Key超時的操做。默認以秒爲單位,也有p字頭的以毫秒爲單位的版本

4- String(字符串類型的Value)

能夠是String,也但是是任意的byte[]類型的數組,如圖片等。String 在 redis 內部存儲默認就是一個字符串,被 redisObject 所引用,當遇到 incr,decr 等操做時會轉成數值型進行計算,此時 redisObject 的 encoding 字段爲int。
https://redis.io/commands#string

  1. 大小限制:最大爲512Mb,基本能夠存儲任意圖片啦。

  2. 經常使用命令的時間複雜度爲O(1),讀寫同樣的快。

  3. 對String表明的數字進行增減操做(沒有指定的Key則設置爲0值,而後在進行操做):Incr/IncrBy/IncrByFloat/Decr/DecrBy(原子性),** 能夠用來作計數器,作自增序列,也能夠用於限流,令牌桶計數等**。key不存在時會建立並貼心的設原值爲0。IncrByFloat專門針對float。。

  4. 設置Value的安全性:SetNx命令僅當key不存在時才Set(原子性操做)。能夠用來選舉Master或作分佈式鎖:全部Client不斷嘗試使用SetNx master myName搶注Master,成功的那位不斷使用Expire刷新它的過時時間。若是Master倒掉了key就會失效,剩下的節點又會發生新一輪搶奪。SetEx, Set + Expire 的簡便寫法,p字頭版本以毫秒爲單位。

  5. 獲取:GetSet(原子性), 設置新值,返回舊值。好比一個按小時計算的計數器,能夠用GetSet獲取計數並重置爲0。這種指令在服務端作起來是舉手之勞,客戶端便方便不少。MGet/MSet/MSetNx, 一次get/set多個key。

  6. 其餘操做:Append/SetRange/GetRange/StrLen,對文本進行擴展、替換、截取和求長度,只對特定數據格式如字段定長的有用,json就沒什麼用。

  7. BitMap的用法:GetBit/SetBit/BitOp,與或非/BitCount, BitMap的玩法,好比統計今天的獨立訪問用戶數時,每一個註冊用戶都有一個offset,他今天進來的話就把他那個位設爲1,用BitCount就能夠得出今天的總人數

5- Hash(HashMap,哈希映射表)

Redis 的 Hash 實際是內部存儲的 Value 爲一個 HashMap,並提供了直接存取這個 Map 成員的接口。Hash將對象的各個屬性存入Map裏,能夠只讀取/更新對象的某些屬性。另外不一樣的模塊能夠只更新本身關心的屬性而不會互相併發覆蓋衝突。

 
 

不一樣程序經過 key(用戶 ID) + field(屬性標籤)就能夠併發操做各自關心的屬性數據
https://redis.io/commands#hash

 

實現原理

Redis Hash 對應 Value 內部實際就是一個 HashMap,實際這裏會有2種不一樣實現,** 這個 Hash 的成員比較少時 Redis 爲了節省內存會採用相似一維數組的方式來緊湊存儲,而不會採用真正的 HashMap 結構,對應的 value redisObject 的 encoding 爲 zipmap,當成員數量增大時會自動轉成真正的 HashMap,此時 encoding 爲 ht**。通常操做複雜度是O(1),要同時操做多個field時就是O(N),N是field的數量。

經常使用操做
  1. O(1)操做:hget、hset等等
  2. O(n)操做:hgetallRedis 能夠直接取到所有的屬性數據,可是若是內部 Map 的成員不少,那麼涉及到遍歷整個內部 Map 的操做,因爲 Redis 單線程模型的緣故,這個遍歷操做可能會比較耗時,而另其它客戶端的請求徹底不響應,這點須要格外注意。
     
     

6- List(雙向鏈表)

Redis list 的應用場景很是多,也是 Redis 最重要的數據結構之一,好比 twitter 的關注列表,粉絲列表等均可以用 Redis 的 list 結構來實現,還提供了生產者消費者阻塞模式(B開頭的命令),經常使用於任務隊列,消息隊列等

實現方式

Redis list 的實現爲一個雙向鏈表,便可以支持反向查找和遍歷,更方便操做,不過帶來了部分額外的內存開銷,Redis 內部的不少實現,包括髮送緩衝隊列等也都是用的這個數據結構。

用做消息隊列中防止數據丟失的解決方法

若是消費者把job給Pop走了又沒處理完就死機了怎麼辦?

  1. 消息生產者保證不丟失

加多一個sorted set,分發的時候同時發到list與sorted set,以分發時間爲score,用戶把job作完了以後要用ZREM消掉sorted set裏的job,而且定時從sorted set中取出超時沒有完成的任務,從新放回list。 若是發生重複能夠在sorted set中在查詢確認一遍,或者將消息的消費接口設計成冪等性。

  1. 消息消費者保證不丟失

爲每一個worker多加一個的list,彈出任務時改用RPopLPush,將job同時放到worker本身的list中,完成時用LREM消掉。若是集羣管理(如zookeeper)發現worker已經掛掉,就將worker的list內容從新放回主list

經常使用操做
  1. 複合操做:RPopLPush/ BRPopLPush,彈出來返回給client的同時,把本身又推入另外一個list,是原子操做。


     
     
  2. 按值進行的操做:LRem(按值刪除元素)、LInsert(插在某個值的元素的先後),複雜度是O(N),N是List長度,由於List的值不惟一,因此要遍歷所有元素,而Set只要O(log(N))。


     
     
  3. 按下表進行操做(下標從0開始,隊列從左到右算,下標爲負數時則從右到左,-1爲右端第一個元素)

時間複雜度爲O(N)

  • LSet :按下標設置元素值。(N爲List的長度)
  • LIndex:按下標返回元素。(N爲index的值)
  • LTrim:限制List的大小,保留指定範圍的元素。(N是移除元素的個數)


     
     
  • LRange:返回列表內指定範圍下標的元素,經常使用於分頁。(N = start+range)


     
     

7- set(HashSet)

Set就是HashSet,能夠將重複的元素隨便放入而Set會自動去重,底層實現也是HashMap,而且 set 提供了判斷某個成員是否在一個 set 集合內的重要接口,這個也是 list 所不能提供的。

實現原理

set 的內部實現是一個 value 永遠爲 null 的 HashMap,實際就是經過計算 hash 的方式來快速排重的,這也是 set 能提供判斷一個成員是否在集合內的緣由。

經常使用操做
  1. 增刪改查:SAdd/SRem/SIsMember/SCard/SMove/SMembers等等。除了SMembers都是O(1)。

  2. 集合操做:SInter/SInterStore/SUnion/SUnionStore/SDiff/SDiffStore,各類集合操做。交集運算能夠用來顯示在線好友(在線用戶 交集 好友列表),共同關注(兩個用戶的關注列表的交集)。O(N),並集和差集的N是集合大小之和,交集的N是小的那個集合的大小的2倍。

8- Sorted Set(插入有序Set集合)

set 不是自動有序的,而** sorted set 能夠經過用戶額外提供一個優先級(score)的參數來爲成員排序而且是插入有序的,即自動排序**。當你須要一個有序的而且不重複的集合列表,那麼能夠選擇 sorted set 數據結構,好比 twitter 的 public timeline 能夠以發表時間做爲 score 來存儲,這樣獲取時就是自動按時間排好序的

實現方式

內部使用 HashMap 和跳躍表(SkipList)來保證數據的存儲和有序

Sorted Set的實現是HashMap(element->score, 用於實現ZScore及判斷element是否在集合內),和SkipList(score->element,按score排序)的混合體。SkipList有點像平衡二叉樹那樣,不一樣範圍的score被分紅一層一層,每層是一個按score排序的鏈表。

經常使用操做

ZAdd/ZRem是O(log(N));ZRangeByScore/ZRemRangeByScore是O(log(N)+M),N是Set大小,M是結果/操做元素的個數。複雜度的log取對數很關鍵,可使,1000萬大小的Set,複雜度也只是幾十不到。可是,若是一次命中不少元素M很大則複雜度很高。

  1. ZRange/ZRevRange,按排序結果的範圍返回元素列表,能夠爲正數與倒數。

  2. ZRangeByScore/ZRevRangeByScore,按score的範圍返回元素,能夠爲正數與倒數。

  3. ZRemRangeByRank/ZRemRangeByScore,按排序/按score的範圍限刪除元素。

  4. ZCount,統計按score的範圍的元素個數。

  5. ZRank/ZRevRank ,顯示某個元素的正/倒序的排名。

  6. ZScore/ZIncrby,顯示元素的Score值/增長元素的Score。

  7. ZAdd(Add)/ZRem(Remove)/ZCard(Count),ZInsertStore(交集)/ZUnionStore(並集),與Set相比,少了IsMember和差集運算。

8- Redis使用與內存優化

上面的一些實現上的分析能夠看出 redis 實際上的內存管理成本很是高,即佔用了過多的內存,屬於用空間換時間。做者對這點也很是清楚,因此提供了一系列的參數和手段來控制和節省內存

建議不要開啓VM(虛擬內存)選項

VM 選項是做爲 Redis 存儲超出物理內存數據的一種數據在內存與磁盤換入換出的一個持久化策略,將嚴重地拖垮系統的運行速度,因此要關閉 VM 功能,請檢查你的 redis.conf 文件中 vm-enabled 爲 no。

設置最大內存選項

最好設置下 redis.conf 中的 maxmemory 選項,該選項是告訴 Redis 當使用了多少物理內存後就開始拒絕後續的寫入請求,該參數能很好的保護好你的 Redis 不會由於使用了過多的物理內存而致使 swap,最終嚴重影響性能甚至崩潰。

通常還須要設置內存飽和回收策略

  1. volatile-lru:從已設置過時時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
  2. volatile-ttl:從已設置過時時間的數據集(server.db[i].expires)中挑選將要過時的數據淘汰
  3. volatile-random:從已設置過時時間的數據集(server.db[i].expires)中任意選擇數據淘汰
  4. allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
  5. allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
  6. no-enviction(驅逐):禁止驅逐數據
控制內存使用的參數

Redis 爲不一樣數據類型分別提供了一組參數來控制內存使用

  1. Hash

redis.conf 配置文件中下面2項

  • **hash-max-zipmap-entries 64 **

含義是當 value 這個 Map 內部不超過多少個成員時會採用線性緊湊格式存儲,默認是64,即 value 內部有64個如下的成員就是使用線性緊湊存儲zipmap,超過該值自動轉成真正的 HashMap(ht)。

  • hash-max-zipmap-value 512

hash-max-zipmap-value 含義是當 value 這個 Map 內部的每一個成員值長度不超過
多少字節就會採用線性緊湊存儲zipmap來節省空間。

以上2個條件任意一個條件超過設置值都會轉換成真正的 HashMap,也就不會再節省內存了,可是也不是越大越好(空間和查改效率須要根據實際狀況來權衡)

  1. List
  • list-max-ziplist-entries 512
    list 數據類型多少節點如下會採用去指針的緊湊存儲格式ziplist
  • list-max-ziplist-value 64
    list 數據類型節點值大小小於多少字節會採用緊湊存儲格式ziplist。
  1. Set
  • set-max-intset-entries 512
    set 數據類型內部數據若是所有是數值型,且包含多少節點如下會採用緊湊格式存儲
Redis內部的優化
  1. Redis 內部實現沒有對內存分配方面作過多的優化,在必定程度上會存在內存碎片,不過大多數狀況下這個不會成爲 Redis 的性能瓶頸。

  2. Redis 緩存了必定範圍的常量數字做爲資源共享,在不少數據類型是數值型則能極大減小內存開銷,默認爲1-10000,能夠從新編譯配置修改源代碼中的一行宏定義 REDIS_SHARED_INTEGERS。

9- 總結

  1. 根據業務須要選擇合適的數據類型,併爲不一樣的應用場景設置相應的緊湊存儲參數。

  2. 當業務場景不須要數據持久化時,關閉全部的持久化方式能夠得到最佳的性能以及最大的內存使用量。

  3. 若是須要使用持久化,根據是否能夠容忍重啓丟失部分數據在快照方式與語句追加方式之間選擇其一,不要使用虛擬內存以及 diskstore 方式。

  4. 不要讓你的 Redis 所在機器物理內存使用超過實際內存總量的3/5

Redis 的持久化使用了 Buffer IO ,所謂 Buffer IO 是指 Redis 對持久化文件的寫入和讀取操做都會使用物理內存的 Page Cache,而當 Redis 的持久化文件過大操做系統會進行Swap,這時你的系統就會有內存還有餘量可是系統不穩定或者崩潰的風險。

參考連接
Java消息隊列任務的平滑關閉
redis消息隊列性能測試及知識點整理

做者:zhanglbjames 連接:https://www.jianshu.com/p/f09480c05e42 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索