前言
Redis作爲應用最廣泛的K-V數據庫,包含了豐富的數據類型。這裏所說的數據類型,其實是指V的數據類型。
Redis的數據類型大致分爲以下幾類:String、Hash、List、Set、Sorted Set、HyperLogLog、Geo。
本篇主要講解前五種常用的數據類型,在講解數據類型之前,先看一下Redis相關的幫助命令。學會使用這些命令,能夠幫我們快速的瞭解Redis
前置命令
啓動Redis客戶端
./redis-cli
啓動成功後,測試Redis是否可用
127.0.0.1:6379> ping
PONG
獲取幫助
直接輸入help
指令,會出現如下提示
127.0.0.1:6379> help
redis-cli 5.0.8
To get help about Redis commands type:
"help @<group>" to get a list of commands in <group>
"help <command>" for help on <command>
"help <tab>" to get a list of possible help topics
"quit" to exit
To set redis-cli preferences:
":set hints" enable online hints
":set nohints" disable online hints
Set your preferences in ~/.redisclirc
根據提示可以知道輸入help @<group>
可以獲取group
中的命令列表。例如要查詢一些通用命令,操作如下(僅列出部分命令)
127.0.0.1:6379> help @generic
DEL key [key ...]
summary: Delete a key
since: 1.0.0
DUMP key
summary: Return a serialized version of the value stored at the specified key.
since: 2.6.0
EXISTS key [key ...]
summary: Determine if a key exists
since: 1.0.0
EXPIRE key seconds
summary: Set a key's time to live in seconds
since: 1.0.0
......
如果不知道有哪些group
,可以直接輸入help @
然後按tab
鍵,就可以自動補全group
的名稱
輸入help <command>
可以直接獲取對應命令的幫助,例如要獲取keys
指令的幫助,可以輸入
127.0.0.1:6379> help keys
KEYS pattern
summary: Find all keys matching the given pattern
since: 1.0.0
group: generic
查看類型
使用命令
type key
可以查看key對應value的類型
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> type hello
string
數據類型
string
Redis的string類型是對外的類型,對內會根據不同的值採取不同的編碼方式。普通的短字符串會用embstr
的方式進行編碼,如果是數值類型,會用int
進行編碼,如果是長字符串(44個字節爲界),會採用raw
的方式進行編碼。
命令OBJECT
可以查看Redis內部對數據的處理,例如查看數據的編碼
127.0.0.1:6379> set xx abcd
OK
127.0.0.1:6379> set yy 1234
OK
127.0.0.1:6379> set zz abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs
OK
127.0.0.1:6379> type xx
string
127.0.0.1:6379> type yy
string
127.0.0.1:6379> type zz
string
127.0.0.1:6379> object encoding xx
"embstr"
127.0.0.1:6379> object encoding yy
"int"
127.0.0.1:6379> object encoding zz
"raw"
根據執行結果可以看出xx
、yy
、zz
的類型都是string,但是內部編碼不一樣。
string最Redis基本的數據類型,Redis沒有直接使用C語言傳統的字符串表示方法(以空字符結尾的字符數組),而是自己構建了簡單動態字符串(Simple Dynamic string, SDS),並將SDS作爲Redis默認字符串表示。string是二進制安全的,可以包含任意類型的數據,最大爲512MB。定義的結構體如下:
struct sdshdr {
int len; // 記錄buf數組大小
int free; // 記錄buf數組還有多少可用空間
char buf[]; // 字符串實體,保存字符串的內容
};
因爲有了對字符串長度定義len
,所以在處理字符串時候不會以零值字節(\0)爲字符串結尾標誌。
二進制安全就是輸入任何字節都能正確處理,即使包含零值字節。
由於string類型的數據都是按照二進制存儲的,所以Redis還提供了一些按位(bit)來操作string數據的命令,如下所示(僅列出與bit
操作相關命令)
127.0.0.1:6379> help @string
BITCOUNT key [start end]
summary: Count set bits in a string
since: 2.6.0
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
summary: Perform arbitrary bitfield integer operations on strings
since: 3.2.0
BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings
since: 2.6.0
BITPOS key bit [start] [end]
summary: Find first bit set or clear in a string
since: 2.8.7
GETBIT key offset
summary: Returns the bit value at offset in the string value stored at key
since: 2.2.0
SETBIT key offset value
summary: Sets or clears the bit at offset in the string value stored at key
since: 2.2.0
首先看下SETBIT
命令,該命令的作用是給key
對應的值的偏移(offset
)設置一個bit
的值,先來看下使用方式
127.0.0.1:6379> setbit hello 1 1
(integer) 0
127.0.0.1:6379> get hello
"@"
127.0.0.1:6379> strlen hello
(integer) 1
setbit hello 1 1
命令就是在offerset
爲1的bit處,把值設置成1(因爲是二進制,所以只能設置成0或者1)。01000000轉換成10進制是64,64是ASCII碼,對應的字符正是@
符號(Linux命令man ascii
可以查看ascii
碼錶)
接着繼續操作
127.0.0.1:6379> setbit hello 9 1
(integer) 0
127.0.0.1:6379> setbit hello 14 1
(integer) 0
127.0.0.1:6379> get hello
"@B"
127.0.0.1:6379> strlen hello
(integer) 2
setbit hello 9 1
setbit hello 14 1
這兩條命令分別在offerset爲9和14的地方把值改成了1,由於超過了一個byte的位數,所以需要兩個byte。兩個byte分別代表ASCII值的64和66,也就是@
和B
。
理解了setbit
,自然就理解了getbit
,此處不再贅述。
再看下BITPOS
命令,該命令描述如下
BITPOS key bit [start] [end]
summary: Find first bit set or clear in a string
since: 2.8.7
這個命令的作用是查找二進制的某個值(只能是0或者1),在[start, end]
區間第一次出現的位置,這個start和end都是以字節爲單位(比如[1, 2]
是指在第二個字節和第三個字節中查找,從0開始計數),具體使用方式如下(繼續上面的操作)
127.0.0.1:6379> bitpos hello 1 0 0
(integer) 1
127.0.0.1:6379> bitpos hello 1 1 1
(integer) 9
bitpos hello 1 0 0
表示在hello
這個key對應的value中的第0個字節到第0個字節(也就是隻查找第0個字節)查找二進制數1。還是看這張圖
第0個字節就是01000000
,查找1所在的位置,偏移量就是1(從0開始)。而bitpos hello 1 1 1
是在第1個字節中查找二進制1,偏移量就是9。
接着再看BITCOUNT
命令,該命令描述如下
BITCOUNT key [start end]
summary: Count set bits in a string
since: 2.6.0
這個命令的作用就是統計某個key對應的value的指定字節範圍內,二進制1出現的次數,start
和end
都是以字節(byte)爲單位。具體使用如下(繼續上面的操作)
127.0.0.1:6379> bitcount hello 0 0
(integer) 1
127.0.0.1:6379> bitcount hello 0 1
(integer) 3
127.0.0.1:6379> bitcount hello 1 1
(integer) 2
二進制1,在第0個字節出現了1次,在第0個和第1個字節出現了3次,在第1個字節出現了2次。所以結果分別是1、3、2。
以上幾個關於bit操作的命令都是操作一個key的,接下來再看可以操作多個key的BITOP
,該命令描述如下
BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings
since: 2.6.0
改命令的作用就是在多個key之間進行位運算
127.0.0.1:6379> setbit xx 1 1
(integer) 0
127.0.0.1:6379> setbit xx 2 1
(integer) 0
127.0.0.1:6379> setbit yy 1 1
(integer) 0
127.0.0.1:6379> setbit yy 3 1
(integer) 0
127.0.0.1:6379> get xx
"`"
127.0.0.1:6379> get yy
"P"
127.0.0.1:6379> bitop and andkey xx yy
(integer) 1
127.0.0.1:6379> get andkey
"@"
127.0.0.1:6379> bitop or orkey xx yy
(integer) 1
127.0.0.1:6379> get orkey
"p"
首先根據前面關於SETBIT
的知識可以知道,四條SETBIT
命令執行完成後,xx
和yy
對應的值的二進制分別可以表示成如下形式
bitop and andkey xx yy
命令會把這兩個值進行按位與操作,並把結果賦值給andkey
。
bitop or orkey xx yy
命令會把這兩個值進行按位或操作,並把結果賦值給orkey
。
圖解過程如下
根據圖示可以知道addkey
和orkey
的值分別爲01000000
和01110000
,轉換成十進制分別是64和112。再根據ASCII碼錶就可以知道分別代表@
和小寫字母p
。
list
list
是有序可重複列表的意思,底層採用雙向鏈表實現,結構如下
瞭解了內部結構,再來看list
相關的命令就簡單了。list
相關的命令可以分爲幾類,利用這些命令,可以用list
來實現棧(stack)、隊列(queue)、阻塞隊列(blocking queue)、數組(array)等結構
首先來實現棧
127.0.0.1:6379> lpush hello a b c d
(integer) 4
127.0.0.1:6379> lrange hello 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> lpop hello
"d"
127.0.0.1:6379> lpop hello
"c"
127.0.0.1:6379> lpop hello
"b"
127.0.0.1:6379> lpop hello
"a"
lpush hello a b c d
表示向以hello
爲key
的list
中依次放入a、b、c、d四個元素。
lrange hello 0 -1
表示打印出hello
中的所有元素,0表示從第一個元素開始,-1表示最後一個元素。因爲Redis是支持負索引的,-1表示最後一個元素,-2表示倒數第二個元素,依次類推。放入元素的順序是a、b、c、d,四個元素均是從左邊放入,相當於都是用的頭插法。四個元素插入完成後,結構應該如圖所示
lpop hello
表示彈出hello
左邊的第一個元素,所以彈出的順序依次爲d、c、b、a。因爲插入的順序是abcd,而彈出的順序是dcba,這樣就實現了棧(stack)的功能。利用rpush
和rpop
也是同理,不過rpush
使用的是尾插法。
接下來實現隊列(queue)的功能
127.0.0.1:6379> lrange hello 0 -1
(empty list or set)
127.0.0.1:6379> lpush hello a b c d
(integer) 4
127.0.0.1:6379> lrange hello 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> rpop hello
"a"
127.0.0.1:6379> rpop hello
"b"
127.0.0.1:6379> rpop hello
"c"
127.0.0.1:6379> rpop hello
"d"
可以看到元素進入的順序是abcd,彈出的順序也是abcd。利用lpush
和rpop
就實現了類似隊列先進先出(FIFO)的功能,同理利用rpush
和lpop
也能實現先進先出(FIFO)的功能。
另外,list
還提供了三個阻塞式的命令
127.0.0.1:6379> help @list
BLPOP key [key ...] timeout
summary: Remove and get the first element in a list, or block until one is available
since: 2.0.0
BRPOP key [key ...] timeout
summary: Remove and get the last element in a list, or block until one is available
since: 2.0.0
BRPOPLPUSH source destination timeout
summary: Pop a value from a list, push it to another list and return it; or block until one is available
since: 2.2.0
操作和之前的命令一樣,只是當沒有可用元素時,會被阻塞。
除此之外,list
還提供了一系列關於索引的操作
127.0.0.1:6379> lrange hello 0 -1
(empty list or set)
127.0.0.1:6379> lpush hello a b c d
(integer) 4
127.0.0.1:6379> lrange hello 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> lindex hello 1
"c"
127.0.0.1:6379> lset hello 0 x
OK
127.0.0.1:6379> lrange hello 0 -1
1) "x"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> linsert hello after c y
(integer) 5
127.0.0.1:6379> lrange hello 0 -1
1) "x"
2) "c"
3) "y"
4) "b"
5) "a"
127.0.0.1:6379> llen hello
(integer) 5
lindex hello 1
查看下標爲1的值
lset hello 0 x
把下標爲0的值改爲x
linsert hello after c y
在元素c
的後面插入元素y
llen hello
查看list
的長度
除此之外,list
還提供了刪除命令lrem
和ltrim
127.0.0.1:6379> lpush hello a b a c a b
(integer) 6
127.0.0.1:6379> lrange hello 0 -1
1) "b"
2) "a"
3) "c"
4) "a"
5) "b"
6) "a"
127.0.0.1:6379> lrem hello 2 a
(integer) 2
127.0.0.1:6379> lrange hello 0 -1
1) "b"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> ltrim hello 1 -2
OK
127.0.0.1:6379> lrange hello 0 -1
1) "c"
2) "b"
lrem hello 2 a
是從左向右刪除2個a
元素
ltrim hello 1 -2
是刪除索引小於1或者索引大於-2的所有元素,即只保留中間部分,刪除首尾數據。
hash
redis中的hash
類型主要用來存儲一些類似對象的結構,常用操作如下
127.0.0.1:6379> hset student name sicimike
(integer) 1
127.0.0.1:6379> hmset student id 1001 age 18
OK
127.0.0.1:6379> hget student id
"1001"
127.0.0.1:6379> hmget student name age
1) "sicimike"
2) "18"
hset
指令用於往key對應的value(student
對象)插入一個屬性和值,hmset
用於同時插入多個
hget
用於獲取student
對象中的某個屬性值,hmget
表示同時獲取多個
127.0.0.1:6379> hkeys student
1) "name"
2) "id"
3) "age"
127.0.0.1:6379> hvals student
1) "sicimike"
2) "1001"
3) "18"
127.0.0.1:6379> hgetall student
1) "name"
2) "sicimike"
3) "id"
4) "1001"
5) "age"
6) "18"
hkeys
、hvals
和hgetall
分別表示獲取student
對象的所有屬性名稱、所有屬性的值、所以屬性名稱和值。
127.0.0.1:6379> hincrby student age 2
(integer) 20
127.0.0.1:6379> hget student age
"20"
127.0.0.1:6379> hincrby student age -2
(integer) 18
127.0.0.1:6379> hget student age
"18"
hincrby
用來給student
對象的某個屬性值加上指定的值,如果加上的是負數,相當於減法。
set
set
是無序不重複的集合,和java集合中的Set
差不多,常用操作如下
127.0.0.1:6379> sadd key1 a b a c d
(integer) 4
127.0.0.1:6379> SMEMBERS key1
1) "d"
2) "b"
3) "a"
4) "c"
sadd
用於往集合中添加元素,支持同時添加多個
smembers
用於返回集合中的所有元素
127.0.0.1:6379> srem key1 a c
(integer) 2
127.0.0.1:6379> SMEMBERS key1
1) "d"
2) "b"
srem
用於從集合中刪除指定元素
以上的操作都是針對一個set
集合的,下面是關於多個集合的求交集、並集、差集。
127.0.0.1:6379> sadd key2 a b c d e
(integer) 5
127.0.0.1:6379> sadd key3 c d e f g
(integer) 5
127.0.0.1:6379> SINTER key2 key3
1) "d"
2) "c"
3) "e"
127.0.0.1:6379> SINTERSTORE destkey key2 key3
(integer) 3
127.0.0.1:6379> SMEMBERS destkey
1) "d"
2) "c"
3) "e"
127.0.0.1:6379> SUNION key2 key3
1) "b"
2) "c"
3) "g"
4) "e"
5) "d"
6) "a"
7) "f"
SINTER
、SINTERSTORE
兩個指令都是求多個集合的交集,不同的是前者會把結果集直接返回給客戶端,後者會把結果集存入指定的key
。
SUNION
是求多個集合的並集
127.0.0.1:6379> sdiff key2 key3
1) "a"
2) "b"
127.0.0.1:6379> sdiff key3 key2
1) "f"
2) "g"
sdiff
命令用於求多個集合的差集,差集是有方向的,key
的順序就代表了方向。所以sdiff key2 key3
和sdiff key3 key2
結果不一樣。
除了以上的命令,set
還提供了兩個獲取隨機元素的命令SRANDMEMBER
和SPOP
先看SRANDMEMBER
的命令描述
127.0.0.1:6379> help SRANDMEMBER
SRANDMEMBER key [count]
summary: Get one or multiple random members from a set
since: 1.0.0
group: set
命令的作用是從set
中獲取一個或多個隨機元素,先來看下相關的操作
127.0.0.1:6379> sadd key4 a b c d e f g
(integer) 7
127.0.0.1:6379> SMEMBERS key4
1) "b"
2) "c"
3) "g"
4) "e"
5) "a"
6) "d"
7) "f"
往set
中放入7個元素。
127.0.0.1:6379> SRANDMEMBER key4 5
1) "c"
2) "g"
3) "d"
4) "a"
5) "f"
SRANDMEMBER key4 5
表示從7個元素中隨機取出5個,不允許重複。
127.0.0.1:6379> SRANDMEMBER key4 10
1) "e"
2) "g"
3) "b"
4) "c"
5) "d"
6) "a"
7) "f"
SRANDMEMBER key4 10
表示從7個元素中取出10個,不允許重複。因爲set
中只有7個元素,所以結果只能取出7個。
127.0.0.1:6379> SRANDMEMBER key4 -5
1) "b"
2) "b"
3) "a"
4) "b"
5) "d"
SRANDMEMBER key4 -5
表示從7個元素中隨機取出5個,允許重複,也就說負數代表允許重複。
127.0.0.1:6379> SRANDMEMBER key4 -10
1) "c"
2) "d"
3) "f"
4) "e"
5) "d"
6) "f"
7) "b"
8) "c"
9) "b"
10) "f"
SRANDMEMBER key4 -10
表示從7個元素中取出10個,允許重複。因爲允許重複,所以可以取出10個。
127.0.0.1:6379> SRANDMEMBER key4 0
(empty list or set)
SRANDMEMBER key4 0
表示從set
中取出0個元素
再來看SPOP
命令,先看SPOP
的命令描述
127.0.0.1:6379> help spop
SPOP key [count]
summary: Remove and return one or multiple random members from a set
since: 1.0.0
group: set
SPOP
命令可以從set
中刪除並返回一個或多個隨機元素
127.0.0.1:6379> sadd key4 a b c d e f g
(integer) 7
127.0.0.1:6379> spop key4
"b"
127.0.0.1:6379> spop key4 2
1) "f"
2) "c"
127.0.0.1:6379> spop key4 0
(empty list or set)
127.0.0.1:6379> spop key4 -2
(error) ERR index out of range
127.0.0.1:6379> SMEMBERS key4
1) "g"
2) "e"
3) "a"
4) "d"
從執行結果來看,確實可以刪除並返回一個或多個隨機元素,並且不支持負數。
sorted_set
除了無序不重複的集合set
外,redis還提供了有序不重複的集合sorted_set
。sorted_set
在添加元素的時候,需要給每個元素設置一個score
,這個數值的大小就是元素的排序的依據。下面是關於sorted_set
的操作
127.0.0.1:6379> zadd key1 9 zhangsan 4 lisi 7 wangwu
(integer) 3
127.0.0.1:6379> zrange key1 0 -1
1) "lisi"
2) "wangwu"
3) "zhangsan"
127.0.0.1:6379> zrange key1 0 -1 withscores
1) "lisi"
2) "4"
3) "wangwu"
4) "7"
5) "zhangsan"
6) "9"
zadd
用於往sorted_set
中添加元素
zrange key1 0 -1
表示查看所有元素(按照score
升序)
zrange key1 0 -1 withscores
表示查看所有元素,並且顯示對應的score
127.0.0.1:6379> ZREVRANGE key1 0 1
1) "zhangsan"
2) "wangwu"
127.0.0.1:6379> ZRANGEBYSCORE key1 7 9
1) "wangwu"
2) "zhangsan"
ZREVRANGE key1 0 1
命令用於輸出score
最高的兩個元素,是按照score
降序的。因爲ZREVRANGE
是反向遍歷的,且不會改變sorted_set
的結構。
ZRANGEBYSCORE key1 7 9
命令用於輸出score
大於等於7,且小於等於9的元素
127.0.0.1:6379> zscore key1 lisi
"4"
127.0.0.1:6379> zrank key1 lisi
(integer) 0
127.0.0.1:6379> ZINCRBY key1 2 lisi
"6"
127.0.0.1:6379> zrange key1 0 -1 withscores
1) "lisi"
2) "6"
3) "wangwu"
4) "7"
5) "zhangsan"
6) "9"
zscore
命令獲取元素的score
zrank
命令獲取元素的排名(score
遞增的排名)
ZINCRBY
命令給某個元素的score
加上指定的值。
以上的命令都是操作單個的sorted_set
,除此之外redis還提供了操作多個sorted_set
的命令ZUNIONSTORE
,先看下命令描述
127.0.0.1:6379> help ZUNIONSTORE
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
summary: Add multiple sorted sets and store the resulting sorted set in a new key
since: 2.0.0
group: sorted_set
這個命令就是給多個sorted_set
求並集。set
的並集很好理解,但是sorted_set
涉及到score
,求並集時應該怎麼處理呢?先看具體的操作
127.0.0.1:6379> zadd key2 30 zhangsan 70 lisi 40 wangwu
(integer) 3
127.0.0.1:6379> zadd key3 40 zhangsan 60 lisi 50 zhaoliu
(integer) 3
127.0.0.1:6379> ZUNIONSTORE destkey 2 key2 key3
(integer) 4
127.0.0.1:6379> zrange destkey 0 -1 withscores
1) "wangwu"
2) "40"
3) "zhaoliu"
4) "50"
5) "zhangsan"
6) "70"
7) "lisi"
8) "130"
wangwu
在key4
中沒有,zhaoliu
在key3
中沒有,求並集之後會直接放入並集destkey
中。對於兩個集合都有的zhangsan
和lisi
,求並集之後,是把他們的score
的和放入並集destkey
中。
127.0.0.1:6379> ZUNIONSTORE destkey 2 key2 key3 weights 2 0.5 aggregate sum
(integer) 4
127.0.0.1:6379> zrange destkey 0 -1 withscores
1) "zhaoliu"
2) "25"
3) "wangwu"
4) "80"
5) "zhangsan"
6) "80"
7) "lisi"
8) "170"
此處求交集,相比上一條命令,多個weights 2 0.5
和aggregate sum
。其中aggregate sum
就是把兩個集合中相同元素的score
相加,只是第一條指令省略了aggregate sum
。
該條命令的含義如圖所示。
127.0.0.1:6379> ZUNIONSTORE destkey 2 key2 key3 aggregate min
(integer) 4
127.0.0.1:6379> zrange destkey 0 -1 withscores
1) "zhangsan"
2) "30"
3) "wangwu"
4) "40"
5) "zhaoliu"
6) "50"
7) "lisi"
8) "60"
理解了上面那條指令,這條也就不難理解了,只是把score
求和變成了取最小的score
。
sorted_set
常用於各種排行榜的實現,無論是排序還是查找sorted_set
都非常高效。這種高效得益於底層的數據結構。
sorted_set
底層採用跳錶(skip list)實現。跳錶插入、刪除、查找操作的時間複雜度是O(logN)
。跳錶的基本結構如圖所示
跳錶的實現是基於有序鏈表,改進的地方在於跳錶除了保存了原始的鏈表,還在鏈表上加了多層類似索引的結構。這樣使得對鏈表的操作不需要一個節點一個節點的遍歷。
總結
本篇主要講解了redis五種常用的數據類型,以及部分類型的底層結構。理解redis數據類型,是用好redis的必要條件。
除了常用的五種類型外,redis還提供了hyperloglog
和geo
兩種類型,分別用來做基數統計和地理信息相關的操作。
參考
- https://www.zhihu.com/question/28705562
- https://zhuanlan.zhihu.com/p/53975333