Redis常用數(shù)據(jù)結(jié)構(gòu)及應(yīng)用場景

1. 概述

Redis 一個(gè)開源的基于鍵值對(duì)(Key-Value)NoSQL 數(shù)據(jù)庫。使用 ANSIC 語言編寫、支持網(wǎng)絡(luò)、基于內(nèi)存但支持持久化。性能優(yōu)秀,并提供多種語言的 API。

我們要首先理解一點(diǎn),我們把 Redis 稱為 KV 數(shù)據(jù)庫,鍵值對(duì)數(shù)據(jù)庫,那就可以把 Redis 內(nèi)部的存儲(chǔ)視為存在著一個(gè)巨大的 Map,對(duì) Map 的操作無非就是get 和 put,然后通過 key 操作這個(gè) key 所對(duì)應(yīng)的 value,而這個(gè) value 的類型可以多種多樣,也就是 Redis 為我們提供的那些數(shù)據(jù)結(jié)構(gòu),比如字符串(String)、哈希(Hash)等等。

Redis 會(huì)將所有數(shù)據(jù)都存放在內(nèi)存中,所以它的讀寫性能非常驚人。不僅如此,Redis 還可以將內(nèi)存的數(shù)據(jù)利用快照和日志的形式保存到硬盤上,這樣在發(fā)生類似斷電或者機(jī)器故障的時(shí)候,內(nèi)存中的數(shù)據(jù)不會(huì)丟失。

除了上述功能以外,Redis 還提供了鍵過期、發(fā)布訂閱、事務(wù)、流水線、Lua 腳本等附加功能。

1.1 應(yīng)用場景

緩存

緩存機(jī)制幾乎在所有的大型網(wǎng)站都有使用,合理地使用緩存不僅可以加快數(shù)據(jù)的訪問速度,而且能夠有效地降低后端數(shù)據(jù)源的壓力。Redis 提供了鍵值過期時(shí)間設(shè)置,并且也提供了靈活控制最大內(nèi)存和內(nèi)存溢出后的淘汰策略。可以這么說,一個(gè)合理的緩存設(shè)計(jì)能夠?yàn)橐粋€(gè)網(wǎng)站的穩(wěn)定保駕護(hù)航。

排行榜系統(tǒng)

排行榜系統(tǒng)幾乎存在于所有的網(wǎng)站,例如按照熱度排名的排行榜,按照發(fā)布時(shí)間的排行榜,按照各種復(fù)雜維度計(jì)算出的排行榜,Redis 提供了列表和有序集合數(shù)據(jù)結(jié)構(gòu),合理地使用這些數(shù)據(jù)結(jié)構(gòu)可以很方便地構(gòu)建各種排行榜系統(tǒng)。

計(jì)數(shù)器應(yīng)用

計(jì)數(shù)器在網(wǎng)站中的作用至關(guān)重要,例如視頻網(wǎng)站有播放數(shù)、電商網(wǎng)站有瀏覽數(shù),為了保證數(shù)據(jù)的實(shí)時(shí)性,每一次播放和瀏覽都要做+1 的操作,如果并發(fā)量很大對(duì)于傳統(tǒng)關(guān)系型數(shù)據(jù)的性能是一種挑戰(zhàn)。Redis 天然支持計(jì)數(shù)功能而且計(jì)數(shù)的性能也非常好,可以說是計(jì)數(shù)器系統(tǒng)的重要選擇。

社交網(wǎng)絡(luò)

贊/踩、粉絲、共同好友/喜好、推送、下拉刷新等是社交網(wǎng)站的必備功能,由于社交網(wǎng)站訪問量通常比較大,而且傳統(tǒng)的關(guān)系型數(shù)據(jù)不太適合保存這種類型的數(shù)據(jù),Redis 提供的數(shù)據(jù)結(jié)構(gòu)可以相對(duì)比較容易地實(shí)現(xiàn)這些功能。

消息隊(duì)列系統(tǒng)

消息隊(duì)列系統(tǒng)可以說是一個(gè)大型網(wǎng)站的必備基礎(chǔ)組件,因?yàn)槠渚哂袠I(yè)務(wù)解耦、 非實(shí)時(shí)業(yè)務(wù)削峰等特性。Redis 提供了發(fā)布訂閱功能和阻塞隊(duì)列的功能,雖然和專業(yè)的消息隊(duì)列比還不夠足夠強(qiáng)大,但是對(duì)于一般的消息隊(duì)列功能基本可以滿足。(我上家公司就用過 Redis 做消息隊(duì)列,雖然后面換了其他 MQ)。

1.2 特性

速度快

正常情況下,Redis 執(zhí)行命令的速度非常快,官方給出的數(shù)字是讀寫性能可以達(dá)到 10 萬/秒。

基于鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu)服務(wù)器

幾乎所有的編程語言都提供了類似字典的功能,例如 Java 里的 map,類似于這種組織數(shù)據(jù)的方式叫作基于鍵值的方式,與很多鍵值對(duì)數(shù)據(jù)庫不同的是,Redis 中的值不僅可以是字符串,而且還可以是具體的數(shù)據(jù)結(jié)構(gòu),這樣不僅能便于在許多應(yīng)用場景的開發(fā),同時(shí)也能夠提高開發(fā)效率。

Redis 的全稱是 Remote Dictionary Server,它主要提供了 5 種數(shù)據(jù)結(jié)構(gòu):字符串、哈希、列表、集合、有序集合,同時(shí)在字符串的基礎(chǔ)之上演變出了位圖(Bitmaps)和 HyperLogLog 兩種數(shù)據(jù)結(jié)構(gòu),并且隨著 LBS (Location BasedService,基于位置服務(wù))的不斷發(fā)展,Redis 中加入有關(guān) GEO(地理信息定位)的功能。

豐富的功能

