Redis入墳(六)分布式集群,概念、原理、實操

課程目標
1、 掌握 Redis 主從復制的配置和原理
2、 掌握 Redis 哨兵機制(Sentinel)原理和實戰(zhàn)
3、 掌握Redis分布式的各種方案對比,包括客戶端Sharding、代理Proxy和Redis Cluster

1 為什么需要Redis集群

  • 性能
    Redis 本身的 QPS 已經(jīng)很高了,但是如果在一些并發(fā)量非常高的情況下,性能還是會受到影響。這個時候我們希望有更多的 Redis 服務來完成工作。
  • 擴展
    第二個是出于存儲的考慮。因為 Redis 所有的數(shù)據(jù)都放在內(nèi)存中,如果數(shù)據(jù)量大,很容易受到硬件的限制。升級硬件收效和成本比太低,所以我們需要有一種橫向擴展的方法。
  • 可用性
    第三個是可用性和安全的問題。如果只有一個 Redis 服務,一旦服務宕機,那么所有的客戶端都無法訪問,會對業(yè)務造成很大的影響。另一個,如果硬件發(fā)生故障,而單機的數(shù)據(jù)無法恢復的話,帶來的影響也是災難性的。

可用性、數(shù)據(jù)安全、性能都可以通過搭建多個 Reids 服務實現(xiàn)。其中有一個是主節(jié)點(master),可以有多個從節(jié)點(slave)。主從之間通過數(shù)據(jù)同步,存儲完全相同的數(shù)據(jù)。如果主節(jié)點發(fā)生故障,則把某個從節(jié)點改成主節(jié)點,訪問新的主節(jié)點。

2 Redis 主從復制

2.1 主從復制配置

例如一主多從,203 是主節(jié)點,在每個slave節(jié)點的redis.conf配置文件增加一行

slaveof  192.168.8.203   6379

//在主從切換的時候,這個配置會被重寫成:

# Generated by CONFIG REWRITE
replicaof  192.168.8.203  6379

或者在啟動服務時通過參數(shù)指定 master 節(jié)點:

/redis-server --slaveof 192.168.8.203 6379

或在客戶端直接執(zhí)行 slaveof xx xx,使該 Redis 實例成為從節(jié)點。
啟動后,查看集群狀態(tài):

redis> info replication

從節(jié)點不能寫入數(shù)據(jù)(只讀),只能從 master 節(jié)點同步數(shù)據(jù)。get 成功,set 失敗。

127.0.0.1:6379> set gupao 666
(error) READONLY You can't write against a read only replica.

主節(jié)點寫入后,slave 會自動從 master 同步數(shù)據(jù)。
斷開復制:

redis> slaveof no one

此時從節(jié)點會變成自己的主節(jié)點,不再復制數(shù)據(jù)。

2.2 主從復制原理

  • 連接階段
  1. slave node 啟動時(執(zhí)行 slaveof 命令),會在自己本地保存 master node 的信息,包括 master node 的 host 和 ip。
  2. slave node 內(nèi)部有個定時任務 replicationCron(源碼 replication.c),每隔 1秒鐘檢查是否有新的 master node 要連接和復制,如果發(fā)現(xiàn),就跟 master node 建立socket 網(wǎng)絡連接,如果連接成功,從節(jié)點為該 socket 建立一個專門處理復制工作的文件事件處理器,負責后續(xù)的復制工作,如接收 RDB 文件、接收命令傳播等。當從節(jié)點變成了主節(jié)點的一個客戶端之后,會給主節(jié)點發(fā)送 ping 請求。
  • 數(shù)據(jù)同步階段
    master node 第一次執(zhí)行全量復制,通過 bgsave 命令在本地生成一份 RDB 快照,將 RDB 快照文件發(fā)給 slave node(如果超時會重連,可以調大 repl-timeout 的值)。slave node 首先清除自己的舊數(shù)據(jù),然后用 RDB 文件加載數(shù)據(jù)。

問題:生成 RDB 期間,master 接收到的命令怎么處理?

開始生成 RDB 文件時,master 會把所有新的寫命令緩存在內(nèi)存中。在 slave node保存了 RDB 之后,再將新的寫命令復制給 slave node。

  • 命令傳播階段
    master node 持續(xù)將寫命令,異步復制給 slave node。

延遲是不可避免的,只能通過優(yōu)化網(wǎng)絡

repl-disable-tcp-nodelay no

當設置為 yes 時,TCP 會對包進行合并從而減少帶寬,但是發(fā)送的頻率會降低,從節(jié)點數(shù)據(jù)延遲增加,一致性變差;具體發(fā)送頻率與 Linux 內(nèi)核的配置有關,默認配置為40ms。當設置為 no 時,TCP 會立馬將主節(jié)點的數(shù)據(jù)發(fā)送給從節(jié)點,帶寬增加但延遲變小。

一般來說,只有當應用對 Redis 數(shù)據(jù)不一致的容忍度較高,且主從節(jié)點之間網(wǎng)絡狀況不好時,才會設置為 yes;多數(shù)情況使用默認值 no。

