想搞懂redis,這一篇文章真的就夠了!

前言

首先科普一下CPU緩存,CPU緩存是指能夠進行高速數據交換的存儲器,它先於內存與CPU交換數據,所以速率很快。緩存的工做原理是當CPU要讀取一個數據的時候,首先在CPU緩存中查找,找到就當即讀取並送給CPU處理;沒有找到,就從速率相對較慢的內存中讀取並送給CPU處理,同時把這個數據所在的數據塊調入緩存中,可使得之後對整塊數據的讀取都從緩存中進行,沒必要再調用內存java


爲何要引入CPU緩存?在解釋以前必須先了解程序的執行過程,首先從硬盤執行程序,存放到內存,再給cpu運算與執行。因爲內存和硬盤的速度相比cpu實在慢太多了,每執行一個程序cpu都要等待內存和硬盤,引入緩存技術即是爲了解決此矛盾,緩存與cpu速度一致,cpu從緩存讀取數據比cpu在內存上讀取快得多,從而提高系統性能。目前主流級CPU都有一級和二級緩存,高端些的甚至有三級緩存node

上面講述的是CPU緩存,讓你讀起下面的文章來好有個藥引子,若是讀不懂沒太大問題,繼續向下讀
重點來了!在開發中咱們經常提到的緩存和上面的CPU緩存有殊途同歸之妙,可是並不等同於CPU緩存,咱們寫服務器程序時,使用緩存的目的無非就是減小數據庫訪問次數下降數據庫的壓力和提高程序的響應時間, 然而根據具體的使用場景又能夠派生出無數種狀況:mysql

  • 好比說程序頻繁讀取數據庫, 可是查詢得到的結果卻老是相同的,這部分相同的結果是否是能夠放入緩存 ?
  • 得到查詢結果要進行復雜的運算,很是消耗時間, 運算結果是否是能夠放入緩存 ?
  • 有一些在網站每一個頁面都須要使用的數據, 好比說用戶數據, 是否是能夠放入緩存 ?

緩存有不少實現方式,谷歌的guava包的Cache、分佈式緩存redis,memcached、EHcache、自定義緩存(例如使用靜態Map實現)等。下面咱們來說解最經常使用的redis,會有一些簡單的操做,尚未下載,不會?別慌,點擊 download.redis.io/releases/下載,下載完成以後的步驟請移步個人redis教程系列;
Redis數據類型和內存原理
redis是一個基於C語言實現的高性能的key-value存儲系統,運行在內存中可是能夠持久化到硬盤上,有着多樣的數據結構string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合),還有一些高級數據結構HyperLogLog、Geo、Pub/Sub。每種類型的存儲在底層都會存在不一樣的編碼格式(redisObject、SDS等)。
Redis到底強在哪裏?程序員

  • 性能極高,Redis能讀的速度是110000次/s,寫的速度是81000次/s 。

-支持多種數據結構,如 string(字符串)、 list(雙向鏈表)、dict(hash表)、set(集合)、zset(排序set)、hyperloglog(基數估算)面試

  • 原子,Redis的全部操做都是原子性的,意思就是要麼成功執行要麼失敗徹底不執行。單個操做是原子性的。多個操做也支持事務,即原子性,經過MULTI和EXEC指令包起來。
  • 支持AOF和RDB持久化操做,數據備份;
  • 豐富的特性,Redis還支持pub/sub, 通知, key 過時等等特性。
    • *


數據類型簡介

String(字符串)
Redis字符串是字節序列。Redis字符串是二進制安全的,這意味着他們有一個已知的長度沒有任何特殊字符終止,因此你能夠存儲任何東西,512M爲上限; 示例:redis

  • set key name存儲一個字符串對象value

Hash(hash表)
Redis的哈希是鍵值對的集合。 Redis的哈希值是字符串字段和字符串值之間的映射,所以它們被用來表示對象。算法

示例:sql

  • hset 存儲一個哈希鍵值對的集合(hset key field value)
  • hget 獲取一個哈希鍵的值(hget key field)

