Redis API的理解和使用

預熱知識點

1.全局命令

  1. 查看所有鍵   keys *
  2. 鍵總數   dbsize
  3. 檢查鍵是否存在  exists key
  4. 刪除鍵   del key [key ...]
  5. 鍵過期  expire key secon
  6. 鍵的數據結構類型  type key

2.數據結構和內部編碼

type命令實際返回的就是當前鍵的數據結構類型,它們分別是:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),但這些只是Redis對外的數據結構,如圖2-1所示。實際上每種數據結構都有自己底層的內部編碼實現,而且是多種實現,這樣Redis會在合適的場景選擇合適的內部編碼,如圖2-2所示。

 

 Redis這樣設計有兩個好處:第一,可以改進內部編碼,而對外的數據結構和命令沒有影響,這樣一旦開發出更優秀的內部編碼,無需改動外部數據結構和命令,例如Redis3.2提供了quicklist,結合了ziplist和linkedlist兩者的優勢,爲列表類型提供了一種更爲優秀的內部編碼實現,而對外部用戶來說基本感知不到。第二,多種內部編碼實現可以在不同場景下發揮各自的優勢,例如ziplist比較節省內存,但是在列表元素比較多的情況下,性能會有所下降,這時候Redis會根據配置選項將列表類型的內部實現轉換爲linkedlist。

3. 單線程架構

Redis客戶端與服務端的模型可以簡化成圖2-3,每次客戶端調用都經歷了發送命令、執行命令、返回結果三個過程。

 

爲什麼單線程還能這麼快?

那麼爲什麼Redis使用單線程模型會達到每秒萬級別的處理能力呢?可以將其歸結爲三點:

  • 第一,純內存訪問,Redis將所有數據放在內存中,內存的響應時長大約爲100納秒,這是Redis達到每秒萬級別訪問的重要基礎。
  • 第二,非阻塞I/O,Redis使用epoll作爲I/O多路複用技術的實現,再加上Redis自身的事件處理模型將epoll中的連接、讀寫、關閉都轉換爲事件,不在網絡I/O上浪費過多的時間。
  • 第三,單線程避免了線程切換和競態產生的消耗。

Redis Api的使用和應用場景

1.字符串

字符串類型是Redis最基礎的數據結構。首先鍵都是字符串類型,而且其他幾種數據結構都是在字符串類型基礎上構建的,所以字符串類型能爲其他四種數據結構的學習奠定基礎。如圖2-7所示,字符串類型的值實際可以是字符串(簡單的字符串、複雜的字符串(例如JSON、XML))、數字(整數、浮點數),甚至是二進制(圖片、音頻、視頻),但是值最大不能超過512MB。

內部編碼
字符串類型的內部編碼有3種:

  • int:8個字節的長整型。
  • embstr:小於等於39個字節的字符串。
  • raw:大於39個字節的字符串。

Redis會根據當前值的類型和長度決定使用哪種內部編碼實現。

典型場景

1.緩存功能

圖2-10是比較典型的緩存使用場景,其中Redis作爲緩存層,MySQL作爲存儲層,絕大部分請求的數據都是從Redis中獲取。由於Redis具有支撐高併發的特性,所以緩存通常能起到加速讀寫和降低後端壓力的作用。
 

2.計數

許多應用都會使用Redis作爲計數的基礎工具,它可以實現快速計數、查詢緩存的功能,同時數據可以異步落地到其他數據源。例如筆者所在團隊的視頻播放數系統就是使用Redis作爲視頻播放數計數的基礎組件,用戶每播放一次視頻,相應的視頻播放數就會自增1。

3.共享Session

如圖2-11所示,一個分佈式Web服務將用戶的Session信息(例如用戶登錄信息)保存在各自服務器中,這樣會造成一個問題,出於負載均衡的考慮,分佈式服務會將用戶的訪問均衡到不同服務器上,用戶刷新一次訪問可能會發現需要重新登錄,這個問題是用戶無法容忍的。