問題:如果從節(jié)點有一段時間斷開了與主節(jié)點的連接是不是要重新全量復制一遍?如果可以增量復制,怎么知道上次復制到哪里?
通過 master_repl_offset 記錄的偏移量

redis> info replication

2.3 主從復制的不足

主從模式解決了數(shù)據(jù)備份和性能(通過讀寫分離)的問題,但是還是存在一些不足:

  1. RDB 文件過大的情況下,同步非常耗時。
  2. 在一主一從或者一主多從的情況下,如果主服務器掛了,對外提供的服務就不可用了,單點問題沒有得到解決。如果每次都是手動把之前的從服務器切換成主服務器,這個比較費時費力,還會造成一定時間的服務不可用。

3 可用性保證之 Sentinel

3.1 Sentinel 原理

如何實現(xiàn)主從的自動切換?我們的思路:

創(chuàng)建一臺監(jiān)控服務器來監(jiān)控所有 Redis 服務節(jié)點的狀態(tài),比如,master 節(jié)點超過一定時間沒有給監(jiān)控服務器發(fā)送心跳報文,就把 master 標記為下線,然后把某一個 slave變成 master。應用每一次都是從這個監(jiān)控服務器拿到 master 的地址。

問題是:如果監(jiān)控服務器本身出問題了怎么辦?那我們就拿不到 master 的地址了,應用也沒有辦法訪問。

那我們再創(chuàng)建一個監(jiān)控服務器,來監(jiān)控監(jiān)控服務器……似乎陷入死循環(huán)了,這個問題怎么解決?這個問題先放著。

Redis 的 Sentinel 就是這種思路:通過運行監(jiān)控服務器來保證服務的可用性。官網(wǎng):https://redis.io/topics/sentinel

從 Redis2.8 版本起,提供了一個穩(wěn)定版本的 Sentinel(哨兵),用來解決高可用的問題。它是一個特殊狀態(tài)的 redis 實例。

我們會啟動一個或者多個 Sentinel 的服務(通過 src/redis-sentinel),它本質上只是一個運行在特殊模式之下的 Redis,Sentinel 通過 info 命令得到被監(jiān)聽 Redis 機器的master,slave 等信息。

為了保證監(jiān)控服務器的可用性,我們會對 Sentinel 做集群的部署。Sentinel 既監(jiān)控所有的 Redis 服務,Sentinel 之間也相互監(jiān)控。

注意:Sentinel 本身沒有主從之分,只有 Redis 服務節(jié)點有主從之分。
概念梳理:master,slave(redis group),sentinel,sentinel 集合

  • 服務下線
    Sentinel 默認以每秒鐘 1 次的頻率向 Redis 服務節(jié)點發(fā)送 PING 命令。如果在down-after-milliseconds 內(nèi)都沒有收到有效回復,Sentinel 會將該服務器標記為下線(主觀下線)。
# sentinel.conf
sentinel down-after-milliseconds <master-name> <milliseconds>

這個時候 Sentinel 節(jié)點會繼續(xù)詢問其他的 Sentinel 節(jié)點,確認這個節(jié)點是否下線,如果多數(shù) Sentinel 節(jié)點都認為 master 下線,master 才真正確認被下線(客觀下線),這個時候就需要重新選舉 master。

  • 故障轉移
    如果 master 被標記為下線,就會開始故障轉移流程。既然有這么多的 Sentinel 節(jié)點,由誰來做故障轉移的事情呢?
    故障轉移流程的第一步就是在 Sentinel 集群選擇一個 Leader,由 Leader 完成故障轉移流程。Sentinle 通過 Raft 算法,實現(xiàn) Sentinel 選舉。

Ratf 算法
在分布式存儲系統(tǒng)中,通常通過維護多個副本來提高系統(tǒng)的可用性,那么多個節(jié)點之間必須要面對數(shù)據(jù)一致性的問題。Raft 的目的就是通過復制的方式,使所有節(jié)點達成一致,但是這么多節(jié)點,以哪個節(jié)點的數(shù)據(jù)為準呢?所以必須選出一個 Leader。

大體上有兩個步驟:領導選舉,數(shù)據(jù)復制。

Raft 是一個共識算法(consensus algorithm)。比如比特幣之類的加密貨幣,就需要共識算法。Spring Cloud 的注冊中心解決方案 Consul 也用到了 Raft 協(xié)議。

Raft 的核心思想:先到先得,少數(shù)服從多數(shù)。

Raft 算法演示:
http://thesecretlivesofdata.com/raft/

總結:
Sentinle 的 Raft 算法和 Raft 論文略有不同。
1、master 客觀下線觸發(fā)選舉,而不是過了 election timeout 時間開始選舉。
2、Leader 并不會把自己成為 Leader 的消息發(fā)給其他 Sentinel。其他 Sentinel 等待 Leader 從 slave 選出 master 后,檢測到新的 master 正常工作后,就會去掉客觀下線的標識,從而不需要進入故障轉移流程。

