【大厂面试】面试官看了赞不绝口的Redis笔记
文章目錄
- 一、Redis簡介
- 二、Redis API的使用和理解
- (一)通用命令
- (二)單線程架構
- (三)數據結構和內部編碼
- (四)字符串
- (五)hash (字典)
- (六)列表
- (七)Set集合
- (八)zset (有序集合)
- 三、Redis 客戶端操作
- 說明
一、Redis簡介
Redis(Remote Dictionary Server)是一個使用ANSI C編寫的開源、支持網絡、基于內存、可選持久性的鍵值對存儲數據庫,也是于開發或者運維都是必須要掌握的非關系型數據庫。
Redis可作為高性能 Key-Value服務器,擁有多種數據結構,并提供豐富的功能以及對高可用分布式的支持。
Redis的具有以下:1. 速度快;2. 功能豐富;3. 可持久化;4. 簡單;5. 多種數據結構;6. 主從復制;7. 支持多種編輯語言;8. 高可用、分布式等。
二、Redis API的使用和理解
下面我們會依次詳細探討常用的Redis 的API。
再說之前,給大家推薦一個網站,用于查閱Redis API: http://redisdoc.com/index.html
文中的部分演示結果來源于該網站
傳送門:Redis 命令參考
(一)通用命令
因為通用命令會涉及Redis的數據結構操作,而Redis的數據結構操作也會涉及到通用命令,所以這兩部分要結合著看。
不同數據結構的操作內容在下面
1. KEYS
| 功能描述 | 查找所有符合給定模式 pattern 的 key |
| 時間復雜度 | O(N), N 為數據庫中 key 的數量。 |
功能描述方面舉例:
- KEYS * 匹配數據庫中所有 key 。
- KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
- KEYS h*llo 匹配 hllo 和 heeeeello 等。
- KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
特殊符號用 \ 隔開。
示例
redis> MSET one 1 two 2 three 3 four 4 # 一次設置 4 個 key OKredis> KEYS *o* 1) "four" 2) "two" 3) "one"redis> KEYS t?? 1) "two"redis> KEYS t[w]* 1) "two"redis> KEYS * # 匹配數據庫內所有 key 1) "four" 2) "three" 3) "two" 4) "one"在生產環境中,使用keys命令取出所有key并沒有什么意義,而且Redis是單線程應用,如果Redis中存的key很多,使用keys命令會阻塞其他命令執行,所以keys命令一般不在生產環境中使用
2. DBSIZE
| 功能描述 | 返回當前數據庫的 key 的數量。 |
| 時間復雜度 | 時間復雜度: O(1) |
示例
redis> DBSIZE (integer) 5redis> SET new_key "hello_moto" # 增加一個 key 試試 OKredis> DBSIZE (integer) 6Redis內置一個計數器,可以實時更新Redis中key的總數,因此dbsize的時間復雜度為O(1),可以在線上使用。
3. EXISTS
| 功能描述 | 檢查給定 key 是否存在。若 key 存在,返回 1 ,否則返回 0 。 |
| 時間復雜度 | O(1) |
4. DEL
| 功能描述 | 刪除給定的一個或多個 key 。不存在的 key 會被忽略。返回值是被刪除 key 的數量。 |
| 時間復雜度 | O(N), N 為被刪除的 key 的數量,其中刪除單個字符串類型的 key ,時間復雜度為O(1);刪除單個列表、集合、有序集合或哈希表類型的 key ,時間復雜度為O(M), M 為以上數據結構內的元素數量。 |
5. EXPIRE
| 功能描述 | 為給定 key 設置生存時間,當 key 過期時(生存時間為 0 ),它會被自動刪除。設置成功返回 1 。 當 key 不存在或者不能為 key 設置生存時間時(比如在低于 2.1.3 版本的 Redis 中你嘗試更新 key 的生存時間),返回 0 。 |
| 時間復雜度 | O(1) |
6. TTL
| 功能描述 | 以秒為單位,返回給定 key 的剩余生存時間(TTL, time to live)。 |
| 時間復雜度 | O(1) |
7. PERSIST
| 功能描述 | 移除給定 key 的生存時間,將這個 key 從“易失的”(帶生存時間 key )轉換成“持久的”(一個不帶生存時間、永不過期的 key )。當生存時間移除成功時,返回 1 . 如果 key 不存在或 key 沒有設置生存時間,返回 0 。 |
| 時間復雜度 | O(1) |
8. TYPE
| 功能描述 | 返回 key 所儲存的值的類型。 |
| 時間復雜度 | O(1) |
值的類型有:
- none (key不存在)
- string (字符串)
- list (列表)
- set (集合)
- zset (有序集)
- hash (哈希表)
- stream (流)
8. del
| 功能描述 | 刪除給定的一個或多個 key 。不存在的 key 會被忽略。返回值是被刪除 key 的數量。 |
| 時間復雜度 | O(N), N 為被刪除的 key 的數量,其中刪除單個字符串類型的 key ,時間復雜度為O(1);刪除單個列表、集合、有序集合或哈希表類型的 key ,時間復雜度為O(M), M 為以上數據結構內的元素數量。 |
9. scan
| 功能描述 | 查找(limit個)(符合給定模式 pattern )的 key ,返回值是符合 條件的key |
| 時間復雜度 | 增量式迭代命令每次執行的復雜度為 O(1) , 對數據集進行一次完整迭代的復雜度為 O(N) , 其中 N 為數據集中的元素數量。 |
剛開始我們已經介紹過了keys,它的缺點非常明顯:
為了解決這個問題, 2.8 版本中的Redis加?了scan。
scan 相? keys 具備有以下特點:
scan 參數提供了三個參數,第?個是 cursor 整數值,第?個是key 的正則模式,第三個是遍歷的limit。第?次遍歷時,cursor 值為 0,然后將返回結果中第?個整數值作為下?次遍歷的cursor。?直遍歷到返回的 cursor 值為 0 時結束。
127.0.0.1:6379> scan 0 match key99* count 1000 1) "13976" 2) 1) "key9911"2) "key9974"3) "key9994"4) "key9910"5) "key9907"6) "key9989"7) "key9971"8) "key99" 127.0.0.1:6379> scan 13976 match key99* count 1000 1) "1996" 2) 1) "key9982"2) "key9997" 3) "key9963"4) "key996"5) "key9912"6) "key9999"7) "key9921"8) "key994"9) "key9956"10) "key9919" 127.0.0.1:6379> scan 1996 match key99* count 1000 1) "12594" 2) 1) "key9939"2) "key9941"3) "key9967"4) "key9938"5) "key9906"6) "key999"7) "key9909"... 127.0.0.1:6379> scan 11687 match key99* count 1000 1) "0" 2) 1) "key9969"2) "key998"3) "key9986"4) "key9968"5) "key9965"6) "key9990"7) "key9915"8) "key9928"9) "key9908"剛才也強調了,limit是遍歷的key的個數,從上?的過程可以看到雖然提供的 limit 是 1000,但是返回的結果,有的只有 10 個。
scan 指令返回的游標就是第?維數組的位置索引,我們將這個位置索引稱為槽 (slot)。如果不考慮字典的擴容縮容,直接按數組下標挨個遍歷就?了。limit 參數就表示需要遍歷的槽位數,之所以返回的結果可能多可能少,是因為不是所有的槽位上都會掛接鏈表,有些槽位可能是空的,還有些槽位上掛接的鏈表上的元素可能會有多個。每?次遍歷都會將 limit 數量的槽位上掛接的所有鏈表元素進?模式匹配過濾后,?次性返回給客戶端。
scan除了可以遍歷所有的 key 之外,還可以對指定的容器集合進?遍歷。
?如 zscan 遍歷 zset 集合元素,hscan遍歷 hash 字典的元素、sscan 遍歷 set 集合的元素。
常用的通用命令已經介紹完了,下面我們探討一個Redis總要面臨的問題:
在 Redis 中有可能會形成很?的對象,?如?個?個很?的 zset。
這樣的對象對 Redis 的集群數據遷移帶來了挑戰,在集群環境下,如果某個 key 太?,可能會導致數據遷移卡頓。另外在內存分配上,如果 key 太?,那么當它需要擴容時,會?次性申請更?的?塊內存,這也可能會導致卡頓。如果這個? key 被刪除,內存會?次性回收,卡頓現象也有可能再產?。
在平時的開發中,盡量避免? key 的產?。如果遇到 Redis 的內存?起?落的現象,有可能是因為? key 導致的,這時候你就需要定位這個大 key,進?步定位出具體的業務來源,然后再改進相關業務代碼設計。
關于大key的尋找,可以通過 scan 指令,對于掃描出來的每?個 key,使? type 指令獲得 key 的類型,然后使?相應數據結構的 size 或者 len ?法來得到它的??,對于每?種類型,保留??的前 N 名作為掃描結果展示出來。(需要編寫腳本)
除此之外, Redis 官?在redis-cli 指令中提供了這樣的掃描功能
redis-cli -h 127.0.0.1 -p 7001 –-bigkeys如果你擔?這個指令會?幅抬升 Redis 的 ops ,還可以增加?個休眠參數。
redis-cli -h 127.0.0.1 -p 7001 –-bigkeys -i 0.1 # 每隔 100 條 scan 指令就會休眠 0.1s,ops 就不會劇烈抬升,但是掃描的時間會變?。redis中的OPS 即operation per second 每秒操作次數。意味著每秒對Redis的持久化操作
(二)單線程架構
Redis內部使用單線程架構。Redis一個瞬間只能執行一條命令,不能執行兩條命令
Redis單線程速度這么快的原因可大致歸結三個:
1.純內存
Redis把所有的數據都保存在內存中,而內存的響應速度是非??斓?/p>
2.非阻塞IO
Redis使用epoll異步非阻塞模型 ,Redis自身實現了事件處理
3.避免線程切換和競態消耗
在使用多線程編程中,線程之間的切換也會消耗一部分CPU資源,如果不合理的實現多線程編程,可能比單線程還要慢
主要原因是 純內存。
不過第二條和第三條倒是面試中經常會問到,尤其是第二條。為了便于大家,理解更深刻,我們這里探討一下操作系統的IO
用戶程序進行IO的讀寫,依賴于底層的IO讀寫,基本上會用到底層的read&write兩大系統調用。
read系統調用,并不是直接從物理設備把數據讀取到內存中;write系統調用,也不是直接把數據寫入到物理設備。上層應用無論是調用操作系統的read,還是調用操作系統的write,都會涉及緩沖區。具體來說,調用操作系統的read,是把數據從內核緩沖區復制到進程緩沖區;而write系統調用,是把數據從進程緩沖區復制到內核緩沖區。
緩沖區的目的,是為了減少頻繁地與設備之間的物理交換。外部設備的直接讀寫,涉及操作系統的中斷。發生系統中斷時,需要保存之前的進程數據和狀態等信息,而結束中斷之后,還需要恢復之前的進程數據和狀態等信息。為了減少這種底層系統的時間損耗、性能損耗,于是出現了內存緩沖區。
有了內存緩沖區,上層應用使用read系統調用時,僅僅把數據從內核緩沖區復制到上層應用的緩沖區(進程緩沖區);上層應用使用write系統調用時,僅僅把數據從進程緩沖區復制到內核緩沖區中。底層操作會對內核緩沖區進行監控,等待緩沖區達到一定數量的時候,再進行IO設備的中斷處理,集中執行物理設備的實際IO操作,這種機制提升了系統的性能。至于什么時候中斷(讀中斷、寫中斷),由操作系統的內核來決定,用戶程序則不需要關心。
從數量上來說,在Linux系統中,操作系統內核只有一個內核緩沖區。而每個用戶程序(進程),有自己獨立的緩沖區,叫作進程緩沖區。所以,用戶程序的IO讀寫程序,在大多數情況下,并沒有進行實際的IO操作,而是在進程緩沖區和內核緩沖區之間直接進行數據的交換。
有了對操作系統IO的基本認識之后,還要提一下操作系統四種主要的IO模型
Redis的IO模型是IO多路復用,有意思的是Java 的NIO模型,也是IO多路復用(不是同步非阻塞IO)
有興趣的可以查閱相關的資料,或者不著急的 可以等我接下來的博文(過段日子會有網絡編程詳解的博文,對IO作深入探究)
這里還要強調一下,由于Redis單線程一次只運行一條命令,我們要拒絕長(慢)命令
keys flushallflushdbslow lua scriptmutil/execoperate(三)數據結構和內部編碼
Redis每種數據結構及對應的內部編碼如下圖所示
你會發現 數據結構 內部編碼方式有不同的方式,其實這是時間換空間 空間換時間的做法,選擇何種內部編碼要結合實際情況。
(四)字符串
字符串 string 是 Redis 最簡單的數據結構。Redis 所有的數據結構都是以唯?的 key 字符串作為名稱,然后通過這個唯? key 值來獲取相應的 value 數據。不同類型的數據結構的差異就在于 value 的結構不?樣。
字符串的value值類型有三種:1. 字符串;2. 整型;3.二進制。
Redis 的字符串是動態字符串,是可以修改的字符串,內部結構實現上類似于 Java 的 ArrayList,采?預分配冗余空間的?式來減少內存的頻繁分配,當字符串?度?于 1M時,擴容都是加倍現有的空間,如果超過 1M,擴容時?次只會多擴1M 的空間。需要注意的是字符串最??度為 512M。
我們看一下它的常用API
1. GET
| 功能描述 | 返回與鍵 key 相關聯的字符串值。 如果鍵 key 不存在, 那么返回特殊值 nil ; 否則, 返回鍵 key 的值。如果鍵 key 的值并非字符串類型, 那么返回一個錯誤, 因為 GET 命令只能用于字符串值。 |
| 時間復雜度 | O(1) |
示例
對不存在的鍵 key 或是字符串類型的鍵 key 執行 GET 命令:
對不是字符串類型的鍵 key 執行 GET 命令:
redis> DEL db (integer) 1redis> LPUSH db redis mongodb mysql (integer) 3redis> GET db (error) ERR Operation against a key holding the wrong kind of value2. set
| 功能描述 | 將字符串值 value 關聯到 key 。如果 key 已經持有其他值, SET 就覆寫舊值, 無視類型。當 SET 命令對一個帶有生存時間(TTL)的鍵進行設置之后, 該鍵原有的 TTL 將被清除。 |
| 時間復雜度 | O(1) |
可選參數
從 Redis 2.6.12 版本開始, SET 命令的行為可以通過一系列參數來修改:
- EX seconds : 將鍵的過期時間設置為 seconds 秒。 執行 SET key value EX seconds 的效果等同于執行 SETEX key seconds value 。
- PX milliseconds : 將鍵的過期時間設置為 milliseconds 毫秒。 執行 SET key value PX milliseconds 的效果等同于執行 PSETEX key milliseconds value 。
- NX : 只在鍵不存在時, 才對鍵進行設置操作。 執行 SET key value NX 的效果等同于執行 SETNX key value 。
- XX : 只在鍵已經存在時, 才對鍵進行設置操作。
關于返回值
- 在 Redis 2.6.12 版本以前, SET 命令總是返回 OK 。
- 從 Redis 2.6.12 版本開始, SET 命令只在設置操作成功完成時才返回 OK ; 如果命令使用了 NX 或者 XX 選項, 但是因為條件沒達到而造成設置操作未執行, 那么命令將返回空批量回復(NULL Bulk Reply)。
3. INCR
| 功能描述 | 為鍵 key 儲存的數字值加上一。如果鍵 key 不存在, 那么它的值會先被初始化為 0 , 然后再執行 INCR 命令。如果鍵 key 儲存的值不能被解釋為數字, 那么 INCR 命令將返回一個錯誤。本操作的值限制在 64 位(bit)有符號數字表示之內。返回值是DECR 命令會返回鍵 key 在執行減一操作之后的值。 |
| 時間復雜度 | O(1) |
對儲存數字值的鍵 key 執行 DECR 命令:
redis> SET page_view 20 OKredis> INCR page_view (integer) 21redis> GET page_view # 數字值在 Redis 中以字符串的形式保存 "21"對不存在的鍵執行 DECR 命令:
redis> EXISTS count (integer) 0redis> DECR count (integer) -14. DECR
| 功能描述 | 將鍵 key 儲存的整數值減去減量 decrement 。如果鍵 key 不存在, 那么鍵 key 的值會先被初始化為 0 , 然后再執行 DECRBY 命令。如果鍵 key 儲存的值不能被解釋為數字, 那么 DECRBY 命令將返回一個錯誤。本操作的值限制在 64 位(bit)有符號數字表示之內。返回值是DECRBY 命令會返回鍵在執行減法操作之后的值。 |
| 時間復雜度 | O(1) |
對已經存在的鍵執行 DECRBY 命令:
redis> SET count 100 OKredis> DECRBY count 20 (integer) 80對不存在的鍵執行 DECRBY 命令:
redis> EXISTS pages (integer) 0redis> DECRBY pages 10 (integer) -105. INCRBY
| 功能描述 | 為鍵 key 儲存的數字值加上增量 increment 。如果鍵 key 不存在, 那么鍵 key 的值會先被初始化為 0 , 然后再執行 INCRBY 命令。如果鍵 key 儲存的值不能被解釋為數字, 那么 INCRBY 命令將返回一個錯誤。本操作的值限制在 64 位(bit)有符號數字表示之內。返回值為在加上增量 increment 之后, 鍵 key 當前的值。 |
| 時間復雜度 | O(1) |
示例演示與上面類似
6. DECRBY
| 功能描述 | 將鍵 key 儲存的整數值減去減量 decrement 。如果鍵 key 不存在, 那么鍵 key 的值會先被初始化為 0 , 然后再執行 DECRBY 命令。如果鍵 key 儲存的值不能被解釋為數字, 那么 DECRBY 命令將返回一個錯誤。本操作的值限制在 64 位(bit)有符號數字表示之內。 返回值DECRBY 命令會返回鍵在執行減法操作之后的值。 |
| 時間復雜度 | O(1) |
示例演示與上面類似
使用上面這一些命令,其實我們就可以做一些事情了。
應用
1.比如說記錄每個用戶博文的訪問量
incr userid:pageview(單線程:無競爭)
2.緩存用戶的基本信息(數據源在 MySQL中),信息被序列化存放在value中。
一般而言,需要通過我們自定義規則的key,從Redis獲取value,如果key存在的話,則直接獲取value使用;如果不存在的話,從Mysql中讀取使用,然后存在Redis中。
主要的命令是 get 和 set
3.分布式id生成器
如果集群規模和運算不太復雜的話,可以用Redis生成分布式id,因為Redis單線程的特點,一次只執行一條指令,保證了id值的唯一。
主要的命令還是incr
7. SETNX
| 功能描述 | 只在鍵 key 不存在的情況下, 將鍵 key 的值設置為 value 。若鍵 key 已經存在, 則 SETNX 命令不做任何動作。命令在設置成功時返回 1 , 設置失敗時返回 0 。 |
| 時間復雜度 | O(1) |
8. SETEX
| 功能描述 | 將鍵 key 的值設置為 value , 并將鍵 key 的生存時間設置為 seconds 秒鐘。如果鍵 key 已經存在, 那么 SETEX 命令將覆蓋已有的值。命令在設置成功時返回 OK 。 當 seconds 參數不合法時, 命令將返回一個錯誤。 |
| 時間復雜度 | O(1) |
SETEX 命令的效果和以下兩個命令的效果類似:
SET key value EXPIRE key seconds # 設置生存時間SETEX 和這兩個命令的不同之處在于 SETEX 是一個原子(atomic)操作, 它可以在同一時間內完成設置值和設置過期時間這兩個操作, 因此 SETEX 命令在儲存緩存的時候非常實用。
這兩個命令的典型應用就是分布式鎖了。
?如?個操作要修改?戶的狀態,修改狀態需要先讀出?戶的狀態,在內存?進?修改,改完了再存回去。如果這樣的操作同時進?了,就會出現并發問題。這個時候就要使?到分布式鎖來限制程序的并發執?。Redis 分布式鎖使??常?泛,必須要掌握。
分布式鎖本質上要實現的?標就是在 Redis ??占?個“位置”,當別的進程也要來占時,發現“位置”被占了,就只好放棄或者稍后
再試。
占位置?般是使? setnx(set if not exists) 指令,只允許被?個客戶端占據。先來先占,?完了,再調? del 指令釋放位置。
如果邏輯執?到中間出現異常了,可能會導致 del指令沒有被調?,這樣就會陷?死鎖,鎖永遠得不到釋放。于是我們在拿到鎖之后,再給鎖加上?個過期時間。
但如果在 setnx 和 expire 之間服務器進程突然掛掉了,可能是因為機器掉電或者是被?為殺掉的,就會導致expire 得不到執?,也會造成死鎖。
原因是 setnx 和 expire 是兩條指令?不能保證都一定成功執行。如果這兩條指令可以?起執?就不會出現問題(要么成功,要么失敗)。所以說setex是最佳的方案
上面就是分布式鎖的基本思想。但是在真正投入使用的時候,還會面臨一個常見的問題:超時問題
Redis 的分布式鎖不能解決超時問題,如果在加鎖和釋放鎖之間的邏輯執?的時間太?,超出了鎖的超時限制,就會出現問題。這時候第?個線程持有的鎖過期了,臨界區的邏輯沒有執?完,而第?個線程就提前重新持有了這把鎖,導致臨界區代碼不能嚴格地串?執?。
為了避免這個問題,Redis 分布式鎖不要?于較?時間的任務。
我們會在下篇文章,也就是分布式章節繼續探討分布式鎖。
9. MSET
| 功能描述 | 同時為多個鍵設置值。如果某個給定鍵已經存在, 那么 MSET 將使用新值去覆蓋舊值, 如果這不是你所希望的效果, 請考慮使用 MSETNX 命令, 這個命令只會在所有給定鍵都不存在的情況下進行設置。MSET 是一個原子性(atomic)操作, 所有給定鍵都會在同一時間內被設置, 不會出現某些鍵被設置了但是另一些鍵沒有被設置的情況。MSET 命令總是返回 OK 。 |
| 時間復雜度 | O(N),其中 N 為被設置的鍵數量。 |
同時對多個鍵進行設置:
redis> MSET date "2012.3.30" time "11:00 a.m." weather "sunny" OKredis> MGET date time weather 1) "2012.3.30" 2) "11:00 a.m." 3) "sunny"覆蓋已有的值:
redis> MGET k1 k2 1) "hello" 2) "world"redis> MSET k1 "good" k2 "bye" OKredis> MGET k1 k2 1) "good" 2) "bye"10 . MGET
| 功能描述 | 返回給定的一個或多個字符串鍵的值。如果給定的字符串鍵里面, 有某個鍵不存在, 那么這個鍵的值將以特殊值 nil 表示。MGET 命令將返回一個列表, 列表中包含了所有給定鍵的值。 |
| 時間復雜度 | O(N),其中 N 為被設置的鍵數量。 |
下面說說mset和mget的好處
不使用mget和mset::
客戶端和服務器端可能不在同一個地方
n次get/set=n次網絡時間+n次命令時間
一次mget/mset:
1次mget/mset=1次網絡時間+n次命令時間
隨著n的增大,差距一下子就體現出來了。
下面的命令不太常用,大體過一下:
10. GETSET
GETSET key value
將鍵 key 的值設為 value , 并返回鍵 key 在被設置之前的舊值。
11. STRLEN
STRLEN key
返回鍵 key 儲存的字符串值的長度
12. APPEND
APPEND key value
如果鍵 key 已經存在并且它的值是一個字符串, APPEND 命令將把 value 追加到鍵 key 現有值的末尾。
13. INCRBYFLOAT
INCRBYFLOAT key increment
為鍵 key 儲存的值加上浮點數增量 increment 。
如果鍵 key 不存在, 那么 INCRBYFLOAT 會先將鍵 key 的值設為 0 , 然后再執行加法操作。
如果命令執行成功, 那么鍵 key 的值會被更新為執行加法計算之后的新值, 并且新值會以字符串的形式返回給調用者。
無論是鍵 key 的值還是增量 increment , 都可以使用像 2.0e7 、 3e5 、 90e-2 那樣的指數符號(exponential notation)來表示, 但是, 執行 INCRBYFLOAT 命令之后的值總是以同樣的形式儲存, 也即是, 它們總是由一個數字, 一個(可選的)小數點和一個任意長度的小數部分組成(比如 3.14 、 69.768 ,諸如此類), 小數部分尾隨的 0 會被移除, 如果可能的話, 命令還會將浮點數轉換為整數(比如 3.0 會被保存成 3 )。
此外, 無論加法計算所得的浮點數的實際精度有多長, INCRBYFLOAT 命令的計算結果最多只保留小數點的后十七位。
當以下任意一個條件發生時, 命令返回一個錯誤:
- 鍵 key 的值不是字符串類型(因為 Redis 中的數字和浮點數都以字符串的形式保存,所以它們都屬于字符串類型);
- 鍵 key 當前的值或者給定的增量 increment 不能被解釋(parse)為雙精度浮點數。
14. GETRANGE
GETRANGE key start end
返回鍵 key 儲存的字符串值的指定部分, 字符串的截取范圍由 start 和 end 兩個偏移量決定 (包括 start 和 end 在內)。
負數偏移量表示從字符串的末尾開始計數, -1 表示最后一個字符, -2 表示倒數第二個字符, 以此類推。
GETRANGE 通過保證子字符串的值域(range)不超過實際字符串的值域來處理超出范圍的值域請求。
15. SETRANGE
SETRANGE key offset value
從偏移量 offset 開始, 用 value 參數覆寫(overwrite)鍵 key 儲存的字符串值。
不存在的鍵 key 當作空白字符串處理。
SETRANGE 命令會確保字符串足夠長以便將 value 設置到指定的偏移量上, 如果鍵 key 原來儲存的字符串長度比偏移量小(比如字符串只有 5 個字符長,但你設置的 offset 是 10 ), 那么原字符和偏移量之間的空白將用零字節(zerobytes, “\x00” )進行填充。
因為 Redis 字符串的大小被限制在 512 兆(megabytes)以內, 所以用戶能夠使用的最大偏移量為 2^29-1(536870911) , 如果你需要使用比這更大的空間, 請使用多個 key 。
在對字符串類型有了整體的了解之后,我們看看它具體的結構
Redis 的字符串名字是SDS(Simple Dynamic String)。它的結構是?個帶?度信息的字節數組。
struct SDS<T> {T capacity; // 數組容量T len; // 數組?度byte flags; // 特殊標識位,不理睬它byte[] content; // 數組內容 }capacity 表示所分配數組的?度,len 表示字符串的實際?度。前?API提到?持 append操作(字符串是可修改的)。如果數組沒有冗余空間,那么追加操作必然涉及到分配新數組,然后將舊內容復制過來,再 append 新內容。如果字符串的?
度?常?,這樣的內存分配和復制開銷就會?常?。
上?的 SDS 結構使?了范型 T,這是Redis 對內存做出的優化,不同?度的字符串使?不同的結構體來表示,字符串?較短時,len 和 capacity 可以使? byte 和 short來表示。
Redis 規定字符串的?度不得超過 512M 字節。創建字符串時 len和 capacity ?樣?,不會多分配冗余空間,這是因為絕?多數場景下我們不會使? append 操作來修改字符串。
(五)hash (字典)
Redis 的字典結構為數組 +鏈表?維結構。第?維 hash 的數組位置碰撞時,就會將碰撞的元素使?鏈表串接起來。Redis 的字典的值只能是字符串。當字典很大的時候,會進行rehash,Redis 為了?性能,不能堵塞服務,采?了漸進式 rehash 策略。
漸進式 rehash 保留新舊兩個 hash 結構,查詢時會同時查詢兩個 hash 結構,然后在后續的定時任務中以及hash 操作指令中,循序漸進地將舊 hash 的內容?點點遷移到新的hash 結構中。當搬遷完成了,就會使?新的hash結構取?代之。當 hash 移除了最后?個元素之后,該數據結構?動被刪除,內存被回收。
下面我們看一下它的API, 所有hash的命令都是h開頭
1. HSET hash field value
時間復雜度: O(1)
將哈希表 hash 中域 field 的值設置為 value 。如果給定的哈希表并不存在, 那么一個新的哈希表將被創建并執行 HSET 操作。如果域 field 已經存在于哈希表中, 那么它的舊值將被新值 value 覆蓋。當 HSET 命令在哈希表中新創建 field 域并成功為它設置值時, 命令返回 1 ; 如果域 field 已經存在于哈希表, 并且 HSET 命令成功使用新值覆蓋了它的舊值, 那么命令返回 0 。
設置一個新域:
redis> HSET website google "www.g.cn" (integer) 1redis> HGET website google "www.g.cn"對一個已存在的域進行更新:
redis> HSET website google "www.google.com" (integer) 0redis> HGET website google "www.google.com"2.HGET hash field
時間復雜度: O(1)
返回哈希表中給定域的值。HGET 命令在默認情況下返回給定域的值。如果給定域不存在于哈希表中, 又或者給定的哈希表并不存在, 那么命令返回 nil 。
域存在的情況:
redis> HSET homepage redis redis.com (integer) 1redis> HGET homepage redis "redis.com"域不存在的情況:
redis> HGET site mysql (nil)3.HDEL
HDEL key field [field …]
O(N), N 為要刪除的域的數量。
刪除哈希表 key 中的一個或多個指定域,不存在的域將被忽略。返回值為被成功移除的域的數量,不包括被忽略的域。
4. HSETNX hash field value
時間復雜度: O(1)
當且僅當域 field 尚未存在于哈希表的情況下, 將它的值設置為 value 。如果給定域已經存在于哈希表當中, 那么命令將放棄執行設置操作。如果哈希表 hash 不存在, 那么一個新的哈希表將被創建并執行 HSETNX 命令。HSETNX 命令在設置成功時返回 1 , 在給定域已經存在而放棄執行設置操作時返回 0 。
域尚未存在, 設置成功:
redis> HSETNX database key-value-store Redis (integer) 1redis> HGET database key-value-store "Redis"域已經存在, 設置未成功, 域原有的值未被改變:
redis> HSETNX database key-value-store Riak (integer) 0redis> HGET database key-value-store "Redis"5. HLEN
時間復雜度:O(1)
返回哈希表 key 中域的數量。當 key 不存在時,返回 0 。
redis> HSET db redis redis.com (integer) 1redis> HSET db mysql mysql.com (integer) 1redis> HLEN db (integer) 2redis> HSET db mongodb mongodb.org (integer) 1redis> HLEN db (integer) 36.HMSET
HMSET key field value [field value …]
時間復雜度:O(N), N 為 field-value 對的數量。
同時將多個 field-value (域-值)對設置到哈希表 key 中。此命令會覆蓋哈希表中已存在的域。如果 key 不存在,一個空哈希表被創建并執行 HMSET 操作。如果命令執行成功,返回 OK 。當 key 不是哈希表(hash)類型時,返回一個錯誤。
redis> HMSET website google www.google.com yahoo www.yahoo.com OKredis> HGET website google "www.google.com"redis> HGET website yahoo "www.yahoo.com"7. HMGET
HMGET key field [field …]
時間復雜度:O(N), N 為給定域的數量。
返回哈希表 key 中,一個或多個給定域的值。
如果給定的域不存在于哈希表,那么返回一個 nil 值。因為不存在的 key 被當作一個空哈希表來處理,所以對一個不存在的 key 進行 HMGET 操作將返回一個只帶有 nil 值的表。具體返回一個包含多個給定域的關聯值的表,表值的排列順序和給定域參數的請求順序一樣。
8.HINCRBY
HINCRBY key field increment
時間復雜度:O(1)
為哈希表 key 中的域 field 的值加上增量 increment 。增量也可以為負數,相當于對給定域進行減法操作。如果 key 不存在,一個新的哈希表被創建并執行 HINCRBY 命令。如果域 field 不存在,那么在執行命令前,域的值被初始化為 0 。對一個儲存字符串值的域 field 執行 HINCRBY 命令將造成一個錯誤。本操作的值被限制在 64 位(bit)有符號數字表示之內。執行 HINCRBY 命令之后,返回值哈希表 key 中域 field 的值。
9. HKEYS
時間復雜度:O(N), N 為哈希表的大小。
返回哈希表 key 中的所有域。即一個包含哈希表中所有域的表。當 key 不存在時,返回一個空表。
# 哈希表非空 redis> HMSET website google www.google.com yahoo www.yahoo.com OKredis> HKEYS website 1) "google" 2) "yahoo"# 空哈希表/key不存在 redis> EXISTS fake_key (integer) 0redis> HKEYS fake_key (empty list or set)10.HVALS
HVALS key
時間復雜度:O(N), N 為哈希表的大小。
返回哈希表 key 中所有域的值。即一個包含哈希表中所有值的表。當 key 不存在時,返回一個空表。
11.HGETALL
時間復雜度:O(N), N 為哈希表的大小。
HGETALL key
返回哈希表 key 中,所有的域和值。在返回值里,緊跟每個域名(field name)之后是域的值(value),所以返回值的長度是哈希表大小的兩倍。返回值以列表形式返回哈希表的域和域的值。若 key 不存在,返回空列表。
小心單線程 數據量大的話 會比較慢
12. hsetnx
時間復雜度: O(1)
當且僅當域 field 尚未存在于哈希表的情況下, 將它的值設置為 value 。如果給定域已經存在于哈希表當中, 那么命令將放棄執行設置操作。如果哈希表 hash 不存在, 那么一個新的哈希表將被創建并執行 HSETNX 命令。HSETNX 命令在設置成功時返回 1 , 在給定域已經存在而放棄執行設置操作時返回 0 。
域尚未存在, 設置成功:
redis> HSETNX database key-value-store Redis (integer) 1redis> HGET database key-value-store "Redis"域已經存在, 設置未成功, 域原有的值未被改變:
redis> HSETNX database key-value-store Riak (integer) 0redis> HGET database key-value-store "Redis"13. hincrbyfloat
HINCRBYFLOAT key field increment
時間復雜度:O(1)
為哈希表 key 中的域 field 加上浮點數增量 increment 。如果哈希表中沒有域 field ,那么 HINCRBYFLOAT 會先將域 field 的值設為 0 ,然后再執行加法操作。如果鍵 key 不存在,那么 HINCRBYFLOAT 會先創建一個哈希表,再創建域 field ,最后再執行加法操作。當以下任意一個條件發生時,返回一個錯誤:
- 域 field 的值不是字符串類型(因為 redis 中的數字和浮點數都以字符串的形式保存,所以它們都屬于字符串類型)
- 域 field 當前的值或給定的增量 increment 不能解釋(parse)為雙精度浮點數(double precision floating point number)
返回值為返回值:執行加法操作之后 field 域的值。
知道上面的命令后,就可以做一些事情了。
應用
類似于字符串,我們可以記錄網站每個用戶個人主頁的訪問量
當然還有緩存用戶信息。
對于記錄個人主頁的訪問量,自然字符串要比hash更好點。
但是對于緩存用戶新信息這種邏輯要好好斟酌一下
字符串Key:Value的結構:(第一種方案 String-v1)
key: 'user:userId' value: {"name": "chenxiao","age":100,"pageview": 8000000 }value是序列化的結果
字符串Key:Value的結構:(第二種方案 String-v2)
key: user:userId:name value: chenxiaokey: user:userId:age value: 100key: user:userId:pageView value: 800000相比上面的方案更新屬性更方便 只需要一條
再看看hash形式的方案(hash)
key:user:userIdfield:name value:chenxiaofield:age value:100field:pageView vale:800003種方案比較:
| String v1 | 編程簡單,可能節約內存 | 1.序列化開銷 2.設置屬性要操作整個數據。 |
| String v2 | 直觀,可以部分更新 | 1.內存占用較大 2.key較為分散 |
| hash | 直觀 節省空間 可以部分更新 | 1.編程稍微復雜 2.ttl不好控制 |
(六)列表
Redis 的列表相當于 Java 語???的 LinkedList,數據結構形式為鏈表,插?和刪除操作????#xff0c;時間復雜度為O(1),但是索引定位很慢,時間復雜度為 O(n)。
當列表彈出了最后?個元素之后,該數據結構?動被刪除,內存被回收。
插入元素后,各元素的相對位置確定,遍歷的結果也與之保持一致。鏈表元素可以重復。下面我們看看它的API
1.LPUSH
LPUSH key value [value …]
時間復雜度: O(1)
將一個或多個值 value 插入到列表 key 的表頭。如果有多個 value 值,那么各個 value 值按從左到右的順序依次插入到表頭: 比如說,對空列表 mylist 執行命令 LPUSH mylist a b c ,列表的值將是 c b a ,這等同于原子性地執行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三個命令。如果 key 不存在,一個空列表會被創建并執行 LPUSH 操作。當 key 存在但不是列表類型時,返回一個錯誤。正常返回列表的長度。
# 加入單個元素 redis> LPUSH languages python (integer) 1# 加入重復元素 redis> LPUSH languages python (integer) 2redis> LRANGE languages 0 -1 # 列表允許重復元素 1) "python" 2) "python"# 加入多個元素 redis> LPUSH mylist a b c (integer) 3redis> LRANGE mylist 0 -1 1) "c" 2) "b" 3) "a"2.RPUSH
RPUSH key value [value …]
時間復雜度: O(1)
將一個或多個值 value 插入到列表 key 的表尾(最右邊)。如果有多個 value 值,那么各個 value 值按從左到右的順序依次插入到表尾:比如對一個空列表 mylist 執行 RPUSH mylist a b c ,得出的結果列表為 a b c ,等同于執行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c 。如果 key 不存在,一個空列表會被創建并執行 RPUSH 操作。當 key 存在但不是列表類型時,返回一個錯誤。正常返回列表的長度。
# 添加單個元素 redis> RPUSH languages c (integer) 1# 添加重復元素 redis> RPUSH languages c (integer) 2redis> LRANGE languages 0 -1 # 列表允許重復元素 1) "c" 2) "c"# 添加多個元素 redis> RPUSH mylist a b c (integer) 3redis> LRANGE mylist 0 -1 1) "a" 2) "b" 3) "c"3.LINSERT
LINSERT key BEFORE|AFTER pivot value
時間復雜度: O(N), N 為尋找 pivot 過程中經過的元素數量。
將值 value 插入到列表 key 當中,位于值 pivot 之前或之后。當 pivot 不存在于列表 key 時,不執行任何操作。當 key 不存在時, key 被視為空列表,不執行任何操作。如果 key 不是列表類型,返回一個錯誤。如果命令執行成功,返回插入操作完成之后,列表的長度。 如果沒有找到 pivot ,返回 -1 。 如果 key 不存在或為空列表,返回 0 。
redis> RPUSH mylist "Hello" (integer) 1redis> RPUSH mylist "World" (integer) 2redis> LINSERT mylist BEFORE "World" "There" (integer) 3redis> LRANGE mylist 0 -1 1) "Hello" 2) "There" 3) "World"# 對一個非空列表插入,查找一個不存在的 pivot redis> LINSERT mylist BEFORE "go" "let's" (integer) -1 # 失敗# 對一個空列表執行 LINSERT 命令 redis> EXISTS fake_list (integer) 0redis> LINSERT fake_list BEFORE "nono" "gogogog" (integer) 0 # 失敗4.LPOP
LPOP key
時間復雜度: O(1)
移除并返回列表 key 的頭元素。返回列表的頭元素。 當 key 不存在時,返回 nil 。
redis> LLEN course (integer) 0redis> RPUSH course algorithm001 (integer) 1redis> RPUSH course c++101 (integer) 2redis> LPOP course # 移除頭元素 "algorithm001"5.RPOP
RPOP key
時間復雜度: O(1)
移除并返回列表 key 的尾元素。返回列表的尾元素。 當 key 不存在時,返回 nil 。
redis> RPUSH mylist "one" (integer) 1redis> RPUSH mylist "two" (integer) 2redis> RPUSH mylist "three" (integer) 3redis> RPOP mylist # 返回被彈出的元素 "three"redis> LRANGE mylist 0 -1 # 列表剩下的元素 1) "one" 2) "two"6.LREM
LREM key count value
時間復雜度: O(N), N 為列表的長度。
根據參數 count 的值,移除列表中與參數 value 相等的元素。
count 的值可以是以下幾種:
- count > 0 : 從表頭開始向表尾搜索,移除與 value 相等的元素,數量為 count 。
- count < 0 : 從表尾開始向表頭搜索,移除與 value 相等的元素,數量為 count 的絕對值。
- count = 0 : 移除表中所有與 value 相等的值。
返回值被移除元素的數量。 因為不存在的 key 被視作空表(empty list),所以當 key 不存在時, LREM 命令總是返回 0 。
# 先創建一個表,內容排列是 # morning hello morning helllo morningredis> LPUSH greet "morning" (integer) 1 redis> LPUSH greet "hello" (integer) 2 redis> LPUSH greet "morning" (integer) 3 redis> LPUSH greet "hello" (integer) 4 redis> LPUSH greet "morning" (integer) 5redis> LRANGE greet 0 4 # 查看所有元素 1) "morning" 2) "hello" 3) "morning" 4) "hello" 5) "morning"redis> LREM greet 2 morning # 移除從表頭到表尾,最先發現的兩個 morning (integer) 2 # 兩個元素被移除redis> LLEN greet # 還剩 3 個元素 (integer) 3redis> LRANGE greet 0 2 1) "hello" 2) "hello" 3) "morning"redis> LREM greet -1 morning # 移除從表尾到表頭,第一個 morning (integer) 1redis> LLEN greet # 剩下兩個元素 (integer) 2redis> LRANGE greet 0 1 1) "hello" 2) "hello"redis> LREM greet 0 hello # 移除表中所有 hello (integer) 2 # 兩個 hello 被移除redis> LLEN greet (integer) 07.LTRIM
LTRIM key start stop
時間復雜度: O(N), N 為被移除的元素的數量。
對一個列表進行修剪(trim),就是說,讓列表只保留指定區間內的元素,不在指定區間之內的元素都將被刪除。
這個從字面意思不容易理解 ,我們圖解一下:
運行命令
結果
上面的圖能一下子讓你看懂,它是做什么的了,下面演示一下
8.LINDEX
LINDEX key index
時間復雜度:O(N), N 為到達下標 index 過程中經過的元素數量。因此,對列表的頭元素和尾元素執行 LINDEX 命令,復雜度為O(1)。
返回列表 key 中,下標為 index 的元素。下標(index)參數 start 和 stop 都以 0 為底,也就是說,以 0 表示列表的第一個元素,以 1 表示列表的第二個元素,以此類推。你也可以使用負數下標,以 -1 表示列表的最后一個元素, -2 表示列表的倒數第二個元素,以此類推。如果 key 不是列表類型,返回一個錯誤。返回列表中下標為 index 的元素。 如果 index 參數的值不在列表的區間范圍內(out of range),返回 nil 。
redis> LPUSH mylist "World" (integer) 1redis> LPUSH mylist "Hello" (integer) 2redis> LINDEX mylist 0 "Hello"redis> LINDEX mylist -1 "World"redis> LINDEX mylist 3 # index不在 mylist 的區間范圍內 (nil)9.LLEN
LLEN key
時間復雜度: O(1)
返回列表 key 的長度。如果 key 不存在,則 key 被解釋為一個空列表,返回 0 .如果 key 不是列表類型,返回一個錯誤。
# 空列表 redis> LLEN job (integer) 0# 非空列表 redis> LPUSH job "cook food" (integer) 1redis> LPUSH job "have lunch" (integer) 2redis> LLEN job (integer) 210.LSET
LSET key index value
時間復雜度:對頭元素或尾元素進行 LSET 操作,復雜度為 O(1)。其他情況下,為 O(N), N 為列表的長度。
將列表 key 下標為 index 的元素的值設置為 value 。當 index 參數超出范圍,或對一個空列表( key 不存在)進行 LSET 時,返回一個錯誤。操作成功返回 ok ,否則返回錯誤信息。
# 對空列表(key 不存在)進行 LSETredis> EXISTS list (integer) 0redis> LSET list 0 item (error) ERR no such key# 對非空列表進行 LSETredis> LPUSH job "cook food" (integer) 1redis> LRANGE job 0 0 1) "cook food"redis> LSET job 0 "play game" OKredis> LRANGE job 0 0 1) "play game"# index 超出范圍redis> LLEN list # 列表長度為 1 (integer) 1redis> LSET list 3 'out of range' (error) ERR index out of range下面我們看一下它的應用:
最典型的就是時間軸。
以CSDN APP的bink推薦為例,
當有一個bink發表的時候,要將這個blink插入時間軸第一個位置??梢允荓PUSH的操作。這樣子每次你就能從列表中,看到最新的blink,越往下時間越遠。
11. BLPOP
BLPOP key [key …] timeout
時間復雜度: O(1)
BLPOP 是列表的阻塞式(blocking)彈出原語。它是 LPOP key 命令的阻塞版本,當給定列表內沒有任何元素可供彈出的時候,連接將被 BLPOP 命令阻塞,直到等待超時或發現可彈出元素為止。當給定多個 key 參數時,按參數 key 的先后順序依次檢查各個列表,彈出第一個非空列表的頭元素。
12.BRPOP
BRPOP key [key …] timeout
時間復雜度: O(1)
BRPOP 是列表的阻塞式(blocking)彈出原語。它是 RPOP key 命令的阻塞版本,當給定列表內沒有任何元素可供彈出的時候,連接將被 BRPOP 命令阻塞,直到等待超時或發現可彈出元素為止。當給定多個 key 參數時,按參數 key 的先后順序依次檢查各個列表,彈出第一個非空列表的尾部元素。
下面再看一個Redis的應用:簡易的消息隊列
Redis 的簡易的消息隊列不是專業的消息隊列,它沒有?常多的?級特性,沒有 ack 保證,如果對消息的可靠性有著極致的追求,那么它就不適合使?。但是對于那些只有?組消費者的消息隊列,使? Redis 就可以?常輕松的搞定。
后面會談到Redis的高級特性–發布訂閱。這個也是消息隊列
異步消息隊列
Redis 的 list(列表) 數據結構常?來作為異步消息隊列使?,使?rpush/lpush操作?隊列,使?lpop 和 rpop來出隊列。
客戶端是通過隊列的 pop 操作來獲取消息,然后進?處理。處理完了再接著獲取消息,再進?處理。如此循環往復。
但是如果隊列空了,客戶端就會陷? pop 的死循環,不停地 pop。這樣不但拉?了客戶端的 CPU,redis 的 QPS(每秒執行次數) 也會被拉?,如果這樣不斷輪詢的客戶端有??來個,資源被大量無效占用。通常我們使? sleep 來解決這個問題,讓線程睡?會,睡個 1s 鐘就可以了。不但客戶端的 CPU 能降下來,Redis 的 QPS 也降下來了。不過這樣也并不是很好的手段,
延時隊列
運用blpop/brpop構建延時隊列是一個非常好的方式。這兩個指令的前綴字符b代表的是blocking,也就是阻塞讀。
阻塞讀在隊列沒有數據的時候,會?即進?休眠狀態,?旦數據到來,則?刻醒過來。消息的延遲?乎為零。不過這也帶來一個問題:空閑連接。如果線程?直阻塞在哪?,Redis 的客戶端連接就成了閑置連接,閑置過久,服務器?般會主動斷開連接,減少閑置資源占?。這個時候blpop/brpop會拋出異常來。編寫客戶端消費者的時候要??,注意捕獲異常,還要重試。
在上面簡單說明分布式鎖的時候,沒有提到客戶端在處理請求時加鎖沒加成功怎么辦。
對于這種情況,有 3 種策略來處理加鎖失敗:
(七)Set集合
Redis 的集合相當于 Java 語???的 HashSet,它內部的鍵值對是?序的唯?的。它的內部實現相當于?個特殊的字典,字典中所有的value 都是?個值NULL。當集合中最后?個元素移除之后,數據結構?動刪除,內存被回收。
插入之后,元素的先后位置是不固定的,遍歷的時候無序。
下面我們看一下它的API
1.SADD
SADD key member [member …]
時間復雜度: O(N), N 是被添加的元素的數量。
將一個或多個 member 元素加入到集合 key 當中,已經存在于集合的 member 元素將被忽略。假如 key 不存在,則創建一個只包含 member 元素作成員的集合。當 key 不是集合類型時,返回一個錯誤。正常返回被添加到集合中的新元素的數量,不包括被忽略的元素。
# 添加單個元素 redis> SADD bbs "discuz.net" (integer) 1# 添加重復元素 redis> SADD bbs "discuz.net" (integer) 0# 添加多個元素 redis> SADD bbs "tianya.cn" "groups.google.com" (integer) 2redis> SMEMBERS bbs 1) "discuz.net" 2) "groups.google.com" 3) "tianya.cn"2.SCARD
SCARD key
時間復雜度: O(1)
返回集合 key 的基數(集合中元素的數量)。當 key 不存在時,返回 0 。
3.SMEMBERS
SMEMBERS key
時間復雜度: O(N), N 為集合的基數。返回集合 key 中的所有成員。不存在的 key 被視為空集合。
可以看出來,插入順序,與返回順序不同。要小心使用
4.srandmember和spop
SRANDMEMBER key [count]
時間復雜度: 只提供 key 參數時為 O(1) 。如果提供了 count 參數,那么為 O(N) ,N 為返回數組的元素個數。
如果命令執行時,只提供了 key 參數,那么返回集合中的一個隨機元素。
從 Redis 2.6 版本開始, SRANDMEMBER 命令接受可選的 count 參數:
- 如果 count 為正數,且小于集合基數,那么命令返回一個包含 count 個元素的數組,數組中的元素各不相同。如果 count 大于等于集合基數,那么返回整個集合。
- 如果 count 為負數,那么命令返回一個數組,數組中的元素可能會重復出現多次,而數組的長度為 count 的絕對值。
SPOP key
時間復雜度: O(1)
移除并返回集合中的一個隨機元素。如果只想獲取一個隨機元素,但不想該元素從集合中被移除的話,可以使用 SRANDMEMBER key [count] 命令。
spop從集合彈出 srandmembe不會破壞集合
下面我們看看應用:
CSDN的點贊狀態就可以用set集合進行處理。類似于點贊、踩這種場景,狀態不重復,就可以set集合。
還有CSDN發表博文的時候,給文章打標簽,標簽就可以存儲在set集合中
除此之外,set 結構可以?來存儲活動中獎的?戶 ID,因為有去重功能,可以保證同?個?戶不會中獎兩次。
下面看看集合間的API
5.sdiff sinter sunion
分別是差集 交集 并集。
這個玩法就比較多了。
比如微信上,將每個人擁有的群id都存儲在每個人的set集合中
我們只要對兩個人的集合取交集,就可以得出 我和他的公共群聊的個數。
(八)zset (有序集合)
zset 類似于 Java 的 SortedSet 和HashMap的結合體,
???它是?個 set,保證了內部 value 的唯?性,另???它可以給每個 value 賦予?個 score,代表這個 value 的排序權重。它的內部實現?的是?種叫做「跳躍列表」的數據結構。zset 中最后?個 value 被移除后,數據結構?動刪除,內存被回收。
有序集合結構:
key: user
| 1 | tom |
| 91 | jerry |
| 102 | jeffery |
集合 VS 有序集合
集合:無重復元素、無序、element
有序集合 : 無重復元素、有序、element + score
有序集合相比集合時間復雜度較高
1.ZADD
ZADD key score member [[score member] [score member] …]
時間復雜度: O(M*log(N)), N 是有序集的基數, M 為成功添加的新成員的數量。
將一個或多個 member 元素及其 score 值加入到有序集 key 當中。如果某個 member 已經是有序集的成員,那么更新這個 member 的 score 值,并通過重新插入這個 member 元素,來保證該 member 在正確的位置上。score 值可以是整數值或雙精度浮點數。如果 key 不存在,則創建一個空的有序集并執行 ZADD 操作。當 key 存在但不是有序集類型時,返回一個錯誤。正常返回被成功添加的新成員的數量,不包括那些被更新的、已經存在的成員。
# 添加單個元素 redis> ZADD page_rank 10 google.com (integer) 1# 添加多個元素 redis> ZADD page_rank 9 baidu.com 8 bing.com (integer) 2redis> ZRANGE page_rank 0 -1 WITHSCORES 1) "bing.com" 2) "8" 3) "baidu.com" 4) "9" 5) "google.com" 6) "10"# 添加已存在元素,且 score 值不變 redis> ZADD page_rank 10 google.com (integer) 0redis> ZRANGE page_rank 0 -1 WITHSCORES # 沒有改變 1) "bing.com" 2) "8" 3) "baidu.com" 4) "9" 5) "google.com" 6) "10"# 添加已存在元素,但是改變 score 值 redis> ZADD page_rank 6 bing.com (integer) 0redis> ZRANGE page_rank 0 -1 WITHSCORES # bing.com 元素的 score 值被改變 1) "bing.com" 2) "6" 3) "baidu.com" 4) "9" 5) "google.com" 6) "10"2.ZREM
ZREM key member [member …]
時間復雜度: O(M*log(N)), N 為有序集的基數, M 為被成功移除的成員的數量。
移除有序集 key 中的一個或多個成員,不存在的成員將被忽略。當 key 存在但不是有序集類型時,返回一個錯誤。正常返回值
被成功移除的成員的數量,不包括被忽略的成員。
3.zscore
ZSCORE key member
時間復雜度: O(1)
返回有序集 key 中,成員 member 的 score 值(member 成員的 score 值,以字符串形式表示)。如果 member 元素不是有序集 key 的成員,或 key 不存在,返回 nil 。
1) "tom" 2) "2000" 3) "peter" 4) "3500" 5) "jack" 6) "5000"redis> ZSCORE salary peter # 注意返回值是字符串 "3500"4.ZINCRBY
ZINCRBY key increment member
時間復雜度: O(log(N))
為有序集 key 的成員 member 的 score 值加上增量 increment 。可以通過傳遞一個負數值 increment ,讓 score 減去相應的值,比如 ZINCRBY key -5 member ,就是讓 member 的 score 值減去 5 。當 key 不存在,或 member 不是 key 的成員時, ZINCRBY key increment member 等同于 ZADD key increment member 。當 key 不是有序集類型時,返回一個錯誤。score 值可以是整數值或雙精度浮點數。正常返回值為member 成員的新 score 值,以字符串形式表示。
redis> ZSCORE salary tom "2000"redis> ZINCRBY salary 2000 tom # tom 加薪啦! "4000"5.zcard
ZCARD key
時間復雜度: O(1)
當 key 存在且是有序集類型時,返回有序集的基數。 當 key 不存在時,返回 0 。
6.ZRANGE
ZRANGE key start stop [WITHSCORES]
時間復雜度: O(log(N)+M), N 為有序集的基數,而 M 為結果集的基數。
返回有序集 key 中,指定區間內的成員。其中成員的位置按 score 值遞增(從小到大)來排序。具有相同 score 值的成員按字典序(lexicographical order )來排列。如果你需要成員按 score 值遞減(從大到小)來排列,請使用 ZREVRANGE key start stop [WITHSCORES] 命令。下標參數 start 和 stop 都以 0 為底,也就是說,以 0 表示有序集第一個成員,以 1 表示有序集第二個成員,以此類推。 你也可以使用負數下標,以 -1 表示最后一個成員, -2 表示倒數第二個成員,以此類推。超出范圍的下標并不會引起錯誤。 比如說,當 start 的值比有序集的最大下標還要大,或是 start > stop 時, ZRANGE 命令只是簡單地返回一個空列表。 另一方面,假如 stop 參數的值比有序集的最大下標還要大,那么 Redis 將 stop 當作最大下標來處理??梢酝ㄟ^使用 WITHSCORES 選項,來讓成員和它的 score 值一并返回,返回列表以 value1,score1, …, valueN,scoreN 的格式表示。 客戶端庫可能會返回一些更復雜的數據類型,比如數組、元組等。返回為指定區間內,帶有 score 值(可選)的有序集成員的列表。
redis > ZRANGE salary 0 -1 WITHSCORES # 顯示整個有序集成員 1) "jack" 2) "3500" 3) "tom" 4) "5000" 5) "boss" 6) "10086"redis > ZRANGE salary 1 2 WITHSCORES # 顯示有序集下標區間 1 至 2 的成員 1) "tom" 2) "5000" 3) "boss" 4) "10086"redis > ZRANGE salary 0 200000 WITHSCORES # 測試 end 下標超出最大下標時的情況 1) "jack" 2) "3500" 3) "tom" 4) "5000" 5) "boss" 6) "10086"redis > ZRANGE salary 200000 3000000 WITHSCORES # 測試當給定區間不存在于有序集時的情況 (empty list or set)7.ZRANGEBYSCOR
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
時間復雜度: O(log(N)+M), N 為有序集的基數, M 為被結果集的基數。
返回有序集 key 中,所有 score 值介于 min 和 max 之間(包括等于 min 或 max )的成員。有序集成員按 score 值遞增(從小到大)次序排列。具有相同 score 值的成員按字典序(lexicographical order)來排列(該屬性是有序集提供的,不需要額外的計算)。
可選的 LIMIT 參數指定返回結果的數量及區間(就像SQL中的 SELECT LIMIT offset, count ),注意當 offset 很大時,定位 offset 的操作可能需要遍歷整個有序集,此過程最壞復雜度為 O(N) 時間。
可選的 WITHSCORES 參數決定結果集是單單返回有序集的成員,還是將有序集成員及其 score 值一起返回。 該選項自 Redis 2.0 版本起可用。
區間及無限
min 和 max 可以是 -inf 和 +inf ,這樣一來,你就可以在不知道有序集的最低和最高 score 值的情況下,使用ZRANGEBYSCORE 這類命令。默認情況下,區間的取值使用閉區間 (小于等于或大于等于),你也可以通過給參數前增加 ( 符號來使用可選的開區間 (小于或大于)。
8.zcount
ZCOUNT key min max
時間復雜度: O(log(N)), N 為有序集的基數。
返回有序集 key 中, score 值在 min 和 max 之間(默認包括 score 值等于 min 或 max )的成員的數量。關于參數 min 和 max 的詳細使用方法,請參考 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 命令。返回值為score 值在 min 和 max 之間的成員的數量。
9.zremrangebyrank
ZREMRANGEBYRANK key start stop
時間復雜度: O(log(N)+M), N 為有序集的基數,而 M 為被移除成員的數量。
移除有序集 key 中,指定排名(rank)區間內的所有成員。區間分別以下標參數 start 和 stop 指出,包含 start 和 stop 在內。
下標參數 start 和 stop 都以 0 為底,也就是說,以 0 表示有序集第一個成員,以 1 表示有序集第二個成員,以此類推。 你也可以使用負數下標,以 -1 表示最后一個成員, -2 表示倒數第二個成員,以此類推。返回值為被移除成員的數量。
redis> ZADD salary 2000 jack (integer) 1 redis> ZADD salary 5000 tom (integer) 1 redis> ZADD salary 3500 peter (integer) 1redis> ZREMRANGEBYRANK salary 0 1 # 移除下標 0 至 1 區間內的成員 (integer) 2redis> ZRANGE salary 0 -1 WITHSCORES # 有序集只剩下一個成員 1) "tom" 2) "5000"10.zremrangebyscore
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
時間復雜度: O(log(N)+M), N 為有序集的基數, M 為結果集的基數。
返回有序集 key 中, score 值介于 max 和 min 之間(默認包括等于 max 或 min )的所有的成員。有序集成員按 score 值遞減(從大到小)的次序排列。具有相同 score 值的成員按字典序的逆序(reverse lexicographical order )排列。
除了成員按 score 值遞減的次序排列這一點外, ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 命令一樣。
返回值為指定區間內,帶有 score 值(可選)的有序集成員的列表。
我們看一下它具體的應用:
首先最容易想到的就是排行榜了。
(博主在long long ago 也曾第一過)
類似的,zset 還可以?來存儲學?的成績,value 值是學?的 ID,score 是他的考試成績。我們可以對成績按分數進?排序就可以得到他的名次。
除此之外,zset 可以?來存粉絲列表,value 值是粉絲的?戶 ID,score 是關注時間。我們可以對粉絲列表按關注時間進?排序。
不太常見API有這些,文章上面已經給大家推薦查看API的網站了,用到的時候查一查哈
◆ zrevrank
◆ zrevrange
◆ zrevrangebyscore
◆ interstore
◆ zunionstore
上面的API可以分成這三類
三、Redis 客戶端操作
帶大家過一遍Redis 的API之后,這部分非常非常簡單了。
所有語言的操作,其實都是對Redis API的封裝,封裝前的東西你都知道了,封裝后的不是易如反掌?
以Java為例吧,基于Java語言的客戶端比較熱的是Jedis
在連接方面有兩種方式,一種是直連、一種是線程池(一般都要用到線程池)
直連方式
// 1.生成一個 Jedis對象,這個對象負責和指定Reds節點進行通信 Jedis jedis = new Jedis("localhost", 6379); // Sting set get 操作 jedis.set("Jedis", "hello jedis!"); jedis.get("Jedis"); // 對結果進行自增 jedis.incr("counter")構造方法最多有四個參數
hash操作
是不是很熟悉,其他的類似。
Jedis連接池
直連的弊端很明顯:消耗資源。如果在不同方面,頻繁用到Redis,編寫程序也很麻煩。
所以,連接池是非常重要的的。
方案對比
| 直連 | 簡單方便;適用于少量長期連接的場景 | 1.存在每次新建/關閉TCP開銷;2.資源無法控制,存在連接泄露的可能;3.Jedis對象線程不安全 |
| 連接池 | Jedis預先生成,降低開銷使用連接池的形式保護和控制資源的使用 | 相對于直連,使用相對麻煩尤其在資源的管理上需要很多參數來保證,一旦規劃不合理也會出現問題。 |
簡單使用:
Jedis jedis = null; try{//1.從連接池獲取 jedis對象jedis = jedisPool.getResource();//2.執行操作jedis set(hello","world");} catch(Exception e){eprintStackTrace();}finally {if (jedis != null)//如果使用 JedisPool,cose操作不是關閉連接,代表歸還連接池jedis closedjedis.close();} }說明
唉,寫得太長了,CSDN編輯器不允許我在一篇文章上繼續發揮了。
這是下一篇文章。
傳送門:【大廠面試】面試官看了贊不絕口的Redis筆記(二)
目錄:
這五個專題串過之后,你會對Redis單體,有著非常好的理解了,后面再走就是看源碼了。相信你到這一步已經可以獨當一面了。
我再往下面寫,就是Redis分布式領域相關的東西了,比如說Redis的主從復制、哨兵機制、 Redis cluster特性等。
傳送門: 【大廠面試】面試官看了贊不絕口的Redis筆記(三)分布式篇
目錄:
最后還有一些拓展的內容需要補充。
對了,兄dei,如果你覺得這篇文章可以的話,給俺點個贊再走,管不管?這樣可以讓更多的人看到這篇文章,對我來說也是一種激勵。
如果你有什么問題的話,歡迎留言或者CSDN APP直接與我交流。
總結
以上是生活随笔為你收集整理的【大厂面试】面试官看了赞不绝口的Redis笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS中URL路径
- 下一篇: 定义域计算机,函数的定义域和值域