xiaoming是對於redis的存儲識別Hash的,而Hash真正存儲的是key(year、score),value(1八、99)。
List(鏈表)
Redis的鏈表是簡單的字符串列表,排序插入順序。能夠添加元素到Redis的列表的頭部或尾部,容許添加劇復元素;shell

示例:數據庫

  • lpop key 從左邊移出一個元素,rpop key 從右邊移出一個元素
  • len key 返回鏈表中元素的個數 至關於關係型數據庫中 select count(*)
  • lrange key start end lrange命令將返回索引從start到stop之間的全部元素。Redis的列表起始索引爲0。

Set(集合)
Redis的集合是字符串的無序集合,不容許有重複。

示例:

  • sadd key value 添加一個string元素到,key對應的set集合中,成功返回1,若是元素已經在集合中返回0;
  • smembers key 返回key對應set的全部元素,結果是無序的;

SortedSet(有序集合)zset
Redis 有序集合和集合同樣也是string類型元素的集合,且不容許重複的成員。不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。有序集合的成員是惟一的,但分數(score)卻能夠重複。
示例:

  • zadd key score value 將一個或多個value及其socre加入到set中
  • zrange key start end 0和-1表示從索引爲0的元素到最後一個元素(同LRANGE命令類似)
  • zrangebycore key start end 範圍內按照分數排序輸出
  • zremrangebyscore key start end 可用於範圍刪除操做

Redis內存模型

接下來我會從redis底層內存存儲到系統使用級別來簡單介紹redis,什麼,一聽內存就頭大?那你還想不想要走向人生巔峯迎娶白富美,想就乖乖的看、乖乖讀、乖乖學!

Redis經過info memory查詢內存使用狀況,做爲內存數據庫,在內存中存儲的主要是數據,redis內存主要劃分爲幾部分:

  • 數據:做爲數據庫,數據是最主要的部分;
  • 進程自己運行須要的內存:Redis主進程自己運行確定須要佔用內存,如代碼、常量池等等;
  • 緩衝內存:緩衝內存包括客戶端緩衝區、複製積壓緩衝區、AOF緩衝區等;
  • 內存碎片:內存碎片是Redis在分配、回收物理內存過程當中產生的。

Redis數據內存

上面圖是執行set hello world時所設計到的數據模型;
Redis是鍵值對數據庫,每一個鍵值對都會有一個dictEntry,裏面存儲着指向Key和Value的指針;next指向下一個dictEntry,與本Key-Value無關。 Key和Value又都有相應的存儲結構,每種類型都有至少兩種內部編碼,這樣作的好處在於一方面接口與實現分離,當須要增長或改變內部編碼時,用戶使用不受影響,另外一方面能夠根據不一樣的應用場景切換內部編碼,提升效率。
可是,不管是哪一種類型,redis都不會直接存儲,而是經過redisObject的對象進行存儲,redisObject對象很重要,Redis對象的類型、內部編碼、內存回收、共享對象等功能,都須要redisObject支持。
Redis中還有一種SDS結構也比較重要,SDS是簡單動態字符串(Simple Dynamic String)的縮寫。Redis沒有直接使用C字符串(即以空字符’0’結尾的字符數組)做爲默認的字符串表示,而是使用了SDS。至於它們的結構在這裏暫且不說,我會在下面的文章中詳細介紹。
事務和管道pipline
衆所周知,事務是指一個完整的動做,要麼所有執行,要麼什麼也沒有作,提起事務咱們會首先想到事務的四大特性ACID:
A:原子性(Atomicity) 事務是數據庫的邏輯工做單位,事務中包括的諸操做要麼全作,要麼全不作。
C:一致性(Consistency) 事務執行的結果必須是使數據庫從一個一致性狀態變到另外一個一致性狀態。一致性與原子性是密切相關的。
I:隔離性(Isolation) 一個事務的執行不能被其餘事務干擾。
D:持續性/永久性(Durability) 一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。
Redis事務
簡單聊下redis中的事務,先介紹幾個redis指令,即MULTI、EXEC、DISCARD、WATCH、UNWATCH。這五個指令構成了redis事務處理的基礎。

