Redis 是一個開源的,內存中的數據結構存儲系統,它能夠用做數據庫、緩存和消息中間件。 它支持多種類型的數據結構,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 與範圍查詢, bitmaps, hyperloglogs 和 地理空間(geospatial) 索引半徑查詢等。 Redis 內置了 複製(replication),LUA腳本(Lua scripting), LRU驅動事件(LRU eviction),事務(transactions) 和不一樣級別的 磁盤持久化(persistence), 並經過 Redis哨兵(Sentinel)和自動 分區(Cluster)提供高可用性(high availability)。前端
緩存是高併發場景下提升熱點數據訪問性能的一個有效手段,在開發項目時會常用到。redis
緩存的類型分爲:本地緩存、分佈式緩存和多級緩存。算法
本地緩存 就是在進程的內存中進行緩存,好比咱們的 JVM 堆中,本地緩存是內存訪問,沒有遠程交互開銷,性能最好,可是受限於單機容量,通常緩存較小且沒法擴展。數據庫
分佈式緩存通常都具備良好的水平擴展能力,對較大數據量的場景也能應付自如。缺點就是須要進行遠程請求,性能不如本地緩存。數組
爲了平衡這種狀況,實際業務中通常採用多級緩存,本地緩存只保存訪問頻率最高的部分熱點數據,其餘的熱點數據放在分佈式緩存中。緩存
數據結構:Memcached只支持簡單的key/value數據結構,不像Redis能夠支持豐富的數據類型。服務器
持久化:Memcached沒法進行持久化,數據不能備份,只能用於緩存使用,且重啓後數據所有丟失。網絡
多線程:Redis 使用單線程反而避免了多線程的頻繁上下文切換問題,預防了多線程可能產生的競爭問題。數據結構
Memcache 存在着支持併發性很差、可運維性欠佳、原子性操做不夠、在誤操做時產生數據不一致等問題。多線程
因爲 Redis 只使用單核,而 Memcached 可使用多核,因此 Redis 在存儲小數據時比 Memcached 性能更高。而在 100k 以上的數據中,Memcached 性能要高於 Redis,雖然 Redis 最近也在存儲大數據的性能上進行優化,可是比起 Memcached,仍是稍有遜色。
分佈式:Redis原生支持集羣模式,Memcached沒有原生的集羣模式。
Redis 支持經過Replication進行數據複製,經過master-slave機制,能夠實時進行數據的同步複製,支持多級複製和增量複製,master-slave機制是Redis進行HA的重要手段。
file event handler
,這個文件事件處理器是單線程的,因此 Redis 才叫作單線程的模型。它採用 IO 多路複用機制同時監聽多個 Socket,根據 Socket 上的事件來選擇對應的事件處理器進行處理。
文件事件處理器的結構包含 4 個部分:
多個 Socket 可能會併發產生不一樣的操做,每一個操做對應不一樣的文件事件,可是 IO 多路複用程序會監聽多個 Socket,會將 Socket 產生的事件放入隊列中排隊,事件分派器每次從隊列中取出一個事件,把該事件交給對應的事件處理器進行處理。
爲何Redis單線程也能效率這麼高?
基本:String、Hash、List、Set、SortedSet
進階:HyperLogLog、Geo、Pub/Sub、bitmaps
高級:BloomFilter
String:存儲簡單的對象序列化字符串,應用場景:緩存熱點數據、計數器、會話token
Hash:保存無嵌套的對象屬性
List:列表,應用場景:簡單消息隊列、分頁
Set:無序集合,自動去重,應用場景:共同好友
Sorted Set:有序集合,自動去重,應用場景:排行榜、微博熱搜
Geo:能夠用來保存地理位置,並做位置距離計算或者根據半徑計算位置等。 應用場景:附近的人
Pub/Sub:訂閱/發佈,應用場景:簡單消息隊列
HyperLogLog:用來作基數統計的算法 ,好比數據集 {1, 3, 5, 7, 5, 7, 8}, 那麼這個數據集的基數集爲 {1, 3, 5 ,7, 8}, 基數(不重複元素)爲5。 基數統計就是在偏差可接受的範圍內,快速計算基數。 應用場景:日活躍用戶
Bitmap:支持按bit位來存儲信息,等同於byte數組,計數效率高,應用場景:日活躍用戶、布隆過濾器
bitmaps存儲一億用戶須要12.5M內存
BloomFilter
布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。 存在誤判,可能要查到的元素並無在容器中,可是hash以後獲得的k個位置上值都是1。若是bloom filter中存儲的是黑名單,那麼能夠經過創建一個白名單來存儲可能會誤判的元素。刪除困難。一個放入容器的元素映射到bit數組的k個位置上是1,刪除的時候不能簡單的直接置爲0,可能會影響其餘元素的判斷。
布隆過濾器的原理是,當一個元素被加入集合時,經過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置爲1。檢索時,咱們只要看看這些點是否是都是1就(大約)知道集合中有沒有它了:若是這些點有任何一個0,則被檢元素必定不在;若是都是1,則被檢元素極可能在。這就是布隆過濾器的基本思想。
Bloom Filter跟單哈希函數Bit-Map不一樣之處在於:Bloom Filter使用了k個哈希函數,每一個字符串跟k個bit對應。從而下降了衝突的機率。
Redis 用SDS(Simple Dynamic String)來保存字符串,SDS還被用做緩衝區(buffer)AOF模塊中的AOF緩衝區。
struct sdshdr {
// buf 中已佔用空間的長度
int len;
// buf 中剩餘可用空間的長度
int free;
// 數據空間
char buf[];
};
複製代碼
使用SDS的好處:
List的底層實現之一就是鏈表
typedef struct listNode{
struct listNode *prev;
struct listNode * next;
void * value;
}
複製代碼
整數集合是集合(set)的底層實現之一,當一個集合中只包含整數,且這個集合中的元素數量很少時,redis就會使用整數集合intset做爲集合的底層實現。
typedef struct intset{
//編碼方式
uint32_t enconding;
// 集合包含的元素數量
uint32_t length;
//保存元素的數組
int8_t contents[];
}
複製代碼
整數集合的底層實現爲數組,這個數組以有序,無重複的範式保存集合元素,在有須要時,程序會根據新添加的元素類型改變這個數組的類型。
字典,又稱爲符號表(symbol table)、關聯數組(associative array)或映射(map),是一種用於保存鍵值對的抽象數據結構。相似HashMap,當發生哈希衝突時,採用頭插法向單向鏈表表頭插入元素。
rehash的時候將ht[0]數據從新分配到ht[1]中,將ht[0]釋放,將ht[1]設置成ht[0],最後爲ht[1]分配一個空白哈希表。
漸進式rehash 的詳細步驟:
一、爲ht[1] 分配空間,讓字典同時持有ht[0]和ht[1]兩個哈希表
二、在字典中維持一個索引計數器變量rehashidx,並將它的值設置爲0,表示rehash 開始
三、在rehash 進行期間,每次對字典執行CRUD操做時,程序除了執行指定的操做之外,還會將ht[0]中的數據rehash 到ht[1]表中,而且將rehashidx加1
四、當ht[0]中全部數據轉移到ht[1]中時,將rehashidx 設置成-1,表示rehash 結束
採用漸進式rehash 的好處在於它採起分而治之的方式,避免了集中式rehash 帶來的龐大計算量。
跳錶(skiplist)是一種有序數據結構,它經過在每一個節點中維持多個指向其餘節點的指針,從而達到快速訪問節點的目的。跳躍表是一種隨機化的數據,跳躍表以有序的方式在層次化的鏈表中保存元素,效率和平衡樹媲美 ——查找、刪除、添加等操做均可以在對數指望時間下完成,而且比起平衡樹來講,跳錶的實現要簡單直觀得多。
Redis 只在兩個地方用到了跳錶,一個是實現有序集合鍵,另一個是在集羣節點中用做內部數據結構。
RDB作鏡像全量持久化,AOF作增量持久化。由於RDB會耗費較長時間,不夠實時,在停機的時候會致使大量丟失數據,因此須要AOF來配合使用。
RDB對Redis的性能影響很是小,是由於在同步數據的時候他只是fork了一個子進程去作持久化的,fork是指redis經過建立子進程來進行RDB操做,cow指的是copy on write,子進程建立後,父子進程共享數據段,父進程繼續提供讀寫服務,寫髒的頁面數據會逐漸和子進程分離開來。
RDB在數據恢復時比AOF快,由於數據文件小,每條記錄只保存了一次,AOF一條記錄可能保存屢次操做記錄。RDB文件的存儲格式和Redis數據在內存中的編碼格式是一致的,不須要再進行數據編碼工做,因此在CPU消耗上要遠小於AOF日誌的加載。
缺點:快照截屏間隔可能較久,若是採用RDB進行持久化,服務掛掉可能形成更多數據的丟失;在生成快照時若是文件很大可能致使客戶端卡頓
SAVE命令由服務器進程直接執行保存操做,會阻塞服務器。BGSAVE命令由子進程執行保存操做,不會阻塞服務器。
服務器狀態中會保存全部用save選項設置的保存條件,當任意一個保存條件被知足時,服務器會自動執行BGSAVE命令。
根據默認配置,RDB 五分鐘一次生成快照,可是 AOF 是一秒一次去經過一個後臺的線程fsync
操做,那最多丟這一秒的數據。
AOF在對日誌文件進行操做的時候是以 append-only
的方式去寫的,他只是追加的方式寫數據,天然就少了不少磁盤尋址的開銷了,寫入性能驚人,文件也不容易破損。
AOF的日誌是經過一個叫 很是可讀 的方式記錄的,這樣的特性就適合作 災難性數據誤刪除 的緊急恢復了,好比公司的實習生經過 flushall 清空了全部的數據,只要這個時候後臺重寫還沒發生,你立刻拷貝一份 AOF 日誌文件,把最後一條 flushall 命令刪了就完事了。
缺點:同樣的數據,AOF 文件比 RDB 還要大;AOF開啓後,Redis支持寫的QPS會比RDB支持寫的要低,由於每秒異步刷新一第二天志
AOF文件經過保存全部修改數據庫的寫命令請求來記錄服務器的數據庫狀態;命令請求會先保存到AOF緩衝區裏面,以後再按期寫入並同步到AOF文件。
AOF重寫 首先從數據庫中讀取鍵如今的值,而後用一條命令去記錄鍵值對,代替以前記錄這個鍵值對的多條命令 ,產生一個新的AOF文件,新AOF文件和原AOF保存的數據庫狀態同樣,但體積更小;
在執行BGREWRITEAOF命令時,Redis會維護一個AOF重寫緩衝區,該緩衝區會在子進程建立新的AOF文件期間,記錄服務器執行的全部寫命令。當子進程完成建立新AOF文件以後,服務器會將重寫緩衝區中的全部內容追加到新的AOF文件的末尾。最後,服務器用新的AOF文件替換舊的AOF文件,以此來完成AOF文件重寫操做。
no-appendfsync-on-rewrite
是否在後臺寫時阻塞,默認值no(表示阻塞寫操做)。no表示新的主進程的set操做會被阻塞掉,而yes表示新的主進程的set不會被阻塞,待整個後臺寫完成以後再將這部分set操做同步到aof文件中。但這可能會存在數據丟失的風險(機率很小),若是對性能有要求,能夠設置爲yes,僅在後臺寫時會異步處理命令。
auto-aof-rewrite-percentage
AOF文件的體積比上一次重寫以後的增加比例,假設用戶對Redis設置了配置選項auto-aof-rewrite-percentage 100,那麼當AOF文件的體積比上一次重寫以後的文件大小大了至少一倍(100%)的時候,Redis將執行BGREWRITEAOF命令。
auto-aof-rewrite-min-size
觸發AOF文件重寫的最小的文件大小,即最開始AOF文件必需要達到這個文件大小時才觸發重寫,後面的每次重寫就不會根據這個變量了(根據上一次重寫完成以後的大小) 。
Redis鍵的過時策略,是有按期刪除+惰性刪除兩種。
按期好理解,默認100ms就 隨機 抽一些設置了過時時間的key,去檢查是否過時,過時了就刪了。
惰性刪除,查詢時再判斷是否過時,過時就刪除鍵不返回值。
當新增數據發現內存達到限制時,Redis觸發內存淘汰機制。
maxmemory
配置Redis存儲數據時指定限制的內存大小,好比100m。當緩存消耗的內存超過這個數值時, 將觸發數據淘汰。該數據配置爲0時,表示緩存的數據量沒有限制, 即LRU功能不生效。64位的系統默認值爲0,32位的系統默認內存限制爲3GB。
maxmemory_policy
觸發數據淘汰後的淘汰策略
maxmemory_samples
隨機採樣的精度,也就是隨即取出key的數目。該數值配置越大, 越接近於真實的LRU算法,可是數值越大,相應消耗也變高,對性能有必定影響,樣本值默認爲5。
真實LRU算法須要一個雙向鏈表來記錄數據的最近被訪問順序,比較耗費內存。
Redis 經過對少許鍵進行取樣,而後回收其中的最久未被訪問的鍵。經過調整每次回收時的採樣數量maxmemory-samples,能夠實現調整算法的精度。
Redis 的鍵空間是放在一個哈希表中的,要從全部的鍵中選出一個最久未被訪問的鍵,須要另一個數據結構存儲這些源信息,這顯然不划算。最初,Redis只是隨機的選3個key,而後從中淘汰,後來算法改進到N個key的策略,默認是5個。
Redis 3.0以後又改善了算法的性能,會提供一個待淘汰候選key的pool,裏面默認有16個key,按照空閒時間排好序。更新時從Redis鍵空間隨機選擇N個key,分別計算它們的空閒時間 idle,key只會在pool不滿或者空閒時間大於pool裏最小的時,纔會進入pool,而後從pool中選擇空閒時間最大的key淘汰掉。
Redis爲何不使用真實的LRU實現是由於這須要太多的內存。不過近似的LRU算法(approximated LRU)對於應用而言應該是等價的。
Redis支持三種分佈式部署的方式:主從複製、哨兵模式、集羣模式
Redis可使用主從同步,從從同步。第一次同步時,主節點作一次bgsave,並同時將後續修改操做記錄到內存buffer,待完成後將RDB文件全量同步到複製節點,複製節點接受完成後將RDB鏡像加載到內存。加載完成後,再通知主節點將期間修改的操做記錄同步到複製節點進行重放就完成了同步過程。後續的增量數據經過AOF日誌同步便可,有點相似數據庫的binlog。
能夠進行讀寫分離,分擔了主服務器讀操做的壓力
Redis不具有自動容錯和恢復功能,主機從機的宕機都會致使前端部分讀寫請求失敗,較難支持在線擴容,在集羣容量達到上限時在線擴容會變得很複雜。
當主服務器中斷服務後,能夠將一個從服務器升級爲主服務器,以便繼續提供服務,可是這個過程須要人工手動來操做。 爲此,Redis 2.8中提供了哨兵工具來實現自動化的系統監控和故障恢復功能。
哨兵的做用就是監控Redis系統的運行情況。它的功能包括如下兩個。
(1)監控主服務器和從服務器是否正常運行。 (2)主服務器出現故障時自動將從服務器轉換爲主服務器。
Sentinel是Redis的高可用性(HA)解決方案,由一個或多個Sentinel實例組成的Sentinel系統能夠監視任意多個主服務器,以及這些主服務器屬下的全部從服務器,並在被監視的主服務器進行下線狀態時,自動將下線主服務器屬下的某個從服務器升級爲新的主服務器,而後由新的主服務器代替已下線的主服務器繼續處理命令請求。
Redis提供的sentinel(哨兵)機制,經過sentinel模式啓動redis後,自動監控master/slave的運行狀態,基本原理是:心跳機制+投票裁決,每一個sentinel只有一次選舉的機會,當主庫出現故障,哨兵會投票從庫中選出一個承擔主庫的任務,剩下的仍是從庫
redis集羣是一個由多個主從節點羣組成的分佈式服務器羣,它具備複製、高可用和分片特性。Redis集羣不須要sentinel哨兵也能完成節點移除和故障轉移的功能。須要將每一個節點設置成集羣模式,這種集羣模式沒有中心節點,可水平擴展,據官方文檔稱能夠線性擴展到上萬個節點(官方推薦不超過1000個節點)。redis集羣的性能和高可用性均優於以前版本的哨兵模式,且集羣配置很是簡單。
集羣模式有如下幾個特色:
在哨兵模式中,仍然只有一個Master節點。當併發寫請求較大時,哨兵模式並不能緩解寫壓力。 咱們知道只有主節點才具備寫能力,那若是在一個集羣中,可以配置多個主節點,緩解寫壓力,redis-cluster集羣模式能達到此類要求。
在Redis-Cluster集羣中,能夠給每個主節點添加從節點,主節點和從節點直接遵循主從模型的特性。
當用戶須要處理更多讀請求的時候,添加從節點能夠擴展系統的讀性能。
Redis集羣的主節點內置了相似Redis Sentinel的節點故障檢測和自動故障轉移功能,當集羣中的某個主節點下線時,集羣中的其餘在線主節點會注意到這一點,並對已下線的主節點進行故障轉移。 集羣進行故障轉移的方法和Redis Sentinel進行故障轉移的方法基本同樣,不一樣的是,在集羣裏面,故障轉移是由集羣中其餘在線的主節點負責進行的,因此集羣沒必要另外使用Redis Sentinel。
常見的集羣分片算法有:通常哈希算法、一致性哈希算法以及Hash Slot算法,Redis採用的是Hash Slot
計算方式:hash(key)%N
缺點:若是增長一個redis,映射公式變成了 hash(key)%(N+1)
若是一個redis宕機了,映射公式變成了 hash(key)%(N-1)
在以上兩種狀況下,幾乎全部的緩存都失效了。
先構造出一個長度爲2^32整數環,根據節點名稱的hash值(分佈在[0,2^32-1])放到這個環上。如今要存放資源,根據資源的Key的Hash值(也是分佈在[0,2^32-1]),在環上順時針的找到離它最近的一個節點,就創建了資源和節點的映射關係。
優勢:一個節點宕機時,上面的數據轉移到順時針的下一個節點中,新增一個節點時,也只須要將部分數據遷移到這個節點中,對其餘節點的影響很小
刪除一個節點
缺點:因爲數據在環上分佈不均,可能存在某個節點存儲的數據比較多,那麼當他宕機的時候,會致使大量數據涌入下一個節點中,把另外一個節點打掛了,而後全部節點都掛了
改進:引進了虛擬節點的概念,想象在這個環上有不少「虛擬節點」,數據的存儲是沿着環的順時針方向找一個虛擬節點,每一個虛擬節點都會關聯到一個真實節點
Redis採用的是Hash Slot分片算法,用來計算key存儲位置的。集羣將整個數據庫分爲16384個槽位slot,全部key-value數據都存儲在這些slot中的某一個上。一個slot槽位能夠存放多個數據,key的槽位計算公式爲:slot_number=CRC16(key)%16384,其中CRC16爲16位的循環冗餘校驗和函數。
客戶端可能會挑選任意一個redis實例去發送命令,每一個redis實例接收到命令,都會計算key對應的hash slot,若是在本地就在本地處理,不然返回moved給客戶端,讓客戶端進行重定向到對應的節點執行命令
若是大量的key過時時間設置的過於集中,到過時的那個時間點,Redis可能會出現短暫的卡頓現象。嚴重的話會出現緩存雪崩,咱們通常須要在時間上加一個隨機值,使得過時時間分散一些。
電商首頁常常會使用定時任務刷新緩存,可能大量的數據失效時間都十分集中,若是失效時間同樣,又恰好在失效的時間點大量用戶涌入,就有可能形成緩存雪崩。
緩存雪崩:大量緩存的key同時失效,同時大批量的請求落到了數據庫上,數據庫扛不住掛了。(處理方法:失效時間加上一個隨機值,避免同時失效)
緩存穿透:用戶不斷訪問緩存和數據庫都沒有的數據。(處理方式:請求參數校驗,將查詢不到數據的key放到緩存中,value設爲null)
緩存擊穿:對於熱點數據,若是一直有高併發的請求,恰好緩存失效,這時大量的請求就會落在數據庫上(設置熱點期間不過時)
Redis的單線程的。keys指令會致使線程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。 使用scan指令能夠無阻塞的提取出指定模式的key列表,可是會有必定的重複機率,在客戶端作一次去重就能夠了,可是總體所花費的時間會比直接用keys指令長 。
雖然redis實現了在讀寫操做時,輔助服務器進行漸進式rehash操做,可是若是服務器比較空閒,redis數據庫將很長時間內都一直使用兩個哈希表。因此在redis周期函數中,若是發現有字典正在進行漸進式rehash操做,則會花費1毫秒的時間,幫助一塊兒進行漸進式rehash操做
Redis持久化的數據庫文件便是RDB文件,若是開啓了AOF,數據則持久化在AOF文件中
最近開始寫博客文章,朋友們要是以爲寫得還能夠,拜託你們點個贊讓我知道本身沒有白白花費時間,大家的贊是對我莫大的鼓舞,你們有什麼意見建議也歡迎在評論區共同探討。
不以浮沙築高臺,與諸君共勉。