【大廠面試】面試官看了讚不絕口的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

通用命令 KEYS pattern(pattern 爲正則表達式)
功能描述 查找所有符合給定模式 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
OK

redis> 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

通用命令 DBSIZE
功能描述 返回當前數據庫的 key 的數量。
時間複雜度 時間複雜度: O(1)

示例

redis> DBSIZE
(integer) 5

redis> SET new_key "hello_moto"     # 增加一個 key 試試
OK

redis> DBSIZE
(integer) 6

Redis內置一個計數器,可以實時更新Redis中key的總數,因此dbsize的時間複雜度爲O(1),可以在線上使用。

3. EXISTS

通用命令 EXISTS key
功能描述 檢查給定 key 是否存在。若 key 存在,返回 1 ,否則返回 0 。
時間複雜度 O(1)
redis> SET db "redis"
OK

redis> EXISTS db
(integer) 1

redis> DEL db
(integer) 1

redis> EXISTS db
(integer) 0

4. DEL

通用命令 DEL key [key …]
功能描述 刪除給定的一個或多個 key 。不存在的 key 會被忽略。返回值是被刪除 key 的數量。
時間複雜度 O(N), N 爲被刪除的 key 的數量,其中刪除單個字符串類型的 key ,時間複雜度爲O(1);刪除單個列表、集合、有序集合或哈希表類型的 key ,時間複雜度爲O(M), M 爲以上數據結構內的元素數量。
#  刪除單個 key
redis> SET name huangz
OK

redis> DEL name
(integer) 1


# 刪除一個不存在的 key
redis> EXISTS phone
(integer) 0

redis> DEL phone # 失敗,沒有 key 被刪除
(integer) 0


# 同時刪除多個 key
redis> SET name "redis"
OK

redis> SET type "key-value store"
OK

redis> SET website "redis.com"
OK

redis> DEL name type website
(integer) 3

5. EXPIRE

通用命令 EXPIRE key seconds
功能描述 爲給定 key 設置生存時間,當 key 過期時(生存時間爲 0 ),它會被自動刪除。設置成功返回 1 。 當 key 不存在或者不能爲 key 設置生存時間時(比如在低於 2.1.3 版本的 Redis 中你嘗試更新 key 的生存時間),返回 0 。
時間複雜度 O(1)
redis> SET cache_page "www.google.com"
OK

redis> EXPIRE cache_page 30  # 設置過期時間爲 30 秒
(integer) 1

redis> TTL cache_page    # 查看剩餘生存時間
(integer) 23

redis> EXPIRE cache_page 30000   # 更新過期時間
(integer) 1

redis> TTL cache_page
(integer) 29996

6. TTL

通用命令 TTL key
功能描述 以秒爲單位,返回給定 key 的剩餘生存時間(TTL, time to live)。
時間複雜度 O(1)
redis> FLUSHDB
OK

redis> TTL key
(integer) -2

# key 存在,但沒有設置剩餘生存時間
redis> SET key value
OK

redis> TTL key
(integer) -1

# 有剩餘生存時間的 key
redis> EXPIRE key 10086
(integer) 1

redis> TTL key
(integer) 10084

7. PERSIST

通用命令 PERSIST key
功能描述 移除給定 key 的生存時間,將這個 key 從“易失的”(帶生存時間 key )轉換成“持久的”(一個不帶生存時間、永不過期的 key )。當生存時間移除成功時,返回 1 . 如果 key 不存在或 key 沒有設置生存時間,返回 0 。
時間複雜度 O(1)
redis> SET mykey "Hello"
OK

redis> EXPIRE mykey 10  # 爲 key 設置生存時間
(integer) 1

redis> TTL mykey
(integer) 10

redis> PERSIST mykey    # 移除 key 的生存時間
(integer) 1

redis> TTL mykey
(integer) -1

8. TYPE

通用命令 TYPE key
功能描述 返回 key 所儲存的值的類型。
時間複雜度 O(1)

值的類型有:

  • none (key不存在)
  • string (字符串)
  • list (列表)
  • set (集合)
  • zset (有序集)
  • hash (哈希表)
  • stream (流)
# 字符串
redis> SET weather "sunny"
OK

redis> TYPE weather
string

# 列表
redis> LPUSH book_list "programming in scala"
(integer) 1

redis> TYPE book_list
list

# 集合
redis> SADD pat "dog"
(integer) 1

redis> TYPE pat
set

8. del

通用命令 DEL key [key …]
功能描述 刪除給定的一個或多個 key 。不存在的 key 會被忽略。返回值是被刪除 key 的數量。
時間複雜度 O(N), N 爲被刪除的 key 的數量,其中刪除單個字符串類型的 key ,時間複雜度爲O(1);刪除單個列表、集合、有序集合或哈希表類型的 key ,時間複雜度爲O(M), M 爲以上數據結構內的元素數量。
#  刪除單個 key
redis> SET name huangz
OK

redis> DEL name
(integer) 1

