Redis分布式篇

Redis分布式篇

1 為什么 需要 Redis 集群

1.1 為什么需要集群?

1.1.1 性能

? Redis 本身的 QPS 已經很高了,但是如果在一些并發(fā)量非常高的情況下蛹头,性能還是會受到影響。這個時候我們希望有更多的 Redis 服務來完成工作砰盐。

1.1.2 擴展

? 第二個是出于存儲的考慮。因為 Redis 所有的數據都放在內存中豁护,如果數據量大速缨,很容易受到硬件的限制杏头。升級硬件收效和成本比太低盈包,所以我們需要有一種橫向擴展的方法

1.1.3 可用性

? 第三個是可用性和安全的問題。如果只有一個 Redis 服務醇王,一旦服務宕機呢燥,那么所有的客戶端都無法訪問,會對業(yè)務造成很大的影響寓娩。另一個叛氨,如果硬件發(fā)生故障呼渣,而單機的數據無法恢復的話,帶來的影響也是災難性的寞埠。

? 可用性屁置、數據安全、性能都可以通過搭建多個 Reids 服務實現仁连。其中有一個是主節(jié)點(master)蓝角,可以有多個從節(jié)點(slave)。主從之間通過數據同步饭冬,存儲完全相同的數據使鹅。如果主節(jié)點發(fā)生故障,則把某個從節(jié)點改成主節(jié)點昌抠,訪問新的主節(jié)點患朱。

2 Redis 主從 復制 ( replication )

2.1 主從 復制配置

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

slaveof 192.168.8.203 6379

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

# Generated by CONFIG REWRITE
replicaof 192.168.8.203 6379

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

./redis-server --slaveof 192.168.8.203 637

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

redis> info replication

從節(jié)點不能寫入數據(只讀),只能從 master 節(jié)點同步數據倦淀。get 成功蒋畜,set 失敗。

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

主節(jié)點寫入后撞叽,slave 會自動從 master 同步數據姻成。
斷開復制:

redis> slaveof no one

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

2.2 主從復制原理

2.2.1 連接 階段

? 1科展、slave node 啟動時(執(zhí)行 slaveof 命令),會在自己本地保存 master node 的信息糠雨,包括 master node 的 host 和 ip才睹。
? 2、slave node 內部有個定時任務 replicationCron(源碼 replication.c)甘邀,每隔 1秒鐘檢查是否有新的 master node 要連接和復制琅攘,如果發(fā)現,就跟 master node 建立socket 網絡連接松邪,如果連接成功坞琴,從節(jié)點為該 socket 建立一個專門處理復制工作的文件事件處理器,負責后續(xù)的復制工作逗抑,如接收 RDB 文件剧辐、接收命令傳播等寒亥。當從節(jié)點變成了主節(jié)點的一個客戶端之后,會給主節(jié)點發(fā)送 ping 請求荧关。

2.2.2 數據 同步階段

? 3溉奕、master node 第一次執(zhí)行全量復制,通過 bgsave 命令在本地生成一份 RDB 快照羞酗,將 RDB 快照文件發(fā)給 slave node(如果超時會重連腐宋,可以調大 repl-timeout 的值)。slave node 首先清除自己的舊數據檀轨,然后用 RDB 文件加載數據胸竞。

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

開始生成 RDB 文件時卫枝,master 會把所有新的寫命令緩存在內存中。在 slave node保存了 RDB 之后讹挎,再將新的寫命令復制給 slave node校赤。

2.2.3 命令傳播階段

? 4、master node 持續(xù)將寫命令筒溃,異步復制給 slave node

? 延遲是不可避免的马篮,只能通過優(yōu)化網絡。

repl-disable-tcp-nodelay no

當設置為 yes 時怜奖,TCP 會對包進行合并從而減少帶寬浑测,但是發(fā)送的頻率會降低,從節(jié)點數據延遲增加歪玲,一致性變差迁央;具體發(fā)送頻率與 Linux 內核的配置有關,默認配置為40ms滥崩。當設置為 no 時岖圈,TCP 會立馬將主節(jié)點的數據發(fā)送給從節(jié)點,帶寬增加但延遲變小钙皮。

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

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

通過 master_repl_offset 記錄的偏移量

redis> info replication
1571747119946.png

2.3 主從復制的 不足

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

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

3 可用性保證之 Sentinel

3.1 Sentinel 原理

? 如何實現主從的自動切換?我們的思路:
? 創(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)控服務器來保證服務的可用性。

官網:
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 等信息况褪。


1571747203355.png