問題:怎么讓一個原來的 slave 節(jié)點成為主節(jié)點?

  1. 選出 Sentinel Leader 之后,由 Sentinel Leader 向某個節(jié)點發(fā)送 slaveof no one命令,讓它成為獨立節(jié)點。
  2. 然后向其他節(jié)點發(fā)送 slaveof x.x.x.x xxxx(本機服務),讓它們成為這個節(jié)點的子節(jié)點,故障轉移完成。

問題:這么多從節(jié)點,選誰成為主節(jié)點?

關于從節(jié)點選舉,一共有四個因素影響選舉的結果,分別是斷開連接時長、優(yōu)先級排序、復制數(shù)量、進程 id。

如果與哨兵連接斷開的比較久,超過了某個閾值,就直接失去了選舉權。如果擁有選舉權,那就看誰的優(yōu)先級高,這個在配置文件里可以設置(replica-priority 100),數(shù)值越小優(yōu)先級越高。

如果優(yōu)先級相同,就看誰從 master 中復制的數(shù)據(jù)最多(復制偏移量最大),選最多的那個,如果復制數(shù)量也相同,就選擇進程 id 最小的那個。

Sentinel 的功能總結

  1. 監(jiān)控:Sentinel 會不斷檢查主服務器和從服務器是否正常運行。
  2. 通知:如果某一個被監(jiān)控的實例出現(xiàn)問題,Sentinel 可以通過 API 發(fā)出通知。
  3. 自動故障轉移(failover):如果主服務器發(fā)生故障,Sentinel 可以啟動故障轉移過程。把某臺服務器升級為主服務器,并發(fā)出通知。
  4. 配置管理:客戶端連接到 Sentinel,獲取當前的 Redis 主服務器的地址。

Sentinel 實戰(zhàn)

Sentinel 配置

為了保證 Sentinel 的高可用,Sentinel 也需要做集群部署,集群中至少需要三個Sentinel 實例(推薦奇數(shù)個,防止腦裂)。



以 Redis 安裝路徑/usr/local/soft/redis-5.0.5/為例。
在 204 和 205 的 src/redis.conf 配置文件中添加

slaveof 192.168.8.203 6379

在 203、204、205 創(chuàng)建 sentinel 配置文件(安裝后根目錄下默認有 sentinel.conf):

cd /usr/local/soft/redis-5.0.5
mkdir logs
mkdir rdbs
mkdir sentinel-tmp
vim sentinel.conf

三臺服務器內(nèi)容相同:

daemonize yes
port 26379
protected-mode no
dir "/usr/local/soft/redis-5.0.5/sentinel-tmp"
sentinel monitor redis-master 192.168.8.203 6379 2
sentinel down-after-milliseconds redis-master 30000
sentinel failover-timeout redis-master 180000
sentinel parallel-syncs redis-master 1

上面出現(xiàn)了 4 個'redis-master',這個名稱要統(tǒng)一,并且使用客戶端(比如 Jedis)連接的時候名稱要正確。


Sentinel 驗證

啟動 Redis 服務和 Sentinel

cd /usr/local/soft/redis-5.0.5/src
# 啟動 Redis 節(jié)點
./redis-server ../redis.conf
# 啟動 Sentinel 節(jié)點
./redis-sentinel ../sentinel.conf
# 或者
./redis-server ../sentinel.conf --sentinel

查看集群狀態(tài):
203



204 和 205



模擬 master 宕機,在 203 執(zhí)行:
redis> shutdown

205 被選為新的 Master,只有一個 Slave 節(jié)點



注意看 sentinel.conf 里面的 redis-master 被修改了!
模擬原 master 恢復,在 203 啟動 redis-server。它還是 slave,但是 master 又有兩個 slave 了。

slave 宕機和恢復省略。

Sentinel 連接使用

Jedis 連接 Sentinel,master name 來自于 sentinel.conf 的配置。

private static JedisSentinelPool createJedisPool() {
  String masterName = "redis-master";
  Set<String> sentinels = new HashSet<String>();
  sentinels.add("192.168.8.203:26379");
  sentinels.add("192.168.8.204:26379");
  sentinels.add("192.168.8.205:26379");
  pool = new JedisSentinelPool(masterName, sentinels);
  return pool;
}

Spring Boot 連接 Sentinel

spring.redis.sentinel.master=redis-master
spring.redis.sentinel.nodes=192.168.8.203:26379,192.168.8.204:26379,192.168.8.205:26379

無論是 Jedis 還是 Spring Boot(2.x 版本默認是 Lettuce),都只需要配置全部哨兵的地址,由哨兵返回當前的 master 節(jié)點地址。

哨兵機制的不足

主從切換的過程中會丟失數(shù)據(jù),因為只有一個 master。只能單點寫,沒有解決水平擴容的問題。如果數(shù)據(jù)量非常大,這個時候我們需要多個 master-slave 的 group,把數(shù)據(jù)分布到不同的 group 中。

問題來了,數(shù)據(jù)怎么分片?分片之后,怎么實現(xiàn)路由?

4 Redis 分布式方案

如果要實現(xiàn) Redis 數(shù)據(jù)的分片,我們有三種方案。第一種是在客戶端實現(xiàn)相關的邏輯,例如用取?;蛘咭恢滦怨?key 進行分片,查詢和修改都先判斷 key 的路由。

第二種是把做分片處理的邏輯抽取出來,運行一個獨立的代理服務,客戶端連接到這個代理服務,代理服務做請求的轉發(fā)。

第三種就是基于服務端實現(xiàn)。

  • 客戶端 Sharding


Jedis 客戶端提供了 Redis Sharding 的方案,并且支持連接池。

public class ShardingTest {
    public static void main(String[] args) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();

        // Redis服務器
        JedisShardInfo shardInfo1 = new JedisShardInfo("127.0.0.1", 6379);
        JedisShardInfo shardInfo2 = new JedisShardInfo("192.168.8.205", 6379);

        // 連接池
        List<JedisShardInfo> infoList = Arrays.asList(shardInfo1, shardInfo2);
        ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, infoList);

        ShardedJedis jedis = null;
        try{
            jedis = jedisPool.getResource();
            for(int i=0; i<100; i++){
                jedis.set("k"+i, ""+i);
            }
            for(int i=0; i<100; i++){
                Client client = jedis.getShard("k"+i).getClient();
                System.out.println("取到值:"+jedis.get("k"+i)+","+"當前key位于:" + client.getHost() + ":" + client.getPort());
            }

        }finally{
            if(jedis!=null) {
                jedis.close();
            }
        }
    }
}

使用 ShardedJedis 之類的客戶端分片代碼的優(yōu)勢是配置簡單,不依賴于其他中間件,分區(qū)的邏輯可以自定義,比較靈活。但是基于客戶端的方案,不能實現(xiàn)動態(tài)的服務增減,每個客戶端需要自行維護分片策略,存在重復代碼。

第二種思路就是把分片的代碼抽取出來,做成一個公共服務,所有的客戶端都連接到這個代理層。由代理層來實現(xiàn)請求和轉發(fā)。

4.2 代理 Proxy


典型的代理分區(qū)方案有 Twitter 開源的 Twemproxy 和國內(nèi)的豌豆莢開源的 Codis。

  • Twemproxy
    two-em-proxy
    https://github.com/twitter/twemproxy


    Twemproxy 的優(yōu)點:比較穩(wěn)定,可用性高。
    不足:
    1、出現(xiàn)故障不能自動轉移,架構復雜,需要借助其他組件(LVS/HAProxy +
    Keepalived)實現(xiàn) HA
    2、擴縮容需要修改配置,不能實現(xiàn)平滑地擴縮容(需要重新分布數(shù)據(jù))。

  • Codis
    Codis 是一個代理中間件,用 Go 語言開發(fā)的。
    功能:客戶端連接 Codis 跟連接 Redis 沒有區(qū)別。



分片原理:Codis 把所有的 key 分成了 N 個槽(例如 1024),每個槽對應一個分組,一個分組對應于一個或者一組 Redis 實例。Codis 對 key 進行 CRC32 運算,得到一個32 位的數(shù)字,然后模以 N(槽的個數(shù)),得到余數(shù),這個就是 key 對應的槽,槽后面就是 Redis 的實例。比如4個槽:



Codis 的槽位映射關系是保存在 Proxy 中的,如果要解決單點的問題,Codis 也要做集群部署,多個 Codis 節(jié)點怎么同步槽和實例的關系呢?需要運行一個 Zookeepe(r 或者 etcd/本地文件)。
在新增節(jié)點的時候,可以為節(jié)點指定特定的槽位。Codis 也提供了自動均衡策略。

Codis 不支持事務,其他的一些命令也不支持。
不支持的命令 https://github.com/CodisLabs/codis/blob/release3.2/doc/unsupported_cmds.md

獲取數(shù)據(jù)原理(mget):在 Redis 中的各個實例里獲取到符合的 key,然后再匯總到 Codis 中。

Codis 是第三方提供的分布式解決方案,在官方的集群功能穩(wěn)定之前,Codis 也得到了大量的應用。

4.3Redis Cluster

Redis Cluster 是在 Redis 3.0 的版本正式推出的,用來解決分布式的需求,同時也可以實現(xiàn)高可用。跟 Codis 不一樣,它是去中心化的,客戶端可以連接到任意一個可用節(jié)點。

數(shù)據(jù)分片有幾個關鍵的問題需要解決:

  1. 數(shù)據(jù)怎么相對均勻地分片
  2. 客戶端怎么訪問到相應的節(jié)點和數(shù)據(jù)
  3. 重新分片的過程,怎么保證正常服務
  • 架構
    Redis Cluster 可以看成是由多個 Redis 實例組成的數(shù)據(jù)集合。客戶端不需要關注數(shù)據(jù)的子集到底存儲在哪個節(jié)點,只需要關注這個集合整體。