爲了解決這個問題,可以使用Redis將用戶的Session進行集中管理,如圖2-12所示,在這種模式下只要保證Redis是高可用和擴展性的,每次用戶更新或者查詢登錄信息都直接從Redis中集中獲取。

4.限速
很多應用出於安全的考慮,會在每次進行登錄時,讓用戶輸入手機驗證碼,從而確定是否是用戶本人。但是爲了短信接口不被頻繁訪問,會限制用戶每分鐘獲取驗證碼的頻率,例如一分鐘不能超過5次,如圖2-13所示。

此功能可以使用Redis來實現,下面的僞代碼給出了基本實現思路:

phoneNum = "138xxxxxxxx";
key = "shortMsg:limit:" + phoneNum;
// SET key value EX 60 NX
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key) <=5){
// 通過
}else{
// 限速
}

2.哈希

幾乎所有的編程語言都提供了哈希(hash)類型,它們的叫法可能是哈希、字典、關聯數組。在Redis中,哈希類型是指鍵值本身又是一個鍵值對結構,形如value={{field1,value1},...{fieldN,valueN}},Redis鍵值對和哈希類型二者的關係可以用圖2-14來表示。

內部編碼

哈希類型的內部編碼有兩種:

  • ziplist(壓縮列表):當哈希類型元素個數小於hash-max-ziplist-entries配置(默認512個)、同時所有值都小於hash-max-ziplist-value配置(默認64字節)時,Redis會使用ziplist作爲哈希的內部實現,ziplist使用更加緊湊的結構實現多個元素的連續存儲,所以在節省內存方面比hashtable更加優秀。
  • hashtable(哈希表):當哈希類型無法滿足ziplist的條件時,Redis會使用hashtable作爲哈希的內部實現,因爲此時ziplist的讀寫效率會下降,而hashtable的讀寫時間複雜度爲O(1)。

使用場景

緩存用戶數據記錄多條用戶信息。

相比於使用字符串序列化緩存用戶信息,哈希類型變得更加直觀,並且在更新操作上會更加便捷。可以將每個用戶的id定義爲鍵後綴,

多對fieldvalue對應每個用戶的屬性,類似如下僞代碼:

UserInfo getUserInfo(long id){
// 用戶id作爲key後綴
userRedisKey = "user:info:" + id;
// 使用hgetall獲取所有用戶信息映射關係
userInfoMap = redis.hgetAll(userRedisKey);
UserInfo userInfo;
if (userInfoMap != null) {
// 將映射關係轉換爲UserInfo
userInfo = transferMapToUserInfo(userInfoMap);
} else {
// 從MySQL中獲取用戶信息
userInfo = mysql.get(id);
// 將userInfo變爲映射關係使用hmset保存到Redis中
redis.hmset(userRedisKey, transferUserInfoToMap(userInfo));
// 添加過期時間
redis.expire(userRedisKey, 3600);
}
return userInfo;
}

注意:分析三種方法緩存用戶信息的優缺點

  1. 原生字符串類型:每個屬性一個鍵。
set user:1:name tom
set user:1:age 23
set user:1:city beijing

    優點:簡單直觀,每個屬性都支持更新操作。
    缺點:佔用過多的鍵,內存佔用量較大,同時用戶信息內聚性比較差,所以此種方案一般不會在生產環境使用。

     2.序列化字符串類型:將用戶信息序列化後用一個鍵保存。

set user:1 serialize(userInfo)

  優點:簡化編程,如果合理的使用序列化可以提高內存的使用效率。
  缺點:序列化和反序列化有一定的開銷,同時每次更新屬性都需要把全部數據取出進行反序列化,更新後再序列化到Redis中。

   3.哈希類型:每個用戶屬性使用一對field-value,但是隻用一個鍵保存。

hmset user:1 name tomage 23 city beijing

 優點:簡單直觀,如果使用合理可以減少內存空間的使用。
 缺點:要控制哈希在ziplist和hashtable兩種內部編碼的轉換,hashtable會消耗更多內存。

3.列表

