Redis架構原理及應用實踐

一.Redis簡介

Redis 是完全開源免費的,是一個高性能的key-value類型的內存數據庫。整個數據庫統統加載在內存當中進行操作,定期通過異步操作把數據庫數據flush到硬盤上進行保存。因為是純內存操作,Redis的性能非常出色,每秒可以處理超過 10萬次讀寫操作,是已知性能最快的Key-Value DB。

Redis的出色之處不僅僅是性能,Redis最大的魅力是支持保存多種數據結構,此外單個value的最大限制是1GB,因此Redis可以用來實現很多有用的功能,比方說用List來做FIFO雙向鏈表,實現一個輕量級的高性 能消息隊列服務,用他的Set可以做高性能的tag系統等等。另外Redis也可以對存入的Key-Value設置expire時間。總結來說,使用Redis的好處如下:

1.速度快,因為數據存在內存中,讀的速度是 110000 次 /s, 寫的速度是 81000 次 /s;

2.支持豐富數據類型,支持string,list,set,sorted set,hash;

3.支持事務,操作都是原子性,對數據的更改要么全部執行,要么全部不執行,事務中任意命令執行失敗,其余命令依然被執行。也就是說 Redis 事務不保證原子性,也不支持回滾;事務中的多條命令被一次性發送給服務器,服務器在執行命令期間,不會去執行其他客戶端的命令請求。

4.豐富的特性:可用于緩存,消息(支持 publish/subscribe 通知),按key設置過期時間,過期后將會自動刪除,具體淘汰策略有:

  4.1.volatile-lru:從已經設置過期時間的數據集中,挑選最近最少使用的數據淘汰

  4.2.volatile-ttl:從已經設置過期時間的數據集中,挑選即將要過期的數據淘汰

  4.3.volatile-random:從已經設置過期時間的數據集中,隨機挑選數據淘汰

  4.4.allkeys-lru:從所有的數據集中,挑選最近最少使用的數據淘汰

  4.5.allkeys-random:從所有的數據集中,隨機挑選數據淘汰

  4.6.no-enviction:禁止淘汰數據

具體過期鍵的策略有:定時刪除(緩存過期時間到就刪除,創建timer耗CPU),惰性刪除(獲取的時候檢查,不獲取一直留在內存,對內存不友好),定期刪除(CPU和內存的折中方案)

5.支持數據持久化,可以將內存中的數據保存在磁盤中,重啟的時候可以再次加載進行使用;

6.支持數據的備份,即 master - slave 模式的數據備份。

Redis的主要缺點是數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫,因此Redis適合的場景主要局限在較小數據量的高性能操作和運算上。


二.Redis的數據類型

Redis 支持 5 中數據類型:string(字符串),hash(哈希),list(列表),set(集合),zset(sorted set:有序集合)。每種數據類型的具體命令請參考Redis 命令參考

string

string 是 redis 最基本的數據類型。一個 key 對應一個 value。string 是二進制安全的。也就是說 redis 的 string 可以包含任何數據。比如 jpg 圖片或者序列化的對象。string 類型是 redis 最基本的數據類型,string 類型的值最大能存儲 512 MB。

hash

Redis hash 是一個鍵值對(key - value)集合。Redis hash 是一個 string 類型的 key 和 value 的映射表,hash 特別適合用于存儲對象。并且可以像數據庫中一樣只對某一項屬性值進行存儲、讀取、修改等操作。

list

Redis 列表是簡單的字符串列表,按照插入順序排序。我們可以網列表的左邊或者右邊添加元素。?list 就是一個簡單的字符串集合,和 Java 中的 list 相差不大,區別就是這里的 list 存放的是字符串。list 內的元素是可重復的。可以做消息隊列或最新消息排行等功能。

set

redis 的 set 是字符串類型的無序集合。集合是通過哈希表實現的,因此添加、刪除、查找的復雜度都是 O(1)。redis 的 set 是一個 key 對應著多個字符串類型的 value,也是一個字符串類型的集合,和 redis 的 list 不同的是 set 中的字符串集合元素不能重復,但是 list 可以。利用唯一性,可以統計訪問網站的所有獨立 ip。

Zset

redis zset 和 set 一樣都是字符串類型元素的集合,并且集合內的元素不能重復。不同的是 zset 每個元素都會關聯一個 double 類型的分數。redis 通過分數來為集合中的成員進行從小到大的排序。zset 的元素是唯一的,但是分數(score)卻可以重復。可用作排行榜等場景。

三.redis適用場景

1.會話緩存(Session Cache)

最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如Memcached)的優勢在于:Redis提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的購物車信息全部丟失,大部分人都會不高興的。

2.隊列

Reids在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作為一個很好的消息隊列平臺來使用。Redis作為隊列使用的操作,就類似于本地程序語言(如Python)對 list 的 push/pop 操作。