以3主3從為例,節(jié)點之間兩兩交互,共享數(shù)據(jù)分片、節(jié)點狀態(tài)等信息。

  • CentOS 7 單機安裝Redis Cluster(3主3從)

為了節(jié)省機器,我們直接把6個Redis實例安裝在同一臺機器上(3主3從),只是使用不同的端口號。

機器IP 192.168.8.207

cd /usr/local/soft/redis-5.0.5
mkdir redis-cluster
cd redis-cluster
mkdir 7291 7292 7293 7294 7295 7296

復制redis配置文件到7291目錄

cp /usr/local/soft/redis-5.0.5/redis.conf /usr/local/soft/redis-5.0.5/redis-cluster/7291

修改7291的redis.conf配置文件,內(nèi)容:

cd /usr/local/soft/redis-5.0.5/redis-cluster/7291
>redis.conf
vim redis.conf
port 7291
daemonize yes
protected-mode no
dir /usr/local/soft/redis-5.0.5/redis-cluster/7291/
cluster-enabled yes
cluster-config-file nodes-7291.conf
cluster-node-timeout 5000
appendonly yes
pidfile /var/run/redis_7291.pid

把7291下的redis.conf復制到其他5個目錄。

cd /usr/local/soft/redis-5.0.5/redis-cluster/7291
cp redis.conf ../7292
cp redis.conf ../7293
cp redis.conf ../7294
cp redis.conf ../7295
cp redis.conf ../7296

批量替換內(nèi)容

cd /usr/local/soft/redis-5.0.5/redis-cluster
sed -i 's/7291/7292/g' 7292/redis.conf
sed -i 's/7291/7293/g' 7293/redis.conf
sed -i 's/7291/7294/g' 7294/redis.conf
sed -i 's/7291/7295/g' 7295/redis.conf
sed -i 's/7291/7296/g' 7296/redis.conf

啟動6個Redis節(jié)點

cd /usr/local/soft/redis-5.0.5/
./src/redis-server redis-cluster/7291/redis.conf
./src/redis-server redis-cluster/7292/redis.conf
./src/redis-server redis-cluster/7293/redis.conf
./src/redis-server redis-cluster/7294/redis.conf
./src/redis-server redis-cluster/7295/redis.conf
./src/redis-server redis-cluster/7296/redis.conf

是否啟動了6個進程

ps -ef|grep redis

創(chuàng)建集群
舊版本中的redis-trib.rb已經(jīng)廢棄了,直接用–cluster命令
注意用絕對IP,不要用127.0.0.1

cd /usr/local/soft/redis-5.0.5/src/
redis-cli --cluster create 192.168.8.207:7291 192.168.8.207:7292 192.168.8.207:7293 192.168.8.207:7294 192.168.8.207:7295 192.168.8.207:7296 --cluster-replicas 1

Redis會給出一個預計的方案,對6個節(jié)點分配3主3從,如果認為沒有問題,輸入yes確認

>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7295 to 127.0.0.1:7291
Adding replica 127.0.0.1:7296 to 127.0.0.1:7292
Adding replica 127.0.0.1:7294 to 127.0.0.1:7293
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c 127.0.0.1:7291
   slots:[0-5460] (5461 slots) master
M: 8c878b45905bba3d7366c89ec51bd0cd7ce959f8 127.0.0.1:7292
   slots:[5461-10922] (5462 slots) master
M: aeeb7d7076d9b25a7805ac6f508497b43887e599 127.0.0.1:7293
   slots:[10923-16383] (5461 slots) master
S: ebc479e609ff8f6ca9283947530919c559a08f80 127.0.0.1:7294
   replicates aeeb7d7076d9b25a7805ac6f508497b43887e599
S: 49385ed6e58469ef900ec48e5912e5f7b7505f6e 127.0.0.1:7295
   replicates dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c
S: 8d6227aefc4830065624ff6c1dd795d2d5ad094a 127.0.0.1:7296
   replicates 8c878b45905bba3d7366c89ec51bd0cd7ce959f8
Can I set the above configuration? (type 'yes' to accept): 

注意看slot的分布:
7291 [0-5460] (5461個槽)
7292 [5461-10922] (5462個槽)
7293 [10923-16383] (5461個槽)

集群創(chuàng)建完成

>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 127.0.0.1:7291)
M: dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c 127.0.0.1:7291
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 8c878b45905bba3d7366c89ec51bd0cd7ce959f8 127.0.0.1:7292
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: aeeb7d7076d9b25a7805ac6f508497b43887e599 127.0.0.1:7293
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 8d6227aefc4830065624ff6c1dd795d2d5ad094a 127.0.0.1:7296
   slots: (0 slots) slave
   replicates aeeb7d7076d9b25a7805ac6f508497b43887e599
