redis 分布式

為什么要用Redis集群

  • 為什么要用集群缺狠?

1、性能
Redis 本身的 QPS(官方數(shù)據(jù):10萬/秒) 已經(jīng)很高了,但是如果在一些并發(fā)量非常高的情況下颈将,性能還是會受到影響,這個時候我們希望有更多的 Redis 服務(wù)來完成工作

2言疗、擴展
出于存儲的考慮晴圾,因為 Redis 所有的數(shù)據(jù)都放在內(nèi)存中,如果數(shù)據(jù)量大噪奄,很容易受到硬件的限制死姚,升級硬件收效和成本比太低人乓,所以我們需要有一種橫向擴展的方法

3、可用性
可用性和安全的問題都毒,如果只有一個 Redis 服務(wù)色罚,一旦服務(wù)宕機,那么所有的客戶端都無法訪問账劲,會對業(yè)務(wù)造成很大的影響戳护,另一個,如果硬件發(fā)生故障瀑焦,而單機的數(shù)據(jù)無法恢復(fù)的話腌且,帶來的影響也是災(zāi)難性的

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

Redis主從復(fù)制(replication)

  • 主從復(fù)制配置

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

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

# Generated by CONFIG REWRITE
replicaof 192.168.8.203 6379

或者在啟動服務(wù)時通過參數(shù)指定master節(jié)點:
./redis-server --slaveof 192.168.8.203 6379

或者在客戶端直接執(zhí)行slaveof xxxxxx堪夭,使該redis實例成為從節(jié)點

啟動后,查看集群狀態(tài):
redis> info replication

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

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

主節(jié)點寫入后嚣镜,slave會自動從master同步數(shù)據(jù)

斷開復(fù)制命令:redis> slaveof no one 此時從節(jié)點會變成自己的主節(jié)點爬迟,不再進行復(fù)制數(shù)據(jù)

  • 主從復(fù)制原理
連接階段

1)slave node 啟動時(執(zhí)行 slaveof 命令),會在自己本地保存 master node 的信息菊匿,包括 master node 的 host 和 ip

2)slave node 內(nèi)部有個定時任務(wù) replicationCron(源碼 replication.c)付呕,每隔 1秒鐘檢查是否有新的 master node連接和復(fù)制,如果發(fā)現(xiàn)跌捆,就跟 master node 建立socket 網(wǎng)絡(luò)連接狸吞,如果連接成功妒御,從節(jié)點為該 socket 建立一個專門處理復(fù)制工作的文件事件處理器熔吗,負(fù)責(zé)后續(xù)的復(fù)制工作吆倦,如接收 RDB 文件、接收命令傳播等

當(dāng)從節(jié)點變成了主節(jié)點的一個客戶端之后,會給主節(jié)點發(fā)送 ping 請求

數(shù)據(jù)同步階段

3)master node 第一次執(zhí)行全量復(fù)制潮瓶,通過 bgsave 命令在本地生成一份 RDB 快照陶冷,將 RDB 快照文件發(fā)給 slave node(如果超時會重連,可以調(diào)大 repl-timeout 的值)毯辅,slave node 首先清除自己的舊數(shù)據(jù)埂伦,然后用 RDB 文件加載數(shù)據(jù)

生成 RDB 期間,master 接收到的命令怎么處理思恐?
開始生成 RDB 文件時沾谜,master 會把所有新的寫命令緩存在內(nèi)存中,在 slave node保存了 RDB 之后壁袄,再將新的寫命令復(fù)制給 slave node

命令傳輸階段

4)master node 持續(xù)將寫命令类早,異步復(fù)制給 slave node

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

repl-disable-tcp-nodelay no命令解析:
當(dāng)設(shè)置為 yes 時嗜逻,TCP 會對包進行合并從而減少帶寬,但是發(fā)送的頻率會降低缭召,從節(jié)點數(shù)據(jù)延遲增加栈顷,一致性變差;具體發(fā)送頻率與 Linux 內(nèi)核的配置有關(guān)嵌巷,默認(rèn)配置為40ms