# 刪除一個不存在的 key
redis> EXISTS phone
(integer) 0

redis> DEL phone # 失敗,沒有 key 被刪除
(integer) 0


# 同時刪除多個 key
redis> SET name "redis"
OK

redis> SET type "key-value store"
OK

redis> SET website "redis.com"
OK

redis> DEL name type website
(integer) 3

9. scan

通用命令 SCAN cursor [MATCH pattern] [LIMT count]
功能描述 查找(limit個)(符合給定模式 pattern )的 key ,返回值是符合 條件的key
時間複雜度 增量式迭代命令每次執行的複雜度爲 O(1) , 對數據集進行一次完整迭代的複雜度爲 O(N) , 其中 N 爲數據集中的元素數量。

剛開始我們已經介紹過了keys,它的缺點非常明顯:

  1. ⼀次性查出所有滿⾜條件的 key,萬⼀Redis中有⼏百 w 個 key 滿⾜條件,滿屏都是輸出的結果,眼花繚亂。
  2. keys由於走的是遍歷算法,複雜度是 O(n),如果Redis中有千萬級以上的 key,這個指令就會導致 Redis 服務卡頓,所有讀寫Redis 的其它的指令都會被延後甚⾄會超時報錯,因爲 Redis是單線程程序,順序執⾏所有指令,其它指令必須等到當前的keys 指令執⾏完了纔可以繼續。

爲了解決這個問題, 2.8 版本中的Redis加⼊了scan。

scan 相⽐ keys 具備有以下特點:

  1. 複雜度雖然也是 O(n),但是它是通過遊標(cursor,相當於位置)分步進⾏的,不會阻塞線程;
  2. 提供 limit 參數(可選),可以控制每次返回結果的最⼤條數(實際上是遍歷的key的數量);
  3. 它也提供模式匹配功能;
  4. 服務器不需要爲遊標(cursor)保存狀態,遊標(cursor)的唯⼀狀態就是 scan 返回給客戶端的遊標整數;
  5. 返回的結果可能會有重複,需要客戶端去重複(重要);
  6. 遍歷的過程中如果有數據修改,改動後的數據能不能遍歷到是不確定的;
  7. 單次返回的結果是空的並不意味着遍歷結束,⽽要看返回的遊標值是否爲零;

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把所有的數據都保存在內存中,而內存的響應速度是非常快的

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模型

  1. 同步阻塞IO(Blocking IO)
  2. 同步非阻塞IO(Non-blocking IO)
  3. IO多路複用(IO Multiplexing)
  4. 異步IO(Asynchronous IO)

Redis的IO模型是IO多路複用,有意思的是Java 的NIO模型,也是IO多路複用(不是同步非阻塞IO)

有興趣的可以查閱相關的資料,或者不着急的 可以等我接下來的博文(過段日子會有網絡編程詳解的博文,對IO作深入探究)

這裏還要強調一下,由於Redis單線程一次只運行一條命令,我們要拒絕長(慢)命令

 	keys 
    flushall
    flushdb
    slow lua script
    mutil/exec
    operate

(三)數據結構和內部編碼

Redis每種數據結構及對應的內部編碼如下圖所示
在這裏插入圖片描述
你會發現 數據結構 內部編碼方式有不同的方式,其實這是時間換空間 空間換時間的做法,選擇何種內部編碼要結合實際情況。

(四)字符串

字符串 string 是 Redis 最簡單的數據結構。Redis 所有的數據結構都是以唯⼀的 key 字符串作爲名稱,然後通過這個唯⼀ key 值來獲取相應的 value 數據。不同類型的數據結構的差異就在於 value 的結構不⼀樣。

字符串的value值類型有三種:1. 字符串;2. 整型;3.二進制。

Redis 的字符串是動態字符串,是可以修改的字符串,內部結構實現上類似於 Java 的 ArrayList,採⽤預分配冗餘空間的⽅式來減少內存的頻繁分配,當字符串⻓度⼩於 1M時,擴容都是加倍現有的空間,如果超過 1M,擴容時⼀次只會多擴1M 的空間。需要注意的是字符串最⼤⻓度爲 512M。

我們看一下它的常用API

1. GET

命令 GET key
功能描述 返回與鍵 key 相關聯的字符串值。 如果鍵 key 不存在, 那麼返回特殊值 nil ; 否則, 返回鍵 key 的值。如果鍵 key 的值並非字符串類型, 那麼返回一個錯誤, 因爲 GET 命令只能用於字符串值。
時間複雜度 O(1)

示例
對不存在的鍵 key 或是字符串類型的鍵 key 執行 GET 命令:

redis> GET db
(nil)

redis> SET db redis
OK

redis> GET db
"redis"

對不是字符串類型的鍵 key 執行 GET 命令:

redis> DEL db
(integer) 1

redis> LPUSH db redis mongodb mysql
(integer) 3

redis> GET db
(error) ERR Operation against a key holding the wrong kind of value

2. set

命令 SET key value [EX seconds] [PX milliseconds] [NX|XX]
功能描述 將字符串值 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

