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.緩存預熱

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




鏈接:https://www.jianshu.com/p/6c970eb652d5


免責聲明!

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



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