1. 程式人生 > >【redis】redis基礎命令學習集合

【redis】redis基礎命令學習集合

寫在前面

Redis是一個高速的記憶體資料庫,它的應用十分廣泛,可以說是服務端必學必精的東西。然而,學以致用,無用則無為。學了的東西必須反覆的去用,去實踐,方能有真知。這篇文章記錄了我在redis學習過程中的筆記、理解和實踐,僅供參考。

本章介紹redis基礎中的基礎,常用命令的使用和效果。

string

string型別是redis中最常見的型別了,通過簡單的set、get命令就可以對這個資料結構做增刪操作,應該也是redis最大眾的型別之一,存json、存自增數值、甚至快取圖片。 string的底層是redis作者自定義的一個叫SDS的struct。長下面這樣:

redis是使用c語言實現的

typedef char *sds;
// 省略
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

 

  • len 記錄了字串的長度
  • alloc 表示字串的最大容量(不包含最後多餘的那個位元組)。
  • flags 總是佔用一個位元組。其中的最低3個bit用來表示header的型別。原始碼中的多個header是用來節省記憶體空間的。

這裡有一個疑問,為什麼作者要自定義一個sds而不是直接用c語言的字串呢?

  1. 時間複雜度要求 redis的資料結構設計總是基於最優複雜度方案的,對每一個點的時間、空間複雜度要求非常高,這一點c語言的string就已經不滿足需求了,因為c自帶的字串並不會記錄自身長度資訊,所以每次獲取字串長度的時間複雜度都是o(n),所以redis作者設計SDS時,有一個len欄位,記錄了字串的長度,這樣每次獲取長度時的時間複雜度就是O(1)了。

  2. 緩衝區溢位問題 其實也是c語言不記錄本身長度帶來的問題,當拼接字串的時候,例如 hello + world 因為c不記錄長度,所以在拼接字元的時候需要手動為hello分配五個記憶體空間,然後才能+world,如果忘記分配記憶體,那麼就會產生緩衝區溢位,而redis的解決方案是在SDS中分別記錄len和alloc,表示當前字串長度和最大容量,這樣當進行字串拼接的時候api直接去判斷最大容量是否滿足,滿足就直接插入,不滿足則對 char * 做一次擴容,然後插入,減少了人為出錯的概率,並且可以對alloc適當的進行空間預先分配,減少擴容次數,例如在建立字串hello時,完全可以將alloc長度設定10,這樣在加入world時直接放進去就ok了。

  3. 實現了c語言字串的識別特性,複用了c語言自帶的字串函式 傳統的c語言使用的是n+1的char陣列來表示長度n的字串的,然後在n長度最後加上一個\0 , 所以redis的sds在設計的時候也加上了這個\0,這樣可以複用部分c語言字串的函式。

  4. 二進位制安全 c字串中的字元必須符合某種編碼,比如 ASCII 並且除了字串的末尾之外,字串裡面不能包含空字元(這裡空字元指的是空(\0)不是空格、換行之類的字元),主要是不能儲存二進位制的圖片、視訊、壓縮檔案等內容,而我們知道redis是可以用來快取圖片二進位制資料的。因為redis記錄了字元長度。c沒有記錄長度的時候遇到\0就認為讀到字元結尾了。

可以看出,c語言中字串沒有記錄長度是一個比較麻煩的事兒,如果沒有記錄長度就必須用佔位符確定字元末尾,導致二進位制不安全。如果沒有記錄長度就必須每次統計長度,導致時間複雜度陡增。如果沒有記錄長度在分割字串、拼接字串時麻煩也不少。所以---總的來說,在設計字串的時候,不要忘了記錄長度。

set命令

  • set [key] [value]

set一個key的value值,這個值可以是任意字串。例如:

set redis:demo helloRedis
> OK
get redis:demo
> "helloRedis"

 

  • set [key] [value] [NX] [EX|PX]

set還可以指定另外兩個引數 [NX] 表示 SET if Not eXists , 指定這個引數就是告訴redis,如果key不存在才set。 [EX|PX] 這個引數表示超時時間,ex表示秒數,px表示毫秒數,一般redis通用的表示時間單位是 秒