為了保證監(jiān)控服務器的可用性,我們會對 Sentinel 做集群的部署更耻。Sentinel 既監(jiān)控所有的 Redis 服務测垛,Sentinel 之間也相互監(jiān)控。
注意:Sentinel 本身沒有主從之分秧均,只有 Redis 服務節(jié)點有主從之分食侮。
概念梳理:master号涯,slave(redis group),sentinel锯七,sentinel 集合

3.1.1 服務 下線

? Sentinel 默認以每秒鐘 1 次的頻率向 Redis 服務節(jié)點發(fā)送 PING 命令链快。如果在down-after-milliseconds 內都沒有收到有效回復,Sentinel 會將該服務器標記為下線(主觀下線)眉尸。

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

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

3.1.2 故障 轉移

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

3.1.2.1 Ratf 算法

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

? 大體上有兩個步驟:領導選舉扶平,數據復制帆离。
? Raft 是一個共識算法(consensus algorithm)。比如比特幣之類的加密貨幣结澄,就需要共識算法哥谷。Spring Cloud 的注冊中心解決方案 Consul 也用到了 Raft 協議。
? Raft 的核心思想:先到先得麻献,少數服從多數们妥。
? Raft 算法演示:

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

3.1.2.2 故障轉移

? 問題:怎么讓一個原來的 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)先級排序自赔、復制數量、進程 id柳琢。
? 如果與哨兵連接斷開的比較久绍妨,超過了某個閾值,就直接失去了選舉權柬脸。如果擁有選舉權他去,那就看誰的優(yōu)先級高,這個在配置文件里可以設置(replica-priority 100)倒堕,數值越小優(yōu)先級越高孤页。
? 如果優(yōu)先級相同,就看誰從 master 中復制的數據最多(復制偏移量最大)涩馆,選最多的那個,如果復制數量也相同允坚,就選擇進程 id 最小的那個魂那。

3.2 Sentinel 的功能總結

  • Monitoring. Sentinel constantly checks if your master and slave instances are working as expected.
  • Notification. Sentinel can notify the system administrator, another computer programs, via an API, that something iswrong with one of the monitored Redis instances.
  • Automatic failover. If a master is not working as expected, Sentinel can start a failover process where a slave is promoted to master, the other additional slaves are reconfigured to use the new master, and the applications using the Redis server informed about the new address to use when connecting.
  • Configuration provider. Sentinel acts as a source of authority for clients service discovery:clients connect to Sentinels in order to ask for the address of the current Redis master responsible for a given service. If a failover occurs, Sentinels will report the new address.

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

3.3 Sentinel 實戰(zhàn)

3.3.1 Sentinel 配置

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

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

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

三臺服務器內容相同:

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

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

hostname IP
protected-mode 是否允許外部網絡訪問
dir sentinel 的工作目錄
sentinel monitor sentinel 監(jiān)控的 redis 主節(jié)點
down-after-milliseconds(毫秒) master 宕機多久,才會被 Sentinel 主觀認為下線
sentinel failover-timeout(毫秒 1 同一個 sentinel 對同一個 master 兩次 failover 之間的間隔時間侠驯。
2. 當一個 slave 從一個錯誤的 master 那里同步數據開始計算時間抡秆。直到
slave 被糾正為向正確的 master 那里同步數據時。
3.當想要取消一個正在進行的 failover 所需要的時間陵霉。
4.當進行 failover 時笆呆,配置所有 slaves 指向新的 master 所需的最大時間。
parallel-syncs 這個配置項指定了在發(fā)生 failover 主備切換時最多可以有多少個 slave 同時對新的 master 進行 同步民镜,這個數字越小袜瞬,完成 failover 所需的時間就越長,但是如果這個數字越大效床,就意味著越 多的 slave 因為 replication 而不可用睹酌。可以通過將這個值設為 1 來保證每次只有一個 slave 處于不能處理命令請求的狀態(tài)

3.3.2 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):

redis> info replication

203

1571747818355.png

204 和 205

1571747829582.png

模擬 master 宕機剩檀,在 203 執(zhí)行:

redis> shutdown

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

1571747852515.png

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

slave 宕機和恢復省略.

3.3.3 Sentinel 連接使用

Jedis 連接 Sentinel

package sentinel;

import redis.clients.jedis.JedisSentinelPool;

import java.util.HashSet;
import java.util.Properties;
import java.util.Set;


public class JedisSentinelTest {
    private static JedisSentinelPool pool;

    private static JedisSentinelPool createJedisPool() {
        // master的名字是sentinel.conf配置文件里面的名稱
        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;
    }