S: ebc479e609ff8f6ca9283947530919c559a08f80 127.0.0.1:7294
   slots: (0 slots) slave
   replicates dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c
S: 49385ed6e58469ef900ec48e5912e5f7b7505f6e 127.0.0.1:7295
   slots: (0 slots) slave
   replicates 8c878b45905bba3d7366c89ec51bd0cd7ce959f8
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

重置集群

重置集群的方式是在每個節(jié)點上個執(zhí)行cluster reset,然后重新創(chuàng)建集群。

重啟集群

使用./src/redis-server ./7291/redis.conf命令可以將實例重新啟動,啟動完成之后,自動加入到集群當中,其他節(jié)點一樣。

關閉集群

./src/redis-cli -c -h 127.0.0.1 -p 7291 shutdown

集群伸縮

1.擴容集群

1.準備新節(jié)點
2.加入集群

使用redis-cli 語法:add-node 新節(jié)點ip 端口 已存在節(jié)點ip 端口

指定主從

使用redis-cli 語法(加入時指定):add-node 新節(jié)點ip 端口 已存在節(jié)點ip 端口 --cluster-slave --cluster-master-id masterID

使用原生命令 語法:cluster replicate node-id

3.遷移槽和數(shù)據(jù)

1.槽遷移計劃

語法:/redis-cli --cluster reshard 已存在節(jié)點ip : 端口

/usr/local/bin/redis-cli --cluster reshard 192.168.204.188:7000

2.遷移數(shù)據(jù)

執(zhí)行流程:提示要分配多少槽-》接收節(jié)點ID-》all/done

3.添加從節(jié)點

2.縮容集群

1.下線遷移槽

語法:redis-cli --cluster reshard --cluster-from 要遷出節(jié)點ID --cluster-to 接收槽節(jié)點ID --cluster-slots 遷出槽數(shù)量 已存在節(jié)點ip 端口

/usr/local/bin/redis-cli --cluster reshard --cluster-from a2fdd1359d03acacf2a6e558acbc006639445d53 --cluster-to 1794864d5f8af79e88cfc0f699f02b6341c78b5c --cluster-slots 1366 192.168.0.104 7000

2.忘記節(jié)點.關閉節(jié)點

語法: redis-cli --cluster del-node 已存在節(jié)點IP:端口 要刪除的節(jié)點ID

/usr/local/bin/redis-cli --cluster del-node 192.168.0.104:7000 8de55e2a7419983184cede9daab5d36ee9da1fa3

連接到客戶端

redis-cli -p 7291
redis-cli -p 7292
redis-cli -p 7293

批量寫入值

cd /usr/local/soft/redis-5.0.5/redis-cluster/
vim setkey.sh

執(zhí)行

chmod +x setkey.sh
./setkey.sh

每個節(jié)點分布的數(shù)據(jù)

127.0.0.1:7292> dbsize
(integer) 6683
127.0.0.1:7293> dbsize
(integer) 6665
127.0.0.1:7291> dbsize
(integer) 6652

其他命令,比如添加節(jié)點、刪除節(jié)點,重新分布數(shù)據(jù):

redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN
                 --cluster-replicas <arg>
  check          host:port
                 --cluster-search-multiple-owners
  info           host:port
  fix            host:port
                 --cluster-search-multiple-owners
  reshard        host:port
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      host:port
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id
  call           host:port command arg arg .. arg
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-copy
                 --cluster-replace
  help           

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

附錄:

集群命令

cluster info :打印集群的信息
cluster nodes :列出集群當前已知的所有節(jié)點(node),以及這些節(jié)點的相關信息。
cluster meet :將 ip 和 port 所指定的節(jié)點添加到集群當中,讓它成為集群的一份子。
cluster forget <node_id> :從集群中移除 node_id 指定的節(jié)點(保證空槽道)。
cluster replicate <node_id> :將當前節(jié)點設置為 node_id 指定的節(jié)點的從節(jié)點。
cluster saveconfig :將節(jié)點的配置文件保存到硬盤里面。

槽slot命令

cluster addslots [slot …] :將一個或多個槽(slot)指派(assign)給當前節(jié)點。
cluster delslots [slot …] :移除一個或多個槽對當前節(jié)點的指派。
cluster flushslots :移除指派給當前節(jié)點的所有槽,讓當前節(jié)點變成一個沒有指派任何槽的節(jié)點。
cluster setslot node <node_id> :將槽 slot 指派給 node_id 指定的節(jié)點,如果槽已經(jīng)指派給另一個節(jié)點,那么先讓另一個節(jié)點刪除該槽>,然后再進行指派。
cluster setslot migrating <node_id> :將本節(jié)點的槽 slot 遷移到 node_id 指定的節(jié)點中。
cluster setslot importing <node_id> :從 node_id 指定的節(jié)點中導入槽 slot 到本節(jié)點。
cluster setslot stable :取消對槽 slot 的導入(import)或者遷移(migrate)。

鍵命令