一、multi用來組裝提供事務;
二、exec執行全部事務塊內的命令。
三、discard取消事務,放棄執行事務塊內的全部命令。
四、watch監視一個(或多個) key ,若是在事務執行以前這個(或這些) key 被其餘命令所改動,那麼事務將被打斷。
五、unwatch取消 watch 命令對全部 key 的監視。

Redis事務能夠一次執行多個命令,會經歷三個階段:開始事務,命令入隊,執行事務。而且帶有如下三個重要的保證:

一、批量操做在發送 EXEC 命令前被放入隊列緩存。

二、收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其他的命令依然被執行。

三、在事務執行過程,其餘客戶端提交的命令請求不會插入到事務執行命令序列中。

在上面的例子中,咱們看到了QUEUED的字樣,這表示咱們在用MULTI組裝事務時,每個命令都會進入到內存隊列中緩存起來,若是出現QUEUED則表示咱們這個命令成功插入了緩存隊列,在未來執行EXEC時,這些被QUEUED的命令都會被組裝成一個事務來執行。
對於事務的執行來講,若是redis開啓了AOF持久化的話,那麼一旦事務被執行,事務中的命令便會經過write命令一次性寫入到磁盤中(下面會介紹持久化)。
在事務執行中,常常會遇到兩類問題,一是調用EXEC以前的問題,另外一個是調用EXEC以後的問題。

調用EXEC以前的問題

「調用EXEC以前的錯誤」,有多是因爲語法有誤致使的,也可能時因爲內存不足致使的。只要出現某個命令沒法成功寫入緩衝隊列的狀況,redis都會進行記錄,在客戶端調用EXEC時,redis會拒絕執行這一事務。(這時2.6.5版本以後的策略。在2.6.5以前的版本中,redis會忽略那些入隊失敗的命令,只執行那些入隊成功的命令)。

redis無情的拒絕了事務的執行,緣由是「以前出現了錯誤」;(error) EXECABORT Transaction discarded because of previous errors。
調用EXEC以後的問題
對於「調用EXEC以後的錯誤」,redis則採起了徹底不一樣的策略,即redis不會理睬這些錯誤,而是繼續向下執行事務中的其餘命令。這是由於,對於應用層面的錯誤,並非redis自身須要考慮和處理的問題,因此一個事務中若是某一條命令執行失敗,並不會影響接下來的其餘命令的執行。

看到這裏可能不少人會提出問題:你前面不是說事務有一個特性是原子性,事務中的操做要麼所有執行,要不所有不執行。那上面的狀況豈不是違背了原子性?是否是意味着redis不支持事務原子性?
解惑:redis事務不支持事務回滾機制,redis事務執行過程當中,若是一個命令出現錯誤,那麼就返回錯誤,下面的命令仍是會繼續執行下去。正是由於redis事務不支持事務回滾,若是事務出現了命令執行錯誤,只會返回當前命令的錯誤給客戶端,不會影響下面的命令的執行,因此不少人以爲和關係型數據庫(MySQL) 不同,而 MySQL 的事務是具備原子性的,因此你們都認爲 Redis 事務不支持原子性。

其實,正常狀況下,redis事務是支持原子性的,它也是要不全部命令執行成功,要不一個命令都不執行。看咱們上面介紹的調用EXEC以前的錯誤的實例,在事務開始後,用戶能夠輸入事務要執行的命令;在命令入事務隊列前,會對命令進行檢查,若是命令不存在或者是命令參數不對,則會返回錯誤可客戶端,而且修改客戶端狀態。當後面客戶端執行 EXEC 命令時,服務器就會直接拒絕執行此事務了。
Redis不支持事務回滾,可是它會檢查事務中的每個命令是否錯誤(不支持檢查程序員我的的邏輯錯誤),若是有錯誤便不會執行事務,只有經過redis這一層的檢查纔會開啓事務執行而且會所有執行(並不會保證所有執行成功),因此客觀來說redis事務是支持原子性的。
思考redis和mysql、oracle這種關係型數據庫事務的區別,首先redis定位是nosql非關係數據庫,而mysql、oracle這種是關係型數據庫。

