在Redis複製的基礎上(不包括由Redis Cluster或Redis Sentinel做爲附加層提供的高可用性功能),有一個很是簡單的使用和配置leader follower(主從)複製:它容許從屬Redis實例準確主實例的副本。每次連接斷開時,slave將自動從新鏈接到master,而且不管master發生什麼狀況,它都會嘗試成爲它的精確副本。java
該系統使用三種主要機制:redis
Redis默認使用異步複製,即低延遲和高性能,是絕大多數Redis用例的天然複製模式。可是,Redisslave異步確認它們與master按期收到的數據量。所以主設備不會每次等待從設備處理命令,可是若是須要,它知道哪一個從設備已經處理了什麼命令。這容許具備可選的同步複製。算法
客戶端可使用WAIT命令請求某些數據的同步複製。可是,WAIT只能確保在其餘Redis實例中存在指定數量的已確認副本,但它不會將一組Redis實例轉換爲具備強一致性的CP系統:在故障轉移期間,確認的寫入仍然可能丟失,具體取決於關於Redis持久性的確切配置。然而,對於WAIT,在失敗事件以後丟失寫入的機率大大下降到某些難以觸發的故障模式。數據庫
您能夠查看Sentinel或Redis集羣文檔,以獲取有關高可用性和故障轉移的更多信息。本文檔的其他部分主要描述了Redis基本複製的基本特徵。安全
如下是有關Redis複製的一些很是重要的事實:bash
redis.conf
以免持久存儲到磁盤,而後鏈接配置爲save from time to time的從服務器,或者啓用AOF。可是,必須當心處理此設置,由於從新啓動的主服務器將以空數據集開始:若是從服務器嘗試與其同步,則從服務器也將被清空。每一個Redis主服務器都有一個replication ID:它是一個大的僞隨機字符串,用於標記數據集的給定敘述。每一個主服務器還會爲生成的每一個複製流字節遞增一個偏移量,以便將其發送到從屬服務器,方便使用修改數據集的新更改來更新從屬服務器的狀態。即便沒有實際鏈接的slave,複製偏移也會增長,以下:服務器
Replication ID, offset
複製代碼
用來標識主數據集的確切版本。網絡
命令./redis-cli -p 6379 -a 你的密碼 info server | grep run
能夠查看具體的id:併發
當slave鏈接到master時,它們使用PSYNC
命令發送它們的舊主replication ID以及它們到目前爲止處理的偏移量。這樣master只用發送所需的增量部分。可是,若是主緩衝區中沒有足夠的backlog,或者從屬設備指不知道歷史記錄(複製ID),則發生徹底從新同步:在這種狀況下,從屬設備將得到數據集的完整副本, 從頭開始。less
這是徹底同步在更多細節中的工做方式:
主服務器啓動後臺保存過程以生成RDB文件。同時它開始緩衝從客戶端收到的全部新寫命令。後臺保存完成後,主服務器將數據庫文件傳輸到從服務器,從服務器將其保存在磁盤上,而後將其加載到內存中。而後,主設備將全部緩衝的命令發送到從設備。這是做爲命令流完成的,而且與Redis協議自己的格式相同。
您能夠經過telnet本身嘗試。在服務器執行某些工做時鏈接到Redis端口併發出SYNC命令。您將看到批量傳輸,而後主服務器接收的每一個命令都將在telnet會話中從新發出。實際上,較新的Redis實例再也不使用SYNC舊協議,但其仍然存在向後兼容性:它不容許部分從新同步,所以如今用PSYNC
代替。
如前所述,當主 - 從鏈路因爲某種緣由而關閉時,從設備可以自動從新鏈接。若是主設備接收到多個併發從屬同步請求,它將執行單個後臺保存以便爲全部這些請求提供服務。
1)bgsave時間。
2)RDB文件網絡傳輸時間。
3)從節點清空數據時間。
4)從節點加載RDB的時間。
5)可能的AOF重寫時間。
配置基本Redis複製很簡單:只需將如下行添加到從屬配置文件:
slaveof 192.168.1.1 6379
複製代碼
固然,您須要將192.168.1.1 6379替換爲您的主IP地址(或主機名)和端口。或者,您能夠調用SLAVEOF命令,主控主機將啓動與slave的同步。
還有一些參數用於調整主機在內存中採起的replication backlog以執行部分從新同步。
可使用repl-diskless-sync配置參數啓用無盤複製。傳輸開始後,等待在第一個slave以後到達的更多slaves的延遲由repl-diskless-sync-delay 參數控制。
1)進入redis目錄,複製redis.conf
文件爲redis-6380.conf
cp redis.conf redis-6380.conf
複製代碼
2)修改redis-6380.conf的端口號,保存的rdb文件名,日誌名等參數。
3)找到REPLICATION模塊,修改以下:
replicaof 127.0.0.1 6379
4)若是主節點redis設置了密碼,修改masterauth屬性:
在箭頭處填上密碼。
5)啓動從結點:
./redis-server ../redis-6380.conf
複製代碼
6)使用命令./redis-cli -p 6380 -a 你的密碼 info replication
查看從節點狀態:
能夠發現role爲slave,而且master_link_status爲up,說明啓動成功。說明一下,上圖的slave_repl_offset就是所謂的偏移量。
嘗試使用redis-cli在主節點中寫入一個值:
./redis-cli -p 6379 -a 你的密碼
> set hello world
複製代碼
進入從節點,輸入get hello
,能夠發現已經可以正確獲取值。
7)可使用命令slaveof no one
取消從節點模式。
從Redis 2.6開始,slave支持默認啓用的只讀模式。此行爲由slave-read-only
redis.conf文件中的選項控制,能夠在運行時使用CONFIG SET啓用和禁用。
只讀slave將拒絕全部寫入命令,所以因爲錯誤而寫入slave是不可能的。這並不意味着該功能旨在將從屬實例暴露給互聯網,或者更廣泛地意味着將不受信任的客戶端存在的網絡,由於管理命令喜歡DEBUG
或CONFIG
仍然啓用。可是,經過使用該rename-command
指令禁用redis.conf中的命令,能夠提升只讀實例的安全性。
您可能想知道爲何能夠恢復只讀設置並具備能夠經過寫入操做做爲目標的從屬實例。若是slave和master從新同步或者slave從新啓動,那麼這些寫操做將被丟棄,但有一些合法的用例可用於在可寫slave中存儲短暫數據。
例如,計算慢速設置或排序集合操做並將它們存儲到本地key中是屢次觀察可寫slave的用例。
可是請注意,版本4.0以前的可寫slave沒法設置到期時間。這意味着若是您使用EXPIRE或爲key設置最大TTL的其餘命令,key將泄漏,雖然您在使用讀取命令訪問key時可能再也看不到它,您將在key計數中看到它仍在使用內存。所以,一般混合可寫slave(之前的版本4.0)和帶有TTL的鍵會產生問題。
Redis 4.0 RC3及更高版本徹底解決了這個問題,如今可寫的從設備可以像主設備那樣用TTL刪除key,但用DB編號大於63的key除外(但默認狀況下Redis實例只有16個數據庫)。
另請注意,因爲Redis 4.0從屬寫入僅是本地的,而且不會傳播到附加到實例的子從屬。相反,子slave將始終接收與頂層master向中間slave發送的複製流相同的複製流。例如,在如下設置中:
A ---> B ---> C
複製代碼
即便B
是可寫的,C
也不會看到B
寫入,而是具備與主實例相同的數據集A
。
1)讀寫分離:讀流量分攤到從節點,寫流量分攤到主節點。
2)可能遇到的問題
一、複製數據延遲。
二、讀到過時數據(3.2已解決)。
三、從節點故障。
1)第一次全量複製
一、第一次不可避免。
二、小主節點、低峯。
2)節點運行ID不匹配
一、主節點重啓(運行ID變化)。
二、故障轉移,例如哨兵或集羣。
3)複製積壓緩衝區不足
一、網絡中斷,部分複製沒法知足。
二、增大複製緩衝區配置rel_backlog_size,網絡「加強」。
1)單節點複製風暴
問題:主節點重啓,多從節點複製。
解決:更換複製拓撲。
2)單機器複製風暴
問題:機器宕機後,大量全量複製。
解決:主節點分散多機器。
Redis Sentinel爲Redis提供高可用性。實際上,這意味着使用Sentinel能夠建立一個Redis部署,能夠在沒有人爲干預的狀況下抵禦某些類型的故障。
Redis Sentinel還提供其餘附屬任務,如監控,通知,並充當客戶端的配置提供程序。
這是宏觀級別的Sentinel功能的完整列表(即大圖):
Redis Sentinel是一個分佈式系統:
Sentinel自己設計爲在多個Sentinel進程協同工做的配置中運行。讓多個Sentinel進程協做的優點以下:
Sentinels,Redis實例(主服務器和從服務器)以及鏈接到Sentinel和Redis的客戶端的總和也是具備特定屬性的更大的分佈式系統。在本文檔中,將逐步介紹概念,從瞭解Sentinel的基本屬性所需的基本信息,到更復雜的信息(可選),以瞭解Sentinel的工做原理。
Sentinel的當前版本稱爲Sentinel 2。它是使用更強大和更簡單的預測算法(本文檔中解釋)重寫初始Sentinel實現。
自Redis 2.8發佈Redis Sentinel的穩定版本。
在不穩定的分支中進行了新的開發,一旦它們被認爲是穩定的,新的特徵有時會被反導到最新的穩定分支中。
Redis 2.6附帶的Redis Sentinel版本1已棄用,不該使用。
若是您正在使用redis-sentinel
可執行文件(或者若是您的可執行文件具備該名稱的符號連接redis-server
),則可使用如下命令行運行Sentinel:
redis-sentinel /path/to/sentinel.conf
複製代碼
或者,您能夠直接使用redis-server
在Sentinel模式下啓動它的可執行文件:
redis-server /path/to/sentinel.conf --sentinel
複製代碼
兩種方式都是同樣的。
可是**,在運行Sentinel時必須**使用配置文件,由於系統將使用此文件以保存從新啓動時將從新加載的當前狀態。若是沒有給出配置文件或配置文件路徑不可寫,Sentinel將拒絕啓動。
默認狀況下,Sentinels會偵聽與TCP端口26379的鏈接,所以要使 Sentinels正常工做,必須打開服務器的端口26379 以接收來自其餘Sentinel實例的IP地址的鏈接。不然,Sentinels沒法通話,也沒法就該怎麼作達成一致,所以永遠不會執行故障轉移。
Redis源代碼分發包含一個名爲sentinel.conf
的文件 ,它是一個能夠用來配置Sentinel的自我記錄的示例配置文件,可是典型的最小配置文件以下所示:
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5
複製代碼
您只須要指定要監視的主服務器,爲每一個單獨的主服務器(可能有任意數量的從服務器)提供不一樣的名稱。無需指定可被自動發現的slaves。Sentinel將自動更新配置有關slaves的其餘信息(以便在從新啓動時保留信息)。每次在故障轉移期間將slave升級爲master而且每次發現新的Sentinel時,也會重寫該配置。
上面的示例配置基本上監視兩組Redis實例,每組實例由主設備和未定義數量的從設備組成。一組實例被調用mymaster
,另外一組被調用resque
。
sentinel monitor
語句參數的含義以下:
sentinel monitor <master-group-name> <ip> <port> <quorum>
複製代碼
爲清楚起見,讓咱們逐行檢查配置選項的含義:
第一行用於告訴Redis監視一個名爲mymaster的主服務器,它位於地址127.0.0.1和端口6379,鏈接數爲2。一切都很明顯,可是quorum參數:
所以,例如,若是您有5個Sentinel進程,而且給定主服務器的quorum設置爲值2,則會發生如下狀況:
實際上,這意味着在故障期間,若是大多數Sentinel進程沒法通話(在少數分區中也沒有故障轉移),Sentinel永遠不會啓動故障轉移。
其餘選項幾乎老是如下列形式:
sentinel <option_name> <master_name> <option_value>
複製代碼
並用於如下目的:
down-after-milliseconds
是對於Sentinel開始認爲它已關閉而且實例不能到達的(不管是不回覆咱們的PING仍是回覆錯誤)以毫秒爲單位的時間。parallel-syncs
設置可在同一故障轉移後從新配置以使用新主服務器的從服務器數。數字越小,故障轉移過程完成所需的時間就越多,可是若是從屬服務器配置爲提供舊數據,則可能不但願全部從屬服務器同時與主服務器從新同步。雖然複製過程對於從屬設備大部分是非阻塞的,可是有一段時間它中止從主設備加載批量數據。您可能但願經過將此選項設置爲值1來確保一次只能訪問一個slave。其餘選項在本文檔的其他部分中進行了描述,並記錄在sentinel.conf
Redis發行版附帶的示例文件中。
可使用該SENTINEL SET
命令在運行時修改全部配置參數。有關詳細信息,請參閱「 **在運行時從新配置Sentinel」**部分。
如今您已瞭解Sentinel的基本信息,您可能想知道應該在哪裏放置Sentinel進程,須要多少Sentinel進程等等。本節介紹幾個示例部署。
請注意,咱們永遠不會顯示僅使用兩個Sentinels的設置,由於Sentinels老是須要與大多數人交談才能啓動故障轉移。
+----+ +----+
| M1 |---------| R1 |
| S1 | | S2 |
+----+ +----+
Configuration: quorum = 1
複製代碼
請注意,爲了執行不一樣的故障轉移,須要多數,而後將最新配置傳播到全部Sentinels。另請注意,在沒有任何協議的狀況下,在上述設置的單個方面進行故障轉移的能力將很是危險:
+----+ +------+
| M1 |----//-----| [M1] |
| S1 | | S2 |
+----+ +------+
複製代碼
在上面的配置中,咱們以徹底對稱的方式建立了兩個主服務器(假設S2能夠在未經受權的狀況下進行故障轉移)。客戶端能夠無限期地向雙方寫入,而且沒法理解分區什麼時候恢復正確的配置,以防止永久性的裂腦狀況。
因此請始終在三個不一樣的盒子中至少部署三個Sentinels。
這是一個很是簡單的設置,其優勢是易於調整以得到額外的安全性。它基於三個框,每一個框都運行Redis進程和Sentinel進程。
+----+
| M1 |
| S1 |
+----+
|
+----+ | +----+
| R2 |----+----| R3 |
| S2 | | S3 |
+----+ +----+
Configuration: quorum = 2
複製代碼
若是主M1發生故障,S2和S3將贊成故障,而且可以受權故障轉移,使客戶可以繼續。
在每一個Sentinel設置中,Redis被異步複製,老是存在丟失一些寫入的風險,由於給定的已確認寫入可能沒法到達被提高爲主設備的從設備。可是在上面的設置中,因爲客戶端使用舊主服務器進行分區,所以風險較高,以下圖所示:
+----+
| M1 |
| S1 | <- C1 (writes will be lost)
+----+
|
/
/
+------+ | +----+
| [M2] |----+----| R3 |
| S2 | | S3 |
+------+ +----+
複製代碼
在這種狀況下,網絡分區隔離了舊的主M1,所以從屬R2被提高爲主。可是,與舊主服務器位於同一分區的客戶端(如C1)可能會繼續將數據寫入舊主服務器。這個數據將永遠丟失,由於當分區將癒合時,主機將被從新配置爲新主機的從機,丟棄其數據集。
使用如下Redis複製功能能夠緩解此問題,若是主服務器檢測到再也不可以將其寫入傳輸到指定數量的從服務器,則容許中止接受寫入。
min-slaves-to-write 1
min-slaves-max-lag 10
複製代碼
使用上述配置(請參閱redis.conf
Redis發行版中的自注釋示例以獲取更多信息)Redis實例做爲主服務器時,若是沒法寫入至少1個從服務器,將中止接受寫入。因爲複製是異步的,所以沒法實際寫入意味着從屬設備已斷開鏈接,或者未向咱們發送超過指定max-lag
秒數的異步確認。
使用此配置,上例中的舊Redis主M1將在10秒後變爲不可用。當分區恢復時,Sentinel配置將收斂到新的配置,客戶端C1將可以獲取有效配置並繼續使用新主設備。
可是沒有免費的午飯。經過這種改進,若是兩個從屬設備關閉,主設備將中止接受寫入。這是一個折衷。
1)將三個redis(一主二從)啓動。
2)配置sentinel.conf
文件:
一、去掉protected-mode no的註釋,即讓它生效
二、將daemonize改爲yes
三、修改logfile爲26379.log
四、寫入redis密碼:sentinel auth-pass mymaster 你的密碼(注意這裏須要將其配置在sentinel monitor mymaster 你的服務器ip 6379 2語句之下,否則會報「No such master with specified name.」的錯誤。
五、修改dir
3)進入src目錄,使用命令./redis-sentinel ../sentinel.conf
進行啓動。
4)使用命令:
./redis-cli -p 26379
>info
複製代碼
獲得結果以下:
5)複製sentinel.conf爲sentinel-26380.conf與sentinel-26381.conf,修改端口等信息,重複上述步驟。而且須要將myid註釋,最終,sentinels的數目將等於3。
1)Sentinel地址集合。
2)masterName。
3)不是代理模式。
package com.lamarsan.sentinel.util;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
* className: RedisUtil
* description: TODO
*
* @author hasee
* @version 1.0
* @date 2018/12/26 10:57
*/
public class RedisUtil {
private static final Logger myLogger = Logger.getLogger("com.lamarsan.sentinel.util");
private static JedisSentinelPool pool = null;
static {
try {
Set<String> sentinels = new HashSet<String>();
sentinels.add("你的ip:26380");
sentinels.add("你的ip.140:26379");
sentinels.add("你的ip:26381");
String masterName = "mymaster";
String password = "123456";
JedisPoolConfig config = new JedisPoolConfig();
config.setMinIdle(8);
config.setMaxTotal(100);
config.setMaxIdle(100);
config.setMaxWaitMillis(10000);
pool = new JedisSentinelPool(masterName, sentinels, config, password);
try {
pool.getResource();
} catch (JedisConnectionException e) {
myLogger.info(e.getMessage());
e.printStackTrace();
}
} catch (Exception e) {
myLogger.info(e.getMessage());
e.printStackTrace();
}
}
private static void returnResource(JedisSentinelPool pool, Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
/**
* <p>經過key獲取儲存在redis中的value</p>
* <p>並釋放鏈接</p>
*
* @param key
* @return 成功返回value 失敗返回null
*/
public static String get(String key) {
Jedis jedis = null;
String value = null;
try {
jedis = pool.getResource();
value = jedis.get(key);
} catch (Exception e) {
if (jedis != null) {
jedis.close();
}
e.printStackTrace();
} finally {
returnResource(pool, jedis);
}
return value;
}
/**
* <p>向redis存入key和value,並釋放鏈接資源</p>
* <p>若是key已經存在 則覆蓋</p>
*
* @param key
* @param value
* @return 成功 返回OK 失敗返回 0
*/
public static String set(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.set(key, value);
} catch (Exception e) {
if (jedis != null) {
jedis.close();
}
e.printStackTrace();
return "0";
} finally {
returnResource(pool, jedis);
}
}
.....
}
複製代碼
package com.lamarsan.sentinel;
import com.lamarsan.sentinel.util.RedisUtil;
/**
* className: Test
* description: TODO
*
* @author hasee
* @version 1.0
* @date 2019/9/13 15:54
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
RedisUtil.setnx("hello", "world");
while (true) {
Thread.sleep(10000);
String result = RedisUtil.get("hello");
System.out.println(result);
}
}
}
複製代碼
1)每10秒每一個sentinel對master和slave執行info
一、發現slave結點。
二、確認主從關係。
2)每2秒每一個sentinel經過master結點的channel交換信息(pub/sub)
一、經過_sentinel_:hello頻道交互。
二、交互對節點的」見解「和自身信息。
3)每1秒每一個sentinel對其餘sentinel和redis執行ping。
1)機器下線:例如過保等狀況。
2)機器性能不足:例如CPU、內存、硬盤、網絡等。
3)節點自身故障:例如服務不穩定等。
sentinel failover <masterName>
臨時下線仍是永久下線,例如是否作一些清理工做,也要考慮讀寫分離的狀況。