除了 5 種數(shù)據(jù)結(jié)構(gòu),Redis 還提供了許多額外的功能:提供了鍵過期功能,可以用來實(shí)現(xiàn)緩存。

提供了發(fā)布訂閱功能,可以用來實(shí)現(xiàn)消息系統(tǒng)。支持 Lua 腳本功能,可以利用 Lua 創(chuàng)造出新的 Redis 命令。提供了簡單的事務(wù)功能,能在一定程度上保證事務(wù)特性。提供了流水線(Pipeline)功能,這樣客戶端能將一批命令一次性傳到 Redis,減少了網(wǎng)絡(luò)的開銷。

簡單穩(wěn)定

Redis 的簡單主要表現(xiàn)在三個(gè)方面。

首先,Redis 的源碼很少,早期版本的代碼只有 2 萬行左右,3.0 版本以后由于添加了集群特性,代碼增至 5 萬行左右。

其次,Redis 使用單線程模型,這樣不僅使得 Redis 服務(wù)端處理模型變得簡單,而且也使得客戶端開發(fā)變得簡單。

最后,Redis 不需要依賴于操作系統(tǒng)中的類庫。

Redis 雖然很簡單,但是不代表它不穩(wěn)定。實(shí)際的運(yùn)行中很少出現(xiàn)因?yàn)?Redis 自身 bug 而宕掉的情況。

客戶端語言多

Redis 提供了簡單的 TCP 通信協(xié)議,很多編程語言可以很方便地接人到 Redis。

持久化

通常看,將數(shù)據(jù)放在內(nèi)存中是不安全的,一旦發(fā)生斷電或者機(jī)器故障,重要的數(shù)據(jù)可能就會(huì)丟失,因此Redis提供了兩種持久化方式:RDB 和 AOF,即可以用兩種策略將內(nèi)存的數(shù)據(jù)保存到硬盤中,這樣就保證了數(shù)據(jù)的可持久性。

主從復(fù)制

Redis 提供了復(fù)制功能,實(shí)現(xiàn)了多個(gè)相同數(shù)據(jù)的 Redis 副本,復(fù)制功能是分布式Redis 的基礎(chǔ)。

高可用和分布式

Redis Sentinel,它能夠保證 Redis 節(jié)點(diǎn)的故障發(fā)現(xiàn)和故障自動(dòng)轉(zhuǎn)移。Redis 從 3.0 版本正式提供了分布式實(shí)現(xiàn) Redis Cluster,它是 Redis 真正的分布式實(shí)現(xiàn),提供了高可用、讀寫和容量的擴(kuò)展性。

2. 下載安裝

目前演示的安裝操作是基于 Centos7 下講解。

官方下載地址:https://redis.io/download

# 先新建一個(gè)目錄
mkdir /usr/local/redis
cd /usr/local/redis
# 下載
wget https://download.redis.io/releases/redis-6.2.6.tar.gz
# 解壓
tar -xzvf redis-6.2.6.tar.gz
# 編譯
cd redis-6.2.6
make

2.1 啟動(dòng)

Redis 有三種方法啟動(dòng) Redis:默認(rèn)配置、帶參數(shù)啟動(dòng)、配置文件啟動(dòng)。

1?? 默認(rèn)配置

進(jìn)入安裝好的 Redis 的 src目錄下執(zhí)行以下命令:

./redis-server 
image-20211215203151805

可以看到直接使用 redis-server 啟動(dòng) Redis 后,會(huì)打印出一些日志,通過日志 可以看到一些信息:

當(dāng)前的 Redis 版本的是 64 位的 6.2.6,默認(rèn)端口是 6379。Redis 建議使用配置文件來啟動(dòng),所以這種方式是不會(huì)在生產(chǎn)環(huán)境中使用的。

2?? 參數(shù)啟動(dòng)

redis-server 加上要修改配置名和值(可以是多對(duì)),沒有設(shè)置的配置將使用默認(rèn)配置,例如:如果要用 6380 作為端口啟動(dòng) Redis,那么可以執(zhí)行:

./redis-server --port 6380 
image-20211215203128782

不過這種方式一般也用得比較少。

3?? 配置文件啟動(dòng)

將配置寫到指定文件里,并啟動(dòng),主要修改的是安裝目錄下的 redis.conf文件。

./redis-server ../redis.conf
image-20211215203701640

2.2 操作

Redis 服務(wù)啟動(dòng)完成后,就可以使用 redis-cli 連接和操作 Redis 服務(wù)。

image-20211215203942143

2.3 停止

Redis 提供了 shutdown 命令來停止 Redis 服務(wù),例如我們目前已經(jīng)啟動(dòng)的 Redis 服務(wù),可以執(zhí)行:

./redis-cli -p 6379 shutdown 

Redis 服務(wù)端將會(huì)顯示:

2853:M 15 Dec 2021 20:41:26.593 # User requested shutdown...
2853:M 15 Dec 2021 20:41:26.593 * Saving the final RDB snapshot before exiting.
2853:M 15 Dec 2021 20:41:26.594 * DB saved on disk
2853:M 15 Dec 2021 20:41:26.594 * Removing the pid file.
2853:M 15 Dec 2021 20:41:26.594 # Redis is now ready to exit, bye bye...

除了可以通過 shutdown 命令關(guān)閉 Redis 服務(wù)以外,還可以通過 kill 進(jìn)程號(hào)的方式關(guān)閉掉 Redis,但是強(qiáng)烈不建議使用 kill -9 強(qiáng)制殺死 Redis 服務(wù),不但不會(huì)做持久化操作,還會(huì)造成緩沖區(qū)等資源不能被優(yōu)雅關(guān)閉,極端情況會(huì)造成 AOF 和復(fù)制丟失數(shù)據(jù)的情況。

shutdown 還有一個(gè)參數(shù),代表是否在關(guān)閉 Redis 前,生成持久化文件:

./redis-cli -p 6379 shutdown nosave/save

