[04][02][03] Redis 分布式篇

為什么需要 Redis 集群

為什么需要集群?

性能

Redis 本身的 QPS 已經(jīng)很高了,但是如果在一些并發(fā)量非常高的情況下,性能還是會(huì)受到影響.這個(gè)時(shí)候我們希望有更多的 Redis 服務(wù)來完成工作

擴(kuò)展

第二個(gè)是出于存儲(chǔ)的考慮.因?yàn)?Redis 所有的數(shù)據(jù)都放在內(nèi)存中,如果數(shù)據(jù)量大,很容易受到硬件的限制.升級(jí)硬件收效和成本比太低,所以我們需要有一種橫向擴(kuò)展的方法

可用性

第三個(gè)是可用性和安全的問題.如果只有一個(gè) Redis 服務(wù),一旦服務(wù)宕機(jī),那么所有的客戶端都無法訪問,會(huì)對(duì)業(yè)務(wù)造成很大的影響.另一個(gè),如果硬件發(fā)生故障,而單機(jī)的數(shù)據(jù)無法恢復(fù)的話,帶來的影響也是災(zāi)難性的
可用性,數(shù)據(jù)安全,性能都可以通過搭建多個(gè) Reids 服務(wù)實(shí)現(xiàn).其中有一個(gè)是主節(jié)點(diǎn)(master),可以有多個(gè)從節(jié)點(diǎn)(slave).主從之間通過數(shù)據(jù)同步,存儲(chǔ)完全相同的數(shù)據(jù).如果主節(jié)點(diǎn)發(fā)生故障,則把某個(gè)從節(jié)點(diǎn)改成主節(jié)點(diǎn),訪問新的主節(jié)點(diǎn)

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

主從復(fù)制配置

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

slaveof 192.168.8.203 6379

//在主從切換的時(shí)候,這個(gè)配置會(huì)被重寫成:

# Generated by CONFIG REWRITE
replicaof 192.168.8.203 6379

或者在啟動(dòng)服務(wù)時(shí)通過參數(shù)指定 master 節(jié)點(diǎn):

./redis-server --slaveof 192.168.8.203 6379

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

redis> info replication

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

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

主節(jié)點(diǎn)寫入后,slave 會(huì)自動(dòng)從 master 同步數(shù)據(jù)
斷開復(fù)制:

redis> slaveof no one

此時(shí)從節(jié)點(diǎn)會(huì)變成自己的主節(jié)點(diǎn),不再?gòu)?fù)制數(shù)據(jù)

主從復(fù)制原理

連接階段

  • slave node 啟動(dòng)時(shí)(執(zhí)行 slaveof 命令),會(huì)在自己本地保存 master node 的信息,包括 master node 的 host 和 ip
  • slave node 內(nèi)部有個(gè)定時(shí)任務(wù) replicationCron(源碼 replication.c),每隔 1 秒鐘檢查是否有新的 master node 要連接和復(fù)制,如果發(fā)現(xiàn),就跟 master node 建立 socket 網(wǎng)絡(luò)連接,如果連接成功,從節(jié)點(diǎn)為該 socket 建立一個(gè)專門處理復(fù)制工作的文件事件處理器,負(fù)責(zé)后續(xù)的復(fù)制工作,如接收 RDB 文件,接收命令傳播等

當(dāng)從節(jié)點(diǎn)變成了主節(jié)點(diǎn)的一個(gè)客戶端之后,會(huì)給主節(jié)點(diǎn)發(fā)送 ping 請(qǐng)求

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

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

問題:生成 RDB 期間,master 接收到的命令怎么處理
開始生成 RDB 文件時(shí),master 會(huì)把所有新的寫命令緩存在內(nèi)存中.在 slave node 保存了 RDB 之后,再將新的寫命令復(fù)制給 slave node

命令傳播階段

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

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

repl-disable-tcp-nodelay no

當(dāng)設(shè)置為 yes 時(shí),TCP 會(huì)對(duì)包進(jìn)行合并從而減少帶寬,但是發(fā)送的頻率會(huì)降低,從節(jié)點(diǎn)數(shù)據(jù)延遲增加,一致性變差亭饵;具體發(fā)送頻率與 Linux 內(nèi)核的配置有關(guān),默認(rèn)配置為 40ms.當(dāng)設(shè)置為 no 時(shí),TCP 會(huì)立馬將主節(jié)點(diǎn)的數(shù)據(jù)發(fā)送給從節(jié)點(diǎn),帶寬增加但延遲變小
一般來說,只有當(dāng)應(yīng)用對(duì) Redis 數(shù)據(jù)不一致的容忍度較高,且主從節(jié)點(diǎn)之間網(wǎng)絡(luò)狀況不好時(shí),才會(huì)設(shè)置為 yes;多數(shù)情況使用默認(rèn)值 no

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

