Redis能用來作什麼

緩存數據庫目前最經常使用的兩種就是 Redis 和 Memcached,與 Memcached 相比 Redis 其一大特色是支持豐富的數據類型(Memcached 只能用 string 類型)。Redis 由於其豐富的數據結構所以應用範圍不侷限於緩存,有不少場景用 Redis 來實現能夠大大減小工做量。這篇文章我想總結一下 Redis 不一樣數據結構能夠應用的場景。固然 Redis 的使用場景遠不止文章所列的這些,不過了解了一些業界的用法,能夠開闊本身的思路。html

若是要靈活應用 Redis,首先要熟悉 Redis 各類數據結構所支持的各類命令。不過本文不打算對命令最介紹,由於已經有了許多關於這方面的資料。關於命令的如何使用,能夠參考下面兩篇文章:redis

中文版本能夠看:redisdoc.com/index.html算法

英文版本能夠看官網:redis.io/commands數據庫

String

簡介

String 數據類型是最經常使用、最簡單的 key-value 類型,普通的 key-value 存儲均可以歸爲此類。value 不只能夠是字符串,也能夠是數字。string 是二進制安全的,因此你徹底能夠把一個圖片文件的內容做爲 string 來存儲。Redis 的 string 能夠徹底實現目前 Memcached 的功能。除了提供與 Memcached 同樣的get、set、incr、decr 等操做外,Redis還額外提供了下面一些操做:數組

  • 獲取字符串長度
  • 往字符串末尾append內容
  • 設置和截取字符串的某一段內容
  • 位操做,redis最大能夠支持2^23 - 1位的位操做
  • 批量設置一系列字符串的內容

應用場景

緩存

緩存是使用最多的場景了,對於字符串和數字能夠直接存取。不過更多時候面臨的是須要將一個結構體或者對象裏的數據緩存起來。存儲時能夠將結構化數據先序列化,再set到redis中,查詢時,先get到後再反序列化到對象中。緩存

使用緩存時,儘可能要設定過時時間,否則緩存數據過多會很快將redis撐滿。安全

計數統計

Redis是處理命令是單線程處理的,所以Redis的INCR、INCRBY、DECR、DECRBY等指令能夠實現原子計數的效果。對於業務上一些簡單的統計和計數需求能夠經過Redis的這些命令來實現。bash

GetSet設置新值,返回舊值。好比實現一個計數器,能夠用GetSet獲取計數並重置爲0。session

分佈式id生成器

分佈式id生成器應用最普遍的是Twitter開源的SnowFlake算法。若是併發請求量不是很大的狀況下,也可用Redis的INCR和INCRBY命令實現idmaker,即生成全局惟一的id,並且仍是嚴格自增的。數據結構

定時過時數據

Redis能夠經過EXPIRE命令給任意key設置過時時間,所以對於須要按期過時的數據能夠Redis來存儲,能夠很方便的實現過時功能。好比實現一個分佈式session系統。創建session會話時,將session_key存儲到Redis中,並設定過時時間。驗證session_key時先根據uid路由到對應的redis,如取不到session_key,則表示session_key已過時,須要從新登陸;如取到session_key且校驗經過則從新更新此session_key的過時時間便可。

分佈式選主

Set nx或SetNx命令僅當key不存在時才Set成功。能夠用來選舉Master或實現分佈式鎖:全部Client不斷嘗試使用SetNx master myName搶注Master,成功的那位不斷使用Expire刷新它的過時時間。若是Master掛掉了key就會失效,剩下的節點又會發生新一輪搶奪。

分佈式鎖

加鎖時經過set nx ex|px命令獲取鎖,若是set成功,說明加鎖成功。加鎖成功時,同時會設定一個超時自動釋放的時間,避免發生死鎖。釋放鎖時經過lua腳本先get到鎖信息,確認爲加鎖者則del刪除這個key,完成鎖的釋放。關於Redis分佈式鎖,後面會單獨用一篇文章來講明。

加鎖命令:

SET resource-name anystring NX EX max-lock-time
複製代碼

釋放鎖:

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end
複製代碼

位操做進行數據統計

Redis的GetBit,SetBit,BitOp,BitCount命令用來進行位操做。BitMap的玩法,好比統計一個用戶的簽到次數,用戶籤一次到就將相應的offset的位置1,而後經過bitcount就能夠統計指定範圍內1的個數,也就是該用戶的簽到次數。能夠分別統計,一週內的簽到次數,一個月內的簽到次數等。

字符串操做

Redis的Append,SetRange,GetRange,StrLen命令,分別實現對文本進行擴展、替換、截取和求長度,對特定數據格式很是有用。

頻率限制

對於HTTP請求,爲了防止接口惡意被刷或者限制用戶的操做頻率,經常會使用頻率限制組件,限制用戶在一個時間週期(好比1秒)內,只能請求接口2次。而Redis經過lua腳本和INCR命令能夠很方便的實現週期性的頻率限制。示例代碼以下:

# KEYS和ARGV是redis命令傳入lua腳本的參數,KEYS[1]是頻率限制的KEY,ARGV[1]是增長的次數,ARGV[2]是KEY過時時間
   # 將計數加ARGV[1],若是KEY不存在,INCRBY命令會建立KEY,並初始化該KEY的值爲ARGV[1]
   "local current = redis.call('INCRBY',KEYS[1],ARGV[1]);"
   # 若是增長後的值與傳入的值相同,說明是新建立的KEY,表示是該週期內第一次更新,則給該key設定過時時間
   "if tonumber(current) == tonumber(ARGV[1]) "
   "then "
   "redis.call('EXPIRE',KEYS[1], ARGV[2]) "
   "end "
   # 返回當前計數值
   "return current";
複製代碼

實現方式

String 在 redis 內部存儲就是一個字符串,不過該字符串是 Redis 本身實現的動態字符串對象,動態字符能夠自動進行擴容。對於整數也是按字符串進行存儲,不過執行 INCR 這類對數值類型才能進行的命令時,Redis 會將字符串先轉換成數值類型進行運算,而後將運算結果再轉成字符串寫進內存中。

Hash

簡介

Hash存的是字符串和字符串值之間的映射。Hash將對象的各個屬性存入Map裏,能夠只讀取/更新對象的某些屬性。

應用場景

緩存結構化數據

對於用戶信息好比用戶的暱稱、年齡、性別、積分等,若是使用字符串類型進行緩存,須要將用戶數據先序列化後再存儲,這時,若只需修改其中某一項屬性值,須要將全部值反序列化出來,而後修改該項數據的值,再序列化後存儲回去。這樣序列化與反序列化的開銷比較大。

string和hash均可以儲存結構化數據,那麼這兩種數據結構該如何選擇呢?

[1] 若是大多數時候要訪問結構化數據中的大多數字段,則使用string,反之則使用hash;

[2] 若是大多數時候只修改結構化數據中某一個字段的值,則使用hash,反之則使用string;

沒有固定的選擇方法和模式,須要根據須要權衡考量。

不過這裏須要注意,Redis提供了接口(hgetall)能夠直接取到所有的屬性數據,可是若是內部hash的成員不少,那麼涉及到遍歷整個內部Map的操做,因爲Redis單線程模型的緣故,這個遍歷操做可能會比較耗時,而對其它客戶端的請求徹底不響應,這點須要格外注意。

建索引

好比User對象,除了id有時還要按name來查詢,能夠單獨額外建一個key爲name_id的Hash對象保存從name到id的映射關係。在插入User對象時(set user:101 {"id":101,"name":"calvin"}), 順便往這個hash插入一條(hset name_id calvin 101),這時calvin做爲hash裏的一個key,值爲101。按name查詢的時候,用hget user:name:id calvin 就能從名爲calvin的key裏取出id。假如須要使用多種索引來查找某條數據時可使用,一個hash key搞定,避免使用多個string key存放索引值。

計數等功能

hash結構的字段也能夠支持與string相同的HINCRBY等操做,所以一樣可用於實現idmaker,計數等。相比與string類型的優點是hash能夠只用一個key實現多個不一樣的計數。若是一個

實現方式

Redis Hash對應Value內部實際就是一個HashMap,這裏會有2種不一樣實現,這個Hash的成員比較少時Redis爲了節省內存會採用相似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,對應的value redisObject的encoding爲zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding爲ht。

List

簡介

List 是一個雙向鏈表,支持雙向的 pop/push。由於採用的是鏈表來實現,所以即便 list 裏有百萬個元素,也能夠在常數時間複雜度內完成 push 操做。不過鏈表也使得按 index 訪問元素的時間複雜度變成了 O(N)。從左 push 仍是從右 push,江湖規矩通常從左端 push,右端 pop,即lpush/rpop,並且還有 blocking 的版本 blpop/brpop,客戶端能夠阻塞在那直到有消息到來。還有 rpoplpush/ brpoplpush,彈出來返回給 client的同時,把本身又推入另外一個 list,llen 獲取列表的長度。還有按值進行的操做:lrem(按值刪除元素)、linster(插在某個值的元素的先後),複雜度是 O(N),N 是 list 長度,由於 list 的值不惟一,因此要遍歷所有元素,而 set 查找只要 O(log(N)) 的時間複雜度。

應用場景

各類列表

好比 twitter 的關注列表、粉絲列表、評論列表等也能夠用 redis 的 list 結構來實現。

消息隊列

能夠利用 list 的 push 操做,將任務存在 list 中,而後工做線程再用 pop 操做將任務取出執行。若是消費者取到消息後就宕機了怎麼辦?

解決方法之一是加多一個 sorted set,分發的時候同時發到 list 與 sorted set,以分發時間爲 score,用戶把任務作完了以後要用 ZREM 消掉 sorted set 裏的 job,而且定時從 sorted set中取出超時沒有完成的任務,從新放回 list。

另外一個作法是爲每一個 worker 多加一個的 list,彈出任務時改用 rpoplpush,將 消息同時放到 worker 本身的 list 中,完成時用 lrem 消掉。若是集羣管理(如 zookeeper)發現 worker 已經掛掉,就將 worker 的 list 內容從新放回主 list。

可是這兩種方法對於一樣的數據都要存儲多份,並不高效。更好的辦法是使用 ack 機制來保證消息的可靠性,使用 rrange 來取消息,經過單獨的位置偏移量來記錄消費的位置,收到 ack 後更新偏移量,而後刪除已經消費的元素。

分頁查詢

利用 lrange 能夠很方便的實現 list 內容分頁的功能。

取最新的 N 條數據

取最新 N 個數據的操做:lpush 用來插入一個內容 ID,做爲關鍵字存儲在列表頭部。lrem 用來限制列表中的項目數最多爲5000。若是用戶須要的檢索的數據量超越這個緩存容量,這時才須要把請求發送到數據庫。

實現方式

Redis list 的實現是壓縮列表或者雙向鏈表,數據較少時用壓縮列表,數據較多時用鏈表。便可以支持反向查找和遍歷,更方便操做,不過帶來了部分額外的內存開銷,redis 內部的不少實現,包括髮送緩衝隊列等也都是用的這個數據結構。

Set

簡介

是一種無序的集合,集合中的元素沒有前後順序,不重複。將重複的元素放入 set 會自動去重。

應用場景

數據去重

一個沒有排序要求的集合,可是要求元素不能重複,那麼 set 是最合適的選擇。而且 set 提供了判斷某個成員是否在一個 set 集合內的重要接口,這個也是 list 所不能提供的。

交併集實現共同關注

將用戶的關注列表放在一個 set 中,set 能夠保證去重,這樣就不會重複關注,接口能夠實現冪等。另外 redis 還爲集合提供了求交集、並集、差集等操做,能夠很是方便的實現如共同關注、共同喜愛、二度好友等功能,對上面的全部集合操做,你還可使用不一樣的命令選擇將結果返回給客戶端仍是存集到一個新的集合中。又好比 QQ 有一個社交功能叫作「好友標籤」,你們能夠給你的好友貼標籤,好比「大美女」、「土豪」、「歐巴」等等,這裏也能夠把每個用戶的標籤都存儲在一個集合之中。

實現方式:

set 的內部實現是整數集合或者是 hashmap,所以 set 能夠很快的實現查詢用戶是否在 set 中。

Sorted Set

簡介

有序集合,與 set 相比 sorted set 在知足去重的要求下還實現了排序功能,也使得有序集合的使用場景會更多。有序集合元素放入集合時還要提供該元素的分數,有序集合會根據分數進行排序。

使用場景

按時間順序排列的列表

不少場景須要按照時間順序排列列表,通常須要時間最近的排在最前面,那麼就能夠用時間戳作score來實現按時間排序的列表。

排行榜

sorted set能夠根據分數進行排序,所以能夠將排行榜的指標數據做爲分數,用戶信息做爲value保存在sorted set結構中。 獲得前100名高分用戶很簡單:ZREVRANGE leaderboard 0 99。查詢某個用戶的排名也很簡單:ZRANK leaderboard 。

按照某種權重排列的列表

好比掘金推薦的文章會根據時間和熱度等信息來計算每一個文章的權重值,能夠用這個權重值作score,value爲文章的id,那麼就能夠實現熱榜的文章列表了。

延時任務

延時任務使用的場景也很是多,好比電商業務用戶30分鐘內未付款就自動取消訂單等。經過redis的zset能夠很方便的實現延時任務,具體能夠看我這篇文章:基於REDIS實現延時任務

實現方式

sorted set的使用跳躍表(SkipList)來現實數據有序存儲。另外爲了實現較快查詢指定元素的分數,redis 還單獨在 hashmap 裏放了成員到 score 的映射,所以能夠在 O(1) 的時間複雜度類查詢指定元素的分數。

相關文章
相關標籤/搜索