默認(rèn)是 save,生成持久化文件,如果是 nosave 則不生成持久化文件。

3. 全局命令

在了解 Redis 的數(shù)據(jù)結(jié)構(gòu)之前,先了解 Redis 的一些全局命令。

命令 說明
keys * 查看所有鍵,同時(shí)也支持通配符,如 keys n*
dbsize 返回當(dāng)前數(shù)據(jù)庫中鍵的總數(shù)
exists 檢查鍵是否存在,存在返回 1,不存在返回 0,如 exists name
del 刪除鍵,無論值是什么數(shù)據(jù)結(jié)構(gòu)類型,del 命令都可以將其刪除。返回刪除鍵個(gè)數(shù),刪除不存在鍵返回 0。同時(shí) del 命令可以支持刪除多個(gè)鍵,如 del name age
expire Redis 支持對(duì)鍵添加過期時(shí)間,當(dāng)超過過期時(shí)間后,會(huì)自動(dòng)刪除鍵,時(shí)間單位秒,如 expire name 10
ttl ttl 命令會(huì)返回鍵的剩余過期時(shí)間,若返回 -1 則表示鍵沒設(shè)置過期時(shí)間,-2 鍵不存在
type 返回鍵的數(shù)據(jù)結(jié)構(gòu)類型
randomkey 隨機(jī)返回一個(gè)鍵
rename 鍵重命名,為了防止被強(qiáng)行 rename,Redis 提供了 renamenx 命令,確保只有 newKey 不存在時(shí)候才被覆蓋。由于重命名鍵期間會(huì)執(zhí)行 del 命令刪除舊的鍵,如果鍵對(duì)應(yīng)的值比較大,會(huì)存在阻塞 Redis 的可能性

注:

  1. dbsize 命令在計(jì)算鍵總數(shù)時(shí)不會(huì)遍歷所有鍵,而是直接獲取 Redis 內(nèi)置的鍵總數(shù)變量,所以 dbsize 命令的時(shí)間復(fù)雜度是 O(1)。而 keys 命令會(huì)遍歷所有鍵,所以它的時(shí)間復(fù)雜度是 o(n),當(dāng) Redis 保存了大量鍵時(shí)線上環(huán)境禁止使用 keys 命令;
  2. 除了 expire、ttl 命令以外,Redis 還提供了 expireat、pexpire,pexpireat、pttl、persist 等一系列命令,可自行查驗(yàn)。

4. 基本數(shù)據(jù)結(jié)構(gòu)

Redis 提供了一些數(shù)據(jù)結(jié)構(gòu)供我們往 Redis 中存取數(shù)據(jù),最常用的的有 5 種,字符串(String)、哈希(Hash)、列表(list)、集合(set)、有序集合(ZSET)。

4.1 String

字符串類型是 Redis 最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)。首先鍵都是字符串類型,而且其他幾種數(shù)據(jù)結(jié)構(gòu)都是在字符串類型基礎(chǔ)上構(gòu)建的,所以字符串類型能為其他四種數(shù)據(jù)結(jié)構(gòu)的學(xué)習(xí)奠定基礎(chǔ)。字符串類型的值實(shí)際可以是字符串(簡單的字符串、復(fù) 雜的字符串(例如 JSON、XML))、數(shù)字(整數(shù)、浮點(diǎn)數(shù)),甚至是二進(jìn)制(圖片、音頻、視頻),但是值最大不能超過 512 MB。

1?? 常用命令

設(shè)置值 set

set key value [ex seconds] [px milliseconds] [nxlxx]
  • ex seconds:為鍵設(shè)置秒級(jí)過期時(shí)間。
  • px milliseconds:為鍵設(shè)置毫秒級(jí)過期時(shí)間。
  • nx:鍵必須不存在,才可以設(shè)置成功,用于添加。
  • xx:與 nx 相反,鍵必須存在,才可以設(shè)置成功,用于更新。

其中,ex 參數(shù)和 expire 命令基本一樣。還有一個(gè)需要特別注意的地方是如果一個(gè)字符串已經(jīng)設(shè)置了過期時(shí)間,然后你調(diào)用了 set 方法修改了它,它的過期時(shí)間會(huì)消失。

除了 set 選項(xiàng),Redis 還提供了 setex 和 setnx 兩個(gè)命令:

  • setex key seconds value
  • setnx key value

setex 和 setnx 的作用和 ex 和 nx 選項(xiàng)是一樣的。也就是,setex 為鍵設(shè)置秒級(jí)過期時(shí)間,setnx 設(shè)置時(shí)鍵必須不存在,才可以設(shè)置成功。

有什么應(yīng)用場景嗎?

以 setnx 命令為例子,由于 Redis 的單線程命令處理機(jī)制,如果有多個(gè)客戶端同時(shí)執(zhí)行 setnx key value,根據(jù) setnx 的特性只有一個(gè)客戶端能設(shè)置成功,setnx 可以作為分布式鎖的一種實(shí)現(xiàn)方案。

獲取值 get

get key

如果要獲取的鍵不存在,則返回 nil

另外,除了單個(gè)設(shè)置和獲取鍵值,Redis 還支持批量操作。

批量設(shè)置值 mset

mset name ayue age 20 sex 男

批量獲取值 mget

mget name age sex
image-20211216163431238

如果有些鍵不存在,那么它的值為 nil,結(jié)果是按照傳入鍵的順序返回。

批量操作命令可以有效提高效率,假如沒有 mget 這樣的命令,要執(zhí)行 n 次 get 命令具體耗時(shí)如下:

n 次 get 時(shí)間 = n 次網(wǎng)絡(luò)時(shí)間 + n 次命令時(shí)間

使用 mget 命令后,要執(zhí)行 n 次 get 命令操作具體耗時(shí)如下:

n 次 get 時(shí)間 = 1 次網(wǎng)絡(luò)時(shí)間 + n 次命令時(shí)間