redis> info replication

主從復(fù)制的不足

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

  • RDB 文件過大的情況下,同步非常耗時(shí)
  • 在一主一從或者一主多從的情況下,如果主服務(wù)器掛了,對(duì)外提供的服務(wù)就不可用了,單點(diǎn)問題沒有得到解決.如果每次都是手動(dòng)把之前的從服務(wù)器切換成主服務(wù)器,這個(gè)比較費(fèi)時(shí)費(fèi)力,還會(huì)造成一定時(shí)間的服務(wù)不可用

可用性保證之 Sentinel

Sentinel 原理

如何實(shí)現(xiàn)主從的自動(dòng)切換?我們的思路:
創(chuàng)建一臺(tái)監(jiān)控服務(wù)器來監(jiān)控所有 Redis 服務(wù)節(jié)點(diǎn)的狀態(tài),比如,master 節(jié)點(diǎn)超過一定時(shí)間沒有給監(jiān)控服務(wù)器發(fā)送心跳報(bào)文,就把 master 標(biāo)記為下線,然后把某一個(gè) slave 變成 master.應(yīng)用每一次都是從這個(gè)監(jiān)控服務(wù)器拿到 master 的地址

問題是:如果監(jiān)控服務(wù)器本身出問題了怎么辦?那我們就拿不到 master 的地址了,應(yīng)用也沒有辦法訪問
那我們?cè)賱?chuàng)建一個(gè)監(jiān)控服務(wù)器,來監(jiān)控監(jiān)控服務(wù)器……似乎陷入死循環(huán)了,這個(gè)問題怎么解決?這個(gè)問題先放著
Redis 的 Sentinel 就是這種思路:通過運(yùn)行監(jiān)控服務(wù)器來保證服務(wù)的可用性

官網(wǎng):https://redis.io/topics/sentinel
從 Redis2.8 版本起,提供了一個(gè)穩(wěn)定版本的 Sentinel(哨兵),用來解決高可用的問題.它是一個(gè)特殊狀態(tài)的 redis 實(shí)例
我們會(huì)啟動(dòng)一個(gè)或者多個(gè) Sentinel 的服務(wù)(通過 src/redis-sentinel),它本質(zhì)上只是一個(gè)運(yùn)行在特殊模式之下的 Redis,Sentinel 通過 info 命令得到被監(jiān)聽 Redis 機(jī)器的 master,slave 等信息

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

注意:Sentinel 本身沒有主從之分,只有 Redis 服務(wù)節(jié)點(diǎn)有主從之分
概念梳理:master,slave(redisgroup),sentinel,sentinel 集合

服務(wù)下線

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

# sentinel.conf
sentinel down-after-milliseconds <master-name> <milliseconds>

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

故障轉(zhuǎn)移

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

Ratf 算法

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

大體上有兩個(gè)步驟:領(lǐng)導(dǎo)選舉,數(shù)據(jù)復(fù)制
Raft 是一個(gè)共識(shí)算法(consensus algorithm).比如比特幣之類的加密貨幣,就需要共識(shí)算法.SpringCloud 的注冊(cè)中心解決方案 Consul 也用到了 Raft 協(xié)議
Raft 的核心思想:先到先得,少數(shù)服從多數(shù)

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

總結(jié):Sentinle 的 Raft 算法和 Raft 論文略有不同

  • master 客觀下線觸發(fā)選舉,而不是過了 election timeout 時(shí)間開始選舉
  • Leader 并不會(huì)把自己成為 Leader 的消息發(fā)給其他 Sentinel.其他 Sentinel 等待 Leader 從 slave 選出 master 后,檢測(cè)到新的 master 正常工作后,就會(huì)去掉客觀下線的標(biāo)識(shí),從而不需要進(jìn)入故障轉(zhuǎn)移流程

故障轉(zhuǎn)移

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

  • 選出 Sentinel Leader 之后,由 Sentinel Leader 向某個(gè)節(jié)點(diǎn)發(fā)送 slaveof no one 命令,讓它成為獨(dú)立節(jié)點(diǎn)
  • 然后向其他節(jié)點(diǎn)發(fā)送 slaveof x.x.x.xxxxx(本機(jī)服務(wù)),讓它們成為這個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn),故障轉(zhuǎn)移完成

問題:這么多從節(jié)點(diǎn),選誰(shuí)成為主節(jié)點(diǎn)?
關(guān)于從節(jié)點(diǎn)選舉,一共有四個(gè)因素影響選舉的結(jié)果,分別是斷開連接時(shí)長(zhǎng),優(yōu)先級(jí)排序,復(fù)制數(shù)量,進(jìn)程 id
如果與哨兵連接斷開的比較久,超過了某個(gè)閾值,就直接失去了選舉權(quán).如果擁有選舉權(quán),那就看誰(shuí)的優(yōu)先級(jí)高,這個(gè)在配置文件里可以設(shè)置(replica-priority 100),數(shù)值越小優(yōu)先級(jí)越高
如果優(yōu)先級(jí)相同,就看誰(shuí)從 master 中復(fù)制的數(shù)據(jù)最多(復(fù)制偏移量最大),選最多的那個(gè),如果復(fù)制數(shù)量也相同,就選擇進(jìn)程 id 最小的那個(gè)

Sentinel 的功能總結(jié)

  • Monitoring.Sentinelconstantlychecksifyourmasterandslaveinstancesareworkingasexpected
  • Notification.Sentinelcannotifythesystemadministrator,anothercomputerprograms,viaanAPI,thatsomethingiswrongwithoneofthemonitoredRedisinstances
  • Automaticfailover.Ifamasterisnotworkingasexpected,Sentinelcanstartafailoverprocesswhereaslaveispromotedtomaster,theotheradditionalslavesarereconfiguredtousethenewmaster,andtheapplicationsusingtheRedisserverinformedaboutthenewaddresstousewhenconnecting
  • Configurationprovider.Sentinelactsasasourceofauthorityforclientsservicediscovery:clientsconnecttoSentinelsinordertoaskfortheaddressofthecurrentRedismasterresponsibleforagivenservice.Ifafailoveroccurs,Sentinelswillreportthenewaddress

監(jiān)控:Sentinel 會(huì)不斷檢查主服務(wù)器和從服務(wù)器是否正常運(yùn)行
通知:如果某一個(gè)被監(jiān)控的實(shí)例出現(xiàn)問題,Sentinel 可以通過 API 發(fā)出通知

自動(dòng)故障轉(zhuǎn)移(failover):如果主服務(wù)器發(fā)生故障,Sentinel 可以啟動(dòng)故障轉(zhuǎn)移過程.把某臺(tái)服務(wù)器升級(jí)為主服務(wù)器,并發(fā)出通知
配置管理:客戶端連接到 Sentinel,獲取當(dāng)前的 Redis 主服務(wù)器的地址

Sentinel 實(shí)戰(zhàn)

Sentinel 配置

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

hostname IP 地址 節(jié)點(diǎn)角色&端口
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

三臺(tái)服務(wù)器內(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 個(gè)'redis-master',這個(gè)名稱要統(tǒng)一,并且使用客戶端(比如 Jedis)連接的時(shí)候名稱要正確

hostname IP 地址
protected-mode 是否允許外部網(wǎng)絡(luò)訪問
dir sentinel 的工作目錄
sentinel monitorsentinel 監(jiān)控的 redis 主節(jié)點(diǎn)
down-after-milliseconds(毫秒) master 宕機(jī)多久,才會(huì)被 Sentinel 主觀認(rèn)為下線
sentinelfailover-timeout(毫秒) 1.同一個(gè) sentinel 對(duì)同一個(gè) master 兩次 failover 之間的間隔時(shí)間,2.當(dāng)一個(gè) slave 從一個(gè)錯(cuò)誤的 master 那里同步數(shù)據(jù)開始計(jì)算時(shí)間.直到 slave 被糾正為向正確的 master 那里同步數(shù)據(jù)時(shí),3.當(dāng)想要取消一個(gè)正在進(jìn)行的 failover 所需要的時(shí)間,4.當(dāng)進(jìn)行 failover 時(shí),配置所有 slaves 指向新的 master 所需的最大時(shí)間
parallel-syncs 這個(gè)配置項(xiàng)指定了在發(fā)生 failover 主備切換時(shí)最多可以有多少個(gè) slave 同時(shí)對(duì)新的 master 進(jìn)行同步,這個(gè)數(shù)字越小,完成 failover 所需的時(shí)間就越長(zhǎng),但是如果這個(gè)數(shù)字越大,就意味著越多的 slave 因?yàn)?replication 而不可用.可以通過將這個(gè)值設(shè)為 1 來保證每次只有一個(gè) slave 處于不能處理命令請(qǐng)求的狀態(tài)

Sentinel 驗(yàn)證

啟動(dòng) Redis 服務(wù)和 Sentinel

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

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

redis> info replication

203


204和205


模擬 master 宕機(jī),在 203 執(zhí)行:

redis> shutdown

205 被選為新的 Master,只有一個(gè) Slave 節(jié)點(diǎn)


注意看 sentinel.conf 里面的 redis-master 被修改了!
模擬原 master 恢復(fù),在 203 啟動(dòng) redis-server.它還是 slave,但是 master 又有兩個(gè) slave 了
slave 宕機(jī)和恢復(fù)省略

Sentinel 連接使用

Jedis 連接 Sentinel(gupao-jedis:JedisSentinelTest.java)
mastername 來自于 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;
}

SpringBoot 連接 Sentinel(springboot-redis:RedisAppTest.java)

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 還是 SpringBoot(2.x 版本默認(rèn)是 Lettuce),都只需要配置全部哨兵的地址,由哨兵返回當(dāng)前的 master 節(jié)點(diǎn)地址

哨兵機(jī)制的不足

主從切換的過程中會(huì)丟失數(shù)據(jù),因?yàn)橹挥幸粋€(gè) master
只能單點(diǎn)寫,沒有解決水平擴(kuò)容的問題
如果數(shù)據(jù)量非常大,這個(gè)時(shí)候我們需要多個(gè) master-slave 的 group,把數(shù)據(jù)分布到不同的 group 中
問題來了,數(shù)據(jù)怎么分片?分片之后,怎么實(shí)現(xiàn)路由?

Redis 分布式方案

如果要實(shí)現(xiàn) Redis 數(shù)據(jù)的分片,我們有三種方案.第一種是在客戶端實(shí)現(xiàn)相關(guān)的邏輯,例如用取模或者一致性哈希對(duì) key 進(jìn)行分片,查詢和修改都先判斷 key 的路由
第二種是把做分片處理的邏輯抽取出來,運(yùn)行一個(gè)獨(dú)立的代理服務(wù),客戶端連接到這個(gè)代理服務(wù),代理服務(wù)做請(qǐng)求的轉(zhuǎn)發(fā)
第三種就是基于服務(wù)端實(shí)現(xiàn)

客戶端 Sharding

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

ShardedJedis

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();
            }
        }
    }
}

(作業(yè))Sharded 分片的原理?怎么連接到某一個(gè) Redis 服務(wù)?
使用 ShardedJedis 之類的客戶端分片代碼的優(yōu)勢(shì)是配置簡(jiǎn)單,不依賴于其他中間件,分區(qū)的邏輯可以自定義,比較靈活.但是基于客戶端的方案,不能實(shí)現(xiàn)動(dòng)態(tài)的服務(wù)增減,每個(gè)客戶端需要自行維護(hù)分片策略,存在重復(fù)代碼
第二種思路就是把分片的代碼抽取出來,做成一個(gè)公共服務(wù),所有的客戶端都連接到這個(gè)代理層.由代理層來實(shí)現(xiàn)請(qǐng)求和轉(zhuǎn)發(fā)

代理 Proxy

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

Twemproxy

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

Twemproxy 的優(yōu)點(diǎn):比較穩(wěn)定,可用性高
不足:

  • 出現(xiàn)故障不能自動(dòng)轉(zhuǎn)移,架構(gòu)復(fù)雜,需要借助其他組件(LVS/HAProxy + Keepalived)實(shí)現(xiàn) HA
  • 擴(kuò)縮容需要修改配置,不能實(shí)現(xiàn)平滑地?cái)U(kuò)縮容(需要重新分布數(shù)據(jù))

Codis

https://github.com/CodisLabs/codis
Codis 是一個(gè)代理中間件,用 Go 語(yǔ)言開發(fā)
功能:客戶端連接 Codis 跟連接 Redis 沒有區(qū)別

Codis Tewmproxy RedisCluster
重新分片不需要重啟 Yes No Yes
pipeline Yes Yes
多 key 操作的 hash tags {} Yes Yes Yes
重新分片時(shí)的多 key 操作 Yes - No
客戶端支持 所有 所有 支持 cluster 協(xié)議的客戶端

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


Codis 的槽位映射關(guān)系是保存在 Proxy 中的,如果要解決單點(diǎn)的問題,Codis 也要做集群部署,多個(gè) Codis 節(jié)點(diǎn)怎么同步槽和實(shí)例的關(guān)系呢?需要運(yùn)行一個(gè) Zookeeper(或者 etcd/本地文件)
在新增節(jié)點(diǎn)的時(shí)候,可以為節(jié)點(diǎn)指定特定的槽位.Codis 也提供了自動(dòng)均衡策略
Codis 不支持事務(wù),其他的一些命令也不支持

不支持的命令
https://github.com/CodisLabs/codis/blob/release3.2/doc/unsupported_cmds.md

獲取數(shù)據(jù)原理(mget):在 Redis 中的各個(gè)實(shí)例里獲取到符合的 key,然后再匯總到 Codis 中
Codis 是第三方提供的分布式解決方案,在官方的集群功能穩(wěn)定之前,Codis 也得到了大量的應(yīng)用

Redis Cluster

https://redis.io/topics/cluster-tutorial/

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

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

  • 數(shù)據(jù)怎么相對(duì)均勻地分片
  • 客戶端怎么訪問到相應(yīng)的節(jié)點(diǎn)和數(shù)據(jù)
  • 重新分片的過程,怎么保證正常服務(wù)

架構(gòu)

Redis Cluster 可以看成是由多個(gè) Redis 實(shí)例組成的數(shù)據(jù)集合.客戶端不需要關(guān)注數(shù)據(jù)的子集到底存儲(chǔ)在哪個(gè)節(jié)點(diǎn),只需要關(guān)注這個(gè)集合整體
以3主3從為例,節(jié)點(diǎn)之間兩兩交互,共享數(shù)據(jù)分片,節(jié)點(diǎn)狀態(tài)等信息


搭建

https://gper.club/articles/7e7e7f7ff7g5egc7g6d

配置啟動(dòng)進(jìn)入客戶端:

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

批量插入數(shù)據(jù)


問題:Cluster 解決分片的問題,數(shù)據(jù)怎么分布?

數(shù)據(jù)分布

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

哈希后取模

例如,hash(key)%N,根據(jù)余數(shù),決定映射到那一個(gè)節(jié)點(diǎn).這種方式比較簡(jiǎn)單,屬于靜態(tài)的分片規(guī)則.但是一旦節(jié)點(diǎn)數(shù)量變化,新增或者減少,由于取模的 N 發(fā)生變化,數(shù)據(jù)需要重新分布
為了解決這個(gè)問題,我們又有了一致性哈希算法

一致性哈希

一致性哈希的原理:
把所有的哈希值空間組織成一個(gè)虛擬的圓環(huán)(哈希環(huán)),整個(gè)空間按順時(shí)針方向組織.因?yàn)槭黔h(huán)形空間,0 和 2^32-1 是重疊的

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


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


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


刪除了一個(gè)節(jié)點(diǎn) Node4,只影響相鄰的一個(gè)節(jié)點(diǎn)


谷歌的 MurmurHash 就是一致性哈希算法.在分布式系統(tǒng)中,負(fù)載均衡,分庫(kù)分表等場(chǎng)景中都有應(yīng)用

一致性哈希解決了動(dòng)態(tài)增減節(jié)點(diǎn)時(shí),所有數(shù)據(jù)都需要重新分布的問題,它只會(huì)影響到下一個(gè)相鄰的節(jié)點(diǎn),對(duì)其他節(jié)點(diǎn)沒有影響
但是這樣的一致性哈希算法有一個(gè)缺點(diǎn),因?yàn)楣?jié)點(diǎn)不一定是均勻地分布的,特別是在節(jié)點(diǎn)數(shù)比較少的情況下,所以數(shù)據(jù)不能得到均勻分布.解決這個(gè)問題的辦法是引入虛擬節(jié)點(diǎn)(Virtual Node)
比如:2 個(gè)節(jié)點(diǎn),5 條數(shù)據(jù),只有 1 條分布到 Node2,4 條分布到 Node1,不均勻


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



Redis 虛擬槽分區(qū)

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



3 個(gè) Redis 節(jié)點(diǎn),各自負(fù)責(zé)的 slot

Redis 的每個(gè) master 節(jié)點(diǎn)維護(hù)一個(gè) 16384 位(2048bytes=2KB)的位序列,比如:序列的第 0 位是 1,就代表第一個(gè) slot 是它負(fù)責(zé)砂豌;序列的第 1 位是 0,代表第二個(gè) slot 不歸它負(fù)責(zé)
對(duì)象分布到 Redis 節(jié)點(diǎn)上時(shí),對(duì) key 用 CRC16 算法計(jì)算再%16384,得到一個(gè) slot 的值,數(shù)據(jù)落到負(fù)責(zé)這個(gè) slot 的 Redis 節(jié)點(diǎn)上
查看 key 屬于哪個(gè) slot:

redis> cluster keyslot qingshan

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

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

在 key 里面加入{hashtag}即可.Redis 在計(jì)算槽編號(hào)的時(shí)候只會(huì)獲取{}之間的字符串進(jìn)行槽編號(hào)計(jì)算,這樣由于上面兩個(gè)不同的鍵,{}里面的字符串是相同的,因此他們可以被計(jì)算出相同的槽

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

問題:客戶端連接到哪一臺(tái)服務(wù)器?訪問的數(shù)據(jù)不在當(dāng)前節(jié)點(diǎn)上,怎么辦?

客戶端重定向

比如在 7291 端口的 Redis 的 redis-cli 客戶端操作:

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

服務(wù)端返回 MOVED,也就是根據(jù) key 計(jì)算出來的 slot 不歸 7191 端口管理,而是歸 7293 端口管理,服務(wù)端返回 MOVED 告訴客戶端去 7293 端口操作
這個(gè)時(shí)候更換端口,用 redis-cli–p7293 操作,才會(huì)返回 OK.或者用./redis-cli-c-pport 的命令(c 代表 cluster).這樣客戶端需要連接兩次

Jedis 等客戶端會(huì)在本地維護(hù)一份 slot——node 的映射關(guān)系,大部分時(shí)候不需要重定向,所以叫做 smartjedis(需要客戶端支持)

問題:新增或下線了 Master 節(jié)點(diǎn),數(shù)據(jù)怎么遷移(重新分配)?

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

因?yàn)?key 和 slot 的關(guān)系是永遠(yuǎn)不會(huì)變的,當(dāng)新增了節(jié)點(diǎn)的時(shí)候,需要把原有的 slot 分配給新的節(jié)點(diǎn)負(fù)責(zé),并且把相關(guān)的數(shù)據(jù)遷移過來
添加新節(jié)點(diǎn)(新增一個(gè)7297):

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

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

redis-cli --cluster reshard 127.0.0.1:7291

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

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

高可用和主從切換原理

當(dāng) slave 發(fā)現(xiàn)自己的 master 變?yōu)?FAIL 狀態(tài)時(shí),便嘗試進(jìn)行 Failover,以期成為新的 master.由于掛掉的 master 可能會(huì)有多個(gè) slave,從而存在多個(gè) slave 競(jìng)爭(zhēng)成為 master 節(jié)點(diǎn)的過程,其過程如下:

  • slave 發(fā)現(xiàn)自己的 master 變?yōu)?FAIL
  • 將自己記錄的集群 currentEpoch 加 1,并廣播 FAILOVER_AUTH_REQUEST 信息
  • 其他節(jié)點(diǎn)收到該信息,只有 master 響應(yīng),判斷請(qǐng)求者的合法性,并發(fā)送 FAILOVER_AUTH_ACK,對(duì)每一個(gè) epoch 只發(fā)送一次 ack
  • 嘗試 failover 的 slave 收集 FAILOVER_AUTH_ACK
  • 超過半數(shù)后變成新 Master
  • 廣播 Pong 通知其他集群節(jié)點(diǎn)

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

總結(jié):

優(yōu)勢(shì)

  • 無中心架構(gòu)
  • 數(shù)據(jù)按照 slot 存儲(chǔ)分布在多個(gè)節(jié)點(diǎn),節(jié)點(diǎn)間數(shù)據(jù)共享,可動(dòng)態(tài)調(diào)整數(shù)據(jù)分布
  • 可擴(kuò)展性,可線性擴(kuò)展到1000個(gè)節(jié)點(diǎn)(官方推薦不超過1000個(gè)),節(jié)點(diǎn)可動(dòng)態(tài)添加或刪除
  • 高可用性,部分節(jié)點(diǎn)不可用時(shí),集群仍可用.通過增加 Slave 做 standby 數(shù)據(jù)副本,能夠?qū)崿F(xiàn)故障自動(dòng) failover,節(jié)點(diǎn)之間通過 gossip 協(xié)議交換狀態(tài)信息,用投票機(jī)制完成 Slave 到 Master 的角色提升
  • 降低運(yùn)維成本,提高系統(tǒng)的擴(kuò)展性和可用性

不足

  • Client 實(shí)現(xiàn)復(fù)雜,驅(qū)動(dòng)要求實(shí)現(xiàn) Smart Client,緩存 slot smapping 信息并及時(shí)更新,提高了開發(fā)難度,客戶端的不成熟影響業(yè)務(wù)的穩(wěn)定性
  • 節(jié)點(diǎn)會(huì)因?yàn)槟承┰虬l(fā)生阻塞(阻塞時(shí)間大于 clutser-node-timeout),被判斷下線,這種 failover 是沒有必要的
  • 數(shù)據(jù)通過異步復(fù)制,不保證數(shù)據(jù)的強(qiáng)一致性
  • 多個(gè)業(yè)務(wù)使用同一套集群時(shí),無法根據(jù)統(tǒng)計(jì)區(qū)分冷熱數(shù)據(jù),資源隔離性較差,容易出現(xiàn)相互影響的情況
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛮寂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子樟遣,更是在濱河造成了極大的恐慌,老刑警劉巖郭脂,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件年碘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡展鸡,警方通過查閱死者的電腦和手機(jī)屿衅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莹弊,“玉大人涤久,你說我怎么就攤上這事∪坛冢” “怎么了响迂?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)细疚。 經(jīng)常有香客問我蔗彤,道長(zhǎng),這世上最難降的妖魔是什么疯兼? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任然遏,我火速辦了婚禮,結(jié)果婚禮上吧彪,老公的妹妹穿的比我還像新娘待侵。我一直安慰自己,他們只是感情好姨裸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布秧倾。 她就那樣靜靜地躺著怨酝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪那先。 梳的紋絲不亂的頭發(fā)上农猬,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音胃榕,去河邊找鬼盛险。 笑死,一個(gè)胖子當(dāng)著我的面吹牛勋又,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播换帜,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼楔壤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了惯驼?” 一聲冷哼從身側(cè)響起蹲嚣,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎祟牲,沒想到半個(gè)月后隙畜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡说贝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年议惰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乡恕。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡言询,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出傲宜,到底是詐尸還是另有隱情运杭,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布函卒,位于F島的核電站辆憔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏报嵌。R本人自食惡果不足惜虱咧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沪蓬。 院中可真熱鬧彤钟,春花似錦、人聲如沸跷叉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至梆砸,卻和暖如春转质,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帖世。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工休蟹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人日矫。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓赂弓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親哪轿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盈魁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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

  • Redis分布式篇 1 為什么 需要 Redis 集群 1.1 為什么需要集群? 1.1.1 性能 ? R...
    Java小窩閱讀 292評(píng)論 0 0
  • 1.主從復(fù)制的原理: 1) 連接階段 1.slave node 啟動(dòng)時(shí)(執(zhí)行 slaveof 命令),會(huì)在自己本地...
    三個(gè)石頭_260a閱讀 478評(píng)論 0 0
  • 為什么需要Redis 集群 性能 Redis 本身的QPS 已經(jīng)很高了窃诉,但是如果在一些并發(fā)量非常高的情況下杨耙,性能還...
    WEIJAVA閱讀 404評(píng)論 0 2
  • 課程目標(biāo)1、 掌握 Redis 主從復(fù)制的配置和原理2脖旱、 掌握 Redis 哨兵機(jī)制(Sentinel)原理和實(shí)戰(zhàn)...
    源碼之路閱讀 733評(píng)論 0 6
  • 為什么要用Redis集群 為什么要用集群堪遂? 1、性能Redis 本身的 QPS(官方數(shù)據(jù):10萬(wàn)/秒) 已經(jīng)很高了...
    vincent浩哥閱讀 472評(píng)論 0 0