命令 INCR key
功能描述 爲鍵 key 儲存的數字值加上一。如果鍵 key 不存在, 那麼它的值會先被初始化爲 0 , 然後再執行 INCR 命令。如果鍵 key 儲存的值不能被解釋爲數字, 那麼 INCR 命令將返回一個錯誤。本操作的值限制在 64 位(bit)有符號數字表示之內。返回值是DECR 命令會返回鍵 key 在執行減一操作之後的值。
時間複雜度 O(1)

對儲存數字值的鍵 key 執行 DECR 命令:

redis> SET page_view 20
OK

redis> INCR page_view
(integer) 21

redis> GET page_view    # 數字值在 Redis 中以字符串的形式保存
"21"

對不存在的鍵執行 DECR 命令:

redis> EXISTS count
(integer) 0

redis> DECR count
(integer) -1

4. DECR

命令 DECRBY key decrement
功能描述 將鍵 key 儲存的整數值減去減量 decrement 。如果鍵 key 不存在, 那麼鍵 key 的值會先被初始化爲 0 , 然後再執行 DECRBY 命令。如果鍵 key 儲存的值不能被解釋爲數字, 那麼 DECRBY 命令將返回一個錯誤。本操作的值限制在 64 位(bit)有符號數字表示之內。返回值是DECRBY 命令會返回鍵在執行減法操作之後的值。
時間複雜度 O(1)

對已經存在的鍵執行 DECRBY 命令:

redis> SET count 100
OK

redis> DECRBY count 20
(integer) 80

對不存在的鍵執行 DECRBY 命令:

redis> EXISTS pages
(integer) 0

redis> DECRBY pages 10
(integer) -10

5. INCRBY

命令 INCRBY key increment
功能描述 爲鍵 key 儲存的數字值加上增量 increment 。如果鍵 key 不存在, 那麼鍵 key 的值會先被初始化爲 0 , 然後再執行 INCRBY 命令。如果鍵 key 儲存的值不能被解釋爲數字, 那麼 INCRBY 命令將返回一個錯誤。本操作的值限制在 64 位(bit)有符號數字表示之內。返回值爲在加上增量 increment 之後, 鍵 key 當前的值。
時間複雜度 O(1)

示例演示與上面類似

6. DECRBY

命令 DECRBY key decrement
功能描述 將鍵 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

命令 SETNX key value
功能描述 只在鍵 key 不存在的情況下, 將鍵 key 的值設置爲 value 。若鍵 key 已經存在, 則 SETNX 命令不做任何動作。命令在設置成功時返回 1 , 設置失敗時返回 0 。
時間複雜度 O(1)
redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 設置成功
(integer) 1

redis> SETNX job "code-farmer"   # 嘗試覆蓋 job ,失敗
(integer) 0

redis> GET job                   # 沒有被覆蓋
"programmer"

8. SETEX

命令 SETNX key value
功能描述 將鍵 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 key value [key value …]
功能描述 同時爲多個鍵設置值。如果某個給定鍵已經存在, 那麼 MSET 將使用新值去覆蓋舊值, 如果這不是你所希望的效果, 請考慮使用 MSETNX 命令, 這個命令只會在所有給定鍵都不存在的情況下進行設置。MSET 是一個原子性(atomic)操作, 所有給定鍵都會在同一時間內被設置, 不會出現某些鍵被設置了但是另一些鍵沒有被設置的情況。MSET 命令總是返回 OK 。
時間複雜度 O(N),其中 N 爲被設置的鍵數量。

同時對多個鍵進行設置:

redis> MSET date "2012.3.30" time "11:00 a.m." weather "sunny"
OK

redis> 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"
OK

redis> MGET k1 k2
1) "good"
2) "bye"

10 . MGET

命令 MGET key [key …]
功能描述 返回給定的一個或多個字符串鍵的值。如果給定的字符串鍵裏面, 有某個鍵不存在, 那麼這個鍵的值將以特殊值 nil 表示。MGET 命令將返回一個列表, 列表中包含了所有給定鍵的值。
時間複雜度 O(N),其中 N 爲被設置的鍵數量。
redis> SET redis redis.com
OK

redis> SET mongodb mongodb.org
OK

redis> MGET redis mongodb
1) "redis.com"
2) "mongodb.org"

redis> MGET redis mongodb mysql     # 不存在的 mysql 返回 nil
1) "redis.com"
2) "mongodb.org"
3) (nil)

下面說說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 新內容。如果字符串的⻓
度⾮常⻓,這樣的內存分配和複製開銷就會⾮常⼤。