Redis 可以支撐每秒數(shù)萬的讀寫操作,但是這指的是 Redis 服務(wù)端的處理能力,對(duì)于客戶端來說,一次命令除了命令時(shí)間還是有網(wǎng)絡(luò)時(shí)間,假設(shè)網(wǎng)絡(luò)時(shí)間為 1 毫秒,命令時(shí)間為 0.1 毫秒(按照每秒處理 1 萬條命令算),那么執(zhí)行 1000 次 get 命令需要 1.1 秒(1000*1+1000*0.1=1100ms),1 次 mget 命令的需要 0.101 秒 (1*1+1000*0.1=101ms)。

數(shù)字運(yùn)算 incr

incr 命令用于對(duì)值做自增操作,返回結(jié)果分為三種情況:

  1. 值不是整數(shù),返回錯(cuò)誤;
  2. 值是整數(shù),返回自增后的結(jié)果;
  3. 鍵不存在,按照值為 0 自增,返回結(jié)果為 1。
incr key
image-20211216170800153

除了 incr 命令,Redis 提供了 decr(自減)、 incrby(自增指定數(shù)字)、decrby(自減指定數(shù)字)、incrbyfloat(自增浮點(diǎn)數(shù))。

追加指令 append

append 可以向字符串尾部追加值。

append key value
image-20211216171635960

strlen

返回字符串長度。

strlen key

截取字符串 getrange

getrange 截取字符串中的一部分,形成一個(gè)子串,需要指明開始和結(jié)束的偏移量,截取的范圍是個(gè)閉區(qū)間。

image-20211216171944312
命令 說明 時(shí)間復(fù)雜度
get key 獲取值 O(1)
del key [key ...] 刪除key O(N)(N是鍵的個(gè)數(shù))
mset key [key value ...] 批量設(shè)置值 O(N)(N是鍵的個(gè)數(shù))
mget key [key ...] 批量獲取值 O(N)(N是鍵的個(gè)數(shù))
incr key 將 key 中儲(chǔ)存的數(shù)字值增一 O(1)
decr key 將 key 中儲(chǔ)存的數(shù)字值減一 O(1)
incrby key increment 將 key 所儲(chǔ)存的值加上給定的增量值(increment) O(1)
decrby key increment key 所儲(chǔ)存的值減去給定的減量值(decrement) O(1)
incrbyfloat key increment 將 key 所儲(chǔ)存的值加上給定的浮點(diǎn)增量值(increment) O(1)
append key value 如果 key 已經(jīng)存在并且是一個(gè)字符串, APPEND 命令將指定的 value 追加到該 key 原來值(value)的末尾 O(1)
strlen key 返回 key 所儲(chǔ)存的字符串值的長度。 O(1)
setrange key offset value 用 value 參數(shù)覆寫給定 key 所儲(chǔ)存的字符串值,從偏移量 offset 開始 O(1)
getrange key start end 返回 key 中字符串值的子字符 O(N)(N是字符串的長度)

2?? 命令的時(shí)間復(fù)雜度

字符串這些命令中,除了 del 、mset、 mget 支持多個(gè)鍵的批量操作,時(shí)間復(fù)雜度和鍵的個(gè)數(shù)相關(guān),為 O(n),getrange 和字符串長度相關(guān),也是 O(n),其余的命令基本上都是 O(1)的時(shí)間復(fù)雜度,在速度上是非常快的。

3?? 使用場景

字符串類型的使用場景很廣泛,如下:

1、緩存功能

Redis 作為緩存層,MySQL 作為存儲(chǔ)層,絕大部分請(qǐng)求的數(shù)據(jù)都是從 Redis 中獲取。由于 Redis 具有支撐高并發(fā)的特性,所以緩存通常能起到加速讀寫和降低 后端壓力的作用。

2、計(jì)數(shù)

使用 Redis 作為計(jì)數(shù)的基礎(chǔ)工具,它可以實(shí)現(xiàn)快速計(jì)數(shù)、查詢緩存的功能,同時(shí)數(shù)據(jù)可以異步落地到其他數(shù)據(jù)源。

3、共享 Session

一個(gè)分布式 Web 服務(wù)將用戶的 Session 信息(例如用戶登錄信息)保存在各 自服務(wù)器中,這樣會(huì)造成一個(gè)問題,出于負(fù)載均衡的考慮,分布式服務(wù)會(huì)將用戶的訪問均衡到不同服務(wù)器上,用戶刷新一次訪問可能會(huì)發(fā)現(xiàn)需要重新登錄,這個(gè)問題是用戶無法容忍的。

為了解決這個(gè)問題, 可以使用 Redis 將用戶的 Session 進(jìn)行集中管理,在這種模式下只要保證 Redis 是高可用和擴(kuò)展性的,每次用戶更新或者查詢登錄信息都直接從 Redis 中集中獲取。

4、限時(shí)

很多應(yīng)用出于安全的考慮,會(huì)在每次進(jìn)行登錄時(shí),讓用戶輸入手機(jī)驗(yàn)證碼,從而確定是否是用戶本人。但是為了短信接口不被頻繁訪問,會(huì)限制用戶每分鐘獲取驗(yàn)證碼的頻率,例如一分鐘不能超過 5 次。一些網(wǎng)站限制一個(gè) IP 地址不能在一秒鐘之內(nèi)訪問超過 n 次。或者同一 IP 在短時(shí)間內(nèi)多次瀏覽謀篇文章瀏覽次數(shù)不會(huì)一直增加。點(diǎn)贊次數(shù)在短時(shí)間內(nèi)不能重復(fù)點(diǎn)贊。

4.2 Hash

Redis hash 是一個(gè) string 類型的 field(字段) 和 value(值) 的映射表,hash 特別適合用于存儲(chǔ)對(duì)象。

Redis 中每個(gè) hash 可以存儲(chǔ) 232 - 1 鍵值對(duì)(40多億)。

