一、 簡介
- 高性能(目前已知性能最快)
- 讀速度:110000 次 /s
- 寫速度:81000 次 /s
- key-value(單個value的最大限制是1GB)類型的內存數據庫
- 數據庫在內存中進行操作
- 支持數據持久化
- 定期異步操作將數據庫數據flush到硬盤上
- 支持string,list,set,sorted set,hash
- 操作都是原子性
- 支持事務
- 對數據的更改要麼全部執行,要麼全部不執行
- 事務中任意命令執行失敗,其餘命令依然被執行(Redis 事務不保證原子性,也不支持回滾)
- 事務中的多條命令被一次性發送給服務器,服務器在執行命令期間,不會去執行其他客戶端的命令請求
- 支持數據備份( master - slave 模式的數據備份)
- 數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫
- 適合的場景侷限在較小數據量的高性能操作和運算上
二、 可實現的功能
2.1 消息隊列服務
用List來做FIFO雙向鏈表(前一個元素和後一個元素),實現一個輕量級的高性能消息隊列服務
2.2 tag系統
Set可以做高性能的tag系統
2.3 緩存
高併發情況下,將用戶基本信息放到redis中,減少對數據庫的訪問,直接做法就是建立用戶的內存模型
存儲用戶會話緩存信息
2.4 消息(支持 publish/subscribe 通知)
2.5 排行榜/計數器
計數器:Redis在內存中對數字進行遞增或遞減的操作實現的非常好。
排行榜:有序集合(Sorted Set)通過分數來實現
三、淘汰策略
3.1從已經設置過期時間的數據集中
volatile-lru: 挑選最近最少使用的數據淘汰
volatile-ttl: 挑選即將要過期的數據淘汰
volatile-random: 隨機挑選數據淘汰
3.2從所有的數據集中
allkeys-lru: 挑選最近最少使用的數據淘汰
allkeys-random:,隨機挑選數據淘汰
3.3 no-enviction:禁止淘汰數據
四、過期鍵的策略
4.1定時刪除
緩存過期時間到就刪除,創建timer耗CPU
4.2惰性刪除
獲取的時候檢查,不獲取一直留在內存,對內存不友好
4.3定期刪除
CPU和內存的折中方案
五、數據類型分析
5.1 string
最基本的數據類型。
二進制安全的,可以包含任何數據。
最大能存儲 512 MB。
5.2 hash
- 一個鍵值對(key - value)集合。
- 一個 string 類型的 key 和 value 的映射表,
- 適合用於存儲對象
- 可以對對象某一項屬性值進行存儲、讀取、修改等操作。
5.3 list
- 是簡單的字符串列表(集合)。
- 按照插入順序排序。我們可以網列表的左邊或者右邊添加元素。
- 元素是可重複的。
- 適用做消息隊列或最新消息排行等功能。
5.4 set
- 無序
- 通過哈希表實現,因此添加、刪除、查找的複雜度都是 O(1)。
- 是一個 key 對應着多個字符串類型的 value的集合
- 集合元素不能重複
- 可以統計訪問網站的所有獨立ip。
5.5 Zset
- 和set 一樣
- 不同的是zset 每個元素都會關聯一個 double 類型的分數,通過分數來爲集合中的成員進行從小到大的排序。
- value元素是唯一的,但是分數(score)卻可以重複。
- 可用作排行榜等場景。
六、Redis高可用架構
6.1、持久化
6.1.1、RDB
- 定期將存儲的數據生成快照並存儲到磁盤上,
- 可以將快照複製到其他服務器從而創建具有相同數據的服務器副本。
- 系統故障,會丟失最後一次創建快照之後的數據。
- 數據量大,保存快照的時間會很長。
6.1.2、AOF
- 將redis執行過的所有寫指令記錄下來,在下次redis重新啓動時,只要把這些寫指令從前到後再執行一遍,就可以實現數據恢復。
- 將寫命令添加到 AOF 文件(append only file)末尾。
- 對文件進行寫入並不會馬上將內容同步到磁盤上,而是先存儲到緩衝區,然後由操作系統決定什麼時候同步到磁盤。
- AOF有重寫的特性,去除AOF文件中的冗餘寫命令。
- redis重啓的話,則會優先採用AOF方式來進行數據恢復(aop數據更完整)
- 需要設置同步選項,從而確保寫命令同步到磁盤文件上的時機。
6.1.2.1、同步選項
always
- 每個寫命令都同步
- 會嚴重減低服務器的性能
eyerysec
- 每秒同步一次
- 選項比較合適,可以保證系統崩潰時只會丟失一秒左右的數據,且每秒執行一次同步對服務器幾乎沒有任何影響。
no
- 讓操作系統來決定何時同步
- 並不能給服務器性能帶來多大的提升
- 會增加系統崩潰時數據丟失的數量
6.2、複製
爲了解決單點數據庫問題(主從和主備兩種模式),會把數據複製多個副本部署到其他節點上, 實現Redis的高可用性, 保證數據和服務的高度可靠性。
主備(keepalived)模式
主機備機對外提供同一個虛擬IP,客戶端通過虛擬IP進行數據操作,正常期間主機一直對外提供服務,宕機後VIP自動漂移到備機上。
主從模式
當Master宕機後,通過選舉算法從slave中選舉出新Master繼續對外提供服務,主機恢復後以slave的身份重新加入,此模式下可以使用讀寫分離,如果數據量比較大,不希望過多浪費機器,還希望在宕機後,做一些自定義的措施,比如報警、記日誌、數據遷移等操作,推薦使用主從方式,因爲和主從搭配的一般還有個管理監控中心(哨兵)。
6.3、 數據通過過程
6.3.1 、Redis2.8之前同步
- ①從數據庫向主數據庫發送sync(數據同步)命令。
- ②主數據庫接收同步命令後,會保存快照,創建一個RDB文件。
- ③當主數據庫執行完保持快照後,會向從數據庫發送RDB文件,而從數據庫會接收並載入該文件。
- ④主數據庫將緩衝區的所有寫命令發給從服務器執行。
- ⑤以上處理完之後,之後主數據庫每執行一個寫命令,都會將被執行的寫命令發送給從數據庫。可以同步發送也可以異步發送,同步發送可以不用每臺都同步,可以配置一臺master,一臺slave,同時這臺salve又作爲其他slave的master。異步方式無法保證數據的完整性,比如在異步同步過程中主機突然宕機了,也稱這種方式爲數據弱一致性。
- 注意:在Redis2.8之後,主從斷開重連後會根據斷開之前最新的命令偏移量進行增量複製。
6.3.2 、 Redis2.8之後同步
6.4 、哨兵
當主節點出現故障時,由哨兵自動完成故障發現和轉移,並通知應用方,實現高可用性。
6.4.1 、Redis哨兵主要功能
集羣監控:負責監控Redis master和slave進程是否正常工作
消息通知:如果某個Redis實例有故障,那麼哨兵負責發送消息作爲報警通知給管理員
故障轉移:如果master node掛掉了,會自動轉移到slave node上
配置中心:如果故障轉移發生了,通知client客戶端新的master地址
6.4.2 、Redis哨兵的高可用
原理
- 哨兵機制建立了多個哨兵節點(進程),共同監控數據節點的運行狀況。
- 同時哨兵節點之間也互相通信,交換對主從節點的監控狀況。
哨兵用來判斷節點是否正常的依據
每隔1秒每個哨兵會向整個集羣:Master主服務器+Slave從服務器+其他Sentinel(哨兵)進程,發送一次ping命令做一次心跳檢測。
主節點down掉依據(主觀下線和客觀下線)
主觀下線
一個哨兵節點判定主節點down掉是主觀下線
客觀下線
只有半數哨兵節點都判定主觀下線,就會會判定主節點客觀下線
補充
基本上哪個哨兵節點最先判斷出這個主節點客觀下線,就會在各個哨兵節點中發起投票機制Raft算法(選舉算法),最終被投爲領導者的哨兵節點完成主從自動化切換的過程。
6.5 、 集羣
高可用性:在主機掛掉後,自動故障轉移,使前端服務對用戶無影響。
讀寫分離:將主機讀壓力分流到從機上。
6.5.1、小數據到大數據過程
問題
緩存數據量不斷增加時,單機內存不夠使用
解決方案
把數據切分不同部分,分佈到多臺服務器上。 可在客戶端對數據進行分片,數據分片算法詳見一致性Hash詳解、虛擬桶分片。
問題
數據量持續增加時,越來越多的客戶端直接訪問Redis服務器難以管理,而造成風險
解決方案
根據不同場景下的業務申請對應的分佈式集羣。
加入了代理服務(Codis和Twemproxy),通過代理訪問真實的Redis服務器進行讀寫
代理服務(Codis和Twemproxy)
在代理層做安全措施,比如限流、授權、分片,避免客戶端越來越多的邏輯代碼,不但臃腫升級還比較麻煩。
代理層無狀態,可任意擴展節點,對於客戶端來說,訪問代理跟訪問單機Redis一樣。
6.5.2、Redis官網的集羣架構
6.5.2.1、原理
客戶端與Redis(Master)節點直連,不需要中間Proxy層,根據公式HASH_SLOT=CRC16(key) mod 16384,計算出映射到哪個分片上,然後Redis會去相應的節點進行操作
6.5.2.2、優點
- 無需Sentinel哨兵監控,如果Master掛了,自動將Slave切換Master
- 可以進行水平擴容
- 支持自動化遷移
- 當出現某個Slave宕機了,那麼就只有Master了,這時候的高可用性就無法很好的保證了,萬一Master也宕機了,咋辦呢? 針對這種情況,如果說其他Master有多餘的Slave ,集羣自動把多餘的Slave遷移到沒有Slave的Master 中。
6.5.2.3、缺點
- 批量操作是個坑,不同的key會劃分到不同的slot中,因此直接使用mset或者mget等操作是行不通的。
- 解決方案:使用Hashtag保證這些key映射到同一臺Redis節點上。
- 資源隔離性較差,容易出現相互影響的情況。
七、Redis高併發及熱key解決之道
7.1、併發設置key及分佈式鎖
問題
集羣環境下的定時任務,存在A服務器執行任務t1,B服務器也執行了任務t1,如果t1是創建訂單,那麼就會出現重複訂單。
解決方案
- 分佈式鎖
- 使用消息隊列,把並行讀寫進行串行化。
7.2、熱key問題
問題
瞬間有幾十萬的請求去訪問某個固定的key,從而壓垮緩存服務的情況
熱key判斷依據
- 憑藉業務經驗,進行預估哪些是熱key
- 在客戶端進行收集
- 在Proxy層做收集
- 用redis自帶命令(monitor命令、hotkeys參數)
解決方案:
- 利用二級緩存,比如一個HashMap。在你發現熱key以後,把熱key加載到系統的JVM中。查找的時候先去本地jvm查詢,查不到再去redis查詢
- 備份熱key,不要讓key走到同一臺redis上。我們把這個key,在多個redis上都存一份。可以用HOTKEY加上一個隨機數(N,集羣分片數)組成一個新key。
- 熱點數據儘量不要設置過期時間,在數據變更時同步寫緩存,防止高併發下重建緩存的資源損耗。
7.3、緩存穿透
指查詢一個根本不存在的數據,緩存層和存儲層都不會命中,但是出於容錯的考慮,如果從存儲層查不到數據則不寫入緩存層。
7.3.1、影響
將導致不存在的數據每次請求都要到存儲層去查詢,失去了緩存保護後端存儲的意義。
7.3.2、緩存穿透原因
業務自身代碼或者數據出現問題,
一些惡意攻擊、爬蟲等造成大量空命中
7.3.3、解決緩存穿透方案
1)緩存空對象
緩存空對象會有兩個問題
- 空值做了緩存,意味着緩存層中存了更多的鍵,需要更多的內存空間 ( 如果是攻擊,問題更嚴重 ),比較有效的方法是針對這類數據設置一個較短的過期時間,讓其自動剔除。
- 緩存層和存儲層的數據會有一段時間窗口的不一致,例如過期時間設置爲1分鐘,如果此時存儲層添加了這個數據,那此段時間就會出現緩存層和存儲層數據的不一致,更新數據的時候清除掉緩存層中的空對象。
2)布隆過濾器攔截
如下圖所示,在訪問緩存層和存儲層之前,將存在的 key 用布隆過濾器提前保存起來,做第一層攔截。如果布隆過濾器認爲key不存在,那麼就不會訪問存儲層,在一定程度保護了存儲層。
可以參考: 布隆過濾器,可以利用 Redis 的 Bitmaps 實現布隆過濾器redis bitmaps實現布隆過濾器
7.3.4、緩存空對象和布隆過濾器方案對比