cluster keyslot :計算鍵 key 應該被放置在哪個槽上。
cluster countkeysinslot :返回槽 slot 目前包含的鍵值對數(shù)量。
cluster getkeysinslot :返回 count 個 slot 槽中的鍵

問題:Cluster 解決分片的問題,數(shù)據(jù)怎么分布?
如果是希望數(shù)據(jù)分布相對均勻的話,我們首先可以考慮哈希后取模。

  • 哈希后取模
    例如,hash(key)%N,根據(jù)余數(shù),決定映射到那一個節(jié)點。這種方式比較簡單,屬于靜態(tài)的分片規(guī)則。但是一旦節(jié)點數(shù)量變化,新增或者減少,由于取模的 N 發(fā)生變化,數(shù)據(jù)需要重新分布。為了解決這個問題,我們又有了一致性哈希算法。
  • 一致性哈希
    一致性哈希的原理:把所有的哈希值空間組織成一個虛擬的圓環(huán)(哈希環(huán)),整個空間按順時針方向組織。因為是環(huán)形空間,0 和 2^32-1 是重疊的。假設我們有四臺機器要哈希環(huán)來實現(xiàn)映射(分布數(shù)據(jù)),我們先根據(jù)機器的名稱或者 IP 計算哈希值,然后分布到哈希環(huán)中(紅色圓圈)。



    現(xiàn)在有 4 條數(shù)據(jù)或者 4 個訪問請求,對 key 計算后,得到哈希環(huán)中的位置(綠色圓圈)。沿哈希環(huán)順時針找到的第一個 Node,就是數(shù)據(jù)存儲的節(jié)點。



    在這種情況下,新增了一個 Node5 節(jié)點,不影響數(shù)據(jù)的分布。

    刪除了一個節(jié)點 Node4,只影響相鄰的一個節(jié)點

    谷歌的 MurmurHash 就是一致性哈希算法。在分布式系統(tǒng)中,負載均衡、分庫分表等場景中都有應用。
    一致性哈希解決了動態(tài)增減節(jié)點時,所有數(shù)據(jù)都需要重新分布的問題,它只會影響到下一個相鄰的節(jié)點,對其他節(jié)點沒有影響。但是這樣的一致性哈希算法有一個缺點,因為節(jié)點不一定是均勻地分布的,特別是在節(jié)點數(shù)比較少的情況下,所以數(shù)據(jù)不能得到均勻分布。解決這個問題的辦法是引入虛擬節(jié)點(Virtual Node)。比如:2 個節(jié)點,5 條數(shù)據(jù),只有 1 條分布到 Node2,4 條分布到 Node1,不均勻。



    Node1 設置了兩個虛擬節(jié)點,Node2 也設置了兩個虛擬節(jié)點(虛線圓圈)。
    這時候有 3 條數(shù)據(jù)分布到 Node1,1 條數(shù)據(jù)分布到 Node2。

Redis 虛擬槽分區(qū)

Redis 既沒有用哈希取模,也沒有用一致性哈希,而是用虛擬槽來實現(xiàn)的。
Redis 創(chuàng)建了 16384 個槽(slot),每個節(jié)點負責一定區(qū)間的 slot。比如 Node1 負責 0-5460,Node2 負責 5461-10922,Node3 負責 10923-16383。



Redis 的每個 master 節(jié)點維護一個 16384 位(2048bytes=2KB)的位序列,比如:
序列的第 0 位是 1,就代表第一個 slot 是它負責;序列的第 1 位是 0,代表第二個 slot不歸它負責。

對象分布到 Redis 節(jié)點上時,對 key 用 CRC16 算法計算再%16384,得到一個 slot的值,數(shù)據(jù)落到負責這個 slot 的 Redis 節(jié)點上。

查看 key 屬于哪個 slot:
···
127.0.0.1:7291> cluster keyslot congzhizhi
(integer) 12625
127.0.0.1:7291>
···
注意:key 與 slot 的關系是永遠不會變的,會變的只有 slot 和 Redis 節(jié)點的關系。

問題:怎么讓相關的數(shù)據(jù)落到同一個節(jié)點上?
比如有些 multi key 操作是不能跨節(jié)點的,如果要讓某些數(shù)據(jù)分布到一個節(jié)點上,例如用戶 2673 的基本信息和金融信息,怎么辦?

在 key 里面加入{hash tag}即可。Redis 在計算槽編號的時候只會獲取{}之間的字符串進行槽編號計算,這樣由于上面兩個不同的鍵,{}里面的字符串是相同的,因此他們可以被計算出相同的槽。