1?? 常用命令

基本上,哈希的操作命令和字符串的操作命令很類似,很多命令在字符串類型的命令前面加上了 h 字母,代表是操作哈希類型,同時(shí)還要指明要操作的 field 的值。

hset

hset key field value

如果設(shè)置成功會(huì)返回 1,反之會(huì)返回 0。此外 Redis 提供了 hsetnx 命令,它們的關(guān)系就像 set 和 setnx 命令一樣,只不過作用域由鍵變?yōu)?field。

127.0.0.1:6379> hset hash:test name ayue
(integer) 1
127.0.0.1:6379> 

hget

hget key field

獲取值

127.0.0.1:6379> hget hash:test name
"ayue"
127.0.0.1:6379> 

其他命令:

命令 說明 時(shí)間復(fù)雜度
HDEL key field [field] 刪除一個(gè)或多個(gè)Hash的field O(N) N是被刪除的字段數(shù)量
HEXISTS key field 判斷field是否存在于Hash中 O(1)
HGET key field 獲取Hash中field的值 O(1)
HGETALL key 從Hash中讀取全部的域和值 O(N) N是Hash的長度
HINCRBY key field increment 將Hash中指定域的值增加給定的數(shù)字 O(1)
HINCRBYFLOAT key field increment 將Hash中指定域的值增加給定的浮點(diǎn)數(shù) O(1)
HKEYS key 獲取Hash的所有字段 O(N) N是Hash的長度
HLEN key 獲取Hash里所有字段的數(shù)量 O(1)
HMGET key field field 獲取Hash里面指定字段的值 O(N) N是請(qǐng)求的字段數(shù)
HMSET key field value [field value ...] 批量設(shè)置Hash字段值 O(N) N是設(shè)置的字段數(shù)
HSET key field value 設(shè)置Hash里面一個(gè)字段的值 O(1)
HSETNX key field value 設(shè)置Hash的一個(gè)字段,只有當(dāng)這個(gè)字段不存在時(shí)有效 O(1)
HSTRLEN key field 獲取Hash里面指定field的長度 O(1)
HVALS key 獲得 Hash 的所有值 O(N) N是Hash的長度
HSCAN key cursor [MATCH pattern] [COUNT count] 迭代 Hash 里面的元素

2?? 命令的時(shí)間復(fù)雜度

哈希類型的操作命令中,hdel,hmget,hmset 的時(shí)間復(fù)雜度和命令所帶的 field 的個(gè)數(shù)相關(guān) O(k),hkeys,hgetall,hvals 和存儲(chǔ)的 field 的總數(shù)相關(guān),O(N)。其余的命令時(shí)間復(fù)雜度都是 O(1)。

3?? 使用場景

1、存儲(chǔ)對(duì)象

Redis哈希對(duì)象常常用來緩存一些對(duì)象信息,如用戶信息、商品信息、配置信息等。

我們以用戶信息為例,它在關(guān)系型數(shù)據(jù)庫中的結(jié)構(gòu)是這樣的:

id name age
1 Tom 15
2 Jerry 13

而使用Redis Hash存儲(chǔ)其結(jié)構(gòu)如下圖:

image-20211220192747612
hmset user:1 name Tom age 15
hmset user:2 name Jerry age 13

相比較于使用Redis字符串存儲(chǔ),其有以下幾個(gè)優(yōu)缺點(diǎn):

  1. 原生字符串每個(gè)屬性一個(gè)鍵。

    set user:1:name Tom
    set user:1:age 15
    

    優(yōu)點(diǎn):簡單直觀,每個(gè)屬性都支持更新操作。
    缺點(diǎn):占用過多的鍵,內(nèi)存占用量較大,同時(shí)用戶信息內(nèi)聚性比較差,所以此種方案一般不會(huì)在生產(chǎn)環(huán)境使用。

  2. 序列化字符串后,將用戶信息序列化后用一個(gè)鍵保存

    set user:1 serialize(userInfo)
    

    優(yōu)點(diǎn):簡化編程,如果合理的使用序列化可以提高內(nèi)存的使用效率。
    缺點(diǎn):序列化和反序列化有一定的開銷,同時(shí)每次更新屬性都需要把全部數(shù)據(jù)取出進(jìn)行反序列化,更新后再序列化到Redis中。

  3. 序列化字符串后,將用戶信息序列化后用一個(gè)鍵保存

    hmset user:1 name Tom age 15 
    

    優(yōu)點(diǎn):簡單直觀,如果使用合理可以減少內(nèi)存空間的使用。
    缺點(diǎn):要控制哈希在ziplist和hashtable兩種內(nèi)部編碼的轉(zhuǎn)換,hashtable會(huì)消耗更多內(nèi)存。

2、購物車

購物車主要功能是臨時(shí)存放欲購買的商品,然后在結(jié)算或下訂單時(shí),把購物里面的數(shù)據(jù)全部移除。其數(shù)據(jù)結(jié)構(gòu)主要包含的字段有:用戶ID、商品ID、商品數(shù)量等等。通常我們需要實(shí)現(xiàn)以下幾個(gè)功能:

  1. 全選功能,獲取所有該用戶的所有購物車商品;
  2. 商品數(shù)量,購物車圖標(biāo)上要顯示購物車?yán)锷唐返目倲?shù);
  3. 刪除,要能移除購物車?yán)锬硞€(gè)商品;
  4. 增加或減少某個(gè)商品的數(shù)量。
image-20211221154409694