當(dāng)設(shè)置為 no 時萄凤,TCP 會立馬將主節(jié)點的數(shù)據(jù)發(fā)送給從節(jié)點,帶寬增加但延遲變小

一般來說搪哪,只有當(dāng)應(yīng)用對 Redis 數(shù)據(jù)不一致的容忍度較高靡努,且主從節(jié)點之間網(wǎng)絡(luò)狀況不好時,才會設(shè)置為 yes晓折;多數(shù)情況使用默認(rèn)值 no

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

redis> info replication
.....
master_repl_offset:152461
....
  • 主從復(fù)制的不足
    1漾月、RDB 文件過大的情況下,同步非常耗時
    2胃珍、在一主一從或者一主多從的情況下梁肿,如果主服務(wù)器掛了,對外提供的服務(wù)就不可用了觅彰,單點問題沒有得到解決,如果每次都是手動把之前的從服務(wù)器切換成主服務(wù)器吩蔑,這個比較費時費力,還會造成一定時間的服務(wù)不可用

可用性保證之Sentinel

Redis 的 Sentinel 思路:通過運行監(jiān)控服務(wù)器來保證服務(wù)的可用性

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

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


Sentinel.png

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

注意點:Sentinel 本身沒有主從之分,只有 Redis 服務(wù)節(jié)點有主從之分

1暑诸、服務(wù)下線
Sentinel 默認(rèn)以每秒鐘 1 次的頻率向 Redis 服務(wù)節(jié)點發(fā)送 PING 命令蚌讼,如果在down-after-milliseconds 內(nèi)都沒有收到有效回復(fù),Sentinel 會將該服務(wù)器標(biāo)記為下線(主觀下線)

redis.conf 配置文件
# sentinel.conf
sentinel down-after-milliseconds  <master-name> <milliseconds>

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

2凰萨、故障轉(zhuǎn)移
如果 master 被標(biāo)記為下線,就會開始故障轉(zhuǎn)移流程

有這么多的 Sentinel 節(jié)點械馆,由誰來做故障轉(zhuǎn)移的事情呢胖眷?
故障轉(zhuǎn)移流程的第一步就是在 Sentinel 集群選擇一個 Leader,由 Leader 完成故障轉(zhuǎn)移流程霹崎,Sentinle 通過 Raft 算法珊搀,實現(xiàn) Sentinel 選舉

Raft算法

在分布式存儲系統(tǒng)中,通常通過維護多個副本來提高系統(tǒng)的可用性尾菇,那么多個節(jié)點之間必須要面對數(shù)據(jù)一致性的問題

Raft 的目的:通過復(fù)制的方式境析,使所有節(jié)點達成一致,數(shù)據(jù)是由Leader節(jié)點為準(zhǔn)

Raft算法大概分為兩個步驟:領(lǐng)導(dǎo)選舉派诬,數(shù)據(jù)復(fù)制

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

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

Raft算法演示

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

故障轉(zhuǎn)移

怎么讓一個原來的 slave 節(jié)點成為主節(jié)點冯挎?
1底哥、選出 Sentinel Leader 之后,由 Sentinel Leader 向某個節(jié)點發(fā)送 slaveof no one命令,讓它成為獨立節(jié)點
2趾徽、再向其他節(jié)點發(fā)送 slaveof x.x.x.x xxxx(本機服務(wù))续滋,讓它們成為這個節(jié)點的子節(jié)點,故障轉(zhuǎn)移完成

這么多從節(jié)點孵奶,選誰成為主節(jié)點疲酌?
從節(jié)點選舉,一共有四個因素影響選舉的結(jié)果:分別是斷開連接時長了袁、優(yōu)先級排序朗恳、復(fù)制數(shù)量、進程 id

如果與哨兵連接斷開的比較久载绿,超過了某個閾值粥诫,就直接失去了選舉權(quán),如果擁有選舉權(quán)崭庸,那就看誰的優(yōu)先級高怀浆,這個在配置文件里可以設(shè)置(replica-priority 100),數(shù)值越小優(yōu)先級越高冀自,如果優(yōu)先級相同揉稚,就看誰從 master 中復(fù)制的數(shù)據(jù)最多(復(fù)制偏移量最大),選最多的那個熬粗,如果復(fù)制數(shù)量也相同,就選擇進程 id 最小的那個

  • Sentinel的功能總結(jié)

監(jiān)控:Sentinel 會不斷檢查主服務(wù)器和從服務(wù)器是否正常運行

通知:如果某一個被監(jiān)控的實例出現(xiàn)問題余境,Sentinel 可以通過 API 發(fā)出通知

自動故障轉(zhuǎn)移(failover):如果主服務(wù)器發(fā)生故障驻呐,Sentinel 可以啟動故障轉(zhuǎn)移過程,把某臺服務(wù)器升級為主服務(wù)器芳来,并發(fā)出通知

配置管理:客戶端連接到 Sentinel含末,獲取當(dāng)前的 Redis 主服務(wù)器的地址

  • Sentinel實戰(zhàn)

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

hostname IP地址 節(jié)點角色&端口
master 192.168.8.203 Master:6379 / Sentinel:26379
slave1 192.168.8.204 Slave:6379 / Sentinel:26379
slave2 192.168.8.205 Slave:6379 / Sentinel:26379

以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 配置文件(安裝后根目錄下默認(rèn)有 sentinel.conf):

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

編輯sentinel.conf,三臺服務(wù)器內(nèi)容相同:

daemonize yes
#Sentinel 的默認(rèn)端口
port 26379
#是否允許外部網(wǎng)絡(luò)訪問
protected-mode no
#sentinel 的工作目錄
dir "/usr/local/soft/redis-5.0.5/sentinel-tmp"
#sentinel 監(jiān)控的 redis 主節(jié)點
sentinel monitor redis-master 192.168.8.203 6379 2
#master 宕機多久紊搪,才會被 Sentinel 主觀認(rèn)為下線
sentinel down-after-milliseconds redis-master 30000
#1 同一個 sentinel 對同一個 master 兩次 failover 之間的間隔時間
#2. 當(dāng)一個 slave 從一個錯誤的 master 那里同步數(shù)據(jù)開始計算時間,直到slave 被糾正為向正確的 master 那里同步數(shù)據(jù)時
#3.當(dāng)想要取消一個正在進行的 failover 所需要的時間
#4.當(dāng)進行 failover 時蜜葱,配置所有 slaves 指向新的 master 所需的最大時間
sentinel failover-timeout redis-master 180000
#這個配置項指定了在發(fā)生 failover 主備切換時最多可以有多少個 slave 同時對新的 master 進行 同步,這個數(shù)字越小耀石,完成 failover 所需的時間就越長牵囤,但是如果這個數(shù)字越大,就意味著越 多的 slave 因為 replication 而不可用,可以通過將這個值設(shè)為 1 來保證每次只有一個 slave 處于不能處理命令請求的狀態(tài)。
sentinel parallel-syncs redis-master 1

2揭鳞、Sentinel 驗證
啟動redis 服務(wù)和Sentinel(3個redis都啟動起來)

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):
redis> info replication

#203
127.0.0.1:6379>info replication
#Replication 
role:master
connected_slaves:2
slave0:192.168.8.204......
slave1:192.168.8.205......

#204和205
127.0.0.1:6379>info replication
#Replication 
role:slave
master_host:192.168.8.203
......

3炕贵、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 版本默認(rèn)是 Lettuce),都只需要配置全部哨兵的地址野崇,由哨兵返回當(dāng)前的 master 節(jié)點地址

  • 哨兵機制的不足
    1)主從切換的過程中會丟失數(shù)據(jù)鲁驶,因為只有一個 master
    2)只能單點寫,沒有解決水平擴容的問題
    3)如果數(shù)據(jù)量非常大舞骆,這個時候我們需要多個 master-slave 的 group钥弯,把數(shù)據(jù)分布到不同的 group 中