set redis:demo:nxex helloRedis NX EX 20
> OK
set redis:demo:nxex hellostring NX EX 20
> (nil) // 設定失敗

 

這裡有一個值得注意的點是,set nx是跟普通的set互通的 ,什麼意思呢? 就是:

set redis:demo:nxex a
> OK
set redis:demo:nxex b NX EX 20
> (nil) // 普通的set在第二次設定nx的時候依然會設定失敗
del redis:demo:nxex
> OK
set redis:demo:nxex a NX EX 20
> OK
set redis:demo:nxex b
> OK // 就算是nx設定的值,在普通set下依然會成功覆蓋,並且丟失nx和ex的作用

 

  • mset [key] [value] [key] [value] ...

批量設定key value,可以批量設定一堆key,並且它是原子的,也就是這些key要麼全部成功,要麼全部失敗.

請注意,mset是不可以指定過期時間和nx的,如果你希望批量設定key並且有過期時間,那麼你最好自己寫lua指令碼來解決

mset a 1 b 2 c 3 NX EX 20
> (error) ERR wrong number of arguments for MSET

 

  • getset [key] [value]

set之前先get,返回set之前的值

set redis:getset:demo hello
> ok
getset redis:getset:demo world
> "hello"
get redis:getset:demo
> "world"

 

ps這個命令一般用來檢查set之前的值是否正常 注意這個也不能加nx和ex等屬性

get 命令

  • get [key]

獲取一個字串型別的key的值,如果鍵 key 不存在, 那麼返回特殊值 nil ; 否則, 返回鍵 key 的值。

set redis:get:demo hello
> ok
get redis:get:demo
> "hello"
del redis:get:demo
> (integer) 1
get redis:get:demo
> (nil)

 

  • strlen [key]

獲取key字串的長度

set redis:get:demo hello
> ok
strlen redis:get:demo
>  (integer) 5

 

  • mget [key] [key] ...

批量獲取key的值,返回一個list結構

mset a 1 b 2
> ok
mget a b
>  (1) "1" (2) "2"

 

操作命令

  • append [key] [value]

這個命令就是用來拼接字串的

set redis:append:demo hello
> ok
append redis:append:demo world
>  (integer) 10 // 返回了append之後的字串的總長度,也就是上面說的sds中的len欄位,這時候這個key的free也已經被擴容
get redis:append:demo
> hello world

 

注意,當key不存在,append命令依然會成功,並且會當作key是一個字串來拼接

integer

在redis中的integer型別是儲存為字串物件,通過編碼的不同來表示不同的型別

set redis:int:demo 1
> OK
type redis:int:demo
> string // type依然是string
object encoding redis:int:demo
> "int" // 但是編碼現在是int

 

這裡也有一個注意的點,就是redis是不支援任意小數點的,例如你set a 0.5會被儲存為embstr編碼,這時候對它使用incr和decr會報錯

  • incr [key]

將key自增1

set redis:int:demo 1
> OK
incr redis:int:demo
> (integer) 2
set redis:int:demo 0.5
> OK
incr redis:int:demo
> (error) ERR value is not an integer or out of range

 

  • decr [key]

將key自減1 是可以減到負數的

set redis:int:demo 1
> OK
decr redis:int:demo
> (integer) 0
decr redis:int:demo
> (integer) -1

 

  • incrby [key] [integer]

將key自增指定的數字

set redis:int:demo 1
> OK
incrby redis:int:demo 2
> (integer) 3

 

  • decrby [key] [integer]

將key自減指定的數字

set redis:int:demo 1
> OK
decrby redis:int:demo 2
> (integer) -1

 

有趣的實驗

用decrby減去-1會是加法的效果嗎?

set redis:int:demo 1
> OK
decrby redis:int:demo -2
> (integer) 3

 

答案是會增加。

 

hash

hash從原始碼上看,底層在redis中其實叫dict(字典)

看一個插入函式

dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing) // addraw
{
    long index;
    dictEntry *entry;
    dictht *ht;

    if (dictIsRehashing(d)) _dictRehashStep(d); // 判斷是否正在rehash

    /* Get the index of the new element, or -1 if
     * the element already exists. */
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1) // 通過hash演算法,得到key的hash值,如果是-1則返回null
        return NULL;

    /* Allocate the memory and store the new entry.
     * Insert the element in top, with the assumption that in a database
     * system it is more likely that recently added entries are accessed
     * more frequently. */
     // 判斷是否正在rehash 將元素插入到頂部
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0]; 
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

    /* Set the hash entry fields. */
    dictSetKey(d, entry, key);
    return entry;
}

 

字典和hash表的實現都大同小異 可以看到基本上原理是使用hash演算法加桶(table),通過拉鍊法解決hash衝突,當每個槽位的平均容積大於1:1觸發rehash等操作。

set命令

  • hset [key] [field] [value]

將雜湊表 hash 中一個key的 field 的值設定為 value 。 如果給定的雜湊表key並不存在, 那麼一個新的雜湊表key將被建立並執行 HSET 操作。 如果域 field 已經存在於雜湊表中, 那麼它的舊值將被新值 value 覆蓋。

hset redis:hash:demo com redis
> (integer) 1
hset redis:hash:demo com java // 設定同樣的field將更新field 但返回是0
> (integer) 0
hget redis:hash:demo com
> "java"

 

  • hmset [key] [field] [value] ...

批量設定 hash 中一個key的field值為value 如果不存在,則新建再插入。

hmset redis:hash:demo com redis lan java
> OK // 這裡就不再是返回integer了,而是返回了ok
hget redis:hash:demo com
> "redis"

 

  • hsetnx [key] [field] [value]

這個命令與string中的nx引數是一樣的行為,即只有當field不存在key上時,field的設定才生效,否則set失敗 特別注意,這裡第二次nx設定時返回的既不是null也不是報錯,而是返回了0,這裡比較坑一點,所以要在hash中使用hsetnx,你可以嘗試使用lua指令碼實現

hsetnx redis:hash:demo com redis
> (integer) 1
hsetnx redis:hash:demo com java
> (integer) 0 // 既不是null也不是報錯
hget redis:hash:demo com
> "redis" // 第二次設定未生效

 

get命令

  • hget [key] [field]

get一個key的field的值,key或field不存在時都返回為nil

hset redis:hash:demo com redis
> (integer) 1
hget redis:hash:demo com
> "redis"
hget redis:hash:demo empty
> (nil)

 

  • hmget [key] [field1] [field2] ...

批量獲取field的值,這個值返回的是一個list

hmset redis:hash:demo com redis lan java
> OK
hmget redis:hash:demo com lan
> 1) "redis"
> 2) "java"

 

  • hlen [key]...

獲取key中的field數量

hmset redis:hash:demo com redis lan java
> OK
hlen redis:hash:demo
> (integer) 2

 

  • hkeys [key]

獲取key中的所有field的key,返回的是一個list

hmset redis:hash:demo com redis lan java
> OK
hkeys redis:hash:demo
> 1) "com"
> 2) "lan"

 

  • hvals [key]

獲取key中的所有field的value,返回的是一個list

hmset redis:hash:demo com redis lan java
> OK
hvals redis:hash:demo
> 1) "redis"
> 2) "java"

 

  • hgetall [key]

獲取key中的所有的東西,返回的是一個list,按 field,value,field,value的順序排列

hmset redis:hash:demo com redis lan java
> OK
hgetall redis:hash:demo
> 1) "com"
> 2) "redis"
> 3) "lan"
> 4) "java"

 

  • hexists [key] [field]

判斷key中的field是否存在, 返回integer,1表示存在 ,0 表示不存在

hmset redis:hash:demo com redis lan java
> OK
hexists redis:hash:demo com
> (integer) 1 // 1表示存在

 

操作命令

  • hincrby [key] [field] [integer]

與string的incrby表現一致,將key中的field自增一個integer值, 字元和帶小數點不可用