在之前很多電商網(wǎng)站通過 cookie 實(shí)現(xiàn)購物車功能,也就是將整個(gè)購物車都存儲(chǔ)到 cookie里面。

  • 優(yōu)點(diǎn):無須對(duì)數(shù)據(jù)庫進(jìn)行寫入就可以實(shí)現(xiàn)購物車功能,這種方式大大提高了購物車的性能。
  • 缺點(diǎn):程序需要重新解析和驗(yàn)證( validate) cookie,確保 cookie 的格式正確,并且包含的商品都是真正可購買的商品。另外,因?yàn)闉g覽器每次發(fā)送請(qǐng)求都會(huì)連 cookie 一起發(fā)送,所以如果購物車 cookie 的體積比較大,那么請(qǐng)求發(fā)送和處理的速度可能會(huì)有所降低。

而通過 Redis 定義購物車非常簡單:當(dāng)前登錄用戶 ID 號(hào)做為key,商品 ID 號(hào)為 field,加入購物車數(shù)量為 value,如下:

hmset cart:001 prod:01  1 prod:02  1
          |        |    |
          |        |    |
          |        |    |
         key    field value

而對(duì)于上述功能,可以通過 Hash 的相關(guān)命令來操作。

4.3 List

列表( list)類型是用來存儲(chǔ)多個(gè)有序的字符串,a、b、c、d、e 五個(gè)元素從左到右組成了一個(gè)有序的列表,列表中的每個(gè)字符串稱為元素(element),一個(gè)列表最多可以存儲(chǔ) 2-1 個(gè)元素。

在 Redis 中,可以對(duì)列表兩端插入( push)和彈出(pop),還可以獲取指定范圍的元素列表、獲取指定索引下標(biāo)的元素等。

列表是一種比較靈活的數(shù)據(jù)結(jié)構(gòu),它可以充當(dāng)棧和隊(duì)列的角色,在實(shí)際開發(fā)上有很多應(yīng)用場景。

image-20211221105439714

列表類型有兩個(gè)特點(diǎn):

  1. 列表中的元素是有序的,這就意味著可以通過索引下標(biāo)獲取某個(gè)元素或者某個(gè)范圍內(nèi)的元素列表。
  2. 列表中的元素可以是重復(fù)的。

1?? 常用命令

Redis列表對(duì)象常用命令如下表(點(diǎn)擊命令可查看命令詳細(xì)說明):

命令 說明 時(shí)間復(fù)雜度
BLPOP key [key ...] timeout 刪除,并獲得該列表中的第一元素,或阻塞,直到有一個(gè)可用 O(1)
BRPOP key [key ...] timeout 刪除,并獲得該列表中的最后一個(gè)元素,或阻塞,直到有一個(gè)可用 O(1)
BRPOPLPUSH source destination timeout 彈出一個(gè)列表的值,將它推到另一個(gè)列表,并返回它;或阻塞,直到有一個(gè)可用 O(1)
LINDEX key index 獲取一個(gè)元素,通過其索引列表 O(N)
LINSERT key BEFORE AFTER pivot value在列表中的另一個(gè)元素之前或之后插入一個(gè)元素 O(N)
LLEN key 獲得隊(duì)列(List)的長度 O(1)
LPOP key 從隊(duì)列的左邊出隊(duì)一個(gè)元素 O(1)
LPUSH key value [value ...] 從隊(duì)列的左邊入隊(duì)一個(gè)或多個(gè)元素 O(1)
LPUSHX key value 當(dāng)隊(duì)列存在時(shí),從隊(duì)到左邊入隊(duì)一個(gè)元素 O(1)
LRANGE key start stop 從列表中獲取指定返回的元素 O(S+N)
LREM key count value 從列表中刪除元素 O(N)
LSET key index value 設(shè)置隊(duì)列里面一個(gè)元素的值 O(N)
LTRIM key start stop 修剪到指定范圍內(nèi)的清單 O(N)
RPOP key 從隊(duì)列的右邊出隊(duì)一個(gè)元 O(1)
RPOPLPUSH source destination 刪除列表中的最后一個(gè)元素,將其追加到另一個(gè)列表 O(1)
RPUSH key value [value ...] 從隊(duì)列的右邊入隊(duì)一個(gè)元素 O(1)
RPUSHX key value 從隊(duì)列的右邊入隊(duì)一個(gè)元素,僅隊(duì)列存在時(shí)有效 O(1)

2?? 命令的時(shí)間復(fù)雜度

列表類型的操作命令中,llen,lpop,rpop,blpop 和 brpop 命令時(shí)間復(fù)雜度都是 O(1),其余的命令的時(shí)間復(fù)雜度都是 O(n),只不過 n 的值根據(jù)命令不同而不同,比如 lset,lindex 時(shí)間復(fù)雜度和命令后的索引值大小相關(guān),rpush 和 lpush 和插入元素的個(gè)數(shù)相關(guān)等等。

3?? 使用場景

1、消息隊(duì)列

但使用 Redis 做消息隊(duì)列存在很多問題,如消息確認(rèn) ACK,消息丟失等,所以一般來說還是用比較專業(yè)的 MQ 中間件。

2、文章列表

如下面這樣的文章列表,當(dāng)用戶和文章都越來越多時(shí),為了加快程序的響應(yīng)速度,我們可以把用戶自己的文章存入到 List 中,因?yàn)?List 是有序的結(jié)構(gòu),所以這樣又可以完美的實(shí)現(xiàn)分頁功能,從而加速了程序的響應(yīng)速度。

image-20211221154339179

上圖可表示為:

# 深圳衛(wèi)健委發(fā)布一條消息,消息ID為 99
lpush mes:001 99
# 武漢本地寶發(fā)布一條消息,消息ID為 100
lpush mes:001 100
# 獲取消息列表‘
lrange mes:001 0 5

4.4 Set

集合( set)類型也是用來保存多個(gè)的字符串元素,但和列表類型不一樣的是,集合中不允許有重復(fù)元素,并且集合中的元素是無序的,不能通過索引下標(biāo)獲取元素。

一個(gè)集合最多可以存儲(chǔ) 232 - 1 個(gè)元素。Redis 除了支持集合內(nèi)的增刪改查,同時(shí)還支持多個(gè)集合取交集、并集、差集,合理地使用好集合類型,能在實(shí)際開發(fā)中解決很多實(shí)際問題。