/* Append the specified binary-safe string
pointed by 't' of 'len' bytes to the
* end of the specified sds string 's'
.
*
* After the call, the passed sds string is no
longer valid and all the
* references must be substituted with the new
pointer returned by the call.
*/
sds sdscatlen(sds s, const void *t, size_t len) {
	size_t curlen = sdslen(s); // 原字符串⻓度
	// 按需調整空間,如果 capacity 不夠容納追加的內容,就會重新分配字節數組並複製原字符串的內容到新數組中
	s = sdsMakeRoomFor(s,len);
	if (s == NULL) return NULL; // 內存不⾜
	memcpy(s+curlen, t, len); // 追加⽬標字符串的內容到字節數組中
	sdssetlen(s, curlen+len); // 設置追加後的⻓度值
	s[curlen+len] ='\0'; // 讓字符串以\0 結尾,便於調試打印,還可以直接使⽤ glibc 的字符串函數進⾏操作
	return s;
}

上⾯的 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) 1

redis> HGET website google
"www.g.cn"

對一個已存在的域進行更新:

redis> HSET website google "www.google.com"
(integer) 0

redis> HGET website google
"www.google.com"

2.HGET hash field
時間複雜度: O(1)

返回哈希表中給定域的值。HGET 命令在默認情況下返回給定域的值。如果給定域不存在於哈希表中, 又或者給定的哈希表並不存在, 那麼命令返回 nil 。

域存在的情況:

redis> HSET homepage redis redis.com
(integer) 1

redis> HGET homepage redis
"redis.com"

域不存在的情況:

redis> HGET site mysql
(nil)

3.HDEL
HDEL key field [field …]
O(N), N 爲要刪除的域的數量。
刪除哈希表 key 中的一個或多個指定域,不存在的域將被忽略。返回值爲被成功移除的域的數量,不包括被忽略的域。

# 測試數據
redis> HGETALL abbr
1) "a"
2) "apple"
3) "b"
4) "banana"
5) "c"
6) "cat"
7) "d"
8) "dog"


# 刪除單個域
redis> HDEL abbr a
(integer) 1


# 刪除不存在的域
redis> HDEL abbr not-exists-field
(integer) 0


# 刪除多個域
redis> HDEL abbr b c
(integer) 2

redis> HGETALL abbr
1) "d"
2) "dog"

4. HSETNX hash field value
時間複雜度: O(1)
當且僅當域 field 尚未存在於哈希表的情況下, 將它的值設置爲 value 。如果給定域已經存在於哈希表當中, 那麼命令將放棄執行設置操作。如果哈希表 hash 不存在, 那麼一個新的哈希表將被創建並執行 HSETNX 命令。HSETNX 命令在設置成功時返回 1 , 在給定域已經存在而放棄執行設置操作時返回 0 。

域尚未存在, 設置成功:

redis> HSETNX database key-value-store Redis
(integer) 1

redis> HGET database key-value-store
"Redis"

域已經存在, 設置未成功, 域原有的值未被改變:

redis> HSETNX database key-value-store Riak
(integer) 0

redis> HGET database key-value-store
"Redis"

5. HLEN
時間複雜度:O(1)

返回哈希表 key 中域的數量。當 key 不存在時,返回 0 。

redis> HSET db redis redis.com
(integer) 1

redis> HSET db mysql mysql.com
(integer) 1

redis> HLEN db
(integer) 2

redis> HSET db mongodb mongodb.org
(integer) 1

redis> HLEN db
(integer) 3

6.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
OK

redis> 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 值的表。具體返回一個包含多個給定域的關聯值的表,表值的排列順序和給定域參數的請求順序一樣。

redis> HMSET pet dog "doudou" cat "nounou"    # 一次設置多個域
OK

redis> HMGET pet dog cat fake_pet             # 返回值的順序和傳入參數的順序一樣
1) "doudou"
2) "nounou"
3) (nil)                                      # 不存在的域返回nil值

8.HINCRBY
HINCRBY key field increment
時間複雜度:O(1)
爲哈希表 key 中的域 field 的值加上增量 increment 。增量也可以爲負數,相當於對給定域進行減法操作。如果 key 不存在,一個新的哈希表被創建並執行 HINCRBY 命令。如果域 field 不存在,那麼在執行命令前,域的值被初始化爲 0 。對一個儲存字符串值的域 field 執行 HINCRBY 命令將造成一個錯誤。本操作的值被限制在 64 位(bit)有符號數字表示之內。執行 HINCRBY 命令之後,返回值哈希表 key 中域 field 的值。

# increment 爲正數
redis> HEXISTS counter page_view    # 對空域進行設置
(integer) 0

redis> HINCRBY counter page_view 200
(integer) 200

redis> HGET counter page_view
"200"


# increment 爲負數
redis> HGET counter page_view
"200"

redis> HINCRBY counter page_view -50
(integer) 150

redis> HGET counter page_view
"150"


# 嘗試對字符串值的域執行HINCRBY命令
redis> HSET myhash string hello,world       # 設定一個字符串值
(integer) 1

redis> HGET myhash string
"hello,world"

redis> HINCRBY myhash string 1              # 命令執行失敗,錯誤。
(error) ERR hash value is not an integer

redis> HGET myhash string                   # 原值不變
"hello,world"

9. HKEYS
時間複雜度:O(N), N 爲哈希表的大小。