Redis分布式方案

如果要實現(xiàn) Redis 數(shù)據(jù)的分片(高可用),我們有三種方案:
1)在客戶端實現(xiàn)相關(guān)的邏輯督禽,例如用取拇圉或者一致性哈希對 key 進行分片,查詢和修改都先判斷 key 的路由
2)把做分片處理的邏輯抽取出來狈惫,運行一個獨立的代理服務(wù)睛蛛,客戶端連接到這個代理服務(wù),代理服務(wù)做請求的轉(zhuǎn)發(fā)胧谈。
3)基于服務(wù)端實現(xiàn)

  • 客戶端Sharding


    sharding.png

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

public class ShardingTest {
    public static void main(String[] args) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
?
        // Redis 服務(wù)器
        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++){
                System.out.println(jedis.get("k"+i));
            }
        }finally{
            if(jedis!=null) {
                jedis.close();
            }
        }
    }
}

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

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

  • 代理Proxy


    proxy.png

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

Twemproxy
twemproxy.png

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

Codis

Codis 是一個代理中間件俊抵,用 Go 語言開發(fā)的

功能:客戶端連接 Codis 跟連接 Redis 沒有區(qū)別

Codis Tewmproxy Redis Cluster
重新分片不需要重啟 Yes NO Yes
pipeline Yes Yes
多 key 操作的 hash tags {} Yes Yes Yes
重新分片時的多 key 操作 Yes Yes
客戶端支持 所有 所有 支持 cluster 協(xié)議的客戶端
Codis.png

分片原理:Codis 把所有的 key 分成了 N 個槽(例如 1024)谁不,每個槽對應(yīng)一個分組,一個分組對應(yīng)于一個或者一組 Redis 實例徽诲,Codis 對 key 進行 CRC32 運算刹帕,得到一個32 位的數(shù)字吵血,然后模以 N(槽的個數(shù)),得到余數(shù)偷溺,這個就是 key 對應(yīng)的槽蹋辅,槽后面就是 Redis 的實例

例如4個槽:


image.png

Codis 的槽位映射關(guān)系是保存在 Proxy 中的,如果要解決單點的問題挫掏,Codis 也要做集群部署

多個 Codis 節(jié)點怎么同步槽和實例的關(guān)系呢侦另?
需要運行一個 Zookeeper (或者 etcd/本地文件)

在新增節(jié)點的時候,可以為節(jié)點指定特定的槽位尉共。Codis 也提供了自動均衡策略

Codis 不支持事務(wù)褒傅,其他的一些命令也不支持

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

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

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

數(shù)據(jù)分片有幾個關(guān)鍵的問題需要解決:
1礼搁、數(shù)據(jù)怎么相對均勻地分片
2、客戶端怎么訪問到相應(yīng)的節(jié)點和數(shù)據(jù)
3目尖、重新分片的過程馒吴,怎么保證正常服務(wù)

1、架構(gòu)
Redis Cluster 可以看成是由多個 Redis 實例組成的數(shù)據(jù)集合卑雁,客戶端不需要關(guān)注數(shù)據(jù)的子集到底存儲在哪個節(jié)點募书,只需要關(guān)注這個集合整體

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

3 主 3 從架構(gòu).png

2鬼吵、搭建(CentOS 7 單機安裝Redis Cluster 3主3從)
直接把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

復(fù)制redis配置文件到7291目錄:

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

修改7291的配置文件:

port 7291
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復(fù)制到其他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

安裝ruby依賴、rubygems依賴齿椅、gem-redis依賴:

yum install ruby -y
yum install rubygems -y
gem install redis -v 3.0.7

啟動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會給出一個預(yù)計的方案涣脚,對6個節(jié)點分配3主3從示辈,如果認(rèn)為沒有問題,輸入yes確認(rèn):

>>> 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)建集群

連接到客戶端:

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

腳本內(nèi)容:

