redis基礎資料型別--String
String型別的使用
前言
要想學好redis,那麼string這個基本資料型別就一定要掌握,它是最基礎的資料型別,如果是噹噹使用redis的string型別進行資料操作的話,那麼使用redis就好像使用memcache(基於php框架開發的開源的分散式記憶體物件快取系統)。但是我們知道redis的資料結構不只有string這個一個,後續的課程會繼續講解redis的其他資料型別。
介紹
redis的String型別是包含了許多種型別的特殊型別,並且是二進位制安全的,我們可以使用redis的string型別來儲存比如序列化後的物件、比如一張圖片進行二進位制儲存(值的長度不能超過512MB)、比如是一個字串、數值等等。
使用
redis像其他開源專案一樣,也提供命令列的方式,方便我們進行操作,並且提供了redis的客戶端,我們可以在redis客戶端上輸入命令進行操作。下面講解string的各種命令:
(1)、append
如果key已經存在,並且值為字串,那麼這個命令會把value追加到原來值(value)的結尾。 如果key不存在,那麼它將首先建立一個空字串的key,再執行追加操作,這種情況
語法:append key value(key為要追加的key,value是追加的值)
返回值(int型別,為追加後的字串長度)
例子:
redis> exists mykey (integer) 0 redis> append mykey "Hello" (integer) 5 redis> append mykey " World" (integer) 11 redis> get mykey "Hello World" redis>
(2)、bitcount
bitcount是統計字串中被設定為1的bit數,一般情況下,給定的整個字串都會被進行計數,通過指定額外的 start 或 end 引數,可以讓計數只在特定的位上進行。start 和 end 引數的設定和 GETRANGE 命令類似,都可以使用負數值:比如 -1 表示最後一個位,而 -2 表示倒數第二個位,以此類推。不存在的 key 被當成是空字串來處理,因此對一個不存在的 key 進行 BITCOUNT 操作,結果為 0 。
語法:bitcount key [start end],指定額外的start或者end引數,讓技術只在特定的位上進行。
返回值(int型別,為被設定為 1 的位的數量。)
例子:
redis> set mykey "foobar"
OK
redis> bitcount mykey
(integer) 26
redis> bitcount mykey 0 0
(integer) 4
redis> bitcount mykey 1 1
(integer) 6
redis>
模式:使用 bitmap 實現使用者上線次數統計
假設現在我們希望記錄自己網站上的使用者的上線頻率,比如說,計算使用者 A 上線了多少天,使用者 B 上線了多少天,諸如此類,以此作為資料,從而決定讓哪些使用者參加 beta 測試等活動 —— 這個模式可以使用 setbit 和 bitcount 來實現。
比如說,每當使用者在某一天上線的時候,我們就使用 setbit ,以使用者名稱作為 key ,將那天所代表的網站的上線日作為 offset 引數,並將這個 offset 上的為設定為 1 。
舉個例子,如果今天是網站上線的第 100 天,而使用者 peter 在今天閱覽過網站,那麼執行命令setbit peter 100 1 ;如果明天 peter 也繼續閱覽網站,那麼執行命令 setbit peter 101 1 ,以此類推。
當要計算 peter 總共以來的上線次數時,就使用 bitcount 命令:執行 bitcount peter ,得出的結果就是 peter 上線的總天數。
更詳細的實現可以參考博文 Fast, easy, realtime metrics using Redis bitmaps (需要翻牆)
效能:
前面的上線次數統計例子,即使執行 10 年,佔用的空間也只是每個使用者 10*365 位元位(bit),也即是每個使用者 456 位元組。對於這種大小的資料來說, bitcount 的處理速度就像 get 和 incr 這種 O(1) 複雜度的操作一樣快。
如果你的 bitmap 資料非常大,那麼可以考慮使用以下兩種方法:
- 將一個大的 bitmap 分散到不同的 key 中,作為小的 bitmap 來處理。使用 Lua 指令碼可以很方便地完成這一工作。
- 使用 bitcount 的 start 和 end 引數,每次只對所需的部分位進行計算,將位的累積工作(accumulating)放到客戶端進行,並且對結果進行快取 (caching)。
(3)、bitop
對一個或多個儲存二進位制位的字串 key 進行位元操作。
bitop命令支援 AND 、 OR 、 NOT 、 XOR 這四種操作中的任意一種引數:
- bitop and destkey key1 key2 key3 ... keyn,對一個或多個 key 求邏輯並,並將結果儲存到 destkey 。
- bitop or destkey key1 key2 key3 ... keyn,對一個或多個 key 求邏輯或,並將結果儲存到 destkey 。
- bitop xor destkey key1 key2 key3 ... keyn,對一個或多個 key 求邏輯異或,並將結果儲存到 destkey 。
- bitop not destkey key1,對給定 key 求邏輯非,並將結果儲存到 destkey 。
除了 NOT 操作之外,其他操作都可以接受一個或多個 key 作為輸入。
執行結果將始終保持到 destkey 裡面。
語法:bitop operation destkey key[key ...] (operation 表示位操作,分別有and、or、 xor、not,destkey為位操作後儲存的key,key是一個數組,是需要進行位操作的key,可以一個也可以多個)
返回值(int型別 儲存到 destkey 的字串的長度,和輸入 key 中最長的字串長度相等。)
例子:
redis> set key1 "foobar"
OK
redis> set key2 "abcdef"
OK
redis> bitop and dest key1 key2
(integer) 6
redis> get dest
"`bc`ab"
redis>
模式:使用 bitop 實現使用者上線次數統計
bitop是對bitcount命令很好的補充。不同的bitmaps進行組合操作可以獲得目標bitmap以進行人口統計操作。
Fast easy realtime metrics using Redis bitmaps這篇文章介紹了一個有趣的用例。
效能: bitop可能是一個緩慢的命令,它的時間複雜度是O(N)。 在處理長字串時應注意一下效率問題。
(4)、incr
對儲存在指定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)所帶來的額外消耗。
語法:incr key
返回值:(int型別 執行遞增操作後key
對應的值。)
例子:
redis> set mykey "10"
OK
redis> incr mykey
(integer) 11
redis> get mykey
"11"
redis>
例項:計數器
Redis的原子遞增操作最常用的使用場景是計數器。
使用思路是:每次有相關操作的時候,就向Redis伺服器傳送一個incr命令。
例如這樣一個場景:我們有一個web應用,我們想記錄每個使用者每天訪問這個網站的次數。
web應用只需要通過拼接使用者id和代表當前時間的字串作為key,每次使用者訪問這個頁面的時候對這個key執行一下incr命令。
這個場景可以有很多種擴充套件方法:
- 通過結合使用incr和expire命令,可以實現一個只記錄用戶在指定間隔時間內的訪問次數的計數器
- 客戶端可以通過 getset 命令獲取當前計數器的值並且重置為0
- 通過類似於 decr 或者 incrby 等原子遞增/遞減的命令,可以根據使用者的操作來增加或者減少某些值 比如線上遊戲,需要對使用者的遊戲分數進行實時控制,分數可能增加也可能減少。
例項:限速器
限速器是一種可以限制某些操作執行速率的特殊場景。
傳統的例子就是限制某個公共api的請求數目。
假設我們要解決如下問題:限制某個api每秒每個ip的請求次數不超過10次。
我們可以通過incr命令來實現兩種方法解決這個問題。
例項:限速器1
更加簡單和直接的實現如下:
function limit_api_call(ip):
ts=current_unix_time()
keyname = ip+":"+ts
current = get(keyname)
if current != null and current > 10 then
error "too many requests per second"
else
multi
incr(keyname,1)
expire(keyname,10)
exec
perform_api_call()
end
這種方法的基本點是每個ip每秒生成一個可以記錄請求數的計數器。
但是這些計數器每次遞增的時候都設定了10秒的過期時間,這樣在進入下一秒之後,redis會自動刪除前一秒的計數器。
注意上面虛擬碼中我們用到了 multi 和 exec 命令,將遞增操作和設定過期時間的操作放在了一個事務中, 從而保證了兩個操作的原子性。
例項: 限速器 2
另外一個實現是對每個ip只用一個單獨的計數器(不是每秒生成一個),但是需要注意避免竟態條件。 我們會對多種不同的變數進行測試。
function limit_api_call(ip):
current = get(ip)
if current != null and current > 10 then
error "too many requests per secord"
else
value = incr(ip)
if value == 1 then
expire(value,1)
end
perform_api_call()
end
上述方法的思路是,從第一個請求開始設定過期時間為1秒。如果1秒內請求數超過了10個,那麼會拋異常。
否則,計數器會清零。
上述程式碼中,可能會進入競態條件,比如客戶端在執行INCR之後,沒有成功設定EXPIRE時間。這個ip的key 會造成記憶體洩漏,直到下次有同一個ip傳送相同的請求過來。
把上述incr和expire命令寫在lua指令碼並執行eval命令可以避免上述問題(只有redis版本>=2.6才可以使用)
local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
redis.call("expire",KEYS[1],1)
end
還可以通過使用redis的list來解決上述問題避免進入競態條件。
實現程式碼更加複雜並且利用了一些redis的新的feature,可以記錄當前請求的客戶端ip地址。這個有沒有好處 取決於應用程式本身。
function limit_api_call(ip):
cuurent = llen(ip)
if current > 10 then
error "too many requests per secord"
else
if exists(ip) == false
multi
rpush(ip,ip)
expire(ip,1)
exec
else
rpushx(ip,ip)
end
perform_api_call()
end
The rpushx command only pushes the element if the key already exists.
rpushx命令會往list中插入一個元素,如果key存在的話
上述實現也可能會出現競態,比如我們在執行exists指令之後返回了false,但是另外一個客戶端建立了這個key。
(5)、decr
對key對應的數字做減1操作。
如果key不存在,那麼在操作之前,這個key對應的值會被置為0。
如果key有一個錯誤型別的value或者是一個不能表示成數字的字串,就返回錯誤。
這個操作最大支援在64位有符號的整型數字。
語法:decr key
返回值:(int型別 執行遞減操作後key
對應的值。)
例子:
redis> set mykey "10"
OK
redis> decr mykey
(integer) 9
redis> SET mykey "234293482390480948029348230948"
OK
redis> decr mykey
ERR value is not an integer or out of range
redis>
(6)、decrby
將key對應的數字減decrement。如果key不存在,操作之前,key就會被置為0。如果key的value型別錯誤或者是個不能表示成數字的字串,就返回錯誤。這個操作最多支援64位有符號的正型數字。
語法:decrby key decrement(decrement為需要減多少的數值)
返回值:(int型別 減少之後的value值。)
例子:
redis> set mykey "10"
OK
redis> decrby mykey 5
(integer) 5
redis>
(7)、get
返回key的value。如果key不存在,返回特殊值nil。如果key的value不是string,就返回錯誤,因為get命令只處理string型別的 value。
語法:get key
返回值:(key對應的value,或者nil(key不存在時))
例子:
redis> get nonexisting
(nil)
redis> set mykey "Hello"
OK
redis> get mykey
"Hello"
redis>
(8)、getbit
返回key對應的string在offset處的bit值 當offset超出了字串長度的時候,這個字串就被假定為由0位元填充的連續空間。當key不存在的時候,它就認為是一個空字串,所以offset總是超出範圍,然後value也被認為是由0位元填充的連續空間。到記憶體分配。
語法:getbit key offset
返回值:(int型別 在offset處的bit值)
例子:
redis> setbit mykey 7 1
(integer) 0
redis> getbit mykey 0
(integer) 0
redis> getbit mykey 7
(integer) 1
redis> getbit mykey 100
(integer) 0
redis>
(8)、 getrange
返回key對應的字串value的子串,這個子串是由start和end位移決定的(兩者都在string內)。可以用負的位移來表示從string尾部開始數的下標。所以-1就是最後一個字元,-2就是倒數第二個,以此類推。
這個函式處理超出範圍的請求時,都把結果限制在string內。
:getrange key start end
返回值:(string語法型別)
例子:
redis> set mykey "This is a string"
OK
redis> getrange mykey 0 3
"This"
redis> getrange mykey -3 -1
"ing"
redis> getrange mykey 0 -1
"This is a string"
redis> getrange mykey 10 100
"string"
redis>
(9)、incrby
將key對應的數字加increment。如果key不存在,操作之前,key就會被置為0。如果key的value型別錯誤或者是個不能表示成數字的字串,就返回錯誤。這個操作最多支援64位有符號的正型數字。
語法:incrby key increment
返回值:(int型別 增加之後的value值。)
例子:
redis> set mykey "10"
OK
redis> incrby mykey 5
(integer) 15
redis>
(10)、incrbyfloat
通過指定浮點數key
來增長浮點數(存放於string中)的值. 當鍵不存在時,先將其值設為0再操作.下面任一情況都會返回錯誤:
- key 包含非法值(不是一個string).
- 當前的key或者相加後的值不能解析為一個雙精度的浮點值.(超出精度範圍了)
如果操作命令成功, 相加後的值將替換原值儲存在對應的鍵值上, 並以string的型別返回. string中已存的值或者相加引數可以任意選用指數符號,但相加計算的結果會以科學計數法的格式儲存. 無論各計算的內部精度如何, 輸出精度都固定為小數點後17位.
語法:incrbyfloat key increment
返回值:(String型別,當前key增加increment後的值。)
例子:
redis> set mykey 10.50
OK
redis> incrbyfloat mykey 0.1
"10.6"
redis> set mykey 5.0e3
OK
redis> incrbyfloat mykey 2.0e2
"5200"
redis>
(11)、mget
返回所有指定的key的value。對於每個不對應string或者不存在的key,都返回特殊值nil
。正因為此,這個操作從來不會失敗。
語法:mget key[key...] (key為陣列,可以一個或者多個)
返回值:(list連結串列結構 指定的key對應的values的list)
例子:
redis> set key1 "Hello"
OK
redis> set key2 "World"
OK
redis> mget key1 key2 nonexisting
1) "Hello"
2) "World"
3) (nil)
redis>
(12)、mset
可以設定多個key和value,當使用mset命令設定已經存在value的key時,mset命令會用新的value去取代原來的value,mset是原子的,所以所有給定的keys是一次性set的。
語法:mset key value[key value ...]
返回值:總是ok,因為是原子性的,不會失敗
例子:
redis> mset key1 "Hello" key2 "World"
OK
redis> get key1
"Hello"
redis> get key2
"World"
redis>
(13)、msetnx
可以設定多個key和value,當使用msetnx設定一個已經存在的key時,那麼操作是不會成功的。對應給定的keys到他們相應的values上。只要有一個key已經存在,msetnx一個操作都不會執行。 由於這種特性,msetnx可以實現要麼所有的操作都成功,要麼一個都不執行,這樣可以用來設定不同的key,來表示一個唯一的物件的不同欄位。msetnx是原子的,所以所有給定的keys是一次性set的。
語法:msetnx key value[key value...]
返回值:
- 1 如果所有的key被set
- 0 如果沒有key被set(至少其中有一個key是存在的)
例子:
redis> msetnx key1 "Hello" key2 "there"
(integer) 1
redis> msetnx key2 "there" key3 "world"
(integer) 0
redis> mset key1 key2 key3
1) "Hello"
2) "there"
3) (nil)
redis>
(14)、psetnx
設定key對應字串value,並且設定key在給定的seconds時間之後超時過期(毫秒),可以使用pttl命令檢視時間
語法:psetnx key milliseconds value
返回值:總是ok
例子:
redis> psetnx mykey 1000 "Hello"
OK
redis> pttl mykey
(integer) 999
redis> get mykey
"Hello"
redis>
(15)、set
將鍵set設定為指定的“字串”值。如果 key 已經儲存了一個值,那麼這個操作會直接覆蓋原來的值,並且忽略原始型別。當set命令執行成功之後,之前設定的過期時間都將失效。
語法:set key value [ex seconds] [px milliseconds] [xx] [nx] ex
- exseconds – 設定鍵key的過期時間,單位時秒
- px milliseconds – 設定鍵key的過期時間,單位時毫秒
- nx– 只有鍵key不存在的時候才會設定key的值
- xx– 只有鍵key存在的時候才會設定key的值
返回值:如果set命令正常執行那麼回返回OK
,否則如果加了nx 或者 xx選項,但是沒有設定條件。那麼會返回nil。
例子:
redis> set mykey "Hello"
OK
redis> get mykey
"Hello"
redis>