    public static void main(String[] args) {
        JedisSentinelPool pool = createJedisPool();
        pool.getResource().set("qingshan", "qq"+System.currentTimeMillis());
        System.out.println(pool.getResource().get("qingshan"));
    }
}

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


import com.gupaoedu.util.RedisUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisAppTests {

    @Autowired
    RedisUtil util;

    @Test
    public void contextLoads() {
        util.set("boot", "2673--" +System.currentTimeMillis());
        System.out.println(util.get("boot"));
    }

}

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é)點地址砸民。

3.4 哨兵機制的不足

? 主從切換的過程中會丟失數據,因為只有一個 master。
? 只能單點寫岭参,沒有解決水平擴容的問題反惕。
? 如果數據量非常大,這個時候我們需要多個 master-slave 的 group演侯,把數據分布到不同的 group 中姿染。
? 問題來了,數據怎么分片蚌本?分片之后盔粹,怎么實現路由?

4 Redis 分布式方案

? 如果要實現 Redis 數據的分片程癌,我們有三種方案舷嗡。第一種是在客戶端實現相關的邏輯,例如用取那独颍或者一致性哈希對 key 進行分片进萄,查詢和修改都先判斷 key 的路由。
? 第二種是把做分片處理的邏輯抽取出來锐峭,運行一個獨立的代理服務中鼠,客戶端連接到這個代理服務,代理服務做請求的轉發(fā)沿癞。
? 第三種就是基于服務端實現援雇。

4.1 客戶端 Sharding

1571748107273.png

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

4.1.1 ShardedJedis

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++){
            System.out.println(jedis.get("k"+i));
        }
        ?
        }finally{
            if(jedis!=null) {
            jedis.close();
            }
        }
    }
}

? 使用 ShardedJedis 之類的客戶端分片代碼的優(yōu)勢是配置簡單惫搏,不依賴于其他中間件,分區(qū)的邏輯可以自定義蚕涤,比較靈活筐赔。但是基于客戶端的方案,不能實現動態(tài)的服務增減揖铜,每個客戶端需要自行維護分片策略茴丰,存在重復代碼。
? 第二種思路就是把分片的代碼抽取出來天吓,做成一個公共服務贿肩,所有的客戶端都連接到這個代理層。由代理層來實現請求和轉發(fā)龄寞。

4.2 代理 Proxy

1571748259203.png

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

4.2.1 Twemproxy

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

1571748281722.png

Twemproxy 的優(yōu)點:比較穩(wěn)定,可用性高萄焦。

不足:
1、出現故障不能自動轉移,架構復雜拂封,需要借助其他組件(LVS/HAProxy +
Keepalived)實現 HA
2茬射、擴縮容需要修改配置,不能實現平滑地擴縮容(需要重新分布數據)冒签。

4.2.2 Codis

https://github.com/CodisLabs/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 No
客戶端支持 所有 所有 支持 cluster 協議的客戶端
1571748437326.png

? 分片原理:Codis 把所有的 key 分成了 N 個槽(例如 1024)刚梭,每個槽對應一個分組,一個分組對應于一個或者一組 Redis 實例票唆。Codis 對 key 進行 CRC32 運算朴读,得到一個32 位的數字,然后模以 N(槽的個數)走趋,得到余數衅金,這個就是 key 對應的槽,槽后面就是 Redis 的實例簿煌。比如 4 個槽:

1571748485305.png

? Codis 的槽位映射關系是保存在 Proxy 中的氮唯,如果要解決單點的問題,Codis 也要做集群部署姨伟,多個 Codis 節(jié)點怎么同步槽和實例的關系呢惩琉?需要運行一個 Zookeeper (或者 etcd/本地文件)。

? 在新增節(jié)點的時候夺荒,可以為節(jié)點指定特定的槽位瞒渠。Codis 也提供了自動均衡策略。Codis 不支持事務般堆,其他的一些命令也不支持在孝。

不支持的命令
https://github.com/CodisLabs/codis/blob/release3.2/doc/unsupported_cmds.md
獲取數據原理(mget):在 Redis 中的各個實例里獲取到符合的 key,然后再匯總到 Codis 中淮摔。
Codis 是第三方提供的分布式解決方案私沮,在官方的集群功能穩(wěn)定之前,Codis 也得到了大量的應用和橙。

4.3 Redis Cluster

https://redis.io/topics/cluster-tutorial/
Redis Cluster 是在 Redis 3.0 的版本正式推出的仔燕,用來解決分布式的需求,同時也可以實現高可用魔招。跟 Codis 不一樣晰搀,它是去中心化的,客戶端可以連接到任意一個可用節(jié)點办斑。
數據分片有幾個關鍵的問題需要解決:
1外恕、數據怎么相對均勻地分片
2杆逗、客戶端怎么訪問到相應的節(jié)點和數據
3、重新分片的過程鳞疲,怎么保證正常服務

4.3.1 架構

? Redis Cluster 可以看成是由多個 Redis 實例組成的數據集合罪郊。客戶端不需要關注數據的子集到底存儲在哪個節(jié)點尚洽,只需要關注這個集合整體悔橄。
? 以 3 主 3 從為例,節(jié)點之間兩兩交互腺毫,共享數據分片癣疟、節(jié)點狀態(tài)等信息。

1571748577107.png

4.3.2 搭建

首先潮酒,本篇要基于單實例的安裝睛挚,你的機器上已經有一個Redis
博客 www.sundablog.com

為了節(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的配置文件

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復制到其他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

批量替換內容

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已經廢棄了,直接用–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)建集群

連接到客戶端

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

腳本內容

#!/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
chmod +x setkey.sh
./setkey.sh

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

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é)點耳贬,重新分布數據:

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é)點兔毒,如果槽已經指派給另一個節(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 目前包含的鍵值對數量围小。
cluster getkeysinslot :返回 count 個 slot 槽中的鍵

4.3.3 數據分布

? 如果是希望數據分布相對均勻的話昵骤,我們首先可以考慮哈希后取模。

4.3.3.1 哈希后 取模

? 例如肯适,hash(key)%N变秦,根據余數,決定映射到那一個節(jié)點框舔。這種方式比較簡單蹦玫,屬于靜態(tài)的分片規(guī)則。但是一旦節(jié)點數量變化刘绣,新增或者減少樱溉,由于取模的 N 發(fā)生變化,數據需要重新分布纬凤。
? 為了解決這個問題福贞,我們又有了一致性哈希算法。

4.3.3.2 一致性

? 一致性哈希的原理:
? 把所有的哈希值空間組織成一個虛擬的圓環(huán)(哈希環(huán))停士,整個空間按順時針方向組織挖帘。因為是環(huán)形空間,0 和 2^32-1 是重疊的恋技。
? 假設我們有四臺機器要哈希環(huán)來實現映射(分布數據)拇舀,我們先根據機器的名稱或者 IP 計算哈希值蜻底,然后分布到哈希環(huán)中(紅色圓圈)骄崩。

1571749016225.png

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

1571749032547.png

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

1571749046917.png

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

1571749059910.png

? 谷歌的 MurmurHash 就是一致性哈希算法彻况。在分布式系統(tǒng)中,負載均衡舅踪、分庫分表等場景中都有應用纽甘。

? 一致性哈希解決了動態(tài)增減節(jié)點時,所有數據都需要重新分布的問題抽碌,它只會影響到下一個相鄰的節(jié)點悍赢,對其他節(jié)點沒有影響。
? 但是這樣的一致性哈希算法有一個缺點货徙,因為節(jié)點不一定是均勻地分布的左权,特別是在節(jié)點數比較少的情況下,所以數據不能得到均勻分布痴颊。解決這個問題的辦法是引入虛擬節(jié)點(Virtual Node)赏迟。
? 比如:2 個節(jié)點,5 條數據蠢棱,只有 1 條分布到 Node2锌杀,4 條分布到 Node1,不均勻泻仙。

1571749095499.png

Node1 設置了兩個虛擬節(jié)點糕再,Node2 也設置了兩個虛擬節(jié)點(虛線圓圈)。
這時候有 3 條數據分布到 Node1饰豺,1 條數據分布到 Node2亿鲜。

1571749105882.png

? Redis 虛擬槽分區(qū)

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

1571749129064.png

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

? 對象分布到 Redis 節(jié)點上時髓介,對 key 用 CRC16 算法計算再%16384惕鼓,得到一個 slot的值,數據落到負責這個 slot 的 Redis 節(jié)點上唐础。

? 查看 key 屬于哪個 slot:

redis> cluster keyslot sunda

注意:key 與 slot 的關系是永遠不會變的箱歧,會變的只有 slot 和 Redis 節(jié)點的關系矾飞。

問題:怎么讓相關的數據落到同一個節(jié)點上?

比如有些 multi key 操作是不能跨節(jié)點的呀邢,如果要讓某些數據分布到一個節(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

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

4.3.4 客戶端 重定向

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

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

? 服務端返回 MOVED剪验,也就是根據 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é)點,數據怎么遷移(重新分配)每聪?

4.3.5 數據遷移

因為 key 和 slot 的關系是永遠不會變的旦棉,當新增了節(jié)點的時候,需要把原有的 slot
分配給新的節(jié)點負責药薯,并且把相關的數據遷移過來绑洛。

添加新節(jié)點(新增一個 7297):

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

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

redis-cli --cluster reshard 127.0.0.1:7291

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

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

4.3.6 高可用 和主 從 切換原理

? 當 slave 發(fā)現自己的 master 變?yōu)?FAIL 狀態(tài)時,便嘗試進行 Failover晾匠,以期成為新的master茶袒。由于掛掉的master可能會有多個slave,從而存在多個slave競爭成為master節(jié)點的過程凉馆, 其過程如下:
? 1.slave 發(fā)現自己的 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.超過半數后變成新 Master嗦董、
? 6.廣播 Pong 通知其他集群節(jié)點母谎。

