前言
如果問你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、應用場景
既然是 有序的,不可重復的列表
,那么就可以做一些排行榜相關的場景。
- 排行榜(商品銷量,視頻評分,用戶游戲分數)
- 新聞熱搜。
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的數據內部編碼,抽空在單獨寫一篇文章。