前言
當今網際網路領域中,高并發、高可用已經成為了開發人員必須要面對的挑戰。作為一款優秀的分布式記憶體資料庫,Redis 在這方面表現得相當出色。然而,為了滿足大規模應用程式的需求,單節點 Redis 已經無法滿足要求,是以需要搭建 Redis Cluster 叢集來實作高可用性、高擴充性和高性能的目标。在本文中,我們将詳細講解如何搭建 Redis Cluster 叢集,并分享一些實踐經驗和注意事項,幫助讀者了解 Redis Cluster 叢集的設計原理和使用方法,進而更好地應對高并發場景下的挑戰。
Redis 叢集說明
Redis 叢集主要有以下幾種:Redis Cluster(Redis叢集模式),Redis Sentinel(Redis哨兵模式),Redis分片(Sharding)+複制(Replication)(這是一種手動實作的方式,通過将資料分片存儲在多個Redis執行個體上,每個執行個體負責管理部分資料,并使用複制機制在執行個體之間同步資料)。
本文搭建的是Redis Cluster 叢集,Redis官方提供的分布式解決方案,從Redis 3.0版本開始引入。它通過分片(sharding)和複制(replication)來實作高可用性和擴充性。Redis Cluster将資料分散存儲在多個節點上,每個節點負責管理部分資料,并在需要時進行資料複制,以提供故障恢複和負載均衡。
Redis Cluster叢集使用的插槽算法是一緻性雜湊演算法,Redis Cluser采用虛拟槽分區,所有的鍵根據哈希函數映射到0~16383個整數槽内,計算公式:slot=CRC16(key)&16383。
由于采用高品質的雜湊演算法,每個槽所映射的資料通常比較均勻。
環境
- 很久之前工作中淘汰三台實體小主機,系統為Centos7.6
- Docker版本 24.0.2
- Redis版本 6.2.6
本人打算在每台實體機上分别搭建兩個節點,一共六個節點組成3對Master--Slave,即 3個Master 3個Slave。
開始行動
- 拉取redis鏡像
docker search redis
docker pull redis
直接docker pull redis 将會拉取redis 最新鏡像,可以按需拉取自己所需版本。
docker images 檢視本地鏡像
- 在每台實體機上建立虛拟網絡,供每台實體機上的docker虛拟主機之間通信。
docker network create redis-net 建立網絡
docker network ls 檢視網絡
- 編寫shell腳本,生成redis.conf 配置檔案
ip=`ifconfig enp2s0 |grep "inet " |awk '{print $2}'`
for port in $(seq 6379 6380);
do
mkdir -p ./node-${port}/conf
touch ./node-${port}/conf/redis.conf
cat << EOF > ./node-${port}/conf/redis.conf
port ${port}
requirepass 123
bind 0.0.0.0
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip ${ip}
cluster-announce-port ${port}
cluster-announce-bus-port 1${port}
EOF
done
配置參數說明:
- port:節點端口;
- requirepass:設定密碼,通路時需要驗證
- protected-mode:保護模式,預設值 yes,即開啟。開啟保護模式以後,需配置bind ip 或者設定通路密碼;關閉保護模式,外部網絡可以直接通路;
- daemonize:是否以守護線程的方式啟動(背景啟動),預設 no;
- appendonly:是否開啟 AOF 持久化模式,預設 no;
- cluster-enabled:是否開啟叢集模式,預設 no;
- cluster-config-file:叢集節點資訊檔案;
- cluster-node-timeout:叢集節點連接配接逾時時間;
- cluster-announce-ip:叢集節點 IP
- 注意: 如果你想要你的redis叢集可以供外網通路,這裡直接填 伺服器的IP 位址即可
- 如若為了安全,隻是在伺服器内部進行通路,這裡還需要做一些修改。
- cluster-announce-port:叢集節點映射端口;
- cluster-announce-bus-port:叢集節點總線端口,内部節點之間通信所需的TCP端口。
因為本人有三台實體機,對應的内網IP分别是 192.168.0.200,192.168.0.201,192.168.0.202,是以設定 cluster-announce-ip 參數時需動态擷取每台實體機的IP,每台機器上啟動兩個節點,對應的端口分别是6379和6380,Redis 預設規定内部節點通信端口為節點端口+10000即16379和16380。在每台實體機上執行以上腳本,會在目前項目目錄下生成兩個節點檔案夾,此檔案夾是redis配置檔案和資料的存放目錄,在運作docker容器時會映射到虛拟機裡。
- 編寫shell腳本,啟動容器
ip=`ifconfig enp2s0 |grep "inet " |awk '{print $2}'|cut -c 11-`
for port in $(seq 6379 6380)
do
docker run -it -d -p ${port}:${port} -p 1${port}:1${port} \
--privileged=true -v ./node-${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \
--privileged=true -v ./node-${port}/data:/data \
--restart always --name redis_${ip}_${port} --net redis-net \
--sysctl net.core.somaxconn=1024 redis redis-server /usr/local/etc/redis/redis.conf
done;
docker 啟動時的參數不做過多說明,為了區分6個節點,在容器命名時加入了伺服器IP的後半段,命名規則是:redis_${ip}_${port}
ip=`ifconfig enp2s0 |grep "inet " |awk '{print $2}'|cut -c 11-`
截取到目前伺服器iP的後半段,比如192.168.0.200 的ip擷取到的就是200
在每台機器上執行以上腳本,即可啟動容器。
- 開始建立Redis Cluster叢集
随便進入到一個節點容器中
docker exec -it redis_201_6379 bash
執行
redis-cli -a 123 --cluster create 192.168.0.201:6379 192.168.0.201:6380 192.168.0.200:6379 192.168.0.200:6380 192.168.0.202:6379 192.168.0.202:6380 --cluster-replicas 1
-a 後面為目前節點設定的密碼,直接用此參數會有一個警告,是因為在指令行直接使用銘文密碼,會在曆史記錄中暴露密碼,導緻不安全行為發生,但不影響指令執行。
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe
執行後出現下圖代表 成功:
插槽會均勻分布在各個節點上。進入任意節點檢視叢集狀态資訊:
檢視節點資訊在
- 指令行測試
以叢集方式登入redis_202_6379 節點:
redis-cli -c -a 123 set k1 tie
同樣的方式登入redis_202_6380節點:
redis-cli -c -p 6380 -a 123 get k1
得到如下結果:
- 代碼測試
建立Golang腳本
package main
import (
"fmt"
"github.com/go-redis/redis/v8"
"golang.org/x/net/context"
"time"
)
var clusterClient *redis.ClusterClient
func init() {
// 連接配接redis叢集
ctx := context.Background()
clusterClient = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{ // 填寫master主機
"192.168.0.200:6379",
"192.168.0.201:6379",
"192.168.0.202:6379",
},
Password: "123", // 設定密碼
DialTimeout: 50 * time.Microsecond, // 設定連接配接逾時
ReadTimeout: 50 * time.Microsecond, // 設定讀取逾時
WriteTimeout: 50 * time.Microsecond, // 設定寫入逾時
})
// 發送一個ping指令,測試是否通
s := clusterClient.Do(ctx, "ping").String()
fmt.Println(s)
}
func main() {
ctx := context.Background()
err := clusterClient.Set(ctx, "k1", "tiesheng", 0).Err()
if err != nil {
panic(err)
}
getValue := clusterClient.Get(ctx, "k1")
fmt.Println(getValue.Val(), getValue.Err())
}
執行得到如下結果:
到此 ,已經大功告成了!!
後期随着業務量的增加,可以動态對叢集進行擴容
# 将192.168.0.200:6381節點添加到192.168.0.200:6379節點所在的叢集中
redis-cli --cluster add-node 192.168.0.200:6381 192.168.0.200:6379
# 将192.168.0.200:6382 節點添加到192.168.0.200:6379 對應的叢集中,并且加入的節點為從節點,
# 對應的主節點 id是e0b5798ead8281982053f952d3f2605c2818a0e8
redis-cli --cluster add-node 192.168.0.200:6382 192.168.0.200:6379 --cluster-slave --cluster-master-id e0b5798ead8281982053f952d3f2605c2818a0e8
節點Id可以通過指令 Cluster nodes 檢視。
go實作CRC16
package main
import (
"fmt"
"hash/crc32"
)
func crc16(data []byte) uint16 {
crcTable := crc32.MakeTable(0x8005) // CRC-16-IBM
// 使用CRC32計算得到32位的校驗值
crc := crc32.Checksum(data, crcTable)
// 取校驗值的低16位作為CRC16校驗值
crc16 := uint16(crc & 0xffff)
return crc16
}
func main() {
data := []byte("hello world")
result := crc16(data)
fmt.Printf("CRC16: 0x%04x\n", result)
}