在關係型數據庫中執行的sql查詢能夠是至關複雜的,sql真正開始執行的時候纔會進行檢查分析(有些狀況可能會預編譯),沒有事務隊列這一律念,mysql數據庫不知道下一條sql是否正確,因此有必要支持事務回滾。可是在redis中,redis使用了事務隊列來將命令存儲起來而且會進行格式檢查,提早能夠知道命令是否正確,因此若是隻要有一個命令是錯誤的,那麼這個事務是不能執行的。
Redis 做者認爲基本只會出如今開發環境的編程錯誤其實在生產環境基本是不可能出現的(例如對 String 類型的數據庫鍵執行 LPUSH 操做),因此他以爲不必爲了這事務回滾機制而改變 Redis 追求簡單高效的設計主旨。
因此最後,其實 Redis 事務真正支持原子性的前提:開發者不要傻不拉幾的寫有邏輯問題的代碼!

Redis管道技術

Redis是一種基於客戶端-服務端模型以及請求/響應協議的TCP服務。這意味着一般狀況下一個請求會遵循如下步驟:客戶端向服務端發送一個查詢請求,並監聽Socket返回,一般是以阻塞模式,等待服務端響應。服務端處理命令,並將結果返回給客戶端。
Redis 管道技術能夠在服務端未響應時,客戶端能夠繼續向服務端發送請求,並最終一次性讀取全部服務端的響應。形象點說明就是對於redis來講通常是同步模式來請求返回結果,而管道技術可讓redis能夠實現異步的訪問,客戶端不須要等待服務端的返回結果,能夠持續的向服務端發送請求,等待最終把結果所有讀取。
持久化機制(RDB和AOF)
Redis持久化
數據持久化技術,也是Redis的一大特點。主要做用是數據的備份,將內存中的數據持久化到硬盤上,保證數不會由於服務的退出而形成丟失。Redis是內存數據庫,咱們須要按期的將redis中的數據以某種形式(數據或者命令)存儲在硬盤上,當下次redis重啓時,利用持久化的技術能夠實現數據的恢復。有時爲了進行災難備份,咱們也能夠將持久化生成的數據文件拷貝到一個遠程位置。

和我們在朋友圈看見好看的圖片同樣,得把它保存到手機,這樣下次才能找到它繼續用;而這裏的內存就至關於我們的腦子,腦子通過多天的「打磨」忘卻了,而存起來更便於下次找到它!

Redis持久化分爲RDB持久化和AOF持久化:前者將當前數據保存到硬盤,後者則是將每次執行的寫命令保存到硬盤(相似於MySQL的binlog);因爲AOF持久化的實時性更好,即當進程意外退出時丟失的數據更少,所以AOF是目前主流的持久化方式,不過RDB持久化仍然有其用武之地。
RDB持久化

