Redis 五种数据结构以及三种高级数据结构解析以及使用
一、前言
在 Redis 最重要最基礎(chǔ)就屬 它豐富的數(shù)據(jù)結(jié)構(gòu)了,Redis 之所以能脫穎而出很大原因是他數(shù)據(jù)結(jié)構(gòu)豐富,可以支持多種場景。并且 Redis 的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)以及應(yīng)用場景在面試中是相當(dāng)常見的,接下來就和大家聊聊 Redis 的數(shù)據(jù)結(jié)構(gòu)。
Redis數(shù)據(jù)結(jié)構(gòu)有:string、list、hash、set、sorted set 這五個是大家都知道的,但Redis還有更高級得數(shù)據(jù)結(jié)構(gòu),比如:HyperLogLog、Geo、BloomFilter 這幾個數(shù)據(jù)結(jié)構(gòu),接下來聊聊Redis得這些數(shù)據(jù)結(jié)構(gòu)吧。
二、String
基本概念:String 是 Redis 最簡單最常用的數(shù)據(jù)結(jié)構(gòu),也是 Memcached 唯一的數(shù)據(jù)結(jié)構(gòu)。在平時的開發(fā)中,String 可以說是使用最頻繁的了。
底層實現(xiàn):
- 如果一個字符串對象保存的是整數(shù)值, 并且這個整數(shù)值可以用 long 類型來表示, 那么字符串對象會將整數(shù)值保存在字符串對象結(jié)構(gòu)的 ptr 屬性里面(將 void* 轉(zhuǎn)換成 long ), 并將字符串對象的編碼設(shè)置為 int 。
- 如果字符串對象保存的是一個字符串值,并且這個字符串值的長度大于 39 字節(jié), 那么字符串對象將使用一個簡單動態(tài)字符串(SDS)來保存這個字符串值, 并將對象的編碼設(shè)置為raw。
- 如果字符串對象保存的是一個字符串值, 并且這個字符串值的長度小于等于 39 字節(jié), 那么字符串對象將使用 embstr 編碼的方式來保存這個字符串值。
使用:
> redis_cli # 啟動redis-cli 客戶端 > set hello world # 將鍵 hello 的值設(shè)置為 world OK # set 命令成功后 會返回 OK > get hello # 通過 get 命令獲取 鍵為 hello 的值 "world" # 獲得到的值 > del hello # 刪除鍵為 hello 的值 (integer) 1 # 返回的是刪除的數(shù)量 > mset a 10 b 20 c 30 # 批量的設(shè)置值 OK > mget a b c # 批量的返回值 1)"10" 2)"20" 3)"30" > exists hello # 是否存在該鍵 (integer) 1 # 1 表示存在,0 表示不存在 > expire hello 10 # 給 hello 設(shè)置過期時間,單位,秒 (integer) 1 # 返回1代表成功,0代表key不存在或無法設(shè)置過期時間 > pexpire hello 10 # 給 hello 設(shè)置過期時間,單位,毫秒 (integer) 1 # 返回1代表成功,0代表key不存在或無法設(shè)置過期時間接下來會重點講一下 set key value [EX seconds] [PX milliseconds] [NX|XX] 這個一系列命令,這塊還是挺重要的,也很容易混淆。
reids 每次對 以前的值覆蓋時,會 清空 TLL 值。(TTL 是過期時間)
- EX second:設(shè)置鍵的過期時間為 second 秒。 SET key value EX second 效果等同于 SETEX key
second value 。 - PX millisecond :設(shè)置鍵的過期時間為 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
- NX:只在鍵不存在時,才對鍵進(jìn)行設(shè)置操作。 SET key value NX 效果等同于 SETNX key value 。
- XX :只在鍵已經(jīng)存在時,才對鍵進(jìn)行設(shè)置操作。
在開發(fā)過程中,用 redis 來實現(xiàn)鎖是很常用的操作。結(jié)合 NX 以及 EX 來實現(xiàn)。
> set hello world NX EX 10 # 成功加鎖,過期時間是 10s OK > set hello wolrd NX EX 10 # 在10s內(nèi)執(zhí)行這個命令返回錯誤,因為上一次的鎖還沒有釋放 (nil) > del hello # 釋放了鎖 OK > set hello world NX EX 10 # 成功加鎖,過期時間是 10s OK > setnx hello world # 也可以這么寫 > setex hello 10 wolrd鎖可以通過設(shè)置過期時間以及手動 del 刪除來釋放鎖。
string 的命令比較常用就多介紹了點,下面的命令我就挑重點介紹了。
應(yīng)用場景:
- 緩存功能:string 最常用的就是緩存功能,會將一些更新不頻繁但是查詢頻繁的數(shù)據(jù)緩存起來,以此來減輕 DB 的壓力。
- 計數(shù)器:可以用來計數(shù),通過 incr 操作,如統(tǒng)計網(wǎng)站的訪問量、文章訪問量等。
三、List
基本概念: list 是有序可重復(fù)列表,和 Java 的 List 蠻像的,查詢速度快,可以通過索引查詢;插入刪除速度慢。
底層實現(xiàn):
- 列表對象的編碼可以是 ziplist 或者 linkedlist 。 >- 列表對象保存的所有字符串元素的長度都小于 64字節(jié)并且保存的元素數(shù)量小于 512 個,使用 ziplist 編碼;否則使用 linkedlist;
使用:
> lpush mylist a # 從左邊插入數(shù)據(jù) (ineteger)1 > lpush mylist b (integer)1 > rpush mylist c # 從右邊插入數(shù)據(jù) (integer)1 > lrange mylist 0 -1 # 檢索數(shù)據(jù),lrange 需要兩個索引,左閉右閉;0 就是從第 0 個,-1 是倒數(shù)第一個,-2 倒數(shù)第二個...以此類推 1)"b" 2)"a" 3)"c" > lrange mylist 0 -2 # 0 到 倒數(shù)第 2 個 1)"b" 2)"a" > lpush mylist a b c # 批量插入 (integer)3 > lpop mylist # 從左側(cè)彈出元素 "b" > rpop mylist # 從右側(cè)彈出元素 "c" > rpop mylist # 當(dāng)列表中沒有元素時返回 null (nil) > brpoop mylist 5 # 從右側(cè)彈出元素,如果列表沒有元素,會阻塞住,如果 5 s后還是沒有元素則返回 1)"mylist" # 列表名 2)"b" # 彈出元素 > del mylist # 刪除列表 (integer)1使用場景:
- 消息隊列:Redis 的 list 是有序的列表結(jié)構(gòu),可以實現(xiàn)阻塞隊列,使用左進(jìn)右出的方式。Lpush 用來生產(chǎn) 從左側(cè)插入數(shù)據(jù),Brpop 用來消費,用來從右側(cè) 阻塞的消費數(shù)據(jù)。
- 數(shù)據(jù)的分頁展示: lrange命令需要兩個索引來獲取數(shù)據(jù),這個就可以用來實現(xiàn)分頁,可以在代碼中計算兩個索引值,然后來 redis 中取數(shù)據(jù)。
- 可以用來實現(xiàn)粉絲列表以及最新消息排行等功能。
四、Hash
簡介:Redis 散列可以存儲多個鍵值對之間的映射。和字符串一樣,散列存儲的值既可以是字符串又可以是數(shù)值,并且用戶同樣可以對散列存儲的數(shù)字值執(zhí)行自增或自減操作。這個和 Java 的 HashMap 很像,每個 HashMap 有自己的名字,同時可以存儲多個 k/v 對。
底層實現(xiàn):
- 哈希對象的編碼可以是 ziplist 或者 hashtable >
- 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于 64字節(jié)并且保存的鍵值對數(shù)量小于 512 個,使用ziplist 編碼;否則使用hashtable;
使用:
> hset student name 張三 # 可以理解為忘名叫student的map中添加 kv 鍵值對 (integer)1 # 返回1 代表 不存在這個key,并且添加成功 > hset student sex 男 (integer)1 > hset student name 張三 (integer)0 # 返回0 因為這個key已經(jīng)存在 > hgetall student 1)"name" 2)"張三" 3)"sex" 4)"男" > hdel student name #刪除這key (integer)1 # 返回 1 同樣代表整個 key 存在 并且刪除成功 > hdel student name (integer)0 # 返回 0 是因為 該 key 已經(jīng)不存在應(yīng)用場景:
- Hash 更適合存儲結(jié)構(gòu)化的數(shù)據(jù),比如 Java 中的對象;其實 Java 中的對象也可以用 string 進(jìn)行存儲,只需要將對象序列化成 json串就可以,但是如果這個對象的某個屬性更新比較頻繁的話,那么每次就需要重新將整個對象序列化存儲,這樣消耗開銷比較大。可如果用 hash 來存儲對象的每個屬性,那么每次只需要更新要更新的屬性就可以。
- 購物車場景:可以以用戶的id為key,商品的id為存儲的field,商品數(shù)量為鍵值對的value,這樣就構(gòu)成了購物車的三個要素。
五、Set
基本概念:Redis 的set和list都可以存儲多個字符串,他們之間的不同之處在于,list是有序可重復(fù),而set是無序不可重復(fù)。
底層實現(xiàn):
- 集合對象的編碼可以是 intset 或者 hashtable 。 > - 集合對象保存的所有元素都是整數(shù)值并且保存的元素數(shù)量不超過 512個,使用intset 編碼;否則使用hashtable;
使用:
> sadd family mother # 嘗試將 mother 添加進(jìn) family 集合中 (integer)1 # 返回 1 表示添加成功,0 表示元素已經(jīng)存在集合中 > sadd family father (integer)1 > sadd family father (intger)0 > smembers family # 獲取集合中所有的元素 1)"mother" 2)"father" > sismember family father # 判斷 father 是否在 family 集合中 (integer)1 # 1 存在;0 不存在 > sismber family son (integer)0 > srem family son # 移除 family 集合中元素 son (integer)1 # 1 表示存在并且移除成功;0 表示存在該元素 > srem family som (integer)0 > sadd family1 mother (integer)1 > smembers family 1)"mother" 2)"father" > smember family1 1)"mother" > sinter family family1 # 獲取 family 和 family1 的交集 1)"mother" > sadd family1 son (integer)1 > sunion family family1 # 獲取 family 和 family1 的并集 1)"mother" 2)"father" > sdiff family family1 # 獲取 family 和 family1 的差集(就是family有但是family1沒有的元素) 1)"father"應(yīng)用場景:
- 標(biāo)簽:可以將博客網(wǎng)站每個人的標(biāo)簽用 set 集合存儲,然后還按每個標(biāo)簽 將用戶進(jìn)行歸并。
- 存儲好友/粉絲:set具有去重功能;還可以利用set并集功能得到共同好友之類的功能。
六、Sorted Set
基本概念:有序集合和散列一樣,都用于存儲鍵值對:其中有序集合的每個鍵稱為成員(member),都是獨一無二的,而有序集合的每個值稱為分值(score),都必須是浮點數(shù)。可以根據(jù)分?jǐn)?shù)進(jìn)行排序,有序集合是Redis里面唯一既可以根據(jù)成員訪問元素(這一點和散列一樣),又可以根據(jù)分值以及分值的排列順序來訪問元素的結(jié)構(gòu)。和Redis的其他結(jié)構(gòu)一樣,用戶可以對有序集合執(zhí)行添加、移除和獲取等操作。
底層實現(xiàn):
- 有序集合的編碼可以是 ziplist 或者 skiplist
- 有序集合保存的元素數(shù)量小于 128 個并且保存的所有元素成員的長度都小于 64字節(jié)。使用 ziplist 編碼;否則使用skiplist;
使用:
> zadd class 100 member1 # 將member1元素及其score值100加入到 有序集合 class中 (integer)1 > zadd class 90 member2 80 member3 # 批量添加 (integer)2 > zrange class 0 -1 withscores # 獲取有序集合中的值與score,并按 score 排序 1)"member3" 2)"80" 3)"member2" 4)"90" 5)"member1" 6)"100" > zrem class member1 # 刪除 class 中 的member1 (integer)1應(yīng)用場景:
- 排行榜:有序集合最常用的場景。如新聞網(wǎng)站對熱點新聞排序,比如根據(jù)點擊量、點贊量等。
- 帶權(quán)重的消息隊列:重要的消息 score大一些,普通消息 score 小一些,可以實現(xiàn)優(yōu)先級高的任務(wù)先執(zhí)行。
七、HyperLogLog
基本概念:
Redis 在 2.8.9 版本添加了 HyperLogLog 結(jié)構(gòu)。
-
Redis HyperLogLog 是用來做基數(shù)統(tǒng)計的算法,HyperLogLog
的優(yōu)點是,在輸入元素的數(shù)量或者體積非常非常大時,計算基數(shù)所需的空間總是固定 的、并且是很小的。 -
在 Redis 里面,每個 HyperLogLog 鍵只需要花費 12 KB 內(nèi)存,就可以計算接近 2^64 個不同元素的基
數(shù)。這和計算基數(shù)時,元素越多耗費內(nèi)存就越多的集合形成鮮明對比。 -
但是,因為 HyperLogLog 只會根據(jù)輸入元素來計算基數(shù),而不會儲存輸入元素本身,所以 HyperLogLog
不能像集合那樣,返回輸入的各個元素。
使用:
這里就拿一個統(tǒng)計網(wǎng)站2021年5月23日,有多少用戶登錄舉例
應(yīng)用場景:
- 可以用來統(tǒng)計網(wǎng)站的登陸人數(shù)以及其他指標(biāo)
八、GEO
基本概念:
在 Redis 3.2 版本中新增了一種叫 geo 的數(shù)據(jù)結(jié)構(gòu),它主要用來存儲地理位置信息,并對存儲的信息進(jìn)行操作。
使用:
geoadd 用于存儲指定的地理空間位置,可以將一個或多個經(jīng)度(longitude)、緯度(latitude)、位置名稱(member)添加到指定的 key 中。
geopos 用于從給定的 key 里返回所有指定名稱(member)的位置(經(jīng)度和緯度),不存在的返回 nil。
> GEOPOS beijing "蘑菇睡不著" "故宮" 1) 1)116.4052852)39.912835 2)(nil)geodist 用于返回兩個給定位置之間的距離。
單位參數(shù):
- m :米,默認(rèn)單位。
- km :千米。
- mi :英里。
- ft :英尺。
應(yīng)用場景:
-
用于存儲地理信息以及對地理信息作操作的場景。
-
科普一個地理小知識:
經(jīng)度范圍:-180 - 180。從0°經(jīng)線算起,向東、向西各分作180°,以東的180°屬于東經(jīng),習(xí)慣上用“E”作代號,以西的180°屬于西經(jīng),習(xí)慣上用“W”作代號。0°位置是:英國格林威治(Greenwich)天文臺子午儀中心的經(jīng)線為本初子午線。
緯度范圍:-90 - 90。位于赤道以北的點的緯度叫北緯,記為N;位于赤道以南的點的緯度稱南緯,記為S。為了研究問題方便,人們把緯度分為低、
中、高緯度。0°~30°為低緯度, 30°~ 60°為中緯度, 60~90°為高緯度。**
九、BloomFilter
基本概念:
一種數(shù)據(jù)結(jié)構(gòu),是由一串很長的二進(jìn)制向量組成,可以將其看成一個二進(jìn)制數(shù)組。既然是二進(jìn)制,那么里面存放的不是0,就是1,但是初始默認(rèn)值都是0。他的主要作用是:判斷一個元素是否在某個集合中。比如說,我想判斷20億的號碼中是否存在某個號碼,如果直接插DB,那么數(shù)據(jù)量太大時間會很慢;如果將20億數(shù)據(jù)放到 緩存 中,緩存也裝不下。這個時候用 布隆過濾器 最合適了,布隆過濾器的原理是:
- 添加元素 當(dāng)要向布隆過濾器中添加一個元素key時,我們通過多個hash函數(shù),算出一個值,然后將這個值所在的方格置為1。
> - 判斷元素是否存在:
判斷元素是否存在,是先將元素經(jīng)過多個hash函數(shù)計算,計算到多個下標(biāo)值,然后判斷這些下標(biāo)對應(yīng)的元素值是否都為1,如果存在不是 1
的,那么元素肯定不在集合中;如果都是
1,那么元素大概率在集合中,并不能百分之百肯定元素存在集合中,因為多個不同的數(shù)據(jù)通過hash函數(shù)算出來的結(jié)果是會有重復(fù)的,所以會存在某個位置是別的數(shù)據(jù)通過hash函數(shù)置為的1。總的來說:布隆過濾器可以判斷某個數(shù)據(jù)一定不存在,但是無法判斷一定存在。 - 布隆過濾器的優(yōu)缺點:
- 優(yōu)點:優(yōu)點很明顯,二進(jìn)制組成的數(shù)組,占用內(nèi)存極少,并且插入和查詢速度都足夠快。
- 缺點:隨著數(shù)據(jù)的增加,誤判率會增加;還有無法判斷數(shù)據(jù)一定存在;另外還有一個重要缺點,無法刪除數(shù)據(jù)。
使用:
redis 4.0 后可以使用 布隆過濾器的插件RedisBloom,命令如下:
- Redisson 使用布隆過濾器 :
- Guava 使用布隆過濾器:
應(yīng)用場景:
- 解決緩存穿透問題:一般得查詢場景都是先去查詢緩存,如果緩存沒有,那么就去 DB 查詢,如果查到了,先存在緩存 中,然后返回給調(diào)用方。如果查不到就返回空。這種情況如果有人頻繁的請求緩存中沒有得數(shù)據(jù),比如id = -1 得數(shù)據(jù),那么會對 DB造成極大得壓力,這種情況就可以使用 redis得布隆過濾器了,可以先將可能得id都存在布隆過濾器中,當(dāng)查詢來的時候,先去布隆過濾器查,如果查不到直接返回,不請求緩存以及DB,如果存在
布隆過濾器 中,那么才去緩存中取數(shù)據(jù)。 - 黑名單校驗:可以將黑名單中得ip放入到布隆過濾器中,這樣不用每次來都去 db 中查詢了。
文章轉(zhuǎn)自
總結(jié)
以上是生活随笔為你收集整理的Redis 五种数据结构以及三种高级数据结构解析以及使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 互联网日报 | 3月2日 星期二 |
- 下一篇: 产品经理和程序员的黑话