先來復習一下之前學過的幾個基本的全局命令:
- keys:用來查看匹配規則的key
- exists:用來判定執行key是否存在
- del:刪除指定的key
- expire:給key設置過期時間
- ttl:查詢key的過期時間
- type:查詢key對應的value的類型
一、Redis的數據結構
???????type命令實際返回的就是當前鍵的數據結構類型,他們分別是:string(字符串),list(列表),hash(哈希),set(集合),zset(有序集合),但是這些只是Redis對外的數據結構。
???????實際上Redis針對每一個數據結構都有自己的底層內部編碼實現,而且是多種實現,這樣Redis會在合適的場景選擇合適的內部編碼。
???????string類型的raw是最基本的字符串(底層就是持有一個char數組(C++)或者byte數組(Java))【C++里的char是1字節,等價與Java中的byte,而Java中的char是兩個字節的】;string類型的int是用來實現“計數”這樣的功能,當value就是一個整數的時候,此時可能Redis會直接使用int來保存;embstr是針對短字符串進行的特殊優化。
???????hash類型的hashtable是最基本的哈希表;hash類型的ziplist是在哈希表里面的元素比較少的時候可能就優化成ziplist了,壓縮列表能夠節省空間。
???????list類型的linkedlist是鏈表;list類型的ziplist是壓縮列表;從Redis3.2開始,引入了新的實現方式:quicklist,同時兼顧了linkedlist和ziplist的優點,quicklist就是一個鏈表,每一個元素又是一個ziplist把空間和效率都折中的兼顧到~~~(quicklist比較類似于C++中的std::deque)
???????set類型的intset集合中存的都是整數
???????zset類型的skiplist是跳表~~~
為什么要壓縮??
???????Redis上有很多key,可能某些key的value是hash,此時,如果key特別多,對應的hash也特別多,但是每一個hash又不大的情況下,就盡量去壓縮,壓縮之后就可以讓整體占用的內存更小了。
???????可以看到每一種數據結構都有至少兩種以上的內部編碼實現,我們可以通過 object encoding 命令查詢內部編碼:
object encoding key
Redis這樣設計有兩種好處:
- 可以改進內部編碼,而對外的數據結構和命令沒有任何影響,這樣一旦開發出更優秀的內部編碼,無需改動外部數據結構和命令,例如Redis3.2提供了quicklist,結合了ziplist和linkedlist兩者的優勢,為列表類型提供了一種更為優秀的內部編碼實現,而對用戶來說基本無感知。
- 多種內部編碼實現可以在不同場景下發揮各自的優勢,例如ziplist比較節省內存,但是在列表元素比較多的情況下,性能會下降,這時候Redis會根據配置選項將列表類型的內部實現轉換為linkedlist,整個過程用戶同樣無感知。
???????Redis會自動根據當前的實際情況選擇內部的編碼方式,自動適應的,只記思想,不記數字!!類似與這樣的“參數”非常常見,都是“可調的”。
二、Redis的單線程架構
???????Redis使用了單線程架構來實現高性能的內存數據庫服務。Redis只使用一個線程處理所有的命令請求,不是說一個Redis服務器進程內部只有一個線程,其實也有多個線程,多個線程在處理網絡IO。
2.1 引出單線程模型
現在開啟了三個redis-cli客戶端同時執行命令。
客戶端1設置一個字符串鍵值對:
set hello world
客戶端2對counter做自增操作:
incr counter
客戶端3對counter做自增操作:
incr counter
???????我們已經知道了從客戶端發送的命令經歷:發送命令,執行命令,返回結構三個階段,其中發送命令最重要。Redis是采用單線程模型執行命令的是指:雖然三個客戶端看起來是同時要求Redis去執行命令的,但是微觀角度上,這些命令還是采用線性方法去執行的,只是原則上命令的執行順序是不確定的,但是一定不會有兩條命令被同步執行,可以想象Redis內部只有一個服務窗口,多個客戶端按照他們達到的先后順序被排隊在窗口前,依次接受Redis的服務,所以兩條incr命令無論執行順序,結果一定是2,不會發生并發問題。
???????Redis能夠使用單線程模型很好的工作,原因主要在于Redis的核心業務邏輯,都是短平快的,不太消耗CPU資源,也就不太吃多核~?
Redis必須要特別小心某一個操作占用時間長,就會阻塞其他命令的執行!!!?
線程安全問題
???????在多線程中,針對類似這樣的場景:兩個線程嘗試同時對一個變量進行自增,表面上看是自增兩次,實際上可能只自增一次。?
2.2 為什么單線程還能這么快
參照物是數據庫(MySQL)
- 純內存訪問。Redis將所有數據放在內存中,內存的響應時長大約為100納秒,這是Redis達到每秒萬級別訪問的重要基礎。
- 非阻塞IO。Redis使用epoll作為I/O多路復用技術的實現,在加上Redis自身的事件處理模型將epoll中的連接、讀寫、關閉都轉換為事件,不在網絡IO上浪費過多的時間。
- 單線程避免了線程切換和競態產生的消耗。單線程可以簡化數據結構和算法的實現,讓程序模型更簡單;其次多線程避免了在線程競爭同一份共享數據時帶來的切換和等待消耗。
- Redis的核心功能比數據庫的核心功能更簡單。數據庫對于數據的插入刪除查詢……都有更復雜的功能支持,這樣的功能勢必要花費更多的開銷。
???????雖然單線程給Redis帶來很多好處,但是還有一個致命的問題:對于單個命令的執行時間都是有要求的。如果某一個命令執行過長,會導致其他命令全部處于等待隊列中,遲遲等不到響應,造成客戶端的阻塞,對于Redis這種高性能的服務來說是非常重要的,所以Redis是面向快速執行場景的數據庫。
三、String字符串
字符串類型是Redis最基本的數據類型,關于字符串需要特別注意:
- 首先Redis中所有的鍵的類型都是字符串類型,而且其他幾種數據結構也都是在字符串類似基礎上構建的,例如列表和集合的元素類型是字符串類型,所以字符串類型能為其他4中數據結構的學習奠定基礎
- 字符串類型的值實際可以是字符串,包含一般格式的字符串或者類似于JSON、XML格式的字符串;數字可以是整形或者浮點型,甚至是二進制數據,例如圖片、音頻、視頻等。不過一個字符串的最大值不能超過512MB。
???????由于Redis內部存儲字符串完全是按照二進制流的形式保存的,所以Redis是不處理字符集編碼問題的,客戶端傳入的命令中使用的是什么字符集編碼,就存儲什么字符集編碼。 (MySQL的默認字符集是拉丁文,插入中文就會失敗~)?
3.1 常見命令?
???????FLUSHALL可以把Redis上所有的鍵值對都帶走,以后在公司,尤其是生產環境的數據庫中,千萬不敢敲。?
3.1.1 SET
???????將string類型的value設置到key中。如果key之前存在,則覆蓋;無論原來的數據類型是什么。之前關于此key的TTL也全部失效。語法如下:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
Redis文檔給出的語法格式說明:
???????[ ] 相當于一個獨立的單元,表示可選項(可有可無的),其中 | 表示“或者”的意思,多個只能出現一個。[ ] 和 [ ] 之間是可以互相同時存在的。?
時間復雜度為:O(1)
選項:
SET命令支持多種選項來影響他的行為:
- EX——使用秒作為單位設置key的過期時間
- PX——使用毫秒作為單位設置key的過期時間
- NX——只在key不存在時才進行設置,即如果key之前已經存在,設置不執行
- XX ——只在key存在時才進行設置,即如果key之前不存在,設置不執行
注意:由于帶選項的SET命令可以被SETNX、SETEX、PSETEX等命令代替,所以之后的版本中,Redis可能進行合并。?
返回值:
- 如果設置成功,返回OK
- 如果由于SET指定了NX或者XX,但是條件不滿足,SET不會執行,并返回(nil)?
3.1.2 GET
???????獲取key對應的value。如果key不存在,返回nil。如果value的數據類型不是string,會報錯。語法如下:
GET key
時間復雜度為:O(1)
返回值:key對應的value,或者nil當key不存在?
3.1.3 MGET
???????一次性獲取多個key的值。如果對應的key不存在或者對應的數據類型不是string,返回nil。語法如下:
MGET key [key ...]
時間復雜度為:O(N)N是key的數量
返回值:對應value的列表。?
3.1.4 MSET
一次性設置多個key的值。語法如下:
MSET key value [key value ...]
時間復雜度為:O(N)N是key的數量
返回值:永遠是OK
???????使用mget/mset由于可以有效地減少了網絡時間,所以性能相較于更高。假設網絡耗時1毫秒,命令執行時間耗時0.1毫秒。學會使用批量操作,可以有效提高業務處理效率,但是要注意,每次批量操作所發送的鍵的數量也不是無節制的,否則可能造成單一命令執行時間過長,導致Redis阻塞。
3.1.5 SETNX
設置key-value,但是只允許在key之前不存在的情況下。語法如下:
SETNX key value
時間復雜度為:O(1)
返回值:1表示設置成功。0表示沒有設置。?
3.1.6 SETEX和PSETEX
設置key的過期時間,單位是秒和毫秒。
3.2 計數命令
INCR
???????將key對應的string表示的數字加一。如果key不存在,則視為key對應的value是0。如果key對應的string不是一個整形或者范圍超出了64位有符號整形,則報錯。語法如下:
INCR key
時間復雜度為:O(1)
返回值:integer類型的加完后的數值。?
INCRBY
???????將key對應的string表示的數字加上對應的值。如果key不存在,則視為key對應的value是0。如果key對應的string不是一個整形或者范圍超過了64位有符號整形,則報錯。語法如下:
INCRBY key decrement
時間復雜度為:O(1)
返回值:integer類型的加完后的數值。?
DECR
DECRBY
INCRBYFLOAT
???????將key對應的string表示的浮點數加上對應的值。如果對應的值是負數,則視為減去對應的值。如果key不存在,則視為key對應的value是0,。如果key對應的不是string,或者不是一個浮點數,則報錯。允許采用科學計數法表示浮點數。語法如下:
INCRBYFLOAT key increment
時間復雜度為:O(1)
返回值:加/減完后的數值。
???????很多存儲系統和編程語言內部使用CAS機制實現計數功能,會有一定的CPU開銷,但是在Redis中完全不存在這個問題,因為Redis是單線程架構,任何命令到了Redis服務端都要順序執行。?
3.3 其他命令
APPEND
???????如果key已經存在并且是一個string,命令會將value追加到原有string的后邊。如果key不存在,則效果等同于SET命令。語法如下:
APPEND KEY VALUE
時間復雜度為:O(1),追加的字符串一般長度較短,可以視為O(1)
返回值:追加完成之后string的長度。?
???????在啟動Redis客戶端的時候,加上一個 --raw這樣的選項,就可以使Redis客戶端能夠自動把二進制數據嘗試翻譯。操作linux的時候,千萬注意不要亂按 crtl + s
ctrl + s 在XShell中的作用是“凍結當前畫面”
ctrl + q 解除凍結?
GETRANGE
???????返回key對應的string子串,由start和end確定(左閉右閉)。可以使用負數表示倒數。-1表示倒數第一個字符,-2表示倒數第二個字符,其他的與此類似。超過范圍的偏移量會根據string的長度調整成正確的值。語法如下:
GETRANGE key start end
時間復雜度為:O(N),N為[start,end]區間的長度,由于string通常比較短,可以視為是O(1)
返回值:string類型的子串。?
SETRANGE
覆蓋字符串的一部分,從指定的偏移開始。語法如下:
SETRANGE key offset value
時間復雜度為:O(N),N為value的長度,由于一般給的value比較短,通常視為O(1)
返回值:替換后的string的長度。?
STRLEN
獲取key對應的string的長度。當key存放的類似不是string時,報錯。語法如下:
STRLEN key
時間復雜度為:O(1)
返回值:string的長度。或者當key不存在時,返回0
單位是字節。
???????C++中,字符串的長度本身就是用字節為單位;Java中,字符串的長度則是以字符為單位。MySQL的時候,varchar(N)此處的N的單位就是字符,MySQL中的字符也是完整的漢字,這樣的一個字符,也可能是多個字節。
???????Java中的一個char == 2字節,Java中的char基于unicode這樣的編碼方式能夠表示中文等符號~~Java中的char是用的unicode,一個漢字使用兩個字節,Java中的String則是使用的utf8,一個漢字就是3個字節了,Java的標準庫內部,在進行上述的操作過程中,程序員一般是感知不到編碼方式的變換的。
3.4 內部編碼
字符串類型的內部編碼有3種:
- int:8個字節的長整型
- embstr:小于等于39個字節的字符串
- raw:大于39個字節的字符串
Redis會根據當前值的類型和長度動態決定使用哪種內部編碼實現的。
???????Redis存儲小數,本質上還是當做字符串來存儲的,這就和整數相比差距很大了,整數直接使用int來存(準確的說是一個long long)比較方便進行算法運算,小數則是使用字符串來存儲,意味著每次進行算術運算,都需要把字符串轉成小數,進行運算,結果再轉為字符串保存。
3.5 典型使用場景
緩存功能
???????下圖是比較典型的緩存使用場景,其中Redis作為緩沖層,MySQL作為存儲層,絕大多數請求的數據都是從Redis中獲取的。由于Redis具有支撐高并發的特性,所以緩存通常能起到加速讀寫和降低后端壓力的作用。
計數功能
???????許多應用都會使用Redis作為計數的基礎工具,他可以實現快速計數,查詢緩存的功能,同時數據可以異步處理或者落地到其他數據源。如圖所示:例如視頻網站的視頻播放次數可以使用Redis來完成,用戶每播放一次視頻,響應的視頻播放數就會自增1。
???????實際上要開發一個成熟,穩定的真實計數系統,要面臨的挑戰遠不止如此簡單:防作弊、按照不同維度計數,避免單點問題,數據持久化到底層數據源等。?
共享會話(Session)
???????一個分布式Web服務將用戶的Session信息(例如用戶登錄信息)保存在各自的服務器中,但這樣會造成一個問題:處于負載均衡的考慮,分布式服務會將用戶的反問請求均衡到不同的服務器上,并且通常無法保證用戶每次請求都會被均衡到同一臺服務器上,這樣當用戶刷新一次訪問是可能會發現需要重新登錄,這個問題是用戶無法容忍的。
???????為了解決這個問題,可以使用Redis將用戶的Session信息進行集中管理,在這種模式下,只要保證Redis是高可用和可擴展性的,無論用戶被均衡到哪一臺Web服務器上,都集中從Redis中查詢,更新Session信息。
手機驗證碼
???????很多應用處于安全考慮,會在每次進行登錄時,讓用戶輸入手機號并且配合給手機號發送驗證碼,然后讓用戶再次輸入收到的驗證碼并進行驗證,從而確定是否是用戶本人。
四、Hash哈希
???????幾乎所有的主流編程語言都提供了哈希類型,他們的叫法可能是哈希,字典,關聯數組,映射。在Redis中,哈希類型是指值本身又是一個鍵值對結構,形如key = "key",value = { {} }。
???????哈希類型中的映射關系通常為field-value,用于區分Redis整體的鍵值對(key-value),注意這里的value是指field對應的值,不是鍵(key)對應的值,請注意value在不同上下文的作用。
4.1 基本命令
HSET
設置hash中指定的字段(field)的值(value).語法如下:
HGET
HEXISTS
HDEL
HKEYS
HVALS
HGETALL
HMSET
HSCAN
HLEN
HSETNX
HINCRBY
HINCRBYFLOAT
4.2 內部編碼
哈希的內部編碼有兩種:
- ziplist(壓縮列表):當哈希類型元素個數小于 hash-max-ziplist-entries 配置(默認512個),同時所有值都小于 hash-max-ziplist-value 配置(默認64字節)時,Redis會使用ziplist作為哈希的內部實現,ziplist使用更加緊湊的結構實現多個元素的連續存儲,所以在節省內存方面比hashtable更優秀
- hashtable(哈希表):當哈希類型無法滿足ziplist的條件時,Redis會使用hashtable作為哈希的內部實現,因為此時ziplist的讀寫效率會下降,而hashtable的讀寫時間復雜度為O(1)
壓縮:
rar,zip,gzip,7z等一些具體的壓縮算法。
???????壓縮的本質是針對數據進行重新編碼,不同的數據有不同的特點,結合這些特點,進行精妙的設計,重寫編碼之后,就能縮小體積~~
???????ziplist也是精心設計的,目的是節省內存空間,但是進行讀寫元素時,速度是比較慢的。如果元素個數少,慢的并不明顯;如果元素個數太多了,慢就會雪上加霜。?
4.3 使用場景
緩存功能
存儲結構化的數據使用hash類型更合適一些~~~
4.4 緩存方式對比
截止目前為止,我們已經有三種方式緩存用戶信息,下面給出三種方案的實現方法和優缺點分析:
原生字符串類型——使用字符串類型,每一個屬性對應一個鍵
set user:1:name James
set user:1:age 23
set user:1:city Beijing
- 優點:實現簡單,針對個別屬性變更也很靈活
- 缺點:占用過多的鍵,內存占用量較大,同時用戶信息在Redis中比較分散,缺少內聚性,所以這種方案沒有實用性。?
序列化字符串類型,例如JSON格式
set user:1 經過序列化后的??對象字符串
- 優點:針對總是以整體作為操作的信息比較合適,編程也簡單。同時,如果序列化方案選擇合適,內存的使用效率很高。
- 缺點: 本身序列化和反序列需要一定開銷,同時如果總是操作個別屬性則非常不靈活。
哈希類型
hmset user:1 name James age 23 city Beijing
- 優點:簡單,直觀,靈活。尤其是針對信息的局部變更或者獲取操作。
- 缺點:需要控制哈希在ziplist和hashtable兩種內部編碼的轉換,可能會造成內存的較大消耗。
五、list列表
???????列表類型是用來存儲多個有序的字符串,列表中的每一個字符串稱為元素,一個列表最多可以存儲 2^32 - 1 個元素。在Redis中,可以對列表兩端插入和彈出,還可以獲取指定范圍的元素列表、獲取指定索引下標的元素等。列表是一種比較靈活的數據結構,他可以充當棧和隊列的角色,在實際開發中有很多應用場景。
列表類型的特點:
- 列表中的元素是有序的,這個有序不是說這個列表中存儲的元素是有序的,而是說,不同順序的列表不同,即使他們的元素是相同的。
- 區分獲取和刪除的區別,刪除元素的話,會將這個列表的長度減少;但是執行 lindex 4 只會獲取元素,但是列表長度是不會變化的。?
- 列表中的元素是允許重復的。
???????列表(List)相當于數組或者順序表,注意,list內部的結構(編碼方式)并非是一個簡單的數組,而是更接近于“雙端隊列”(deque)。因為當前的List,頭和為都能高效的插入和刪除元素,就可以把這個List當做一個棧或者隊列來使用。
???????Redis有一個典型的應用場景,就是作為消息隊列,最早的時候,就是通過List類型~~,后來,Redis又提供了一個stream類型。
5.1 基本命令
LPUSH
將一個或者多個元素從左側放入(頭插)到list中,語法如下:
LPUSH key element [element ...]
時間復雜度:只插入一個元素為O(1),插入多個元素為O(1),N為插入元素的個數。
返回值:插入后list的長度。
LPUSHX
在key存在時,將一個或者多個元素從左側放入(頭插)到list中。不存在,直接返回。語法如下:
LPUSHX key element [element ...]
時間復雜度:只插入一個元素為O(1),插入多個元素為O(N),N為插入的元素個數。
返回值:插入后的list的長度。?
RPUSH
RPUSHX
LRANGE
獲取從start到stop區間的所有元素,左閉右閉。語法如下:
LRANGE key start stop
時間復雜度為:O(N)
返回值:指定區間的元素?
LPOP
從list左側取出元素(即頭刪)。語法如下:
LPOP key
時間復雜度為:O(1)
返回值:取出的元素或者nil?
RPOP
LINDEX
獲取從左邊第index位置的元素。語法如下:
LINDEX key index
時間復雜度為:O(N)
返回值:取出的元素或者nil?
LINSERT
在特定位置插入元素,語法如下:
LINSERT key <BEFORE | AFTER> pivot element
時間復雜度:O(N)
返回值:插入后的list長度?
LLEN
獲取list長度,語法如下:
LLEN key
時間復雜度:O(1)
返回值:list的長度?
5.2 阻塞版本命令
blpop和brpop是lpop和rpop的阻塞版本,和對應非阻塞版本的作用基本一致,除了:
- 在列表中有元素的情況下,阻塞和非阻塞版本表現是一致的。但是如果列表中沒有元素,非阻塞版本會立即返回nil,但是阻塞版本會根據timeout,阻塞一段時間,期間Redis可以執行其他命令,但是要求執行該命令的客戶端會表現為阻塞狀態。
- 命令中如果設置了多個鍵,那么會從左向右進行遍歷鍵,一旦有一個鍵對應的列表中可以彈出元素,命令立即返回。
- 如多個客戶端同時多個鍵執行pop,則最先執行命令的客戶端會得到彈出的元素。
BLPOP
LPOP的阻塞版本,語法如下:
BLPOP key [key ...] timeout
返回值:取出的元素或者nil?
BRPOP?
RPOP的阻塞版本,語法如下:
BRPOP key [key ...] timeout
?返回值:取出的元素或者nil?
5.3 內部編碼
???????對于現在的列表類型的內部編碼已經變為了quicklist,quicklist箱單與鏈表和壓縮列表的結合。整體還是一個鏈表,鏈表的每一個節點是一個壓縮列表。每一個壓縮列表都不讓他太大,同時再把多個壓縮列表通過鏈式結構連起來~~
下面介紹的編碼已經不在使用了:
列表類型的內部編碼有兩種:
ziplist(壓縮列表):當列表的元素個數小于 list-max-ziplist-entries 配置(默認512個),同時列表中每一個元素的長度都小于 list-max-ziplist-value 配置(默認64字節)時,Redis會選用ziplist來作用列表的內部編碼實現來減少內存消耗。linkedlist(鏈表):當列表類型無法滿足 ziplist 的條件時,Redis會使用 linkedlist 作為列表的內部實現。
5.4 使用場景
存儲多個元素,進行查詢
可以使用列表作為“數組”來存儲多個元素,Redis提供的查詢功能不像MySQL那樣強大。
消息列表
???????Redis可以使用 lpush + brpop 命令組合實現經典的阻塞式生產者-消費者模型隊列,生產者客戶端使用 lpush 從列表左側插入元素,多個消費者客戶端使用 brpop 命令阻塞式地從隊列中“爭搶”隊首元素。通過多個客戶端來保證消費的負載均衡和高可用性。
???????只有一個消費者能“搶到”元素,誰先執行的這個brpop命令,誰就能拿到這個新來的元素,像這樣的設定,就能構成一個“輪詢”。
分頻道的消息列表
???????Redis同樣使用 lpush + brpop 命令,但是通過不同的鍵模擬頻道的概念,不同的消費者可以通過 brpop 不同的鍵值,實現訂閱不同頻道的理念。
???????多個列表/頻道,這種場景非常常見,日常使用的一些程序中,抖音等都有這些。有一個通常來傳輸短視頻數據,還可以有一個通道來傳輸數據,還可以有頻道來傳輸點贊,轉發等,還可以有頻道來傳輸評論和數據。?
微博Timeline
???????每一個用戶都有屬于自己的Timeline(微博列表),現需要分頁展示文章列表。此時可以考慮使用列表,因為列表不但是有序的,同時支持按照索引范圍獲取元素。
選擇列表類型時,請參考:
- 同側存取(lpush + lpop 或者 rpush + rpop)為棧
- 異側存取(lpush + rpop 或者 rpush + lpop)為隊列
六、Set集合
集合類型也是保存多個字符串類型的元素,但是和列表類型不同的是,在集合中:
- 元素之間是無序的
- 元素不允許重復,一個集合中最多可以存儲2^32 - 1個元素
???????Redis除了支持集合內的增刪查改操作,同時還支持多個集合取交集、并集、差集,合理地使用好集合類型,能在實際開發中解決很多問題。
6.1 普通命令
SADD
將一個或者多個元素添加到set中。注意,重復的元素無法添加到set中。語法如下:
SADD key member [member ...]
時間復雜度為:O(1)
返回值:本次添加成功的元素個數?
SMEMBERS
獲取一個set中的所有元素,注意,元素間的順序是無序的。語法如下:
SMEMBERS key
時間復雜度:O(N)
返回值:所有元素的列表?
SISMEMBER
判斷一個元素在不在set中,語法如下:
SISMEMBER key member
時間復雜度:O(1)
返回值:1表示元素在set中,0表示元素不在set中或者key不存在?
SCARD
獲取一個set的基數,即set中的元素個數。語法如下:
SCARD key
時間復雜度為:O(1)
返回值:set內的元素個數?
SPOP
???????從set中刪除并返回一個或者多個元素。注意,由于set內的元素是無序的,所以取出哪一個元素是未定義行為,即可以看做隨機的。語法如下:
SPOP key [count]
時間復雜度:O(N),N是count
返回值:取出的元素?
SMOVE
將一個元素從源set取出并放入目標set中,語法如下:
SMOVE source destination member
時間復雜度:O(1)
返回值:1表示移動成功,0表示失敗?
SREM
將指定的元素從set中刪除,語法如下:
SREM key member [member ...]
時間復雜度:O(N),N是要刪除的元素個數
返回值:本次操作刪除的元素個數
6.2 集合間的操作
SINTER
獲取給定set的交集中的元素,語法如下:
SINTER key [key ...]
時間復雜度:O(N * M),N是最小的集合元素個數,M是最大的集合元素個數
返回值:交集的元素?
SINTERSTORE
獲取給定set的交集中的元素并保存到目標set中。語法如下:
SINTERSTORE destination key [key ...]
時間復雜度:O(N * M),N是最小的集合元素個數,M是最大的集合元素個數
返回值:交集的元素個數
SUNION
SUNIONSTORE
SDIFF
SDIFFSTORE
6.3 內部編碼
集合類型的內部編碼有兩種:
intset(整數集合)
hashtable(哈希表)
6.4 使用場景
???????集合類型比較典型的使用場景是標簽。例如A用戶對娛樂、體育板塊比較感興趣,B用戶對歷史、新聞比較感興趣,這些興趣點可以被抽象為標簽。有了這數據就可以得到喜歡同一個標簽的人,以及用戶的共同愛好的標簽,這些數據對于增強用戶體驗和用戶粘度都非常有幫助。例如,一個電子商務網站會對不同標簽的用戶做不同的產品推薦。
sinter user:1:tags user:2:tags
使用Set來計算用戶之間的共同好友
基于“集合求交集”
使用Set統計UV
去重
一個互聯網產品,如何衡量用戶量,用戶規模??
主要的指標是兩個方面:
PV(page view):用戶每次訪問該服務器,每次訪問都會產生一個pv
UV(user view)?:每一個用戶訪問服務器,都會產生一個uv,但是同一個用戶多次訪問,不會使uv增加,uv需要按照用戶進行去重,上述的去重過程,就可以使用set去實現。
七、Zset有序集合
? ? ? ?有序集合相對于字符串、列表、哈希、集合來說會有一些陌生。他保留了集合不能有重復成員的特點,但是與集合不同的是,有序集合中的每一個元素都有一個唯一的浮點數類型的分數與之關聯,使得有序集合中的元素是可以維護有序性的,但是這個有序不是用下標作為排序依據而是用這個分數。
???????有序集合提供了獲取指定分數和元素范圍查找,計算成員排名等功能,合理地利用有序集合,可以幫助我們在實際開發中解決很多問題。
???????有序集合中的元素是不能重復的,但是分數允許重復。類比于一次考試之后,每一個人一定有一個唯一的分數,但是分數允許相同。
7.1 普通命令
7.2 集合間的操作
7.3 內部編碼
有序集合類型的內部編碼有兩種:
ziplist(壓縮列表)
skiplist(跳表)
7.4 使用場景
有序即可比較典型的使用場景就是排行榜系統。