RDB持久化是將當前進程中的數據生成快照保存到硬盤(所以也稱做快照持久化),保存的文件是通過壓縮的二進制文件,後綴是rdb;當Redis從新啓動時,能夠讀取快照文件恢復數據。RDB持久化的觸發分爲手動觸發和自動觸發兩種。
優勢:
一、體積小:相同的數據量rdb數據比aof的小,由於rdb是緊湊型文件;
二、恢復快:由於rdb是數據的快照,數據複製,不須要從新執行命令;
三、性能高:父進程在保存rdb時候只須要fork一個子進程,無需父進程的進行其餘io操做,也保證了服務器的性能。
缺點:
一、故障丟失:由於rdb是全量的,咱們通常是使用shell腳本實現30分鐘或者1小時或者天天對redis進行rdb備份,可是最少也要5分鐘進行一次的備份,因此當服務死掉後,最少也要丟失5分鐘的數據。
二、耐久性差:相對aof的異步策略來講,由於rdb的複製是全量的,即便是fork的子進程來進行備份,當數據量很大的時候對磁盤的消耗也是不可忽視的,尤爲在訪問量高的時候,fork的時間會延長,致使cpu吃緊,耐久性相對較差。
AOF持久化
AOF持久化(即Append Only File持久化),是將Redis執行的每次寫命令記錄到單獨的日誌文件中(有點像MySQL的binlog);當Redis重啓時再次執行AOF文件中的命令來恢復數據。
它的出現是爲了彌補RDB的不足(數據的不一致性),因此它採用日誌的形式來記錄每一個寫操做,並追加到文件中。咱們能夠設置不一樣的 fsync 策略,好比無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。
AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,Redis 仍然能夠保持良好的性能,而且就算髮生故障停機,也最多隻會丟失一秒鐘的數據( fsync 會在後臺線程執行,因此主線程能夠繼續努力地處理命令請求)。
優勢:
一、數據保證:咱們能夠設置不一樣的fsync策略,通常默認是everysec,也能夠設置每次寫入追加,服務宕機最多丟失一秒數據
二、文件重寫:當aof文件大小到達必定程度的時候,後臺會自動的去執行aof重寫,此過程不會影響主進程,重寫完成後,新的寫入將會寫到新的aof中,舊的就會被刪除掉。
缺點:
一、性能相對較差:恢復數據須要從新執行命令,性能較RDB低;
2.體積相對更大:儘管是將aof文件重寫了,依然大;
3.恢復速度更慢;
主從複製(讀寫分離)
上面介紹的持久化側重解決的是redis數據的單機備份問題(從內存到硬盤),而主從複製則側重解決的是數據的多機熱備份。先來講下熱備份,就是保證服務正常不間斷運行,經過一臺服務器對另外一臺服務器進行實時數據複製,且保障兩邊數據的一致性。咱們將在線的備份稱爲熱備份,而相對的,將脫機數據備份稱爲冷備份。冷備份是在系統不運做時,定時的將數據備份至備份服務器或存儲。
主從複製,是指將一臺Redis服務器的數據,複製到其餘的Redis服務器。前者稱爲主節點(master),後者稱爲從節點(slave);數據的複製是單向的,只能由主節點到從節點。默認狀況下,每臺Redis服務器都是主節點;且一個主節點能夠有多個從節點(或沒有從節點),但一個從節點只能有一個主節點。
主從複製的做用:

  • 數據冗餘:主從複製實現了數據的熱備份,能夠提供遠程備份、數據冗餘,也能夠做爲服務的冗餘。
  • 負載均衡:在主從複製的基礎上,配合讀寫分離,能夠由主節點提供寫服務,由從節點提供讀服務(即寫Redis數據時應用鏈接主節點,讀Redis數據時應用鏈接從節點),分擔服務器負載;尤爲是在寫少讀多的場景下,經過多個從節點分擔讀負載,能夠大大提升Redis服務器的併發量。

主從複製的原理:
主從複製大概分爲三個階段:鏈接創建、數據同步、命令傳播;
(1)鏈接創建階段主要做用是在主從節點之間創建鏈接,爲數據同步作好準備;
(2)創建鏈接好以後即可以進行數據同步,也是從階段數據的初始化,數據同步階段是主從複製最核心的階段,根據主從節點當前狀態的不一樣,能夠分爲全量複製和部分複製。全量複製顧名思義就是把主節點數據所有複製到從節點中進行備份數據,全量複製在主節點數據量較大時效率過低。因而在Redis2.8引入了部分複製,用於處理網絡中斷時的數據複製,主從節點會自動判斷當前狀態適合全量複製仍是部分複製;
(3)數據同步階段完成後,主從節點進入命令傳播階段;在這個階段主節點將本身執行的寫命令發送給從節點,從節點接收命令並執行,從而保證主從節點數據的一致性。在命令傳播階段,除了發送寫命令,主從節點還維持着心跳機制:PING和REPLCONF ACK,心跳機制對於主從複製的超時判斷、數據安全等有做用。
詳細原理請移步至Redis教程主從複製篇!
哨兵機制
提起哨兵,你們想到的是什麼呢?