1?? 常用命令

Redis Set 對(duì)象常用命令如下表(點(diǎn)擊命令可查看命令詳細(xì)說明):

命令 說明 時(shí)間復(fù)雜度
SADD key member [member ...] 添加一個(gè)或者多個(gè)元素到集合(set)里 O(N)
SCARD key 獲取集合里面的元素?cái)?shù)量 O(1)
SDIFF key [key ...] 獲得隊(duì)列不存在的元素 O(N)
SDIFFSTORE destination key [key ...] 獲得隊(duì)列不存在的元素,并存儲(chǔ)在一個(gè)關(guān)鍵的結(jié)果集 O(N)
SINTER key [key ...] 獲得兩個(gè)集合的交集 O(N*M)
SINTERSTORE destination key [key ...] 獲得兩個(gè)集合的交集,并存儲(chǔ)在一個(gè)關(guān)鍵的結(jié)果集 O(N*M)
SISMEMBER key member 確定一個(gè)給定的值是一個(gè)集合的成員 O(1)
SMEMBERS key 獲取集合里面的所有元素 O(N)
SMOVE source destination member 移動(dòng)集合里面的一個(gè)元素到另一個(gè)集合 O(1)
SPOP key [count] 刪除并獲取一個(gè)集合里面的元素 O(1)
SRANDMEMBER key [count] 從集合里面隨機(jī)獲取一個(gè)元素
SREM key member [member ...] 從集合里刪除一個(gè)或多個(gè)元素 O(N)
SUNION key [key ...] 添加多個(gè)set元素 O(N)
SUNIONSTORE destination key [key ...] 合并set元素,并將結(jié)果存入新的set里面 O(N)
[SSCAN key cursor MATCH pattern] [COUNT count] 迭代set里面的元素 O(1)

2?? 命令的時(shí)間復(fù)雜度

scard,sismember 時(shí)間復(fù)雜度為 O(1),其余的命令時(shí)間復(fù)雜度為 O(n),其中 sadd,srem 和命令后所帶的元素個(gè)數(shù)相關(guān),spop,srandmember 和命令后所帶 count 值相關(guān),交集運(yùn)算 O(m*k),k 是多個(gè)集合中元素最少的個(gè)數(shù),m 是鍵個(gè)數(shù),并集、差集和所有集合的元素個(gè)數(shù)和相關(guān)。

3?? 使用場景

1、抽獎(jiǎng)活動(dòng)

常見的抽獎(jiǎng)活動(dòng),比如基于 Redis 實(shí)現(xiàn)抽獎(jiǎng)功能。

SPOP(隨機(jī)移除并返回集合中一個(gè)或多個(gè)元素)SRANDMEMBER(隨機(jī)返回集合中一個(gè)或多個(gè)元素) 命令可以幫助我們實(shí)現(xiàn)一個(gè)抽獎(jiǎng)系統(tǒng),如果允許重復(fù)中獎(jiǎng),可以使用SRANDMEMBER 命令。

活動(dòng) ID 為 001,則

# Tom userID:01 參加活動(dòng)
sadd action:001 01
# Jerry userID:02 參加活動(dòng)
sadd action:001 02
# 開始抽獎(jiǎng)1名中獎(jiǎng)?wù)?srandmember action:001 1 或 spop action:001 1
# 查看有多少用戶參加了本次抽獎(jiǎng)
smembers action:001

2、點(diǎn)贊功能

比如設(shè)計(jì)一個(gè)微信點(diǎn)贊功能。

# 張三用戶ID 為userId:01
# 張三對(duì)消息 ID008點(diǎn)贊啦
sadd zan:008 userId:01

# 張三取消了對(duì)消息008的點(diǎn)贊
srem zan:008 userId:01

# 檢查用戶是否點(diǎn)過贊
sismember zan:008 userId:01

# 獲取消息ID008所有的點(diǎn)贊用戶列表
smembers zan:008

# 消息ID008的點(diǎn)贊數(shù)計(jì)算
scard zan:008

3、關(guān)系設(shè)計(jì)

如我們要設(shè)計(jì)一個(gè)微博的共同關(guān)注,或者可能認(rèn)識(shí)的人。設(shè)計(jì)如下:

① A 關(guān)注的人

sadd A:cares B C D E

② B 關(guān)注的人

sadd B:cares A C D F

③ C 關(guān)注的人

sadd C:cares A F

按照以上條件:

④ A 和 B 共同關(guān)注的人

# D,C
sinter A:cares B:cares

⑤ 我關(guān)注的人也關(guān)注他

# A 關(guān)注的 B 也關(guān)注了 F,返回 1 否則返回 0
sismember B:cares F

⑥ 可能認(rèn)識(shí)的人

# C 可能認(rèn)識(shí)的人 C,D
sdiff B:cares C:cares

4、集合操作

setA={A,B,C}     setB={B, C}

① 集合與集合之間的交集

sinter setA setB-->得到集合{B,C}

② 集合與集合之間的并集

sunion setA setB -->得到集合{A,B,C}

③ 集合與集合之間的差集

sdiff  setA setB-->得到集合{A}
127.0.0.1:6379> SADD setA A B C
(integer) 3
127.0.0.1:6379> SADD setB B C
(integer) 2
127.0.0.1:6379> SINTER setA setB
1) "C"
2) "B"
127.0.0.1:6379> SUNION setA setB
1) "A"
2) "B"
3) "C"
127.0.0.1:6379> SDIFF setA setB
1) "A"
127.0.0.1:6379> 

4.5 ZSet

ZSet,有序集合,相對(duì)于哈希、列表、集合來說會(huì)有一點(diǎn)點(diǎn)陌生,但既然叫有序集合,那么它和集合必然有著聯(lián)系,它保留了集合不能有重復(fù)成員的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下標(biāo)作為排序依據(jù)不同的是,它給每個(gè)元素設(shè)置一個(gè)分?jǐn)?shù)( score)作為排序的依據(jù)。