返回哈希表 key 中的所有域。即一個包含哈希表中所有域的表。當 key 不存在時,返回一個空表。

# 哈希表非空
redis> HMSET website google www.google.com yahoo www.yahoo.com
OK

redis> HKEYS website
1) "google"
2) "yahoo"

# 空哈希表/key不存在
redis> EXISTS fake_key
(integer) 0

redis> HKEYS fake_key
(empty list or set)

10.HVALS
HVALS key
時間複雜度:O(N), N 爲哈希表的大小。
返回哈希表 key 中所有域的值。即一個包含哈希表中所有值的表。當 key 不存在時,返回一個空表。

# 非空哈希表
redis> HMSET website google www.google.com yahoo www.yahoo.com
OK

redis> HVALS website
1) "www.google.com"
2) "www.yahoo.com"


# 空哈希表/不存在的key
redis> EXISTS not_exists
(integer) 0

redis> HVALS not_exists
(empty list or set)

11.HGETALL
時間複雜度:O(N), N 爲哈希表的大小。
HGETALL key
返回哈希表 key 中,所有的域和值。在返回值裏,緊跟每個域名(field name)之後是域的值(value),所以返回值的長度是哈希表大小的兩倍。返回值以列表形式返回哈希表的域和域的值。若 key 不存在,返回空列表。

redis> HSET people jack "Jack Sparrow"
(integer) 1

redis> HSET people gump "Forrest Gump"
(integer) 1

redis> HGETALL people
1) "jack"          # 域
2) "Jack Sparrow"  # 值
3) "gump"
4) "Forrest Gump"

小心單線程 數據量大的話 會比較慢

12. hsetnx
時間複雜度: O(1)

當且僅當域 field 尚未存在於哈希表的情況下, 將它的值設置爲 value 。如果給定域已經存在於哈希表當中, 那麼命令將放棄執行設置操作。如果哈希表 hash 不存在, 那麼一個新的哈希表將被創建並執行 HSETNX 命令。HSETNX 命令在設置成功時返回 1 , 在給定域已經存在而放棄執行設置操作時返回 0 。

域尚未存在, 設置成功:

redis> HSETNX database key-value-store Redis
(integer) 1

redis> HGET database key-value-store
"Redis"

域已經存在, 設置未成功, 域原有的值未被改變:

redis> HSETNX database key-value-store Riak
(integer) 0

redis> 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 域的值。
# 值和增量都是普通小數
redis> HSET mykey field 10.50
(integer) 1
redis> HINCRBYFLOAT mykey field 0.1
"10.6"


# 值和增量都是指數符號
redis> HSET mykey field 5.0e3
(integer) 0
redis> HINCRBYFLOAT mykey field 2.0e2
"5200"


# 對不存在的鍵執行 HINCRBYFLOAT
redis> EXISTS price
(integer) 0
redis> HINCRBYFLOAT price milk 3.5
"3.5"
redis> HGETALL price
1) "milk"
2) "3.5"


# 對不存在的域進行 HINCRBYFLOAT
redis> HGETALL price
1) "milk"
2) "3.5"
redis> HINCRBYFLOAT price coffee 4.5   # 新增 coffee 域
"4.5"
redis> HGETALL price
1) "milk"
2) "3.5"
3) "coffee"
4) "4.5"

知道上面的命令後,就可以做一些事情了。

應用
類似於字符串,我們可以記錄網站每個用戶個人主頁的訪問量

hincrby user chenxiao  pageviewCount

當然還有緩存用戶信息。

對於記錄個人主頁的訪問量,自然字符串要比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: chenxiao

key: user:userId:age
value: 100

key: user:userId:pageView
value: 800000

相比上面的方案更新屬性更方便 只需要一條

再看看hash形式的方案(hash)

key:user:userId

field:name
value:chenxiao

field:age
value:100

field:pageView
vale:80000

3種方案比較:

方案 優點 缺點
String v1 編程簡單,可能節約內存 1.序列化開銷 2.設置屬性要操作整個數據。
String v2 直觀,可以部分更新 1.內存佔用較大 2.key較爲分散
hash 直觀 節省空間 可以部分更新 1.編程稍微複雜 2.ttl不好控制

(六)列表

Redis 的列表相當於 Java 語⾔⾥⾯的 LinkedList,數據結構形式爲鏈表,插⼊和刪除操作⾮常快,時間複雜度爲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) 2

redis> LRANGE languages 0 -1     # 列表允許重複元素
1) "python"
2) "python"


# 加入多個元素
redis> LPUSH mylist a b c
(integer) 3

redis> 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) 2

redis> LRANGE languages 0 -1 # 列表允許重複元素
1) "c"
2) "c"


# 添加多個元素
redis> RPUSH mylist a b c
(integer) 3

redis> 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) 1

redis> RPUSH mylist "World"
(integer) 2

redis> LINSERT mylist BEFORE "World" "There"
(integer) 3

redis> 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) 0