咱們上面說到持久化是爲了解決單機redis的數據存儲問題,主從複製能夠實現數據冗餘,側重於解決數據的多機熱備份。可是主從複製中存在着一個問題就是故障恢復沒法自動化,而redis中的哨兵機制,基於Redis主從複製,主要做用即是解決主節點故障恢復的自動化問題,進一步提升系統的高可用性。可是哨兵機制也存在必定的缺陷,就是寫操做沒法負載均衡,存儲能力受到單機限制。
Redis Sentinel,即Redis哨兵,在Redis 2.8版本開始引入。哨兵的核心功能是主節點的自動故障轉移。下面是Redis官方文檔對於哨兵功能的描述:
監控(Monitoring):哨兵會不斷地檢查主節點和從節點是否運做正常。 自動故障轉移(Automatic failover):當主節點不能正常工做時,哨兵會開始自動故障轉移操做,它會將失效主節點的其中一個從節點升級爲新的主節點,並讓其餘從節點改成複製新的主節點。 配置提供者(Configuration provider):客戶端在初始化時,經過鏈接哨兵來得到當前Redis服務的主節點地址。 通知(Notification):哨兵能夠將故障轉移的結果發送給客戶端。
Redis哨兵機制的架構

它由兩部分組成,哨兵節點和數據節點:
一、哨兵節點:哨兵系統由一個或多個哨兵節點組成,哨兵節點是特殊的redis節點,不存儲數據。 二、數據節點:主節點和從節點都是數據節點。哨兵系統中的主從節點和普通的主從節點沒有什麼區別,故障發現和轉移是由哨兵來控制和完成的,哨兵的本質也是redis節點,只不過不會存儲數據。每個哨兵節點只須要配置監控主節點(能夠配置監控多個主節點),即可以自動發現其餘的哨兵節點和從節點。
哨兵機制原理

  1. 定時任務:每一個哨兵節點維護了3個定時任務。
  • 經過向主從節點發送info命令獲取最新的主從結構
    經過發佈訂閱功能獲取其餘哨兵節點的信息;
    經過向其餘節點發送ping命令進行心跳檢測,判斷是否下線。
  1. 主觀下線:在心跳檢測的定時任務中,若是其餘節點超過必定時間沒有回覆,哨兵節點就會將其進行主觀下線。顧名思義,主觀下線的意思是一個哨兵節點「主觀地」判斷下線;
  2. 客觀下線:哨兵節點在對主節點進行主觀下線後,會經過命令詢問其餘哨兵節點該主節點的狀態;若是判斷主節點下線的哨兵數量達到必定數值,則對該主節點進行客觀下線。
  • 須要特別注意的是,客觀下線是主節點纔有的概念;若是從節點和哨兵節點發生故障,被哨兵主觀下線後,不會再有後續的客觀下線和故障轉移操做。
  1. 選舉領導者哨兵節點:當主節點被判斷客觀下線之後,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,並由該領導者節點對其進行故障轉移操做。
  • 監視該主節點的全部哨兵都有可能被選爲領導者,選舉使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一輪選舉中,哨兵A向B發送成爲領導者的申請,若是B沒有贊成過其餘哨兵,則會贊成A成爲領導者。選舉的具體過程這裏不作詳細描述,通常來講,哨兵選擇的過程很快,誰先完成客觀下線,通常就能成爲領導者。
  1. 故障轉移:選舉出的領導者哨兵,開始進行故障轉移操做,該操做大致能夠分爲3個步驟:

(1)在從節點中選擇新的主節點:選擇的原則是,首先過濾掉不健康的從節點;而後選擇優先級最高的從節點(由slave-priority指定);若是優先級沒法區分,則選擇複製偏移量最大的從節點;若是仍沒法區分,則選擇runid最小的從節點。
(2)更新主從狀態:經過slaveof no one命令,讓選出來的從節點成爲主節點;並經過slaveof命令讓其餘節點成爲其從節點。
(3)將已經下線的主節點設置爲新的主節點的從節點,當原主節點從新上線後,它會成爲新的主節點的從節點。
集羣機制
前面的哨兵機制存在缺陷,寫操做沒法負載均衡,存儲能力受到單機限制。而集羣就是爲了解決這些問題而誕生的,它是redis3.0開始引入的分佈式存儲方案,集羣由多個節點(Node)組成,Redis的數據分佈在這些節點中。集羣中的節點分爲主節點和從節點:只有主節點負責讀寫請求和集羣信息的維護;從節點只進行主節點數據和狀態信息的複製。注意區分和哨兵機制中的主從節點,哨兵中的只有主節點負責寫請求,而從節點負責讀請求。
集羣的主要做用能夠概括爲兩點:數據分區和高可用;這兩點也正是解決上述的兩個問題,數據分區是爲了解決存儲能力受到單機限制,而高可用則是爲了解決寫操做沒法負載均衡以及實現故障恢復自動化。
數據分區存儲
數據分區有順序分區,哈希分區等,而哈希分區具備自然的隨機性,集羣使用的分區方案即是哈希分區的一種。
衡量數據分區方法好壞的標準有不少,其中比較重要的兩個因素是

  • 數據分佈是否均勻
  • 增長或刪減節點對數據分佈的影響。