有序集合中的元素不能重復(fù),但是 score 可以重復(fù),就和一個(gè)班里的同學(xué)學(xué)號(hào)不能重復(fù),但是考試成績可以相同。

有序集合提供了獲取指定分?jǐn)?shù)和元素范圍查詢、計(jì)算成員排名等功能,合理的利用有序集合,能幫助我們?cè)趯?shí)際開發(fā)中解決很多問題。

1?? 常用命令

zadd

向有序集合 top:20211221 添加話題和點(diǎn)擊量。

zadd hot:20211220 10 薇婭逃稅

zadd 命令還有四個(gè)選項(xiàng) nx、xx、ch、incr 四個(gè)選項(xiàng):

  • nx,member 必須不存在,才可以設(shè)置成功,用于添加;
  • xx,member 必須存在,才可以設(shè)置成功,用于更新;
  • ch,返回此次操作后,有序集合元素和分?jǐn)?shù)發(fā)生變化的個(gè)數(shù);
  • incr,對(duì) score 做增加,相當(dāng)于 zincrby 。

Redis列表對(duì)象常用命令如下表:

命令 說明 時(shí)間復(fù)雜度
BZPOPMAX key [key ...] timeout 從一個(gè)或多個(gè)排序集中刪除并返回得分最高的成員,或阻塞,直到其中一個(gè)可用為止 O(log(N))
BZPOPMIN key [key ...] timeout 從一個(gè)或多個(gè)排序集中刪除并返回得分最低的成員,或阻塞,直到其中一個(gè)可用為止 O(log(N))
[ZADD key NXXX] [CH] [INCR] score member [score member ...] 添加到有序set的一個(gè)或多個(gè)成員,或更新的分?jǐn)?shù),如果它已經(jīng)存在 O(log(N))
ZCARD key 獲取一個(gè)排序的集合中的成員數(shù)量 O(1)
ZCOUNT key min max 返回分?jǐn)?shù)范圍內(nèi)的成員數(shù)量 O(log(N))
ZINCRBY key increment member 增量的一名成員在排序設(shè)置的評(píng)分 O(log(N))
ZINTERSTORE 相交多個(gè)排序集,導(dǎo)致排序的設(shè)置存儲(chǔ)在一個(gè)新的關(guān)鍵 O(NK)+O(Mlog(M))
ZLEXCOUNT key min max 返回成員之間的成員數(shù)量 O(log(N))
ZPOPMAX key [count] 刪除并返回排序集中得分最高的成員 O(log(N)*M)
ZPOPMIN key [count] 刪除并返回排序集中得分最低的成員 O(log(N)*M)
ZRANGE key start stop [WITHSCORES] 根據(jù)指定的index返回,返回sorted set的成員列表 O(log(N)+M)
ZRANGEBYLEX key min max [LIMIT offset count] 返回指定成員區(qū)間內(nèi)的成員,按字典正序排列, 分?jǐn)?shù)必須相同。 O(log(N)+M)
ZREVRANGEBYLEX key max min [LIMIT offset count] 返回指定成員區(qū)間內(nèi)的成員,按字典倒序排列,分?jǐn)?shù)必須相同 O(log(N)+M)
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 返回有序集合中指定分?jǐn)?shù)區(qū)間內(nèi)的成員,分?jǐn)?shù)由低到高排序。 O(log(N)+M)
ZRANK key member 確定在排序集合成員的索引 O(log(N))
ZREM key member [member ...] 從排序的集合中刪除一個(gè)或多個(gè)成員 O(M*log(N))
ZREMRANGEBYLEX key min max 刪除名稱按字典由低到高排序成員之間所有成員。 O(log(N)+M)
ZREMRANGEBYRANK key start stop 在排序設(shè)置的所有成員在給定的索引中刪除 O(log(N)+M)
ZREMRANGEBYSCORE key min max 刪除一個(gè)排序的設(shè)置在給定的分?jǐn)?shù)所有成員 O(log(N)+M)
ZREVRANGE key start stop [WITHSCORES] 在排序的設(shè)置返回的成員范圍,通過索引,下令從分?jǐn)?shù)高到低 O(log(N)+M)
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] 返回有序集合中指定分?jǐn)?shù)區(qū)間內(nèi)的成員,分?jǐn)?shù)由高到低排序。 O(log(N)+M)
ZREVRANK key member 確定指數(shù)在排序集的成員,下令從分?jǐn)?shù)高到低 O(log(N))
ZSCORE key member 獲取成員在排序設(shè)置相關(guān)的比分 O(1)
ZUNIONSTORE 添加多個(gè)排序集和導(dǎo)致排序的設(shè)置存儲(chǔ)在一個(gè)新的鍵 O(N)+O(M log(M))
ZSCAN key cursor [MATCH pattern] [COUNT count] 迭代sorted sets里面的元素 O(1)

2?? 命令的時(shí)間復(fù)雜度

參考上表。

3?? 使用場景

有序集合比較典型的使用場景就是排行榜系統(tǒng)。例如視頻網(wǎng)站需要對(duì)用戶上傳的視頻做排行榜,榜單的維度可能是多個(gè)方面的:按照時(shí)間、按照播放數(shù)量、按照獲得的贊數(shù)。

image-20211221212056683

如上熱搜榜,以日期為 key :

① 點(diǎn)擊熱搜,每次加 1

zincrby hot:20211220 1 薇婭逃稅

② 右側(cè)排行實(shí)現(xiàn),展示今日前 50 排名

# zrange 是從低到高返回,zrevrange 反之
zrevrange  hot:20211221 0 49 withscores

本文由博客群發(fā)一文多發(fā)等運(yùn)營工具平臺(tái) OpenWrite 發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容