3.全頁緩存

大型互聯網公司都會使用Redis作為緩存存儲數據,提升頁面相應速度。即使重啟了Redis實例,因為有磁盤的持久化,用戶也不會看到頁面加載速度的下降。

4.排行榜/計數器

Redis在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單。

四.Redis高可用架構

1.持久化

Redis 是內存型數據庫,為了保證數據在斷電后不會丟失,需要將內存中的數據持久化到硬盤上。Redis提供了兩種持久化的方式,分別是RDB(Redis DataBase)和AOF(Append Only File)。

RDB

簡而言之,就是在不同的時間點,將redis存儲的數據生成快照并存儲到磁盤等介質上,可以將快照復制到其他服務器從而創建具有相同數據的服務器副本。如果系統發生故障,將會丟失最后一次創建快照之后的數據。如果數據量大,保存快照的時間會很長。

AOF

換了一個角度來實現持久化,那就是將redis執行過的所有寫指令記錄下來,在下次redis重新啟動時,只要把這些寫指令從前到后再重復執行一遍,就可以實現數據恢復了。將寫命令添加到 AOF 文件(append only file)末尾。

使用 AOF 持久化需要設置同步選項,從而確保寫命令同步到磁盤文件上的時機。這是因為對文件進行寫入并不會馬上將內容同步到磁盤上,而是先存儲到緩沖區,然后由操作系統決定什么時候同步到磁盤。選項同步頻率always每個寫命令都同步,eyerysec每秒同步一次,no讓操作系統來決定何時同步,always 選項會嚴重減低服務器的性能,everysec 選項比較合適,可以保證系統崩潰時只會丟失一秒左右的數據,并且 Redis 每秒執行一次同步對服務器幾乎沒有任何影響。no 選項并不能給服務器性能帶來多大的提升,而且會增加系統崩潰時數據丟失的數量。隨著服務器寫請求的增多,AOF 文件會越來越大。Redis 提供了一種將 AOF 重寫的特性,能夠去除 AOF 文件中的冗余寫命令。

其實RDB和AOF兩種方式也可以同時使用,在這種情況下,如果redis重啟的話,則會優先采用AOF方式來進行數據恢復,這是因為AOF方式的數據恢復完整度更高。如果你沒有數據持久化的需求,也完全可以關閉RDB和AOF方式,這樣的話,redis將變成一個純內存數據庫。

2.復制

Redis為了解決單點數據庫問題,會把數據復制多個副本部署到其他節點上,通過復制,實現Redis的高可用性,實現對數據的冗余備份,保證數據和服務的高度可靠性。Redis有主從和主備兩種方式解決單點問題,主備(keepalived)模式下主機備機對外提供同一個虛擬IP,客戶端通過虛擬IP進行數據操作,正常期間主機一直對外提供服務,宕機后VIP自動漂移到備機上。 主從模式下當Master宕機后,通過選舉算法(Paxos、Raft)從slave中選舉出新Master繼續對外提供服務,主機恢復后以slave的身份重新加入,此模式下可以使用讀寫分離,如果數據量比較大,不希望過多浪費機器,還希望在宕機后,做一些自定義的措施,比如報警、記日志、數據遷移等操作,推薦使用主從方式,因為和主從搭配的一般還有個管理監控中心(哨兵)。

①從數據庫向主數據庫發送sync(數據同步)命令。

②主數據庫接收同步命令后,會保存快照,創建一個RDB文件。

③當主數據庫執行完保持快照后,會向從數據庫發送RDB文件,而從數據庫會接收并載入該文件。

④主數據庫將緩沖區的所有寫命令發給從服務器執行。

⑤以上處理完之后,之后主數據庫每執行一個寫命令,都會將被執行的寫命令發送給從數據庫。可以同步發送也可以異步發送,同步發送可以不用每臺都同步,可以配置一臺master,一臺slave,同時這臺salve又作為其他slave的master。異步方式無法保證數據的完整性,比如在異步同步過程中主機突然宕機了,也稱這種方式為數據弱一致性。

注意:在Redis2.8之后,主從斷開重連后會根據斷開之前最新的命令偏移量進行增量復制。

3.哨兵

哨兵是Redis集群架構中非常重要的一個組件,哨兵的出現主要是解決了主從復制出現故障時需要人為干預的問題。

1.Redis哨兵主要功能

(1)集群監控:負責監控Redis master和slave進程是否正常工作

(2)消息通知:如果某個Redis實例有故障,那么哨兵負責發送消息作為報警通知給管理員

(3)故障轉移:如果master node掛掉了,會自動轉移到slave node上

(4)配置中心:如果故障轉移發生了,通知client客戶端新的master地址

2.Redis哨兵的高可用


原理:當主節點出現故障時,由Redis Sentinel自動完成故障發現和轉移,并通知應用方,實現高可用性。哨兵機制建立了多個哨兵節點(進程),共同監控數據節點的運行狀況。同時哨兵節點之間也互相通信,交換對主從節點的監控狀況。每隔1秒每個哨兵會向整個集群:Master主服務器+Slave從服務器+其他Sentinel(哨兵)進程,發送一次ping命令做一次心跳檢測。這個就是哨兵用來判斷節點是否正常的重要依據,涉及兩個新的概念:主觀下線和客觀下線。一個哨兵節點判定主節點down掉是主觀下線,只有半數哨兵節點都主觀判定主節點down掉,此時多個哨兵節點交換主觀判定結果,才會判定主節點客觀下線。基本上哪個哨兵節點最先判斷出這個主節點客觀下線,就會在各個哨兵節點中發起投票機制Raft算法(選舉算法),最終被投為領導者的哨兵節點完成主從自動化切換的過程。


4.集群

至少部署兩臺Redis服務器構成一個小的集群,主要有2個目的:

高可用性:在主機掛掉后,自動故障轉移,使前端服務對用戶無影響。

讀寫分離:將主機讀壓力分流到從機上。

可在客戶端組件上實現負載均衡,根據不同服務器的運行情況,分擔不同比例的讀請求壓力。


緩存數據量不斷增加時,單機內存不夠使用,需要把數據切分不同部分,分布到多臺服務器上。 可在客戶端對數據進行分片,數據分片算法詳見一致性Hash詳解、虛擬桶分片。


當數據量持續增加時,應用可根據不同場景下的業務申請對應的分布式集群。 這塊最關鍵的是緩存治理這塊,其中最重要的部分是加入了代理服務(Codis和Twemproxy)。 應用通過代理訪問真實的Redis服務器進行讀寫,這樣做的好處是避免越來越多的客戶端直接訪問Redis服務器難以管理,而造成風險,在代理這一層可以做對應的安全措施,比如限流、授權、分片,避免客戶端越來越多的邏輯代碼,不但臃腫升級還比較麻煩。代理這層無狀態的,可任意擴展節點,對于客戶端來說,訪問代理跟訪問單機Redis一樣。



Redis Cluster是Redis官網給出的集群架構


客戶端與Redis節點直連,不需要中間Proxy層,直接連接任意一個Master節點,根據公式HASH_SLOT=CRC16(key) mod 16384,計算出映射到哪個分片上,然后Redis會去相應的節點進行操作

具有如下優點:

(1)無需Sentinel哨兵監控,如果Master掛了,Redis Cluster內部自動將Slave切換Master

(2)可以進行水平擴容

(3)支持自動化遷移,當出現某個Slave宕機了,那么就只有Master了,這時候的高可用性就無法很好的保證了,萬一Master也宕機了,咋辦呢? 針對這種情況,如果說其他Master有多余的Slave ,集群自動把多余的Slave遷移到沒有Slave的Master 中。

缺點:

(1)批量操作是個坑,不同的key會劃分到不同的slot中,因此直接使用mset或者mget等操作是行不通的。如果執行的key數量比較少,就不用mget了,就用串行get操作。如果真的需要執行的key很多,就使用Hashtag保證這些key映射到同一臺Redis節點上。

(2)資源隔離性較差,容易出現相互影響的情況。

五.Redis高并發及熱key解決之道

1.并發設置key及分布式鎖

Redis是一種單線程機制的nosql數據庫,基于key-value,數據可持久化落盤。由于單線程所以Redis本身并沒有鎖的概念,多個客戶端連接并不存在競爭關系,但是利用jedis等客戶端對Redis進行并發訪問時會出現問題。比如多客戶端同時并發寫一個key,一個key的值是1,本來按順序修改為2,3,4,最后是4,但是順序變成了4,3,2,最后變成了2。使用分布式鎖防止并發設置Key的原理及代碼見:使用Redis實現分布式鎖及其優化,另外一種方式是使用消息隊列,把并行讀寫進行串行化。

2.熱key問題

熱key問題說來也很簡單,就是瞬間有幾十萬的請求去訪問redis上某個固定的key,從而壓垮緩存服務的情情況。其實生活中也是有不少這樣的例子。比如XX明星結婚。那么關于XX明星的Key就會瞬間增大,就會出現熱數據問題。那么如何發現熱KEY呢:

1.憑借業務經驗,進行預估哪些是熱key

2.在客戶端進行收集

3.在Proxy層做收集

4.用redis自帶命令(monitor命令、hotkeys參數)

5.自己抓包評估

解決方案:

1.利用二級緩存,比如利用ehcache,或者一個HashMap都可以。在你發現熱key以后,把熱key加載到系統的JVM中。

2.備份熱key,不要讓key走到同一臺redis上。我們把這個key,在多個redis上都存一份。可以用HOTKEY加上一個隨機數(N,集群分片數)組成一個新key。

3.熱點數據盡量不要設置過期時間,在數據變更時同步寫緩存,防止高并發下重建緩存的資源損耗。可以用setnx做分布式鎖保證只有一個線程在重建緩存,其他線程等待重建緩存的線程執行完,重新從緩存獲取數據即可。

3.緩存穿透

緩存穿透是指查詢一個根本不存在的數據,緩存層和存儲層都不會命中,但是出于容錯的考慮,如果從存儲層查不到數據則不寫入緩存層。緩存穿透將導致不存在的數據每次請求都要到存儲層去查詢,失去了緩存保護后端存儲的意義。造成緩存穿透的基本有兩個。第一,業務自身代碼或者數據出現問題,第二,一些惡意攻擊、爬蟲等造成大量空命中,下面我們來看一下如何解決緩存穿透問題。解決緩存穿透的兩種方案:

1)緩存空對象

緩存空對象會有兩個問題:

第一,空值做了緩存,意味著緩存層中存了更多的鍵,需要更多的內存空間 ( 如果是攻擊,問題更嚴重 ),比較有效的方法是針對這類數據設置一個較短的過期時間,讓其自動剔除。

第二,緩存層和存儲層的數據會有一段時間窗口的不一致,可能會對業務有一定影響。例如過期時間設置為 5 分鐘,如果此時存儲層添加了這個數據,那此段時間就會出現緩存層和存儲層數據的不一致,此時可以利用消息系統或者其他方式清除掉緩存層中的空對象。

2)布隆過濾器攔截

如下圖所示,在訪問緩存層和存儲層之前,將存在的 key 用布隆過濾器提前保存起來,做第一層攔截。如果布隆過濾器認為該用戶 ID 不存在,那么就不會訪問存儲層,在一定程度保護了存儲層。有關布隆過濾器的相關知識,可以參考:?布隆過濾器,可以利用 Redis 的 Bitmaps 實現布隆過濾器,GitHub 上已經開源了類似的方案,讀者可以進行參考:redis bitmaps實現布隆過濾器


緩存空對象和布隆過濾器方案對比

4.緩存雪崩

數據未加載到緩存中,或者緩存同一時間大面積的失效,從而導致所有請求都去查數據庫,導致數據庫CPU和內存負載過高,甚至宕機。


可以從以下幾個方面防止緩存雪崩:

1)保證緩存層服務高可用性

和飛機都有多個引擎一樣,如果緩存層設計成高可用的,即使個別節點、個別機器、甚至是機房宕掉,依然可以提供服務,例如前面介紹過的 Redis Sentinel 和 Redis Cluster 都實現了高可用。

2)Redis備份和快速預熱

Redis備份保證master出問題切換為slave迅速能夠承擔線上實際流量,快速預熱保證緩存及時被寫入緩存,防止穿透到庫。

3)依賴隔離組件為后端限流并降級

無論是緩存層還是存儲層都會有出錯的概率,可以將它們視同為資源。作為并發量較大的系統,假如有一個資源不可用,可能會造成線程全部 hang 在這個資源上,造成整個系統不可用。降級在高并發系統中是非常正常的:比如推薦服務中,如果個性化推薦服務不可用,可以降級補充熱點數據,不至于造成前端頁面是開天窗。

在實際項目中,我們需要對重要的資源 ( 例如 Redis、 MySQL、 Hbase、外部接口 ) 都進行隔離,讓每種資源都單獨運行在自己的線程池中,即使個別資源出現了問題,對其他服務沒有影響。但是線程池如何管理,比如如何關閉資源池,開啟資源池,資源池閥值管理,這些做起來還是相當復雜的,這里推薦一個 Java 依賴隔離工具Hystrix(https://github.com/Netflix/Hystrix),如下圖所示。

4)提前演練

在項目上線前,演練緩存層宕掉后,應用以及后端的負載情況以及可能出現的問題,在此基礎上做一些預案設定。

5.緩存預熱

緩存預熱就是系統上線前,將相關的緩存數據直接加載到緩存系統。這樣就可以避免上線后在用戶請求的時候,先查詢數據庫,然后再將數據緩存的問題!用戶直接查詢事先被預熱的緩存數據!

六.Redis內存模型

Redis為什么這么快?一文深入了解Redis內存模型!

一文揭秘單線程的Redis為什么這么快?


參考文章:

學Redis這篇就夠了

Redis緩存的設計、性能、應用與數據集群同步

Redis哨兵、復制、集群的設計原理,以及區別

圖示Redis集群架構詳解

談談redis的熱key問題如何解決

如何解決Redis雪崩、穿透、并發等5大難題

Redis架構之防雪崩設計:網站不宕機背后的兵法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,202評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,297評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,688評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,875評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,438評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,183評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,384評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,612評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,093評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,330評論 2 377

推薦閱讀更多精彩內容