一、理論相關
我們知道,Redis具有高可靠性,其含義包括:
- 資料儘量少丟失 - AOF 和 RDB
- 服務儘量少中斷 - 增加副本冗餘量,將一份資料同時儲存在多個例項上,即主從庫模式
Redis主從庫模式 - 保證資料副本的一致(讀寫分離):
- 讀操作:主庫、從庫都可以接收
- 寫操作:首先到主庫執行,然後,主庫將寫操作同步給從庫
採用讀寫分離的原因:
- 如果客戶端對同一個資料進行多次修改,每一次的修改請求都傳送到不同的例項上,在不同的例項上執行,那麼這個資料在多個例項上的副本就不一致了
- 如果要對不同例項上的資料一致,就涉及到加鎖、例項間協商是否完成修改等操作,會帶來鉅額的開銷
這時我們就引出主從庫同步的原理。
1、主從庫間如何進行第一次同步?
當我們啟動多個 Redis 例項的時候,它們相互之間就可以透過 replicaof(Redis 5.0 之前使用 slaveof)命令形成主庫和從庫的關係,之後會按照三個階段完成資料的第一次同步。
- 主從庫建立連線、協商同步,為全量複製做準備
replicaof 172.16.19.3 6379
- 從庫和主庫建立連線,並告訴主庫即將進行同步,主庫確認回覆後,主從庫間開始同步
- 主庫將所有資料同步給從庫。從庫收到資料後,在本地完成資料載入 - 依賴於記憶體快照生成的RDB檔案
- 從庫接收到RDB檔案後,會先清空當前資料庫 - 從庫在透過replicaof命令開始和主庫同步前,可能儲存了其它資料
- 主庫將資料同步給從庫的過程中,主庫不會被阻塞,仍然可以正常接收請求。為保證主從庫的資料一致性,主庫會在記憶體中用專門的 replication buffer,記錄 RDB 檔案生成後收到的所有寫操作
- 主庫把第二階段執行過程中新收到的寫命令,再傳送給從庫
- 所有的從庫都是和主庫連線,所有的全量複製都是和主庫進行的。
2、主從級聯模式分擔全量複製時的主庫壓力
一次全量複製中,對於主庫需要完成兩個耗時操作:
- 生成RDB檔案 - fork操作會阻塞主執行緒處理正常請求
- 傳輸RDB檔案 - 佔用主庫網路頻寬
至此,我們引出:“主 - 從 - 從”模式
- 分擔主庫壓力
- 將主庫生成RDB和傳輸RDB的壓力,以級聯的方式分散到從庫上
- 部署主從叢集時手動選擇一個庫(比如選擇記憶體資源配置較高的從庫),用於級聯其它從庫
- 在從庫執行命令replicaof 所選從庫IP 6379,建立主從關係
- 主從庫間透過全量複製實現資料同步的過程,以及透過“主 - 從 - 從”模式分擔主庫壓力
- 一旦主從庫完成了全量複製,它們之間就會一直維護一個網路連線,主庫會透過這個連線將後續陸續收到的命令操作再同步給從庫,這個過程也稱為基於長連線的命令傳播,可以避免頻繁建立連線的開銷。
- 風險:網路斷聯或阻塞
3、主從庫間網路斷了怎麼辦?
在 Redis 2.8 之前,如果主從庫在命令傳播時出現了網路閃斷,那麼,從庫就會和主庫重新進行一次全量複製,開銷非常大。
從 Redis 2.8 開始,網路斷了之後,主從庫會採用增量複製的方式繼續同步。
- 為避免環形緩衝區造成的主從庫不一致,可以調整
repl_backlog_size
引數- 緩衝空間大小 = 主庫寫入命令速度 * 操作大小 - 主從庫間網路傳輸命令速度 * 操作大小
- 在實際應用中,考慮到可能存在一些突發的請求壓力,我們通常需要把這個緩衝空間擴大一倍,即 repl_backlog_size = 緩衝空間大小 * 2
- 也可以採用切片叢集來分擔單個主庫的請求壓力
4、小結
- 全量複製
- 一個Redis例項的資料庫不要太大,一個例項大小在幾GB級別比較合適,可以減少RDB檔案生成、傳輸和重新載入的開銷
- 避免多個從庫同時和主庫進行全量複製,給主庫過大同步壓力 - “主-從-從”
- 基於長連線的命令傳播
- 增量複製
- 留意
repl_backlog_size
配置引數
- 留意
二、實踐
執行環境:虛擬機器作業系統:centOS7,IP地址:192.168.88.130
已經安裝好了 docker 和 docker-compose
採用Redis:7.4.0
至此,我們開始在虛擬機器中搭建Redis“主-從-從”模式的主從庫叢集
- 我們先建立好目錄:
[root@centos ~]# mkdir /root/docker/redis-cluster
[root@centos ~]# cd /root/docker/redis-cluster
[root@centos redis-cluster]# mkdir redis0
[root@centos redis-cluster]# mkdir redis1
[root@centos redis-cluster]# mkdir redis2
[root@centos redis-cluster]# mkdir redis3
[root@centos redis-cluster]# mkdir redis4
我們將redis0作為主庫
redis1和redis2作為從庫I和從庫II(slave),redis3和redis4作為從庫II的兩個從庫(主-從-從模式)
- redis0
[root@centos redis-cluster]# mkdir redis0/data
[root@centos redis-cluster]# vi redis0/redis.conf
protected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 關閉 aof 日誌備份
appendonly no
# 自定義密碼
requirepass root
# 啟動埠
port 6379
# 換成自己的虛擬機器的IP
replica-announce-ip 192.168.88.130
- redis1
[root@centos redis-cluster]# mkdir redis1/data
[root@centos redis-cluster]# vi redis1/redis.conf
replicaof [主節點ip] [主節點埠]
,該配置主要是讓當前節點作為從節點,配置具體的主節點的地址和埠(Redis 5.0 之前使用slaveof [主節點ip] [主節點埠]
)masterauth [主節點的訪問密碼]
,該配置主要是在主節點設定密碼的情況下,能夠讓從節點透過密碼訪問主節點
protected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 關閉 aof 日誌備份
appendonly no
# 啟動埠
port 6479
# 將當前 redis 作為 redis0 的 slave
# 由於 docker 使用 host 模式,使用的是宿主機的 ip
replicaof 192.168.88.130 6379
# 自定義密碼
requirepass root
# 訪問 master 節點時需要提供的密碼
masterauth root
masteruser redis0
replica-announce-ip 192.168.88.130
- redis2
[root@centos redis-cluster]# mkdir redis2/data
[root@centos redis-cluster]# vi redis2/redis.conf
protected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 關閉 aof 日誌備份
appendonly no
# 啟動埠
port 6579
# 將當前 redis 作為 redis0 的 slave
# 由於 docker 使用 host 模式,使用的是宿主機的 ip
replicaof 192.168.88.130 6379
# 自定義密碼
requirepass root
# 訪問 master 節點時需要提供的密碼
masterauth root
replica-announce-ip 192.168.88.130
- redis3
[root@centos redis-cluster]# mkdir redis3/data
[root@centos redis-cluster]# vi redis3/redis.conf
protected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 關閉 aof 日誌備份
appendonly no
# 啟動埠
port 6679
# 將當前 redis 作為 redis2 的 slave
# 由於 docker 使用 host 模式,使用的是宿主機的 ip
replicaof 192.168.88.130 6579
# 自定義密碼
requirepass root
# 訪問 master 節點時需要提供的密碼
masterauth root
replica-announce-ip 192.168.88.130
- redis4
[root@centos redis-cluster]# mkdir redis4/data
[root@centos redis-cluster]# vi redis4/redis.conf
protected-mode no
bind 0.0.0.0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
# 關閉 aof 日誌備份
appendonly no
# 啟動埠
port 6779
# 將當前 redis 作為 redis2 的 slave
# 由於 docker 使用 host 模式,使用的是宿主機的 ip
replicaof 192.168.88.130 6579
# 自定義密碼
requirepass root
# 訪問 master 節點時需要提供的密碼
masterauth root
replica-announce-ip 192.168.88.130
接下來,我們在目錄redis-cluster
下新建檔案docker-compose.yml
:
services:
redis0:
image: redis
container_name: redis0
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis0/data:/data
- /root/docker/redis-cluster/redis0/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
redis1:
image: redis
container_name: redis1
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis1/data:/data
- /root/docker/redis-cluster/redis1/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis0
redis2:
image: redis
container_name: redis2
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis2/data:/data
- /root/docker/redis-cluster/redis2/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis0
redis3:
image: redis
container_name: redis3
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis3/data:/data
- /root/docker/redis-cluster/redis3/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis2
redis4:
image: redis
container_name: redis4
restart: always
privileged: true
network_mode: "host"
volumes:
- /root/docker/redis-cluster/redis4/data:/data
- /root/docker/redis-cluster/redis4/redis.conf:/etc/redis.conf
command:
redis-server /etc/redis.conf
depends_on:
- redis2
[root@centos redis-cluster]# vi docker-compose.yml
[root@centos redis-cluster]# docker-compose up -d
部署完成後,我們使用RDM連線部署的所有redis:
測試是否連線成功:
redis0:
redis1:
redis2:
redis3、redis4同理。
測試五個主從庫讀寫操作:
redis0:(可讀可寫)
redis1、redis2:(可讀不可寫)
並且我們發現,redis1和redis2進行了主從庫同步操作,即使我們沒有在redis1和redis2中寫入name:Monica,但它們和redis0建立連線後,主庫會將資料同步給從庫
redis3、redis4作為redis2的從庫,同理,包含redis2的所有資料。
從RDM中我們也可以直觀地看出,我們只對主庫進行了一次寫操作,但其連線的所有從庫(包括從庫的從庫)都包含了這個資料:
透過以上驗證表明:redis 的“主-從-從”模式叢集已經搭建成功。
三、RedisTemplate 操作 Redis 叢集實現讀寫分離
1、新建專案
我們新建一個SpringBoot專案,專案結構如下:
- 引入依賴
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置
application.yml
檔案
spring:
data:
redis:
# 這裡只需配置主節點的資訊即可
# RedisTemplate可以從主節點資訊中獲取從節點資訊
host: 192.168.88.130
port: 6379
password: root
jedis:
pool:
# 最大連線數
max-active: 10
# 最大空閒連線數
max-idle: 5
# 最小空閒
min-idle: 1
# 連線超時時間(毫秒)
max-wait: 8000
- 對
RedisTemplate
進行配置
package com.chen.redisdemo.redisConfig;
import io.lettuce.core.ReadFrom;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @version 1.0
* @Author feiye
* @Date 2024-10-06 12:15
* @className RedisConfig
* @since 1.0
*/
@Configuration
public class RedisConfig {
//你可以將讀取策略,設定為 ReadFrom.REPLICA 表示只從 slave 節點讀取資料
//然後你把 slave 節點全部停掉,然後看看是否能夠讀取成功
@Bean
public LettuceClientConfigurationBuilderCustomizer redisClientConfig() {
//配置 redisTemplate 優先從 slave 節點讀取資料,如果 slave 都當機了,則從 master 讀取
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
//配置 redisTemplate 優先從 slave 節點讀取資料,如果 slave 都當機了,則丟擲異常
//return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//預設的Key序列化器為:JdkSerializationRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setEnableTransactionSupport(true);
return redisTemplate;
}
}
- 編寫測試類
package com.chen.redisdemo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class RedisDemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void writeTest() {
redisTemplate.opsForValue().set("name", "Ross");
}
@Test
void getTest() {
Object name = redisTemplate.opsForValue().get("name");
if (name != null) {
System.out.println(name.toString());
}
}
}
2、如何證明 RedisTemplate 是從 Slave 節點中獲取資料的?
- 首先我們修改一下 RedisConfig 類中的配置,讓 RedisTemplate 只從 Slave 節點讀取資料,不從 master 節點讀取資料。
@Bean
public LettuceClientConfigurationBuilderCustomizer redisClientConfig() {
//配置 redisTemplate 優先從 slave 節點讀取資料,如果 slave 都當機了,則丟擲異常
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA);
}
- 然後我們在 Linux 虛擬機器上,執行以下命令,停掉所有 Slave 節點服務:
[root@centos redis-cluster]# docker-compose stop redis3
[+] Stopping 1/1
✔ Container redis3 Stopped 0.3s
[root@centos redis-cluster]# docker-compose stop redis4
[+] Stopping 1/1
✔ Container redis4 Stopped 0.2s
[root@centos redis-cluster]# docker-compose stop redis2
[+] Stopping 1/1
✔ Container redis2 Stopped 0.2s
[root@centos redis-cluster]# docker-compose stop redis1
[+] Stopping 1/0
✔ Container redis1 Stopped
然後我們執行getTest()測試類,發現報錯:
- 接下來,我們啟動redis3或redis4中的任意一個:
我們發現,如果主節點和從節點全部當機,只要啟動其中一個從節點,主節點就會同時啟動。
- 我們再次啟動測試類getTest():
說明 RedisTemplate 就是從 Slave 節點中讀取資料的。
測試完畢。
個人問題記錄:
在進行部署後發現主從庫連線失敗,詳情如下:
redis0:
redis1:
透過docker logs redis0
檢視日誌,排查錯誤後發現是埠6379被佔用。因為在之前我部署過單機redis,使用了埠6379,但沒有將其kill,導致埠被佔用
本人採用最粗暴的方法就是直接把容器rm了^^
參考博文:
Redis 主從叢集搭建並使用 RedisTemplate 實現讀寫分離
參考書籍:
《Redis核心技術與實戰》