因爲哈希的隨機性,哈希分區基本能夠保證數據分佈均勻,所以在比較哈希分區方案時,重點要看增減節點對數據分佈的影響。
哈希分區又可分爲哈希取餘分區、一致性哈希分區、帶虛擬節點的一致性哈希分區,接下來簡單介紹下。
哈希取餘分區
哈希取餘分區思路很是簡單:計算key的hash值,而後對節點數量進行取餘,從而決定數據映射到哪一個節點上。該方案最大的問題是,當新增或刪減節點時,節點數量發生變化,系統中全部的數據都須要從新計算映射關係,引起大規模數據遷移。
一致性哈希
一致性哈希:將整個哈希值空間組織成一個虛擬的圓環,範圍爲0-2^32-1;對於每一個數據,根據key計算hash值,肯定數據在環上的位置,而後今後位置沿環順時針行走,找到的第一臺服務器就是其應該映射到的服務器。

與哈希取餘分區相比,一致性哈希分區將增減節點的影響限制在相鄰節點。以上圖爲例,若是在node1和node2之間增長node5,則只有node2中的一部分數據會遷移到node5;若是去掉node2,則原node2中的數據只會遷移到node4中,只有node4會受影響。
一致性哈希分區的主要問題在於,當節點數量較少時,增長或刪減節點,對單個節點的影響可能很大,形成數據的嚴重不平衡。仍是以上圖爲例,若是去掉node2,node4中的數據由總數據的1/4左右變爲1/2左右,與其餘節點相比負載太高。
關於哈希一致性的原始論文鏈接:
原始論文《Consistent Hashing and Random Trees》連接以下:
官方連接 - PDF 版本
相關論文《Web Caching with Consistent Hashing》連接以下:
官方連接 - PDF 版本
帶虛擬節點的一致性哈希
集羣採用的是帶虛擬節點的一致性哈希分區,在redis中這裏的虛擬節點被稱爲槽(slot),redis被設計爲16384個槽。槽是介於數據和實際節點之間的虛擬概念;每一個實際節點包含必定數量的槽,每一個槽包含哈希值在必定範圍內的數據。引入槽之後,數據的映射關係由數據hash->實際節點,變成了數據hash->槽->實際節點。
在使用了槽的一致性哈希分區中,槽是數據管理和遷移的基本單位。槽解耦了數據和實際節點之間的關係,增長或刪除節點對系統的影響很小。

舉個簡單例子說明:咱們存取的key會根據crc16的算法得出一個結果,而後把結果對 16384 求餘數,這樣每一個 key 都會對應一個編號在 0-16383 之間的哈希槽,經過這個值,去找到對應的插槽所對應的節點,而後直接自動跳轉到這個對應的節點上進行存取操做。

節點通訊

在哨兵系統中,節點分爲數據節點和哨兵節點:前者存儲數據,後者實現額外的控制功能。在集羣中,沒有數據節點與非數據節點之分:全部的節點都存儲數據,也都參與集羣狀態的維護。爲此,集羣中的每一個節點,都提供了普通端口和集羣端口兩個端口。集羣端口端口是普通端口+10000(10000是固定值,沒法改變),集羣端口只用於節點之間的通訊,如搭建集羣、增減節點、故障轉移等操做時節點間的通訊;不要使用客戶端鏈接集羣接口。節點間通訊發送的消息主要分爲5種:meet消息、ping消息、pong消息、fail消息、publish消息。不一樣的消息類型,通訊協議、發送的頻率和時機、接收節點的選擇等是不一樣的。消息具體含義不在這裏介紹
集羣中的節點須要專門的數據結構來存儲集羣的狀態。節點爲了存儲集羣狀態而提供的數據結構中,最關鍵的是clusterNode和clusterState結構:前者記錄了一個節點的狀態,後者記錄了集羣做爲一個總體的狀態。