user{2673}base=…
user{2673}fin=…
127.0.0.1:7293> set a{qs}a 1
OK
127.0.0.1:7293> set a{qs}b 1
OK
127.0.0.1:7293> set a{qs}c 1
OK
127.0.0.1:7293> set a{qs}d 1
OK
127.0.0.1:7293> set a{qs}e 1
OK
127.0.0.1:7291> cluster keyslot cong{123}zhizhi
(integer) 5970
127.0.0.1:7291> cluster keyslot cong{123}asdfasdf
(integer) 5970
127.0.0.1:7291> cluster keyslot cong{123}zhizhi
(integer) 5970
127.0.0.1:7291> cluster keyslot cong{123}zhongguo
(integer) 5970
127.0.0.1:7291> cluster keyslot cong{123}dele
(integer) 5970

問題:客戶端連接到哪一臺服務器?訪問的數(shù)據(jù)不在當前節(jié)點上,怎么辦?

  • 客戶端重定向
    比如在 7291 端口的 Redis 的 redis-cli 客戶端操作:
127.0.0.1:7291> set qs 1
(error) MOVED 13724 127.0.0.1:7293

服務端返回 MOVED,也就是根據(jù) key 計算出來的 slot 不歸 7191 端口管理,而是歸 7293 端口管理,服務端返回 MOVED 告訴客戶端去 7293 端口操作。

這個時候更換端口,用 redis-cli –p 7293 操作,才會返回 OK。或者用./redis-cli -c -p port 的命令(c 代表 cluster)。這樣客戶端需要連接兩次。

Jedis 等客戶端會在本地維護一份 slot——node 的映射關系,大部分時候不需要重定向,所以叫做 smart jedis(需要客戶端支持)

問題:新增或下線了 Master 節(jié)點,數(shù)據(jù)怎么遷移(重新分配)?
因為 key 和 slot 的關系是永遠不會變的,當新增了節(jié)點的時候,需要把原有的 slot分配給新的節(jié)點負責,并且把相關的數(shù)據(jù)遷移過來。
添加新節(jié)點(新增一個 7297):

redis-cli --cluster add-node 127.0.0.1:7291 127.0.0.1:7297

新增的節(jié)點沒有哈希槽,不能分布數(shù)據(jù),在原來的任意一個節(jié)點上執(zhí)行:

redis-cli --cluster reshard 127.0.0.1:7291

輸入需要分配的哈希槽的數(shù)量(比如 500),和哈希槽的來源節(jié)點(可以輸入 all 或者 id)。

問題:只有主節(jié)點可以寫,一個主節(jié)點掛了,從節(jié)點怎么變成主節(jié)點?

  • 高可用和主從切換原理
    當 slave 發(fā)現(xiàn)自己的 master 變?yōu)?FAIL 狀態(tài)時,便嘗試進行 Failover,以期成為新的master。由于掛掉的master可能會有多個slave,從而存在多個slave競爭成為master節(jié)點的過程, 其過程如下:
  1. slave 發(fā)現(xiàn)自己的 master 變?yōu)?FAIL
  2. 將自己記錄的集群 currentEpoch 加 1,并廣播FAILOVER_AUTH_REQUEST 信息
  3. 其他節(jié)點收到該信息,只有 master 響應,判斷請求者的合法性,并發(fā)送
    FAILOVER_AUTH_ACK,對每一個 epoch 只發(fā)送一次 ack
  4. 嘗試 failover 的 slave 收集 FAILOVER_AUTH_ACK
  5. 超過半數(shù)后變成新 Master
  6. 廣播 Pong 通知其他集群節(jié)點。

Redis Cluster 既能夠實現(xiàn)主從的角色分配,又能夠實現(xiàn)主從切換,相當于集成了Replication 和 Sentinal 的功能。

redis cluster總結

優(yōu)勢

  1. 無中心架構。
  2. 數(shù)據(jù)按照 slot 存儲分布在多個節(jié)點,節(jié)點間數(shù)據(jù)共享,可動態(tài)調整數(shù)據(jù)分布。
  3. 可擴展性,可線性擴展到 1000 個節(jié)點(官方推薦不超過 1000 個),節(jié)點可動
    態(tài)添加或刪除。
  4. 高可用性,部分節(jié)點不可用時,集群仍可用。通過增加 Slave 做 standby 數(shù)據(jù)副本,能夠實現(xiàn)故障自動 failover,節(jié)點之間通過 gossip 協(xié)議交換狀態(tài)信息,用投票機制完成 Slave 到 Master 的角色提升。
  5. 降低運維成本,提高系統(tǒng)的擴展性和可用性。

不足

  1. Client 實現(xiàn)復雜,驅動要求實現(xiàn) Smart Client,緩存 slots mapping 信息并及時更新,提高了開發(fā)難度,客戶端的不成熟影響業(yè)務的穩(wěn)定性。
  2. 節(jié)點會因為某些原因發(fā)生阻塞(阻塞時間大于 clutser-node-timeout),被判斷下線,這種 failover 是沒有必要的。
  3. 數(shù)據(jù)通過異步復制,不保證數(shù)據(jù)的強一致性。
  4. 多個業(yè)務使用同一套集群時,無法根據(jù)統(tǒng)計區(qū)分冷熱數(shù)據(jù),資源隔離性較差,容易出現(xiàn)相互影響的情況。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容