談談Redis五種數據結構及真實應用場景


前言

如果問你redis有哪些數據結構,你肯定可以一口氣說出五種基本數據結構: String(字符串)Hash(哈希)List(列表)Set(集合)zset(有序集合)

你或許還知道它還有三種特殊的數據結構類型:Geospatial、Hyperloglog、Bitmap。

但如果問你在實際項目中用了哪些數據結構。你是不是覺得好像大大部分只是用了String的數據結構,就算緩存一個對象,也只是通過JSONObject.toJSONString(object)將它轉為String存儲。取的時候在把這個json字符串轉為對象。

那么既然redis提供了5種基本數據結構,肯定都有特定的應用場合。

接下來會針對5種基本數據類型,來演示在實際開發中的應用場景。


一、String(字符串)

1. 簡介

String 類型是 Redis 中最基本、最常用的數據類型,甚至被很多玩家當成 Redis 唯一的數據類型去使用。String 類型在 Redis 中是二進制安全(binary safe)的,這意味着 String 值關心二進制的字符串,不關心具體格式,你可以用它存儲 json 格式或 JPEG 圖片格式的字符串。

2、內部編碼

如果存儲數字的話,是用int(8字節長整型)類型的編碼;如果存儲非數字,小於等於39字節的字符串,是embstr編碼;大於39個字節,則是raw編碼。

有關redis的數據內部編碼抽空整理一篇文章單獨寫

3、使用場景

(1) 存儲一些配置數據

在前后分離式開發中,有些數據雖然存儲在數據庫,但是更改特別少。比如有個全國地區表。當前端發起請求后,后台如果每次都從關系型數據庫讀取,會影響網站整體性能。

我們可以在第一次訪問的時候,將所有地區信息存儲到redis字符串中,再次請求,直接從數據庫中讀取地區的json字符串,返回給前端。

(2) 緩存對象

將對象轉為json存儲,比如商品信息,用戶信息。

(3) 數據統計