hset redis:hash:demo inta 1
> (integer) 1
hincrby redis:hash:demo inta 2
> (integer) 3
// 同樣的,可以給定一個負數,這樣就變成自減了
hincrby redis:hash:demo inta -2
> (integer) 1

 

 

list

 

基礎命令

  • lpush [key] [value1] [value2] ...

將多個value插入一個key,這裡注意lpush和rpush的區別,lpush是從list的左邊插入資料,rpush則是從右邊。

rpush redis:list:demo 1 2 3
> (integer) 3
// 使用lrange查詢
lrange redis:list:demo 0 -1
> 1) "3"
> 2) "2"
> 3) "1"   // 這裡對應的值是從左往右插入的

 

  • rpush [key] [value1] [value2] ...

將多個value插入一個key,這裡注意lpush和rpush的區別,lpush是從list的左邊插入資料,rpush則是從右邊。

lpush redis:list:demo 1 2 3
> (integer) 3
// 使用lrange查詢
lrange redis:list:demo 0 -1
> 1) "1"
> 2) "2"
> 3) "3"   // 這裡對應的值是從右往左插入的

 

注意lpush和rpush都是在key不存在的時候,自動建立一個型別list的key,而當這個key已存在但型別不是list時,命令報錯

del redis:list:demo // 刪掉確保不存在
> (integer) n
type redis:list:demo
> none
lpush redis:list:demo 1 2 3
> (integer) 3
type redis:list:demo
> list // 自動建立了key並且型別是list
set redis:string:demo hello
> OK
lpush redis:string:demo 1 2 3
> (error) WRONGTYPE Operation against a key holding the wrong kind of value // key已經存在了但不是list型別

 

  • lrange [key] [start] [end]

讀取一個list,從start下標開始end下標結束,end可以設定為負數

lpush redis:list:demo 1 2 3
> (integer) 3
lrange redis:list:demo 0 1
> 1) 3
> 2) 2
lrange redis:list:demo 0 -1
> 1) "3"
> 2) "2"
> 3) "1"
lrange redis:list:demo 0 -2
> 1) "3"
> 2) "2"

 

  • lpushx [key] [value]

將單個value插入一個型別為list且必須存在的key 如果key不存在,返回0,並不會報錯,lpushx是從list的左邊插入資料,rpushx則是從右邊。

lpushx redis:list:demo 4
> (integer) 4
lpushx empty:key 1
> (integer) 0

 

  • rpushx [key] [value]

將單個value插入一個型別為list且必須存在的key 如果key不存在,返回0,並不會報錯,lpushx是從list的左邊插入資料,rpushx則是從右邊。

rpushx redis:list:demo 5
> (integer) 5
rpushx empty:key 1
> (integer) 0

 

  • rpoplpush [source list] [destination list]

rpoplpush一個命令同時有兩個動作,而且是原子操作,有兩個引數

  1. 將列表 source 中的最後一個元素(最右邊的元素)彈出,並返回給客戶端。
  2. 將 source 彈出的元素插入到列表 destination ,作為 destination 列表的的頭元素(也就是最左邊的元素)

簡單來說就是從list:a取出一個元素丟到list:b

例如 list:a = 1 2 3 list:b = 4 5 6

執行rpoplpush a b 之後:

list:a = 1 2 list:b = 3 4 5 6

返回客戶端被操作的數 3

rpush list:a 1 2 3
> (integer) 3
rpush list:b 4 5 6
> (integer) 3
rpoplpush list:a list:b
> "3" // 返回客戶端被操作的數
// 檢視執行後的情況
lrange list:a 0 -1
> 1) "1"
> 2) "2"
lrange list:b 0 -1
> 1) "3"
> 2) "4"
> 3) "5"
> 4) "6"

 

  • lindex [key] [index]

這個命令簡單實用,獲取key的index下標的元素,不存在返回nil

lindex redis:list:demo 0
> "4"
lindex redis:list:demo 999
> (nil)

 

  • lset [key] [index] [value]

直接設定key的index的value

lset redis:list:demo 0 5
> OK
lindex redis:list:demo 0
> "5"

 

佇列和棧

因為list提供的命令的便利性和多樣性,可以實現很多種資料結構,用的最多的就是佇列和棧兩個地方了,通過不同的方法分支成各種不同型別的佇列,例如雙端佇列,優先順序佇列等。

  • lpop [key] [timeout]

移除並返回列表 key 的左邊第一個元素,當 key 不存在時,返回 nil。

del redis:list:demo
> (integer) n
lpush redis:list:demo 1 2 3
> (integer) 3
lpop redis:list:demo
> "3" // 從左邊取出的數
lrange redis:list:demo 0 -1
> 1) "2" // 刪掉了最左邊的3
> 2) "1"

 

  • rpop [key][timeout]

移除並返回列表 key 的右邊第一個元素,當 key 不存在時,返回 nil。

del redis:list:demo
> (integer) n
lpush redis:list:demo 1 2 3
> (integer) 3
rpop redis:list:demo
> "1" // 從右邊取出的數
lrange redis:list:demo 0 -1
> 1) "3" // 刪掉了最右邊的1
> 2) "2"

 

  • blpop [key] [key ...] [timeout]

阻塞式的lpop,它可以設定多個key和一個timeout,將在這多個key裡面選擇一個列表不為空的key,lpop一個值出來,timeout可以指定一個超時時間,超過將會斷開連結。

什麼是阻塞式呢?

就是說這個操作是需要等待的,可以理解為下面的虛擬碼:

while ((n = list.lpop()) != null) {
    return n;
}

 

就是說如果list的lpop取出不為null時就立刻返回,否則就一直迴圈了。

如果timeout指定為0則表示沒有超時時間,一直等待

下面的示例請開啟兩個終端視窗

// terminal a
lpush redis:list:demo 1 2 3
> (integer) 3
blpop redis:list:demo 0 // 0 表示一直等待
> "3"
blpop redis:list:demo 0 // 0 表示一直等待
> "2"
blpop redis:list:demo 0 // 0 表示一直等待
> "1"
lrange redis:list:demo 0 -1
> (nil) // 此時list已經空了
blpop redis:list:demo 0 // 會一直等待list有新的命令插入

// 等待terminal b

> 1) "redis:list:demo" 等待後返回的結果
> 2) "4"
> (18.83s)
// terminal b
lpush redis:list:demo 4
> (integer) 1 // terminal a 會獲取到這個4

 

可以看到最後一步,當terminal a 最終等到terminal b,push了一個值之後,返回的資料與正常pop的資料不一樣

  • brpop [key] [key ...] [timeout]

參考blpop。基本行為一致,只是brpop是從list的右側pop,而blpop是左側

  • brpoplpush [source list] [destination list] [timeout]

brpoplpush 是 rpoplpush的阻塞版本,你可以直接參考上面rpoplpush命令的解釋,只是rpop變成了brpop,多了等待這一步。

分割

  • ltrim [key] [start] [end]

對一個列表進行修剪(trim),就是說,讓列表只保留指定區間內的元素,不在指定區間之內的元素都將被刪除。

注意,這裡不要搞反了,是將start和end中間的保留,刪除其餘的

del redis:demo:list
> (integer) n
lpush redis:demo:list 1 2 3 4
> (integer) 4
ltrim redis:demo:list 1 2 // 只需要1到2
> OK
lrange redis:demo:list 0 -1
> 1) "3"
> 2) "2"

 

  • lrem [key] [count] [value]

移除count個與value相等的元素。

del redis:demo:list
> (integer) n
lpush redis:demo:list 1 2 2 3 4
> (integer) 5
lrem redis:demo:list 1 2 // 移除1個2
> OK
lrange redis:demo:list 0 -1
> 1) "4"
> 2) "3"
> 3) "2"
> 4) "1"

 

tips,使用這個命令,你可以配合lua指令碼做一個不重複的list, 就是每次在push一個value之前先lrem一下這個value

del redis:demo:list
> (integer) n
lrem redis:demo:list 1 a // 先檢查刪除
> (integer) 0
lpush redis:demo:list a // 再push
> (integer) 1

 

 ...持續更新

 

github: https://github.com/294678380/redis-lerning

h