容錯選舉

在發送消息的過程當中,Redis之間經過互相的ping-pong判斷是否節點能夠鏈接上。若是有一半以上的節點去ping一個節點的時候沒有迴應,集羣就認爲這個節點宕機了,而後去鏈接它的從節點。若是某個節點和全部從節點所有掛掉,咱們集羣就進入fail狀態。還有就是若是有一半以上的主節點宕機,那麼咱們集羣一樣進入fail了狀態。這就是咱們的redis的投票機制。
投票過程是集羣中全部master參與,若是半數以上master節點與master節點通訊超時(cluster-node-timeout),認爲當前master節點掛掉。
Redis到底用在哪裏

  • 緩存:緩存如今幾乎是全部中大型網站都在用的必殺技,合理的利用緩存不只可以提高網站訪問速度,還能大大下降數據庫的壓力。使用緩存提高性能的同時,也會帶來更多的問題,舉幾個例子,若是緩存若是忽然失效形成大量請求打到DB上形成緩存雪崩、緩存擊穿如何處理,那就設計到緩存的過時時間(按期刪除和惰性刪除)和6種內存淘汰機制。若是大量不存在於緩存中的請求過來打到DB上形成緩存穿透如何處理,就涉及到如何進行請求過濾,以及使用高效的布隆過濾器(不知道?移步個人Redis教程系列,面試加分利器!!)。如何保證緩存和數據庫的一致性問題,是要保證強一致性仍是最終一致性。

  • 分佈式問題:分佈式事務、分佈式會話、分佈式鎖等;簡單介紹下

分佈式事務
就是指的會涉及多個數據庫的事務,關鍵在於須要保證多個節點之間的數據寫操做,要麼所有都執行,要麼所有都不執行,可是一臺機器在執行本地事務的時候沒法知道其餘機器中的本地事務的執行結果,因此也就不知道本次事務到底應該 commit 仍是 roolback。常規的解決辦法就是引入一個協調者來統一調度全部分佈式節點的執行,而redis能夠充當這一角色
分佈式會話
指的是集羣環境下,用戶訪問系統隨機分配到不一樣機器之間的用戶狀態跟蹤問題。用戶訪問登陸網站,第一次負載均衡到服務器A上,第二次卻負載均衡到服務器B上,如何讓用戶的狀態數據不會丟失
分佈式鎖
若是咱們一臺機器上多個不一樣線程搶佔同一個資源,而且若是屢次執行會有異常,咱們稱之爲非線程安全。若是是同一臺機器裏面不一樣的java實例,咱們可使用系統的文件讀寫鎖來解決,若是再擴展到不一樣的機器呢?咱們一般用分佈式鎖來解決。

  • 消息隊列:消息隊列是大型網站必用中間件,若有Broker的暴力路由Kafka,有Broker的複雜路由RabbitMQ、RocketMQ以及無Broker的通訊流派ZeroMQ等流行的消息隊列中間件,主要用於業務解耦、流量削峯及異步處理實時性低的業務。Redis提供了發佈/訂閱及阻塞隊列功能,阻塞隊列利用的是超時機制,能實現一個簡單的消息隊列系統。另外,這個不能和專業的消息中間件相比。
  • 自動過時:其實上面介紹的阻塞隊列也是redis的自動過時實例,可是我以爲仍是有必要提一下,Redis針對數據均可以設置過時時間,這個特色也是你們應用比較多的,過時的數據清理無需使用方去關注,性能也比較高。最多見的就是:短信驗證碼、具備時間性的商品展現等,無需像數據庫還要去查時間進行對比。
    • *
相關文章
相關標籤/搜索