列表(list)類型是用來存儲多個有序的字符串,如圖2-18所示,a、b、c、d、e五個元素從左到右組成了一個有序的列表,列表中的每個字符串稱爲元素(element),一個列表最多可以存儲2^32-1個元素。在Redis中,可以對列表兩端插入(push)和彈出(pop),還可以獲取指定範圍的元素列表、獲取指定索引下標的元素等。列表是一種比較靈活的數據結構,它可以充當棧和隊列的角色,在實際開發上有很多應用場景。

列表類型有兩個特點:第一、列表中的元素是有序的,這就意味着可以通過索引下標獲取某個元素或者某個範圍內的元素列表,例如要獲取圖2-19的第5個元素,可以執行lindex user:1:message4(索引從0算起)就可以得到元素e。第二、列表中的元可以是重複的,例如圖2-20所示列表中包含了兩個字符串a。

內部編碼

列表類型的內部編碼有兩種:

  • ziplist(壓縮列表):當列表的元素個數小於list-max-ziplist-entries配置(默認512個),同時列表中每個元素的值都小於list-max-ziplist-value配置時(默認64字節),Redis會選用ziplist來作爲列表的內部實現來減少內存的使用。
  • linkedlist(鏈表):當列表類型無法滿足ziplist的條件時,Redis會使用linkedlist作爲列表的內部實現。

使用場景

1.消息隊列

如圖2-21所示,Redis的lpush+brpop命令組合即可實現阻塞隊列,生產者客戶端使用lrpush從列表左側插入元素,多個消費者客戶端使用brpop命令阻塞式的“搶”列表尾部的元素,多個客戶端保證了消費的負載均衡和高可用性。

2.文章列表

每個用戶有屬於自己的文章列表,現需要分頁展示文章列表。此時可以考慮使用列表,因爲列表不但是有序的,同時支持按照索引範圍獲取元素。

4.集合

集合(set)類型也是用來保存多個的字符串元素,但和列表類型不一樣的是,集合中不允許有重複元素,並且集合中的元素是無序的,不能通過索引下標獲取元素。一個集合最多可以存儲2^32-1個元素。Redis除了支持集合內的增刪改查,同時還支持多個集合取交集、並集、差集,合理地使用好集合類型,能在實際開發中解決很多實際問題。

內部編碼

集合類型的內部編碼有兩種:

  • intset(整數集合):當集合中的元素都是整數且元素個數小於set-max-intset-entries配置(默認512個)時,Redis會選用intset來作爲集合的內部實現,從而減少內存的使用。
  • hashtable(哈希表):當集合類型無法滿足intset的條件時,Redis會使用hashtable作爲集合的內部實現。

使用場景

集合類型比較典型的使用場景是標籤(tag)。例如一個用戶可能對娛樂、體育比較感興趣,另一個用戶可能對歷史、新聞比較感興趣,這些興趣點就是標籤。有了這些數據就可以得到喜歡同一個標籤的人,以及用戶的共同喜好的標籤,這些數據對於用戶體驗以及增強用戶黏度比較重要。

5.有序集合

有序集合,那麼它和集合必然有着聯繫,它保留了集合不能有重複成員的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下標作爲排序依據不同的是,它給每個元素設置一個分數(score)作爲排序的依據。

如圖2-24所示,該有序集合包含kris、mike、frank、tim、martin、tom,它們的分數分別是1、91、200、220、250、251,有序集合提供了獲取指定分數和元素範圍查詢、計算成員排名等功能。

                               

內部編碼

有序集合類型的內部編碼有兩種:

  • ziplist(壓縮列表):當有序集合的元素個數小於zset-max-ziplistentries配置(默認128個),同時每個元素的值都小於zset-max-ziplist-value配置(默認64字節)時,Redis會用ziplist來作爲有序集合的內部實現,ziplist可以有效減少內存的使用。
  • skiplist(跳躍表):當ziplist條件不滿足時,有序集合會使用skiplist作爲內部實現,因爲此時ziplist的讀寫效率會下降。

使用場景

有序集合比較典型的使用場景就是排行榜系統。例如視頻網站需要對用戶上傳的視頻做排行榜,榜單的維度可能是多個方面的:按照時間、按照播放數量、按照獲得的贊數。

 

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