Redis Cluster 既能夠實現主從的角色分配,又能夠實現主從切換京革,相當于集成了Replication 和 Sentinal 的功能

4.3.7 總結

優(yōu)勢
1. 無中心架構奇唤。
2. 數據按照 slot 存儲分布在多個節(jié)點,節(jié)點間數據共享匹摇,可動態(tài)調整數據分布咬扇。
3. 可擴展性,可線性擴展到 1000 個節(jié)點(官方推薦不超過 1000 個)廊勃,節(jié)點可動態(tài)添加或刪除懈贺。
4. 高可用性,部分節(jié)點不可用時坡垫,集群仍可用梭灿。通過增加 Slave 做 standby 數據副本,能夠實現故障自動 failover冰悠,節(jié)點之間通過 gossip 協議交換狀態(tài)信息胎源,用投票機制完成 Slave 到 Master 的角色提升。
5. 降低運維成本屿脐,提高系統(tǒng)的擴展性和可用性涕蚤。

不足
1. Client 實現復雜,驅動要求實現 Smart Client的诵,緩存 slots mapping 信息并及時更新万栅,提高了開發(fā)難度,客戶端的不成熟影響業(yè)務的穩(wěn)定性西疤。
2. 節(jié)點會因為某些原因發(fā)生阻塞(阻塞時間大于 clutser-node-timeout)烦粒,被判斷下線,這種 failover 是沒有必要的。
3. 數據通過異步復制扰她,不保證數據的強一致性兽掰。
4. 多個業(yè)務使用同一套集群時,無法根據統(tǒng)計區(qū)分冷熱數據徒役,資源隔離性較差孽尽,容易出現相互影響的情況。

公眾號

如果大家想要實時關注我更新的文章以及分享的干貨的話忧勿,可以關注我的公眾號杉女。


QQ圖片20191012084332.png
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鸳吸,隨后出現的幾起案子熏挎,更是在濱河造成了極大的恐慌,老刑警劉巖晌砾,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坎拐,死亡現場離奇詭異,居然都是意外死亡养匈,警方通過查閱死者的電腦和手機廉白,發(fā)現死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乖寒,“玉大人,你說我怎么就攤上這事院溺¢灌遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵珍逸,是天一觀的道長逐虚。 經常有香客問我,道長谆膳,這世上最難降的妖魔是什么叭爱? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮漱病,結果婚禮上买雾,老公的妹妹穿的比我還像新娘。我一直安慰自己杨帽,他們只是感情好漓穿,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著注盈,像睡著了一般晃危。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上老客,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天僚饭,我揣著相機與錄音震叮,去河邊找鬼。 笑死鳍鸵,一個胖子當著我的面吹牛苇瓣,可吹牛的內容都是我干的。 我是一名探鬼主播权纤,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钓简,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汹想?” 一聲冷哼從身側響起外邓,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎古掏,沒想到半個月后损话,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡槽唾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年丧枪,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庞萍。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡拧烦,死狀恐怖,靈堂內的尸體忽然破棺而出钝计,到底是詐尸還是另有隱情恋博,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布私恬,位于F島的核電站债沮,受9級特大地震影響,放射性物質發(fā)生泄漏本鸣。R本人自食惡果不足惜疫衩,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荣德。 院中可真熱鬧闷煤,春花似錦、人聲如沸涮瞻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饲宛。三九已至皆愉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幕庐。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工久锥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人异剥。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓瑟由,卻偏偏與公主長得像,于是被迫代替她去往敵國和親冤寿。 傳聞我的和親對象是個殘疾皇子歹苦,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內容