redis> LINSERT fake_list BEFORE "nono" "gogogog"
(integer) 0                                      # 失敗

4.LPOP
LPOP key
時間複雜度: O(1)

移除並返回列表 key 的頭元素。返回列表的頭元素。 當 key 不存在時,返回 nil 。

redis> LLEN course
(integer) 0

redis> RPUSH course algorithm001
(integer) 1

redis> RPUSH course c++101
(integer) 2

redis> LPOP course  # 移除頭元素
"algorithm001"

5.RPOP
RPOP key
時間複雜度: O(1)

移除並返回列表 key 的尾元素。返回列表的尾元素。 當 key 不存在時,返回 nil 。

redis> RPUSH mylist "one"
(integer) 1

redis> RPUSH mylist "two"
(integer) 2

redis> RPUSH mylist "three"
(integer) 3

redis> 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 morning

redis> 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) 5

redis> 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) 3

redis> LRANGE greet 0 2
1) "hello"
2) "hello"
3) "morning"

redis> LREM greet -1 morning    # 移除從表尾到表頭,第一個 morning
(integer) 1

redis> LLEN greet               # 剩下兩個元素
(integer) 2

redis> LRANGE greet 0 1
1) "hello"
2) "hello"

redis> LREM greet 0 hello      # 移除表中所有 hello
(integer) 2                    # 兩個 hello 被移除

redis> LLEN greet
(integer) 0

7.LTRIM
LTRIM key start stop
時間複雜度: O(N), N 爲被移除的元素的數量。
對一個列表進行修剪(trim),就是說,讓列表只保留指定區間內的元素,不在指定區間之內的元素都將被刪除。

這個從字面意思不容易理解 ,我們圖解一下:

在這裏插入圖片描述
運行命令

Itrim listkey 1 4

結果
在這裏插入圖片描述
上面的圖能一下子讓你看懂,它是做什麼的了,下面演示一下

# 情況 1: 常見情況, start 和 stop 都在列表的索引範圍之內
redis> LRANGE alpha 0 -1       # alpha 是一個包含 5 個字符串的列表
1) "h"
2) "e"
3) "l"
4) "l"
5) "o"

redis> LTRIM alpha 1 -1        # 刪除 alpha 列表索引爲 0 的元素
OK

redis> LRANGE alpha 0 -1       # "h" 被刪除了
1) "e"
2) "l"
3) "l"
4) "o"


# 情況 2: stop 比列表的最大下標還要大
redis> LTRIM alpha 1 10086     # 保留 alpha 列表索引 1 至索引 10086 上的元素
OK

redis> LRANGE alpha 0 -1       # 只有索引 0 上的元素 "e" 被刪除了,其他元素還在
1) "l"
2) "l"
3) "o"


# 情況 3: start 和 stop 都比列表的最大下標要大,並且 start < stop
redis> LTRIM alpha 10086 123321
OK

redis> LRANGE alpha 0 -1        # 列表被清空
(empty list or set)


# 情況 4: start 和 stop 都比列表的最大下標要大,並且 start > stop
redis> RPUSH new-alpha "h" "e" "l" "l" "o"     # 重新建立一個新列表
(integer) 5

redis> LRANGE new-alpha 0 -1
1) "h"
2) "e"
3) "l"
4) "l"
5) "o"

redis> LTRIM new-alpha 123321 10086    # 執行 LTRIM
OK

redis> LRANGE new-alpha 0 -1           # 同樣被清空
(empty list or set)

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) 1

redis> LPUSH mylist "Hello"
(integer) 2

redis> 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) 1

redis> LPUSH job "have lunch"
(integer) 2

redis> LLEN job
(integer) 2

10.LSET
LSET key index value
時間複雜度:對頭元素或尾元素進行 LSET 操作,複雜度爲 O(1)。其他情況下,爲 O(N), N 爲列表的長度。

將列表 key 下標爲 index 的元素的值設置爲 value 。當 index 參數超出範圍,或對一個空列表( key 不存在)進行 LSET 時,返回一個錯誤。操作成功返回 ok ,否則返回錯誤信息。

# 對空列表(key 不存在)進行 LSET

redis> EXISTS list
(integer) 0

redis> LSET list 0 item
(error) ERR no such key


# 對非空列表進行 LSET

redis> LPUSH job "cook food"
(integer) 1

redis> LRANGE job 0 0
1) "cook food"

redis> LSET job 0 "play game"
OK

redis> LRANGE job  0 0
1) "play game"


# index 超出範圍

redis> LLEN list                    # 列表長度爲 1
(integer) 1

redis> LSET list 3 'out of range'
(error) ERR index out of range

下面我們看一下它的應用:

最典型的就是時間軸

以CSDN APP的bink推薦爲例,
在這裏插入圖片描述
當有一個bink發表的時候,要將這個blink插入時間軸第一個位置。可以是LPUSH的操作。這樣子每次你就能從列表中,看到最新的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 種策略來處理加鎖失敗:

  1. 直接拋出異常,通知⽤戶稍後重試;
  2. sleep ⼀會再重試;
  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) 2