#!/bin/bash
for ((i=0;i<20000;i++))
do
echo -en "helloworld" | redis-cli -h 192.168.8.207 -p 7291 -c -x set name$i >>redis.log
done

#腳本授權(quán)并執(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 :列出集群當(dāng)前已知的所有節(jié)點(node)险耀,以及這些節(jié)點的相關(guān)信息
cluster meet :將 ip 和 port 所指定的節(jié)點添加到集群當(dāng)中弄喘,讓它成為集群的一份子
cluster forget <node_id> :從集群中移除 node_id 指定的節(jié)點(保證空槽道)
cluster replicate <node_id> :將當(dāng)前節(jié)點設(shè)置為 node_id 指定的節(jié)點的從節(jié)點
cluster saveconfig :將節(jié)點的配置文件保存到硬盤里面

#slot(槽)命令
cluster addslots [slot …] :將一個或多個槽(slot)指派(assign)給當(dāng)前節(jié)點
cluster delslots [slot …] :移除一個或多個槽對當(dāng)前節(jié)點的指派
cluster flushslots :移除指派給當(dāng)前節(jié)點的所有槽,讓當(dāng)前節(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é)點中導(dǎo)入槽 slot 到本節(jié)點
cluster setslot stable :取消對槽 slot 的導(dǎo)入(import)或者遷移(migrate)

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

3贬派、數(shù)據(jù)分布
如果是希望數(shù)據(jù)分布相對均勻的話急但,我們首先可以考慮哈希后取模

1)哈希后取模
例如,hash(key)%N搞乏,根據(jù)余數(shù)波桩,決定映射到那一個節(jié)點,這種方式比較簡單查描,屬于靜態(tài)的分片規(guī)則突委,但是一旦節(jié)點數(shù)量變化新增或者減少冬三,由于取模的 N 發(fā)生變化匀油,數(shù)據(jù)需要重新分布

為了解決這個問題,我們又有了一致性哈希算法

2)一致性哈希
原理:把所有的哈希值空間組織成一個虛擬的圓環(huán)(哈希環(huán))勾笆,整個空間按順時針方向組織敌蚜,因為是環(huán)形空間,0 和 2^32-1 是重疊的

假設(shè)我們有四臺機器要哈希環(huán)來實現(xiàn)映射(分布數(shù)據(jù))窝爪,我們先根據(jù)機器的名稱或者 IP 計算哈希值弛车,然后分布到哈希環(huán)中(紅色圈)


image.png

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


image.png

新增了一個 Node5 節(jié)點邀杏,不影響數(shù)據(jù)的分布


image.png

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


image.png

谷歌的 MurmurHash 就是一致性哈希算法,在分布式系統(tǒng)中望蜡,負(fù)載均衡唤崭、分庫分表等場景中都有應(yī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鳖眼,不均勻


image.png

Node1 設(shè)置了兩個虛擬節(jié)點摩疑,Node2 也設(shè)置了兩個虛擬節(jié)點(虛線圓圈)

這時候有 3 條數(shù)據(jù)分布到 Node1胀糜,1 條數(shù)據(jù)分布到 Node2


image.png

Redis虛擬槽分區(qū)
Redis 既沒有用哈希取模垢油,也沒有用一致性哈希壕探,而是用虛擬槽來實現(xiàn)的

Redis 創(chuàng)建了 16384 個槽(slot)飘诗,每個節(jié)點負(fù)責(zé)一定區(qū)間的 slot与倡,比如 Node1 負(fù)責(zé) 0-5460,Node2 負(fù)責(zé) 5461-10922昆稿,Node3 負(fù)責(zé) 10923-16383

image.png

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

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

查看key屬于哪個slot:
redis> cluster keyslot vincent

注意點:key 與 slot 的關(guān)系是永遠(yuǎn)不會變的畏陕,會變的只有 slot 和 Redis 節(jié)點的關(guān)系

怎么讓相關(guān)的數(shù)據(jù)落到同一個節(jié)點上配乓?
有些 multi key 操作是不能跨節(jié)點的,如果要讓某些數(shù)據(jù)分布到一個節(jié)點上惠毁,例如用戶 666 的基本信息和金融信息犹芹,怎么辦?

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

格式:
user{666}base=…
user{666}finish=…

127.0.0.1:7293> set a{vincent}a 1
OK
127.0.0.1:7293> set a{vincent}b 1
OK
127.0.0.1:7293> set a{vincent}c 1
OK
127.0.0.1:7293> set a{vincent}d 1
OK
127.0.0.1:7293> set a{vincent}e 1
OK

4盐固、客戶端重定向
在 7291 端口的 Redis 的 redis-cli 客戶端操作:

127.0.0.1:7291> set vincent 1
(error) MOVED 13724 127.0.0.1:7293

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

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

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

5、數(shù)據(jù)遷移
** key 和 slot** 的關(guān)系是永遠(yuǎn)不會變的洒嗤,當(dāng)新增了節(jié)點的時候箫荡,需要把原有的 slot分配給新的節(jié)點負(fù)責(zé),并且把相關(guān)的數(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)

6间唉、高可用和主從切換原理
當(dāng) 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 響應(yīng),判斷請求者的合法性昨悼,并發(fā)送FAILOVER_AUTH_ACK蝗锥,對每一個 epoch 只發(fā)送一次 ack
4.嘗試 failover 的 slave 收集 FAILOVER_AUTH_ACK
5.超過半數(shù)后變成新 Master
6.廣播 Ping 通知其他集群節(jié)點

Redis Cluster 既能夠?qū)崿F(xiàn)主從的角色分配,又能夠?qū)崿F(xiàn)主從切換幔戏,相當(dāng)于集成了Replication 和 Sentinal 的功能

7玛追、總結(jié)

優(yōu)勢:

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

不足:

  1. Client 實現(xiàn)復(fù)雜益老,驅(qū)動要求實現(xiàn) Smart Client,緩存 slots mapping 信息并及時更新寸莫,提高了開發(fā)難度捺萌,客戶端的不成熟影響業(yè)務(wù)的穩(wěn)定性
  2. 節(jié)點會因為某些原因發(fā)生阻塞(阻塞時間大于 clutser-node-timeout),被判斷下線膘茎,這種 failover 是沒有必要的
  3. 數(shù)據(jù)通過異步復(fù)制桃纯,不保證數(shù)據(jù)的強一致性
  4. 多個業(yè)務(wù)使用同一套集群時酷誓,無法根據(jù)統(tǒng)計區(qū)分冷熱數(shù)據(jù),資源隔離性較差态坦,容易出現(xiàn)相互影響的情況
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盐数,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伞梯,更是在濱河造成了極大的恐慌玫氢,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壮锻,死亡現(xiàn)場離奇詭異琐旁,居然都是意外死亡,警方通過查閱死者的電腦和手機猜绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門灰殴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掰邢,你說我怎么就攤上這事牺陶。” “怎么了辣之?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵掰伸,是天一觀的道長。 經(jīng)常有香客問我怀估,道長狮鸭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任多搀,我火速辦了婚禮歧蕉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘康铭。我一直安慰自己惯退,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布从藤。 她就那樣靜靜地躺著催跪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪夷野。 梳的紋絲不亂的頭發(fā)上懊蒸,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音悯搔,去河邊找鬼榛鼎。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的者娱。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼苏揣,長吁一口氣:“原來是場噩夢啊……” “哼黄鳍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起平匈,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤框沟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后增炭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忍燥,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年隙姿,在試婚紗的時候發(fā)現(xiàn)自己被綠了梅垄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡输玷,死狀恐怖队丝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欲鹏,我是刑警寧澤机久,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站赔嚎,受9級特大地震影響膘盖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尤误,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一侠畔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袄膏,春花似錦践图、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至斥黑,卻和暖如春揖盘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锌奴。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工兽狭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓箕慧,卻偏偏與公主長得像服球,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子颠焦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容