redis整型可以用來記錄網站訪問量,某個文件的下載量,簽到人數、視頻訪問量等等。(自增自減

(4) 時間內限制請求次數

比如已登錄用戶請求短信驗證碼,驗證碼在5分鍾內有效的場景。
當用戶首次請求了短信接口,將用戶id存儲到redis 已經發送短信的字符串中,並且設置過期時間為5分鍾。當該用戶再次請求短信接口,發現已經存在該用戶發送短信記錄,則不再發送短信。

(5) 訂單號(全局唯一)

有時候你需要去生成一個全局唯一值的時候可以通過redis生成。關鍵命令:incrby(原子自增)。

SET order_no 2001 --假設訂單號從2001開始,這里vlaue必須是int類型
INCRBY order_no 1 --自增1,這個時候返回2002

(6) 分布式session

當我們用nginx做負載均衡的時候,如果我們每個從服務器上都各自存儲自己的session,那么當切換了服務器后,session信息會由於不共享而會丟失,我們不得不考慮第三應用來存儲session。

通過我們用關系型數據庫或者Redis等非關系型數據庫。關系型數據庫存儲和讀取性能遠遠無法跟Redis等非關系型數據庫比。

4、常用命令

--增
set mykey "test"       --為鍵設置新值,並覆蓋原有值
setex mykey 10 "hello" -- 設置指定 Key 的過期時間為10秒,在存活時間可以獲取value
mset key3 "stephen" key4 "liu" --批量設置鍵
--刪
del mykey --刪除已有鍵
--改
incr mykey      --值增加1,若該key不存在,創建key,初始值設為0,增加后結果為1
decrby mykey 5  --值減少5
--查 
exists mykey   --判斷該鍵是否存在,存在返回 1,否則返回0
get mykey      --獲取Key對應的value
mget key3 key4 --批量獲取鍵

二、Hash(哈希)

1、簡介

Hash的數據結構我們可以簡單理解為java中的 Map<String,Map<String,String>,這種結構就特別適合存儲對象,上面的String的類型確實也可以存儲對象,但每次修改對象中的某一個屬性,都要拿出整個json字符串在修改這個屬性,之后在重新插入,而hash的接口特點讓我們可以只修改該對象的某一個屬性。

hash數據類型在存儲上述類型的數據時具有比 String 類型更靈活、更快的優勢,具體的說,使用 String 類型存儲,必然需要轉換和解析 json 格式的字符串,即便不需要轉換,在內存開銷方面,還是 hash 占優勢。

2、內部編碼

哈希類型元素個數小於512個,所有值小於64字節的話,使用ziplist編碼,否則使用hashtable編碼。

4、使用場景

(1) Redisson分布式鎖

Redisson在實現分布式鎖的時候,內部的用的數據就是hash而不是String。因為Redisson為了實現可重入加鎖機制。所以在hash中存入了當前線程ID。

(2) 購物車列表

用戶id為key商品id為field商品數量為value,恰好構成了購物車的3個要素,如下圖所示。

這里涉及的命令如下

hset cart:{用戶id} {商品id} 1  # 添加商品

hincrby cart:{用戶id} {商品id} 1 # 增加數量

hlen cart:{用戶id} # 獲取商品總數

hdel cart:{用戶id} {商品id} # 刪除商品

hgetall cart:{用戶id} #獲取購物車所有商品

說明:當前僅僅是將商品ID存儲到了Redis中,在回顯商品具體信息的時候,還需要拿着商品id查詢一次數據庫。

(3) 緩存對象

hash類型的 (key, field, value) 的結構與對象的(對象id, 屬性, 值)的結構相似,也可以用來存儲對象。

在介紹String類型的應用場景時有所介紹,String + json也是存儲對象的一種方式,那么存儲對象時,到底用String + json還是用hash呢?

兩種存儲方式的對比如下表所示。

string + json hash
效率 很高
容量
靈活性
序列化 簡單 復雜

一般對象用string + json存儲,對象中某些頻繁變化的屬性可以考慮抽出來用hash存儲

3、常用命令

--增
hset key field1 "s"   --若字段field1不存在,創建該鍵及與其關聯的Hash, Hash中,key為field1 ,並設value為s ,若字段field1存在,則覆蓋 
hmset key field1 "hello" field2 "world" -- 一次性設置多個字段    
--刪
hdel key field1 --刪除 key 鍵中字段名為 field1 的字段
del key  -- 刪除鍵    
--改 
hincrby key field 1 --給field的值加1
--查
hget key field1 --獲取鍵值為 key,字段為 field1 的值
hlen key        --獲取key鍵的字段數量
hmget key field1 field2 field3 --一次性獲取多個字段
hgetall key --返回 key 鍵的所有field值及value值
hkeys key   --獲取key 鍵中所有字段的field值
hvals key   --獲取 key 鍵中所有字段的value值

三、List(列表)

1、簡介

List類型是按照插入順序排序的字符串鏈表,一個列表最多可以存儲2^32-1個元素。我們可以簡單理解為就相當於java中的LinkesdList

和數據結構中的普通鏈表一樣,我們可以在其頭部(left)和尾部(right)添加新的元素。在插入時,如果該鍵並不存在,Redis將為該鍵創建一個新的鏈表。與此相反,如果鏈表中所有的元素均被移除,那么該鍵也將會被從數據庫中刪除。

2、內部編碼

如果列表的元素個數小於512個,列表每個元素的值都小於64字節(默認),使用ziplist編碼,否則使用linkedlist編碼。

在Redis 3.2之后就都改用ziplist+鏈表的混合結構,稱之為 quicklist(快速鏈表)。

2、使用場景

有人會考慮用list數據結構來做一些朋友圈的點贊列表、評論列表、排行榜。也不是不可以但我個人覺得這些功能用set或zset來做會更加合適,下面會具體舉例。

(1) 消息隊列

lpop和rpush(或者反過來,lpush和rpop)能實現隊列的功能。

但如果是這樣你發現redis作為消息隊列是不安全的,它不能重復消費,一旦消費就會被刪除,同時做消費者確認ACK也麻煩所以一般在實際開發中一般很少用redis中消息隊列,因為現在已經有Kafka、NSQ、RabbitMQ等成熟的消息隊列了,它們的功能更加完善。

4、常用命令

--增 
lpush mykey a b --若key不存在,創建該鍵及與其關聯的List,依次插入a ,b, 若List類型的key存在,則插入value中
rpush mykey a b --在鏈表尾部先插入b,在插入a(lpush list a b那么讀的時候是b,a的順序,而rpush是怎么放怎么讀出來
--刪
del mykey       --刪除已有鍵 
--改
lset mykey 1 e        --從頭開始, 將索引為1的元素值,設置為新值 e,若索引越界,則返回錯誤信息
--查
lrange mykey 0 -1 --取鏈表中的全部元素,其中0表示第一個元素,-1表示最后一個元素。
lrange mykey 0 2  --從頭開始,取索引為0,1,2的元素
lpop mykey        --獲取頭部元素,並且彈出頭部元素,出棧

四、set(集合)

1、簡介

Redis 中的 set和Java中的HashSet 有些類似,它內部的鍵值對是無序的、唯一的。它的內部實現相當於一個特殊的字典,字典中所有的value都是一個值 NULL。當集合中最后一個元素被移除之后,數據結構被自動刪除,內存被回收。

2、編碼

如果集合中的元素都是整數且元素個數小於512個,使用intset編碼,否則使用hashtable編碼。

應用場景

既然set的集合的特性是:無序的、唯一的。那么我們考慮在一些唯一的場景下使用它。

(1) 抽獎活動

存儲某活動中中獎的用戶ID ,因為有去重功能,可以保證同一個用戶不會中獎兩次。

sadd user 1 2 3 4 5  --把所有員工(名稱或編號)放入抽獎箱   

srandmember user 1   -- 抽取一個一等獎(員工可以重復參與抽獎)

spop user 1   -- 抽取一個一等獎(員工不可以重復參與抽獎)

srandmember user 3 --抽取3個二等獎

smembers user --查看當前抽獎箱中參所有員工

scard user  --查看當前抽獎箱中參與抽獎的人數

(2) 點贊

保證一個用戶只能點一個贊。key 可以是某某文章、微信朋友圈的文章id

sadd key userId  --點贊(/收藏) 

srem key userId  --取消點贊(/收藏)

smembers key     -- 獲取所有點贊(/收藏)用戶  

card key  -- 獲取點贊用戶數量

sismember key userId --判斷是否點贊(/收藏)

(3) 好友人脈

key 可以是 用戶id

sadd userId1 1 2 3 4 5 
sadd userId2 4 5 6 7 8 --某個user的好友id放入集合

sinter userId1 userId2 --獲取共同好友

sdiff userId1 userId2 --給user2推薦user1的好友

sismember userId1 5
sismember userId2 5 --驗證某個用戶是否同時被user1和user2關注

4、常用命令

--增
sadd myset a b c --若key不存在,創建該鍵及與其關聯的set,依次插入a ,b,c。若key存在,則插入value中,若a 在myset中已經存在,則插入了 b 和 c 兩個新成員。

--刪
spop myset       --尾部的b被移出,事實上b並不是之前插入的第一個或最后一個成員
srem myset a d f --若f不存在, 移出 a、d ,並返回2

--改
smove myset myset2 a --將a從 myset 移到 myset2,

--查
sismember myset a --判斷 a 是否已經存在,返回值為 1 表示存在。
smembers myset    --查看set中的內容
scard myset       --獲取Set 集合中元素的數量
srandmember myset --隨機的返回某一成員

五、zset(有序集合)

1、簡介

Sorted-Sets中的每一個成員都會有一個分數(score)與之關聯,Redis正是通過分數來為集合中的成員進行從小到大的排序。成員是唯一的,但是分數(score)卻是可以重復的

2、數據編碼

當有序集合的元素個數小於128個,每個元素的值小於64字節時,使用ziplist編碼,否則使用skiplist(跳躍表)編碼

3、應用場景

既然是 有序的,不可重復的列表,那么就可以做一些排行榜相關的場景。

  1. 排行榜(商品銷量,視頻評分,用戶游戲分數)
  1. 新聞熱搜。

4、常用命令

--增
zadd key 2 "two" 3 "three" --添加兩個分數分別是 2 和 3 的兩個成員
--刪
zrem key one two  --刪除多個成員變量,返回刪除的數量
--改
zincrby key 2 one --將成員 one 的分數增加 2,並返回該成員更新后的分數(分數改變后相應它的index也會改變)
--查 
zrange key 0 -1 WITHSCORES --返回所有成員和分數,不加WITHSCORES,只返回成員
zrange key start stop --按照元素的分值從小到大的順序返回從start 到stop之間的所有元素
zscore key three --獲取成員 three 的分數
zrangebyscore key 1 2 --獲取分數滿足表達式 1 < score <= 2 的成員
zcard key       --獲取 myzset 鍵中成員的數量
zcount key 1 2  --獲取分數滿足表達式 1 <= score <= 2 的成員的數量
zrank key member --獲取元素的排名,從小到大
zrevrank key member --獲取元素的排名,從大到小

這篇文章就先寫到這里,有關redis的數據內部編碼,抽空在單獨寫一篇文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM