Redis之主從複制與哨兵機制
- 一、主從複制概述
- 二、主從複制配置
-
- A、以windows為例
-
- 配置主從複制
- 啟動Redis服務
- 驗證測試
- 檢視主從複制資訊
- B、Linux配置
- 三、主從複制流程
-
- 全量複制流程
- 增量複制
- 主從複制異步性
- 主從其他概念
- Redis全量添加
-
- 逐行插入
- Pipeline 管道插入
- 四、哨兵機制概述
- 五、哨兵工作原理
-
- 定時任務
- 主觀下線
- 客觀下線
- 仲裁
- 六、Sentinel哨兵配置
-
- 新增Redis服務
- 添加哨兵配置
- 啟動Redis服務
- 啟動哨兵服務
- 檢視sentinel的狀态
- Redis(主)--當機
- Redis(主)--重新啟動
- 節點管理
- 故障遷移一緻性
- TILT模式
- 項目中使用Sentinel
一、主從複制概述
redis的複制功能是支援多個資料庫之間的資料同步。一類是主資料庫(master)一類是從資料庫(slave),主資料庫可以進行讀寫操作,當發生寫操作的時候自動将資料同步到從資料庫,而從資料庫一般是隻讀的,并接收主資料庫同步過來的資料,一個主資料庫可以有多個從資料庫,而一個從資料庫隻能有一個主資料庫。
通過redis的複制功能可以很好的實作資料庫的讀寫分離,提高伺服器的負載能力。主資料庫主要進行寫操作,而從資料庫負責讀操作。
主從複制過程:
1:當一個從資料庫啟動時,會向主資料庫發送sync指令
2:主資料庫接收到sync指令後會開始在背景儲存快照(執行rdb操作),并将儲存期間接收到的指令緩存起來
3:當快照完成後,redis會将快照檔案和所有緩存的指令發送給從資料庫
4:從資料庫收到後,會載入快照檔案并執行收到的緩存的指令
二、主從複制配置
準備3個Redis,分别修改redis.conf(Linux) 或 redis.windows-service.conf(windows)
//主Redis的IP及端口 : slaveof ip 6379
slaveof <masterip> <masterport>
//若主Redis啟用密碼則需配置 : masterauth 123456
masterauth <master-password>
A、以windows為例
配置主從複制
準備3個全新Redis,修改redis.windows-service.conf
Redis(主) :使用預設配置
Redis-2 (從) :port 6380 slaveof 127.0.0.1 6379
Redis-3(從) :port 6381 slaveof 127.0.0.1 6379
啟動Redis服務
啟動Redis(主) 後分别啟動 Redis-2 (從)與Redis-3(從) 。
驗證測試
主Redis寫入
127.0.0.1:6379> set name redis
OK
127.0.0.1:6379> get name
"redis"
127.0.0.1:6379>
從Redis-2讀取
D:\Program Files (x86)\Redis>redis-cli.exe -p 6380 -h 127.0.0.1
127.0.0.1:6380> keys *
1) "name"
127.0.0.1:6380> get name
"redis"
從Redis-3讀取
D:\Program Files (x86)\Redis>redis-cli.exe -p 6381 -h 127.0.0.1
127.0.0.1:6381> keys *
1) "name"
127.0.0.1:6381> get name
"redis"
127.0.0.1:6381>
檢視主從複制資訊
Redis Info 指令以一種易于了解和閱讀的格式,傳回關于 Redis 伺服器的各種資訊和統計數值。
檢視主Redis主從複制資訊 : info replication
# Replication
# 節點角色
role:master
# 從節點數量
connected_slaves:2
# 從節點狀态資訊,offset:從節點已複制資料偏移量416 lag:延遲時間
slave0:ip=127.0.0.1,port=6380,state=online,offset=416,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=416,lag=1
# 主節點ID
master_replid:172cc5b955bb4ccb8ec9b4ca6469e4087d03cafa
# 主從發生改變後,該值發生變化
master_replid2:0000000000000000000000000000000000000000
# 主節點寫入資料偏移量416
master_repl_offset:416
# 避免全量複制
second_repl_offset:-1
# 緩沖區開啟狀态 1:開啟
repl_backlog_active:1
# 緩沖區大小
repl_backlog_size:1048576
# 從哪裡開始寫
repl_backlog_first_byte_offset:1
# 目前緩沖去長度
repl_backlog_histlen:416
檢視從Redis主從複制資訊
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
# 主從最後一次同步時間 3秒之前
master_last_io_seconds_ago:3
# 主從的同步狀态 0:未同步 1:正在同步
master_sync_in_progress:0
# 從節點複制的偏移量500
slave_repl_offset:500
# 從節點選舉為主節點的機率
slave_priority:100
# 從節點隻讀模式 1:開啟
slave_read_only:1
# 連接配接到從節點的資訊
connected_slaves:0
master_replid:172cc5b955bb4ccb8ec9b4ca6469e4087d03cafa
master_replid2:0000000000000000000000000000000000000000
# 主節點寫入的偏移量500
master_repl_offset:500
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:15
repl_backlog_histlen:486
B、Linux配置
Linux配置主從複制與在Windows環境配置大同小異
# 放行通路IP限制
bind 0.0.0.0
# 端口
port 6379
# 背景啟動
daemonize yes
# 日志存儲目錄及日志檔案名
logfile "/usr/local/program/redis/log/redis.log"
# 持久化資料存儲在本地的檔案名
dbfilename dump.rdb
# 持久化資料存儲在本地的路徑 rdb資料檔案和aof資料檔案存儲目錄
dir /usr/local/program/redis/data
# 設定密碼
requirepass 123456
# 從節點通路主節點密碼,必須與requirepass一緻
masterauth 123456
# aof功能開關,預設為no,關閉狀态
appendonly yes
# 指定aof檔案名稱
appendfilename appendonly.aof
# 從節點隻讀模式,預設yes
replica-read-only yes
# 從節點添加該配置
slaveof 127.0.0.1 6380
三、主從複制流程
全量複制流程
Slave發送Sync指令到Master
Master收到該指令後執行bgsave指令,生成rdb檔案
master生成rdb檔案成功後,向Slave發送該檔案
Slave接收rdb檔案,丢棄舊資料,重新加載RDB檔案。
master在生成rdb檔案期間,記錄期間增量指令到緩沖區,最後Slave從緩沖區擷取這些指令
增量複制
增量複制就是Slave初始化後開始正常工作時主伺服器發生的寫操作同步到從伺服器的過程。
複制過程就是主服務每執行一個寫指令就會向從伺服器發送相同的寫指令,從伺服器接收并執行收到的寫指令
主從複制異步性
主從複制對于主從Redis伺服器時非阻塞的,當從伺服器在進行主從同步過程中,主Redis任然可以處理通路請求
主從複制對于從Redis伺服器時非阻塞的,從Redis在進行主從複制過程中也可以接收查詢請求,隻不過這時候從Redis傳回的可能是以前的舊資料。
主從其他概念
過期Key
Salve不會讓key過期,而是等待master讓key過期,master讓key過期時會發送一個del指令并傳輸到所有從伺服器。
加速複制
預設情況下,master節點接收Sync指令後執行bgsave操作,将資料先儲存到磁盤,如果磁盤性能差,那麼寫入磁盤會消耗大量性能。在Redis2.8.18時進行了改進,可以設定無需寫入磁盤直接發送生成rdb快照給salve,加快複制速度。
# 開啟加速複制 預設no
repl-diskless-sync yes
Redis全量添加
全量插入十幾萬資料測試
逐行插入
@Resource
private RedisTemplate redisTemplate;
@Resource
private GoodsMapper goodsMapper;
@Test
public void test1() {
List<Goods> goodsList = goodsMapper.findAll();
long start = System.currentTimeMillis();
goodsList.forEach(item -> {
Map<String, Object> goodsMap = BeanUtil.beanToMap(item);
String key = "goods:" + item.getId();
redisTemplate.opsForHash().putAll(key, goodsMap);
});
long end = System.currentTimeMillis();
log.info("執行時間:{}", end - start); // 執行時間:54796
}
Pipeline 管道插入
@Resource
private RedisTemplate redisTemplate;
@Resource
private GoodsMapper goodsMapper;
@Test
public void test2() {
List<Goods> goodsList = goodsMapper.findAll();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
long start = System.currentTimeMillis();
List<Long> list = redisTemplate.executePipelined((RedisCallback<Long>) connection -> {
for (Goods goods : goodsList) {
try {
String key = "goods:" + goods.getId();
Map<String, Object> goodsMap = BeanUtil.beanToMap(goods);
Map<byte[], byte[]> goodsStringMap= Maps.newHashMap();
goodsMap.forEach((k, v) -> {
goodsStringMap.put(stringRedisSerializer.serialize(k), jackson2JsonRedisSerializer.serialize(v));
});
connection.hMSet(stringRedisSerializer.serialize(key), goodsStringMap);
} catch (Exception e) {
log.info(goods.toString());
continue;
}
}
return null;
});
long end = System.currentTimeMillis();
log.info("執行時間:{}", end - start); //執行時間:25315
}
四、哨兵機制概述
Redis的哨兵(sentinel) 系統用于管理多個 Redis 伺服器,該系統執行以下三個任務:
監控(Monitoring):
哨兵(sentinel) 會不斷地檢查Master和Slave是否運作正常。
提醒(Notification):
當被監控的某個Redis出現問題時, 哨兵(sentinel)可以通過 API 向管理者或者其他應用程式發送通知。
自動故障遷移(Automatic failover):
當一個Master不能正常工作時,哨兵(sentinel) 會開始一次自動故障遷移操作,它會将失效Master的其中一個Slave更新為新的Master, 并讓失效Master的其他Slave改為複制新的Master; 當用戶端試圖連接配接失效的Master時,叢集也會向用戶端傳回新Master的位址,使得叢集可以使用Master代替失效Master。
哨兵是一個分布式系統
哨兵(sentinel) 是一個分布式系統,可以在一個架構中運作多個哨兵(sentinel) 程序,這些程序使用流言協定(gossipprotocols)來接收關于Master是否下線的資訊,并使用投票協定(agreement protocols)來決定是否執行自動故障遷移,以及選擇哪個Slave作為新的Master.
每個哨兵(sentinel)會向其它哨兵(sentinel)、master、slave定時發送消息,以确認對方是否”活”着,如果發現對方在指定時間(可配置)内未回應,則暫時認為對方已挂掉(即”主觀認為當機” Subjective Down,簡稱sdown).
若“哨兵群”中的多數sentinel,都報告某一master沒響應,系統才認為該master"徹底死亡"(即:客觀上的真正down機,Objective Down,簡稱odown),通過一定的vote算法,從剩下的slave節點中,選一台提升為master,然後自動修改相關配置.
雖然哨兵(sentinel) 為一個單獨的可執行檔案 redis-sentinel ,但實際上它隻是一個運作在特殊模式下的Redis伺服器,可以在啟動一個普通 Redis伺服器時通過給定 --sentinel 選項來啟動哨兵(sentinel).
五、哨兵工作原理
定時任務
Sentinel内部有3個定時任務
每1秒每個Sentinel對其他Sentinel和Redis節點執行PING操作,即監控
每2秒每個Sentinel通過master節點的channel交換資訊,即釋出與訂閱(Publish、Subscribe)
每10秒每個Sentinel會對master和Slave執行Info指令,擷取統計資訊
主觀下線
單個Sentinel執行個體對伺服器做出下線判斷,即單個Sentinel由于接收不到訂閱、網絡不同等情況認為某個服務下線
客觀下線
多個Sentinel執行個體對同一個伺服器做出下線判斷,并且通過指令互相交流後,得出伺服器下線的判斷,然後開始failover。
仲裁
仲裁是指配置檔案中的quorum選項,quorum的值一般設定為Sentinel個數的1/2+1,如3個Sentinel就設定為2.
六、Sentinel哨兵配置
Linux環境下修改sentinel.conf配置檔案。windows環境下,在redis目錄中建立一個sentinel.conf檔案。
需注意的是,至少3個Sentinel執行個體,産生大于1/2的結果
新增Redis服務
在上面主從複制基礎上,新增一個新的Redis
Redis-4(從):port 6382 slaveof 127.0.0.1 6379
添加哨兵配置
Redis根目錄建立sentinel.conf配置檔案
# 目前Sentinel服務運作的端口
port 26379
# 哨兵監聽的主伺服器IP端口号 選舉次數 主執行個體判斷為失效至少需要1個Sentinel程序的同意,隻要同意Sentinel的數量不達标,自動failover就不會執行
sentinel monitor mymaster 127.0.0.1 6379 1
# 3s内mymaster無響應,則認為mymaster當機了
# 隻有一個Sentinel程序将執行個體标記為主觀下線并不一定會引起執行個體的自動故障遷移:隻有在足夠數量的Sentinel都将一個執行個體标記為主觀下線之後,執行個體才會被标記為客觀下線,這時自動故障遷移才會執行
sentinel down-after-milliseconds mymaster 3000
# 如果10秒後,mysater仍沒啟動過來,則啟動failover
sentinel failover-timeout mymaster 10000
# 執行故障轉移時, 最多有1個從伺服器同時對新的主伺服器進行同步
sentinel parallel-syncs mymaster 1
# 哨兵監聽需要密碼認證
#sentinel auth-pass mymaster 123456
# 線程守護
daemonize no
# 日志路徑
logfile "D:/sentinel.log"
啟動Redis服務
分别啟動Redis(主),Redis-2(從),Redis-3(從),Redis-4(從)
啟動哨兵服務
D:\Program Files (x86)\Redis -4>redis-server.exe sentinel.conf --sentinel
檢視sentinel的狀态
D:\Program Files (x86)\Redis>redis-cli.exe -p 26379 -h 127.0.0.1
127.0.0.1:26379> info
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=3,sentinels=1
Redis(主)–當機
當Redis(主)當機,Sentinel會通過選舉(算法)機制,從Salve中選出一個作為新Master。
停止Redis(主)後挨着檢視每個從Redis,發現Redis-3(從)已經切換成master了
D:\Program Files (x86)\Redis>redis-cli.exe -p 6381 -h 127.0.0.1
127.0.0.1:6381> info
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6382,state=online,offset=21452,lag=0
slave1:ip=127.0.0.1,port=6380,state=online,offset=21452,lag=0
master_replid:fcadd2d0ff34ebedf8404acb3a5186622776139d
master_replid2:f3b4792067d4b4fee22f6490a1251887df8d722c
master_repl_offset:21452
second_repl_offset:16012
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:21452
Redis(主)–重新啟動
檢視主動複制資訊,發現原來的Redis(主)由主節點變為了從節點
# Replication
role:slave
master_host:127.0.0.1
master_port:6381
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:68256
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:fcadd2d0ff34ebedf8404acb3a5186622776139d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:68256
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:65532
repl_backlog_histlen:2725
節點管理
添加Sentinel
添加單個Sentinel時,隻需要啟動配置 sentinel monitor masterName監視目前活動主伺服器的新Sentinel即可。
添加多個Sentinel時,建議一個一個添加,添加結束後使用指令SENTINEL MASTER mastername檢查所有Sentinel是否已完全擷取到所有Master的資訊
删除Sentinel
Sentinel不會完全清除已添加過的Sentinel資訊,是以要移除一個Sentinel需要遵循以下步驟:
停止要删除的Sentinel程序
SENTINEL RESET * 向多有其它Sentinel執行個體發送指令
執行指令 SENTINEL MASTER mastername檢查每個Sentinel顯示哈哈的Sentinel數量是否一緻
故障遷移一緻性
Redis使用分布式一緻性算法Raft算法,保證了故障遷移一緻性。在同一個周期中不會産生多個領頭者,也就不會産生多個遷移,進而保證了故障遷移一緻性。
TILT模式
TILT模式就是目前伺服器壓力大或其他原因導緻不能擷取系統時間,沒有有效的做出指令回複的時間間隔判斷,那麼就會進入TILT保護模式,當該模式正常運作30秒鐘,期間還是無法擷取系統時間則延遲該模式。
項目中使用Sentinel
配置檔案配置
# Redis
redis:
# port: 6379
# host: 127.0.0.1
timeout: 3000
database: 1
password: 123456
sentinel:
master: mymaster
nodes: 192.168.30.25:26379,192.168.30.26:26379,192.168.30.27:26379
配置類方式
@Component
public class RedisConfig {
@Bean
public RedisConnectionFactory connectionFactory(){
RedisSentinelConfiguration configuration=new RedisSentinelConfiguration();
configuration.setMaster("mymaster");
configuration.sentinel("192.168.30.25",26379);
configuration.sentinel("192.168.30.26",26379);
configuration.sentinel("192.168.30.27",26379);
configuration.setDatabase(1);
configuration.setPassword("123456");
//return new JedisConnectionFactory(configuration);
return new LettuceConnectionFactory(configuration);
}
}