[天天進步一點點]Redis筆記:經常使用的基本數據類型

Redis經常使用的基本數據類型

激勵:人人都有一個大廠的心,堅持本身的夢想,你就是世界。java

乏味:筆記很無聊,須要去品味。node

堅持:天天進步一點點,當知道的越多,才發現不知道的也越多。web

String

最基本也是最經常使用的數據類型,也被叫作Binary-safe stringsredis

  • 能夠用來存儲字符串、正數、浮點數。

操做命令

  • 批量操做(原子性數據庫

    mset key1 val1 key2 val2數組

  • 設置值,若是key存在,則不成功緩存

    setnx key安全

    說明:基於該操做能夠實現分佈式鎖,而後用del key來釋放鎖。session

    存在問題:若是del key失敗了,會致使其它節點永遠獲取不到鎖。數據結構

    解決方法:給key加上過時時間,可使用expire命令,單是這樣不是原子性操做。

    最好的辦法就是使用setnx key value [expriation EX seconds | PX milliseconds] [NX|XX]。

    用例:

    set lock 1 EX | 10 NX

  • 整數值遞增/減

    遞增:

    • incr key
    • incrby key num

    遞減:

    • decr key
    • decrby key num

    浮點數:

    • Incrbyfloat key num

    備註:這裏的num是要增長/減小的值。

  • 批量獲取

    mget key1 key2

  • 獲取長度

    strlen key

  • 字符串追加

    append key val

  • 獲取指定範圍的字符

    getrange key start end

    備註:start:開始位置, end:結束位置。

實現原理

結構圖

備註:SDS:Simple Dynamic String,redis中字符串的實現。

  • SDS結構,下邊源碼是sdshdr8, SDS又分爲:sdshdr5(2^5 = 32byte)、sdshdr8(2^8 = 256byte)、sdshdr16(2^16 = 65536byte = 64KB)、sdshdr3二、sdshdr64。

    struct __attribute__ ((__packed__)) sdshdr8 {
      uint8_t len; /* 當前字符數組的長度 */
      uint8_t alloc; /*當前字符數組總共分配的內存大小 */
      unsigned char flags; /* 當前字符數組的屬性、用來標識究竟是 sdshdr8 仍是 sdshdr16 等 */
      char buf[]; /* 字符串真正的值 */
    };
    複製代碼
  • redis爲何要用SDS來實現字符串?

    緣由:c語言自己是沒有字符串類型的,只能用字符數組char[]來實現。

    1. 使用字符數組必須先給目標變量分配足夠的空間,不然會有溢出的狀況;
    2. 若是要獲取字符數據的長度,必須遍歷整個字符數組,比較耗時(時間複雜度是O(n));
    3. 字符串長度若是發生變動,需求從新分配內存空間;
    4. c語言中字符串的尾部是以‘\0’來標示的,所以不能存儲圖片、音視頻、壓縮文件等二進制保存的內容,二進制不安全,這裏也解釋了redis爲何是 Binary-safe strings

    SDS特色:

    1. 不用擔憂內存溢出問題,會自動擴容;
    2. 內部結構存儲了字符串長度(定義了len屬性),獲取字符串長度時間複雜度是O(1)的;
    3. 經過空間預分配和惰性空間釋放來防止屢次重複分配內存;
    4. 判斷是否結束的標示是len屬性,它一樣以‘\0’結尾,是由於這樣可使用c語言中的函數庫;
數據模型

Redis是KV形式的數據庫,它是經過hashtable實現的,因此每一個鍵值對都有一個dictEntry(源碼參考:dict.h)。

typedef struct dictEntry {
 void *key; /* key 關鍵字定義 */
 union {
  void *val;
  uint64_t u64; /* value 定義 */
  int64_t s64;
  double d;
 } v;
 struct dictEntry *next; /* 指向下一個鍵值對節點 */
} dictEntry; 
複製代碼
  • key是字符串,可是redis沒有直接使用C語言中的字符數組,而是存儲在自定定義的SDS中。

  • value既不是直接做爲字符串存儲,也不是直接使用SDS,而是存儲在redisObject中。實際上五種經常使用的數據類型的任何一種都是經過redisObject來存儲的。

  • redisObject定義在src/server.h文件中。

    typedef struct redisObject {
      unsigned type:4/* 對象的類型,包括:OBJ_STRING、OBJ_LIST、OBJ_HASH、OBJ_SET、OBJ_ZSET */
      unsigned encoding:4/* 具體的數據結構 */
      unsigned lru:LRU_BITS; /* 24 位,對象最後一次被命令程序訪問的時間,與內存回收有關 */
      int refcount; /* 引用計數。當 refcount 爲 0 的時候,表示該對象已經不被任何對象引用,則能夠進行垃圾回收了*/
      void *ptr; /* 指向對象實際的數據結構 */
    } robj;
    複製代碼
內部編碼
  • int:存儲8個字節的長整型(long,2^63 - 1);

  • Embstr:表明embstr格式的SDS,存儲小於44個字節的字符串。

  • raw:存儲大於44個字節的字符串(3.2版本之前是39個字節)。

    embstr和raw的區別:

    • embstr只分配一次內存空間(由於redisObject和SDS是連續的),raw須要分配兩次內存空間(爲redisObject和SDS分別分配空間);
    • embstr相對於raw的好處是在於建立時少分配一次空間,刪除的時候少釋放一次空間,全部的數據都是連在一塊兒的,尋找方便;
    • embstr的缺點也很明顯,若是字符串的長度增長鬚要從新分配內存時,整個redisObject和SDS都須要從新分配空間,所以redis中的embstr實現爲只讀。

    int和embstr與raw之間的轉換:

    • 當int數據再也不是整數時,或者大小超過了long的範圍時會將int自動轉化爲embstr;
    • 對於embstr來講,因爲實現時只讀的,所以在對embstr對象進行修改時,會先轉化成raw後再進行修改。只要是對embstr類型的對象進行修改,修改後的對象類型必定是raw,不管是否超過了44個字節的長度限制;
    • int和embstr與raw之間轉換的過程是不可逆的,只能從小內存編碼轉換成大內存編碼(從新set的狀況除外);

    備註long的範圍爲:2^63 - 1

    redis經過這種封裝,能夠根據對象的類型動態的選擇存儲的結構和可以使用的命令,實現節省空間和優化查詢。

應用場景

  • string類型:熱點數據的緩存(例如:新聞內容、報表數據)、對象緩存、全頁緩存,能夠提高熱點數據的訪問速度。

  • 分佈式系統中的共享數據:分佈式的session、分佈式鎖、全局的ID、計數器、限流操做、位統計等。

Hash哈希

操做命令

  • 經常使用的操做:

    • hset key field val
    • hmset key field1 val1 field2 val2 field3 val3
    • hget key field
    • hmget key field1 field2
    • hkeys key
    • hvals key
    • hgetall key
  • key操做:

    • hget exists key
    • hdel key
    • hlen key

實現原理

結構示例圖
存儲類型

包含鍵值的無序散列表,val只能是字符串且不能嵌套其餘類型。

  • hash與string的區別

    1. 把全部相關的值彙集到一個key中,節省內存空間;
    2. 只使用一個key,減小key之間的衝突;
    3. 存儲的是一個完整的對象信息,減小了將對象信息分開存儲的I/O和cpu的消耗;
  • hash不適合的場景

    1. Field不能單獨設置過時時間;
    2. 須要考慮數據量分佈問題,value值很是大的時候,沒法分佈到多個節點;
    3. 沒有bit操做;
存儲原理

redis的hash自己也是一個kv的結構,相似於java中的HashMap。

外層的哈希(redis kv的實現)只用到了hashtable,當存儲hash數據類型時,通常叫作內層的哈希。

內層的哈希底層可使用兩種數據結構實現:ziplist:OBJ_ENCODING_ZIPLIST(壓縮列表),hashtable:OBJ_ENCODING_HT(哈希表)。

  • ziplist壓縮列表: 是一個通過特殊編碼的雙向鏈表,它不存儲指向上一個鏈表節點和指向下一個鏈表節點的指針,而是存儲上一個節點長度和當前節點長度,經過犧牲部分讀寫性能來換取高效的內存空間利用率,是一種時間換空間的思想。只用在字段個數少、字段值小的場景。當hash對象同時知足如下兩個條件的時候,使用ziplist編碼:
  1. 全部的鍵值對的鍵和值的字符串長度都小於等於64byte(一個英文字母一個字節);
  2. 哈希對象保存的鍵值對數量小於512個;
// src/redis.conf配置
hash-max-ziplist-value 64  //ziplist中最大能存放的值長度
hash-max-ziplist-entries 512 //ziplist中最多能存放的entry節點數量
複製代碼

一個哈希對象超過配置的閾值(鍵和值的長度大於64byte,鍵值對個數大於512個)時,會轉換成哈希表hashtable。

  • hashtable(dict):hashtable被稱爲dictionary,它是一個數組+鏈表的結構。

redis的hash默認使用的是ht[0],ht[1]不會初始化和分配空間。

哈希表dictht是用鏈地址法來解決碰撞的問題,在這種情下,哈希表的性能取決於它的大小(size屬性)和它所保存的節點的數量(used屬性)之間的比率;

  • 比率在1:1時(一個哈希表ht只存儲一個節點entry),哈希表的性能最好;
  • 若是節點數量比哈希表的大小要大不少的話(比例用ratio表示,5表示平均一個ht存儲5個entry),那麼哈希表就會退化成個多個鏈表,哈希表自己的性能優點就不存在了。在這個狀況下須要擴容。redis裏面的這種操做叫作rehash。
  • rehash的步驟:
    1. 爲字符ht[1]哈希表分配空間,這個哈希表的空間大小取決於要執行的操做和ht[0]當前包含的鍵值對的數量。(ht[1]的大小爲第一個大於等於ht[0].used*2);
    2. 將全部的ht[0]上的節點rehash到ht[1]上,從新計算hash值和索引,而後放到指定的位置;
    3. 當ht[0]所有遷移到ht[1]後,釋放ht[0]的空間,將ht[1]設置爲ht[0]表,並建立新的ht[1]爲下次rehash作準備。

應用場景

  • String:String 類型的hash均可以作。
  • 存儲對象:一個對象或一張表的數據(比string節省更多的key空間,集中管理)。

List列表

操做命令

  • 元素增減

    lpush key val

    lpush key val1 val2

    rpush key val1

    lpop key

    rpop key

    blpop key

    brpop key

  • 取值

    lindex key index

    lrange key start stop

存儲類型

存儲有序的字符串(從左到右),元素能夠重複。能夠當簡單的隊列和棧使用.

結構圖
存儲原理

3.2版本以前,數據量較小的時候用ziplist存儲,達到閾值時轉換爲linkedlist進行存儲,分別對應OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_LINKEDLIST。

3.2版本以後統一使用了quicklist來存儲。quicklist存儲了一個雙向鏈表,每一個節點都是一個ziplist。

  • quicklist

    • quicklist(快速列表)是ziplist和linkedlist的結合體。
    • quicklist.h,head和tail指向雙向鏈表的表頭和表尾。
    typedef struct quicklist {
        quicklistNode *head; /* 指向雙向列表的表頭 */
        quicklistNode *tail; /* 指向雙向列表的表尾 */
        unsigned long count;
        /* 全部的 ziplist 中一共存了多少個元素 */
        unsigned long len;
        /* 雙向鏈表的長度,node 的數量 */
        int fill : 16;
        /* fill factor for individual nodes */
        unsigned int compress : 16/* 壓縮深度,0:不壓縮; */
    } quicklist;
    複製代碼

    配置參數(redis.conf):

    • List-max-ziplist-size(fill):正數表示單個ziplist最多包含的entry個數。負數表明單個ziplist的大小,默認8k。(-1:4KB;-2:8KB;-3:16KB;-4:32KB;-5:64KB)
    • List-compress-depth(compress):壓縮深度,默認是0。(1:首尾的ziplist不壓縮;2:首尾第一第二個ziplist不壓縮,以此類推)

應用場景

  • 用戶消息的時間線(timeline)

  • 消息隊列:list提供兩個阻塞的彈出操做:blpop/brpop,能夠設置超時時間。

    • blpop:blpop key1 timeout移除並獲取列表的第一個元素,若是列表沒有元素會阻塞列表直到等待超時或發現可彈出的元素爲止;

    • brpop:brpop key1 timeout移除並獲取列表的最後一個元素,超時機制同blpop;

    • 隊列:先進先出:rpush、blpop,左頭右尾,右邊進入隊列,左邊出隊列;

    • 棧:先進後出:rpsuh和brpop

Set集合

操做命令

  • 添加一個/多個元素

    sadd key member

    sadd key member1 member2

  • 獲取全部元素

    smembers key

  • 統計元素個數

    scard key

  • 隨機獲取一個元素

    srandmember key

  • 隨機彈出一個元素

    spop key

  • 移除一個/多個

    srem key member

    srem key member1 member2

  • 查看元素是否存在

    sismember key member

存儲類型

String類型的無序集合,最大存儲數量爲2^32 - 1。

結構圖
存儲原理

redis用intset或hashtable來存儲set。若是元素都是整數類型,就用intset存儲,若是不是整數類型,就用hashtable存儲,若是元素個數超過512個,也會用hashtable存儲。

應用場景

  • 抽獎:隨機獲取元素
  • 點贊、簽到、打卡
  • 數據的標籤,數據的篩選
  • 用戶關注、推薦模型:能夠用set來取並集,差集,交集。

ZSet有序集合

操做命令

  • 添加元素

    zadd key [NX|XX] [CH] [INCR] score member [score member ...]

  • 獲取所有元素

    zrange key start stop [WITHSCORES]

    zrevrange key start stop [WITHSCORES]

  • 根據分值區間獲取元素

    zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

  • 移除元素

    zrem key member [member ...]

  • 統計元素個數

    zcard key

  • 分值遞增

    zincrby key increment member

  • 根據分值統計個數

    zcount key min max

  • 獲取元素rank

    zrank key member

  • 獲取元素score

    zscore key member

存儲類型

sorted set:有序的set,每一個元素都有一個score。若是score相同,按照key的ASCII碼排序。

結構圖
存儲原理

同時知足如下條件使用ziplist:

  1. 元素個數小於128;
  2. 全部member的長度都小於64字節;

在ziplist的內部,按照score排序遞增來存儲,插入的時候要移動以後的數據。超過閾值以後使用skiplist+dict存儲。

  • skiplist(跳躍表)

假設咱們要查找22這個值,

  1. 22首先和5比較,而後再和17比較,22比它們都大,因此繼續向後比較;
  2. 當22和27比較時,發現22小於27,則會進入下面的鏈表,開始比較;
  3. 22正好時下一個鏈表的值,這樣就順利找到了。

假如要查找25,根據上邊流程第三步發現25比22大,會繼續向後查詢,而後又比27小,說明要查詢的25在原鏈表中不存在。

在整個查詢過程當中,因爲新增長了指針,查詢的時候就不須要遍歷整個鏈表的節點,這樣查詢的次數大概減小了一半,這個就叫作跳躍表。

應用場景

  • 熱搜排行榜:例如視頻網站須要用戶上傳的視頻作排行榜,榜單維護多是多方面,按時間、按照播放量、按照得到的點贊次數等。
  • 帶權重的隊列:好比普通消息的sorce爲1,重要消息的score爲2,而後工做線程能夠選擇按score的倒序來獲取工做任務,讓重要的任務優先執行。

BitMaps

BitMaps是在字符串類型上面定義的位操做,一個字節有8個二進制位組成。

操做命令

  • 獲取value在offset處的值

    getbit key offset

  • 修改二進制數據

    setbit key offset value

  • 統計二進制位中1的個數

    bitcount key

    bitcount key [start end] //獲取start到end的位置的1

應用場景

  • 用戶訪問統計
  • 在線用戶統計
  • 布隆過濾器

其餘數據類型

  • Hyperloglogs:提供了一種不精準的基數統計方法,比較適合用來作大規模數據的去重統計。例如:網站的UV。

  • Geospatial:能夠用來保存地理位置,並做位置距離計算或者根據半徑計算位置等,例如:用redis來實現附近的人或者計算最優地圖路徑。

  • Streams:5.0版本之後推出的數據類型,支持多播的可持久化的消息隊列,用於實現發佈訂閱功能(借鑑了kafka的設計)。


相關文章
相關標籤/搜索