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
可以看到直接使用 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
不過這種方式一般也用得比較少。
3?? 配置文件啟動(dòng)
將配置寫到指定文件里,并啟動(dòng),主要修改的是安裝目錄下的 redis.conf
文件。
./redis-server ../redis.conf
2.2 操作
Redis 服務(wù)啟動(dòng)完成后,就可以使用 redis-cli 連接和操作 Redis 服務(wù)。
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 的可能性 |
注:
- 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 命令;
- 除了 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
如果有些鍵不存在,那么它的值為 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é)果分為三種情況:
- 值不是整數(shù),返回錯(cuò)誤;
- 值是整數(shù),返回自增后的結(jié)果;
- 鍵不存在,按照值為 0 自增,返回結(jié)果為 1。
incr key
除了 incr 命令,Redis 提供了 decr(自減)、 incrby(自增指定數(shù)字)、decrby(自減指定數(shù)字)、incrbyfloat(自增浮點(diǎn)數(shù))。
追加指令 append
append 可以向字符串尾部追加值。
append key value
strlen
返回字符串長度。
strlen key
截取字符串 getrange
getrange 截取字符串中的一部分,形成一個(gè)子串,需要指明開始和結(jié)束的偏移量,截取的范圍是個(gè)閉區(qū)間。
命令 | 說明 | 時(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)如下圖:
hmset user:1 name Tom age 15
hmset user:2 name Jerry age 13
相比較于使用Redis字符串存儲(chǔ),其有以下幾個(gè)優(yōu)缺點(diǎn):
-
原生字符串每個(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)境使用。 -
序列化字符串后,將用戶信息序列化后用一個(gè)鍵保存
set user:1 serialize(userInfo)
優(yōu)點(diǎn):簡化編程,如果合理的使用序列化可以提高內(nèi)存的使用效率。
缺點(diǎn):序列化和反序列化有一定的開銷,同時(shí)每次更新屬性都需要把全部數(shù)據(jù)取出進(jìn)行反序列化,更新后再序列化到Redis中。 -
序列化字符串后,將用戶信息序列化后用一個(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è)功能:
- 全選功能,獲取所有該用戶的所有購物車商品;
- 商品數(shù)量,購物車圖標(biāo)上要顯示購物車?yán)锷唐返目倲?shù);
- 刪除,要能移除購物車?yán)锬硞€(gè)商品;
- 增加或減少某個(gè)商品的數(shù)量。
在之前很多電商網(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)用場景。
列表類型有兩個(gè)特點(diǎn):
- 列表中的元素是有序的,這就意味著可以通過索引下標(biāo)獲取某個(gè)元素或者某個(gè)范圍內(nèi)的元素列表。
- 列表中的元素可以是重復(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)速度。
上圖可表示為:
# 深圳衛(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ù)。
如上熱搜榜,以日期為 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ā)布