redis> SMEMBERS bbs
1) "discuz.net"
2) "groups.google.com"
3) "tianya.cn"

2.SCARD
SCARD key
時間複雜度: O(1)
返回集合 key 的基數(集合中元素的數量)。當 key 不存在時,返回 0 。

redis> SADD tool pc printer phone
(integer) 3

redis> SCARD tool   # 非空集合
(integer) 3

redis> DEL tool
(integer) 1

redis> SCARD tool   # 空集合
(integer) 0

3.SMEMBERS
SMEMBERS key
時間複雜度: O(N), N 爲集合的基數。返回集合 key 中的所有成員。不存在的 key 被視爲空集合。

# key 不存在或集合爲空

redis> EXISTS not_exists_key
(integer) 0

redis> SMEMBERS not_exists_key
(empty list or set)


# 非空集合

redis> SADD language Ruby Python Clojure
(integer) 3

redis> SMEMBERS language
1) "Python"
2) "Ruby"
3) "Clojure"

可以看出來,插入順序,與返回順序不同。要小心使用

4.srandmember和spop
SRANDMEMBER key [count]
時間複雜度: 只提供 key 參數時爲 O(1) 。如果提供了 count 參數,那麼爲 O(N) ,N 爲返回數組的元素個數。

如果命令執行時,只提供了 key 參數,那麼返回集合中的一個隨機元素。
從 Redis 2.6 版本開始, SRANDMEMBER 命令接受可選的 count 參數:

  • 如果 count 爲正數,且小於集合基數,那麼命令返回一個包含 count 個元素的數組,數組中的元素各不相同。如果 count 大於等於集合基數,那麼返回整個集合。
  • 如果 count 爲負數,那麼命令返回一個數組,數組中的元素可能會重複出現多次,而數組的長度爲 count 的絕對值。
# 添加元素
redis> SADD fruit apple banana cherry
(integer) 3

# 只給定 key 參數,返回一個隨機元素
redis> SRANDMEMBER fruit
"cherry"

redis> SRANDMEMBER fruit
"apple"

# 給定 3 爲 count 參數,返回 3 個隨機元素
# 每個隨機元素都不相同
redis> SRANDMEMBER fruit 3
1) "apple"
2) "banana"
3) "cherry"

# 給定 -3 爲 count 參數,返回 3 個隨機元素
# 元素可能會重複出現多次
redis> SRANDMEMBER fruit -3
1) "banana"
2) "cherry"
3) "apple"

redis> SRANDMEMBER fruit -3
1) "apple"
2) "apple"
3) "cherry"

# 如果 count 是整數,且大於等於集合基數,那麼返回整個集合
redis> SRANDMEMBER fruit 10
1) "apple"
2) "banana"
3) "cherry"

# 如果 count 是負數,且 count 的絕對值大於集合的基數
# 那麼返回的數組的長度爲 count 的絕對值
redis> SRANDMEMBER fruit -10
1) "banana"
2) "apple"
3) "banana"
4) "cherry"
5) "apple"
6) "apple"
7) "cherry"
8) "apple"
9) "apple"
10) "banana"

# SRANDMEMBER 並不會修改集合內容
redis> SMEMBERS fruit
1) "apple"
2) "cherry"
3) "banana"

# 集合爲空時返回 nil 或者空數組
redis> SRANDMEMBER not-exists
(nil)

redis> SRANDMEMBER not-eixsts 10
(empty list or set)
討論 

SPOP key
時間複雜度: O(1)
移除並返回集合中的一個隨機元素。如果只想獲取一個隨機元素,但不想該元素從集合中被移除的話,可以使用 SRANDMEMBER key [count] 命令。

redis> SMEMBERS db
1) "MySQL"
2) "MongoDB"
3) "Redis"

redis> SPOP db
"Redis"

redis> SMEMBERS db
1) "MySQL"
2) "MongoDB"

redis> SPOP db
"MySQL"

redis> SMEMBERS db
1) "MongoDB"

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

score value
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) 2

redis> 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) 0

redis> 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) 0

redis> 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 存在但不是有序集類型時,返回一個錯誤。正常返回值
被成功移除的成員的數量,不包括被忽略的成員。

# 測試數據
redis> ZRANGE page_rank 0 -1 WITHSCORES
1) "bing.com"
2) "8"
3) "baidu.com"
4) "9"
5) "google.com"
6) "10"


# 移除單個元素
redis> ZREM page_rank google.com
(integer) 1

redis> ZRANGE page_rank 0 -1 WITHSCORES
1) "bing.com"
2) "8"
3) "baidu.com"
4) "9"

# 移除多個元素
redis> ZREM page_rank baidu.com bing.com
(integer) 2

redis> ZRANGE page_rank 0 -1 WITHSCORES
(empty list or set)

# 移除不存在元素
redis> ZREM page_rank non-exists-element
(integer) 0

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 。

