Redis對象——字元串

  • 2019 年 10 月 15 日
  • 筆記

文章導航-readme

前言

    上一篇文章Redis之對象篇——Redis對象系統簡介簡單介紹了Redis的對象系統。Redis使用對象來表示資料庫中的鍵和值每個對象都由一個redisObject結構表示,該結構中和保存數據有關的三個屬性分別是type屬性、 encoding屬性和ptr屬性。

typedef struct redisObiect{      //類型      unsigned type:4;      //編碼      unsigned encoding:4;      //指向底層數據結構的指針      void *ptr;  }

    字元串對象是 Redis 中最基本的數據類型,也是我們工作中最常用的數據類型。redis中的鍵都是字元串對象,而且其他幾種數據結構都是在字元串對象基礎上構建的。字元串對象的值實際可以是字元串、數字、甚至是二進位,最大不能超過512MB 。

一、內部實現

    Redis字元串對象底層的數據結構實現主要是int和簡單動態字元串SDS(這個字元串,和我們認識的C字元串不太一樣,了解具體請看圖解Redis之數據結構篇——簡單動態字元串SDS),其通過不同的編碼方式映射到不同的數據結構。

字元串對象的內部編碼有3種 :intrawembstr。Redis會根據當前值的類型和長度來決定使用哪種編碼來實現。

  1. 如果一個字元串對象保存的是整數值,並且這個整數值可以用long類型來表示,那麼字元串對象會將整數值保存在字元串對象結構的ptr屬性裡面(將void*轉換成1ong),並將字元串對象的編碼設置為int

  2. 如果字元串對象保存的是一個字元串值,並且這個字元串值的長度大於32位元組,那麼字元串對象將使用一個簡單動態字元串(SDS)來保存這個字元串值,並將對象的編碼設置為raw

  3. 如果字元串對象保存的是一個字元串值,並且這個字元申值的長度小於等於32位元組,那麼字元串對象將使用一個簡單動態字元串(SDS)來保存這個字元串值,並將對象的編碼設置為embstr

    embstr編碼是專門用於保存短字元串的一種優化編碼方式,我們可以看到embstrraw編碼都會使用SDS來保存值,但不同之處在於embstr會通過一次記憶體分配函數來分配一塊連續的記憶體空間來保存redisObjectSDS。而raw編碼會通過調用兩次記憶體分配函數來分別分配兩塊空間來保存redisObjectSDS。Redis這樣做會有很多好處。

  • embstr編碼將創建字元串對象所需的記憶體分配次數從raw編碼的兩次降低為一次
  • 釋放 embstr編碼的字元串對象同樣只需要調用一次記憶體釋放函數
  • 因為embstr編碼的字元串對象的所有數據都保存在一塊連續的記憶體裡面可以更好的利用CPU快取提升性能。

    Redis中根據數據類型和長度來使用不同的編碼和數據結構存儲存在於Redis中的每一種對象類型上。其這種小細節上的優化令我嘆服不止,後續我們會看到Redis中到處都是這種記憶體與性能上的小細節優化!

二、常用命令

    字元串類型的命令比較多 ,我們先來了解幾個日常開發中常用的。

1 設置值

redis> set testKey testValue  OK
set key value [ex seconds] [px milliseconds] [nx|xx]
  • ex seconds:為鍵設置秒級過期時間。

    如命令:set username xiaoming ex 100相當於執行下面兩條命令

    SET username xiaoming  EXPIRE username 100

    set key value [ex seconds]操作是原子性的,相比連續執行上面兩個命令,它更快。

  • px milliseconds:為鍵設置毫秒級過期時間。

  • nx:鍵必須不存在,才可以設置成功,用於添加。

    //mykey 不存在  redis> set mykey "Hello" nx  (integer) 1  //mykey 已經存在  redis> set mykey "World" nx  (integer) 0  redis> GET mykey  "Hello"  redis> 

    由於set key value nx同樣是原子性的操作,因此可以作為分散式鎖的一種實現方案。

  • xx:與nx相反,鍵必須存在,才可以設置成功,用於更新

以上幾個命令的替代命令是SETNX, SETEX,PSETEX,但是由於SET命令加上選項已經可以完全取代SETNX, SETEX,PSETEX的功能,所以在將來的版本中,redis可能會不推薦使用並且最終拋棄這幾個命令。

2 獲取值

get key

返回keyvalue。如果key不存在,返回特殊值nil。如果keyvalue不是string,就返回錯誤,因為GET只處理string類型的values

redis> GET nokey  (nil)  redis> SET mykey "Hello World"  OK  redis> GET mykey  "Hello World"

3 批量設置值

    由於Redis目前的應用非常廣泛,目前大多數公司對Redis的調用基本都會有一層自己的封裝,看起來就像是在調用本地快取一樣,對於批量性的操作,一些對於Redis不太了解的可能就像使用本地快取一樣進行循環set。這樣對性能是有很大的損耗的。實際上Redis提供了批量操作的命令。

MSET key value [key value ...]

對應給定的keys到他們相應的values上。MSET會用新的value替換已經存在的value,就像普通的SET命令一樣。如果不想覆蓋已經存在的values,可以使用MSETNX key value [key value ...]

注意:MSET是原子的,所以所有給定的keys是一次性set的。客戶端不可能看到這種一部分keys被更新而另外的沒有改變的情況。

redis> MSET key1 "Hello" key2 "World"  OK  redis> GET key1  "Hello"  redis> GET key2  "World"

4 批量獲取值

MGET key [key ...]

結果是按照傳入鍵的順序返回所有指定的key的value。對於每個不對應string或者不存在的key,都返回特殊值nil。

redis> SET key1 "Hello"  OK  redis> SET key2 "World"  OK  redis> MGET key1 key2 nokey  1) "Hello"  2) "World"  3) (nil)

    Redis可以支撐每秒數萬的讀寫操作,但是這指的是Redis服務端的處理能力,對於客戶端來說,一次命令除了命令時間還是有網路時間,如n次get操作