redis > ZADD salary 2000 tom    # 添加一個成員
(integer) 1

redis > ZCARD salary
(integer) 1

redis > ZADD salary 5000 jack   # 再添加一個成員
(integer) 1

redis > ZCARD salary
(integer) 2

redis > EXISTS non_exists_key   # 對不存在的 key 進行 ZCARD 操作
(integer) 0

redis > ZCARD non_exists_key
(integer) 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 這類命令。默認情況下,區間的取值使用閉區間 (小於等於或大於等於),你也可以通過給參數前增加 ( 符號來使用可選的開區間 (小於或大於)。

(integer) 0
redis> ZADD salary 5000 tom
(integer) 0
redis> ZADD salary 12000 peter
(integer) 0

redis> ZRANGEBYSCORE salary -inf +inf               # 顯示整個有序集
1) "jack"
2) "tom"
3) "peter"

redis> ZRANGEBYSCORE salary -inf +inf WITHSCORES    # 顯示整個有序集及成員的 score 值
1) "jack"
2) "2500"
3) "tom"
4) "5000"
5) "peter"
6) "12000"

redis> ZRANGEBYSCORE salary -inf 5000 WITHSCORES    # 顯示工資 <=5000 的所有成員
1) "jack"
2) "2500"
3) "tom"
4) "5000"

redis> ZRANGEBYSCORE salary (5000 400000            # 顯示工資大於 5000 小於等於 400000 的成員
1) "peter"

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 之間的成員的數量。

redis> ZRANGE salary 0 -1 WITHSCORES    # 測試數據
1) "jack"
2) "2000"
3) "peter"
4) "3500"
5) "tom"
6) "5000"

redis> ZCOUNT salary 2000 5000          # 計算薪水在 2000-5000 之間的人數
(integer) 3

redis> ZCOUNT salary 3000 5000          # 計算薪水在 3000-5000 之間的人數
(integer) 2

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) 1

redis> ZREMRANGEBYRANK salary 0 1       # 移除下標 0 至 1 區間內的成員
(integer) 2

redis> 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 值(可選)的有序集成員的列表。

redis > ZADD salary 10086 jack
(integer) 1
redis > ZADD salary 5000 tom
(integer) 1
redis > ZADD salary 7500 peter
(integer) 1
redis > ZADD salary 3500 joe
(integer) 1

redis > ZREVRANGEBYSCORE salary +inf -inf   # 逆序排列所有成員
1) "jack"
2) "peter"
3) "tom"
4) "joe"

redis > ZREVRANGEBYSCORE salary 10000 2000  # 逆序排列薪水介於 10000 和 2000 之間的成員
1) "peter"
2) "tom"
3) "joe"

我們看一下它具體的應用:
首先最容易想到的就是排行榜了。
(博主在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 hset("myhash",f1",v1);
jedis hset("myhash",f2,v2);
// 輸出結果:{1=v1,f2=V2}
jedis.hgetAll(myhash");

是不是很熟悉,其他的類似。

Jedis連接池
直連的弊端很明顯:消耗資源。如果在不同方面,頻繁用到Redis,編寫程序也很麻煩。
在這裏插入圖片描述
所以,連接池是非常重要的的。
在這裏插入圖片描述
方案對比

方案 優點 缺點
直連 簡單方便;適用於少量長期連接的場景 1.存在每次新建/關閉TCP開銷;2.資源無法控制,存在連接泄露的可能;3.Jedis對象線程不安全
連接池 Jedis預先生成,降低開銷使用連接池的形式保護和控制資源的使用 相對於直連,使用相對麻煩尤其在資源的管理上需要很多參數來保證,一旦規劃不合理也會出現問題。
// 初始化 Jedis連接池,通常來講 Jedis Pool是單例的。
 GenericObjectPoolConf poolConfig new GenericObjectPoolConfigO Jedis Pool jedis Pool new JedisPool(poolConfig, 127.0.0. 1",6379;

簡單使用:

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 closed
		jedis.close();
	}
}

說明

唉,寫得太長了,CSDN編輯器不允許我在一篇文章上繼續發揮了。

這是下一篇文章。

傳送門:【大廠面試】面試官看了讚不絕口的Redis筆記(二)

目錄:
在這裏插入圖片描述
這五個專題串過之後,你會對Redis單體,有着非常好的理解了,後面再走就是看源碼了。相信你到這一步已經可以獨當一面了。

我再往下面寫,就是Redis分佈式領域相關的東西了,比如說Redis的主從複製、哨兵機制、 Redis cluster特性等。

傳送門: 【大廠面試】面試官看了讚不絕口的Redis筆記(三)分佈式篇

目錄:
在這裏插入圖片描述
最後還有一些拓展的內容需要補充。


對了,兄dei,如果你覺得這篇文章可以的話,給俺點個贊再走,管不管?這樣可以讓更多的人看到這篇文章,對我來說也是一種激勵。

如果你有什麼問題的話,歡迎留言或者CSDN APP直接與我交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章