使用get命令

n次get時間 = n次網路時間 + n次命令時間

mget操作

n次get時間 = 1次網路時間 + n次命令時間

而在實際開發中因為Redis的處理能力已經足夠高,性能瓶頸的因素往往是網路。

學會使用批量操作,有助於提高效率,但是要掌握一個平衡的度,每次批量操作所發送的命令數並不是無節制的由於Redis是單執行緒架構,如果數量過多可能造成Redis阻塞或者網路擁塞。

5 計數

incr key

對存儲在指定key的數值執行原子的加1操作。

如果指定的key不存在,那麼在執行incr操作之前,會先將它的值設定為0

如果指定的key中存儲的值不是字元串類型(fix:)或者存儲的字元串類型不能表示為一個整數,

那麼執行這個命令時伺服器會返回一個錯誤(eq:(error) ERR value is not an integer or out of range)。

這個操作僅限於64位的有符號整型數據。

注意: 由於redis並沒有一個明確的類型來表示整型數據,所以這個操作是一個字元串操作。

執行這個操作的時候,key對應存儲的字元串被解析為10進位的64位有符號整型數據

事實上,Redis 內部採用整數形式(Integer representation)來存儲對應的整數值,所以對該類字元串值實際上是用整數保存,也就不存在存儲整數的字元串表示(String representation)所帶來的額外消耗。

redis> SET mykey "1"  OK  redis> INCR mykey  (integer) 2  redis> GET mykey  "3"  redis> 

除了incr命令, Redis提供了decr(自減)incrby(自增指定數字)decrby(自減指定數字)incrbyfloat(自增浮點數)

decr key  incrby key increment  decrby key decrement  incrbyfloat key increment

6 其它

    對於常用的redis字元串命令和一些其它的命令我們列一個表格以便來更直觀的看到。

命令 描述 時間複雜度
set key value [ex seconds] [px milliseconds] [nx|xx] 設置值 O(1)
get key 獲取值 O(1)
del key [key ...] 刪除key O(N)(N是鍵的個數)
mset key [key value ...] 批量設置值 O(N)(N是鍵的個數)
mget key [key ...] 批量獲取值 O(N)(N是鍵的個數)
incr key 將 key 中儲存的數字值增一 O(1)
decr key 將 key 中儲存的數字值減一 O(1)
incrby key increment 將 key 所儲存的值加上給定的增量值(increment) O(1)
decrby key increment key 所儲存的值減去給定的減量值(decrement) O(1)
incrbyfloat key increment 將 key 所儲存的值加上給定的浮點增量值(increment) O(1)
append key value 如果 key 已經存在並且是一個字元串, APPEND 命令將指定的 value 追加到該 key 原來值(value)的末尾 O(1)
strlen key 返回 key 所儲存的字元串值的長度。 O(1)
setrange key offset value 用 value 參數覆寫給定 key 所儲存的字元串值,從偏移量 offset 開始 O(1)
getrange key start end 返回 key 中字元串值的子字元 O(N)(N是字元串的長度)

三、常用場景

    reids字元串的使用場景應該是最為廣泛的,甚至有些對redis其它幾種對象不太熟悉的人,基本所有場景都會使用字元串(序列化一下直接扔進去)。在眾多的使用場景中總結一下大概分以下幾種。

1. 作為快取層

    如上圖,Redis經常作為快取層,來快取一些熱點數據。來加速讀寫性能從而降低後端的壓力。一般在讀取數據的時候會先從Redis中讀取,如果Redis中沒有,再從資料庫中讀取。在Redis作為快取層使用的時候,必須注意一些問題,如:快取穿透、雪崩以及快取更新問題……

2. 計數器限速器分散式系統ID

    計數器限速器分散式ID等主要是利用Redis字元串自增自減的特性。

  • 計數器:經常可以被用來做計數器,如微博的評論數、點贊數、分享數,抖音作品的收藏數,京東商品的銷售量、評價數等。
  • 限速器:如驗證碼介面訪問頻率限制,用戶登陸時需要讓用戶輸入手機驗證碼,從而確定是否是用戶本人,但是為了簡訊介面不被頻繁訪問,會限制用戶每分鐘獲取驗證碼的頻率,例如一分鐘不能超過5次。
  • 分散式ID:由於Redis自增自減的操作是原子性的因此也經常在分散式系統中用來生成唯一的訂單號、序列號等。

3. 分散式系統共享session

    通常在單體系統中,Web服務將會用戶的Session資訊(例如用戶登錄資訊)保存在自己的伺服器中。但是在分散式系統中,這樣做會有問題。因為分散式系統通常有很多個服務,每個服務又會同時部署在多台機器上,通過負載均衡機制將將用戶的訪問均衡到不同伺服器上。這個時候用戶的請求可能分發到不同的伺服器上,從而導致用戶登錄保存Session是在一台伺服器上,而讀取Session是在另一台伺服器上因此會讀不到Session。

    這種問題通常的做法是把Session存到一個公共的地方,讓每個Web服務,都去這個公共的地方存取Session。而Redis就可以是這個公共的地方。(資料庫、memecache等都可以各有優缺點)。

4. 二進位存儲

    由於Redis字元串可以存儲二進位數據的特性,因此也可以用來存儲一些二進位數據。如圖片、 音頻、 影片等。

參考

《Redis設計與實現》

《Redis開發與運維》

《Redis官方文檔》

—–END—–