Redis集群搭建及原理


一、哨兵模式

在 redis3.0之前,redis使用的哨兵架構,它借助 sentinel 工具來監控 master 節點的狀態;如果 master 節點異常,則會做主從切換,將一台 slave 作為 master。

哨兵模式的缺點

(1)當master掛掉的時候,sentinel 會選舉出來一個 master,選舉的時候是沒有辦法去訪問Redis的,會存在訪問瞬斷的情況;若是在電商網站大促的時候master給掛掉了,幾秒鍾損失好多訂單數據;

(2)哨兵模式,對外只有master節點可以寫,slave節點只能用於讀。盡管Redis單節點最多支持10W的QPS,但是在電商大促的時候,寫數據的壓力全部在master上。

(3)Redis的單節點內存不能設置過大,若數據過大在主從同步將會很慢;在節點啟動的時候,時間特別長;(從節點上有主節點的所有數據)

二、Redis集群

1、Redis集群的介紹

   Redis集群是一個由多個主從節點群組成的分布式服務集群,它具有復制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完成節點移除和故障轉移的功能。需要將每個節點設置成集群模式,這種集群模式沒有中心節點,可水平擴展,據官方文檔稱可以線性擴展到上萬個節點(官方推薦不超過1000個節點)。redis集群的性能和高可用性均優於之前版本的哨兵模式,且集群配置非常簡單。

2、Redis集群的優點

(1)Redis集群有多個master,可以減小訪問瞬斷問題的影響

  若集群中有一個master掛了,正好需要向這個master寫數據,這個操作需要等待一下;但是向其他master節點寫數據是不受影響的。

(2)Redis集群有多個master,可以提供更高的並發量;  

(3)Redis集群可以分片存儲,這樣就可以存儲更多的數據;

3、Redis集群的搭建

Redis的集群搭建最少需要3個master節點,我們這里搭建3個master,每個下面掛一個slave節點,總共6個Redis節點;(3台機器,每台機器一主一從)

第1台機器: 192.168.1.1   8001端口  8002端口

第2台機器: 192.168.1.2   8001端口  8002端口

第3台機器: 192.168.1.3   8001端口  8002端口

第一步:創建文件夾

mkdir -p /usr1/redis/redis-cluster/8001 /usr1/redis/redis-cluster/8002

第二步:將redis安裝目錄下的 redis.conf 文件分別拷貝到8001目錄下

cp /usr1/redis-5.0.3/redis.conf /usr1/redis/redis-cluster/8001

第三步:修改redis.conf文件以下內容

port 8001 daemonize yes pidfile "/var/run/redis_8001.pid" #指定數據文件存放位置,必須要指定不同的目錄位置,不然會丟失數據 dir /usr1/redis/redis-cluster/8001/ #啟動集群模式 cluster-enabled yes #集群節點信息文件,這里800x最好和port對應上 cluster-config-file nodes-8001.conf # 節點離線的超時時間 cluster-node-timeout 5000 #去掉bind綁定訪問ip信息 #bind 127.0.0.1 #關閉保護模式 protected-mode no #啟動AOF文件 appendonly yes #如果要設置密碼需要增加如下配置: #設置redis訪問密碼 requirepass redis-pw #設置集群節點間訪問密碼,跟上面一致 masterauth redis-pw

第四步,把上面修改好的配置文件拷貝到8002文件夾下,並將8001修改為8002:

cp /usr1/redis/redis-cluster/8001/redis.conf /usr1/redis/redis-cluster/8002 cd /usr1/redis/redis-cluster/8002/ vim redis.conf #批量修改字符串 :%s/8001/8002/g

第五步,將本機(192.168.1.1)機器上的文件拷貝到另外兩台機器上

scp /usr1/redis/redis-cluster/8001/redis.conf  root@192.168.1.2:/usr1/redis/redis-cluster/8001/
scp /usr1/redis/redis-cluster/8002/redis.conf  root@192.168.1.2:/usr1/redis/redis-cluster/8002/
scp /usr1/redis/redis-cluster/8001/redis.conf root@192.168.1.3:/usr1/redis/redis-cluster/8001/ scp /usr1/redis/redis-cluster/8002/redis.conf root@192.168.1.3:/usr1/redis/redis-cluster/8002/

第六步,分別啟動這6個redis實例,然后檢查是否啟動成功

/usr1/redis/redis-5.0.3/src/redis-server /usr1/redis/redis-cluster/8001/redis.conf /usr1/redis/redis-5.0.3/src/redis-server /usr1/redis/redis-cluster/8002/redis.conf 
ps -ef | grep redis

 第七步,使用 redis-cli 創建整個 redis 集群(redis5.0版本之前使用的ruby腳本 redis-trib.rb)

運行以上命令,完成搭建

/usr1/redis/redis-5.0.3/src/redis-cli -a redis-pw --cluster create --cluster-replicas 1 192.168.1.1:8001 192.168.1.1:8002 192.168.1.2:8001 192.168.1.2:8002 192.168.1.3:8001 192.168.1.3:8002

說明: 

  -a :密碼;

   --cluster-replicas 1:表示1個master下掛1個slave; --cluster-replicas 2:表示1個master下掛2個slave。

擴展

  查看幫助命令: src/redis‐cli --cluster help 

create:創建一個集群環境host1:port1 ... hostN:portN call:可以執行redis命令 add-node:將一個節點添加到集群里,第一個參數為新節點的ip:port,第二個參數為集群中任意一個已經存在的節點的ip:port del-node:移除一個節點 reshard:重新分片 check:檢查集群狀態

 第八步,驗證集群

(1)連接任意一個客戶端

/usr1/redis/redis-5.0.3/src/redis-cli -a redis-pw -c -h 192.168.1.1 -p 8001

說明:‐a表示服務端密碼;c表示集群模式;-h指定ip地址;-p表示端口號

(2)查看集群的信息: cluster info   

(3) 查看節點列表:  cluster nodes  slave 對應的 master 從上面也可以看到;

從上面可以看到 slave掛在哪個 master 下面;

 在 /usr1/redis/redis-cluster/8001/nodes-8001.conf 文件中存儲了節點信息;

(4)進行數據操作驗證;

(5)關閉集群則需要逐個進行關閉,使用命令:

/usr/local/redis‐5.0.3/src/redis‐cli ‐a redis-pw ‐c ‐h 192.168.1.1 ‐p 8001 shutdown /usr/local/redis‐5.0.3/src/redis‐cli ‐a redis-pw ‐c ‐h 192.168.1.1 ‐p 8002 shutdown ......

注意:在創建集群的時候,需要把所有節點機器上的防火關閉,保證 Redis的服務端口和集群節點通信的 gossip 端口能通;

  systemctl stop firewalld # 臨時關閉防火牆

  systemctl disable firewalld # 禁止開機啟動

 4、Redis集群原理分析

   Redis Cluster 將所有數據划分為 16384 個 slots(槽位),每個節點負責其中一部分槽位。槽位的信息存儲於每個節點中。只有master節點會被分配槽位,slave節點不會分配槽位。

  當Redis Cluster 的客戶端來連接集群時,它也會得到一份集群的槽位配置信息,並將其緩存在客戶端本地。這樣當客戶端要查找某個 key 時,可以直接定位到目標節點。同時因為槽位的信息可能會存在客戶端與服務器不一致的情況,還需要糾正機制來實現槽位信息的校驗調整。

槽位定位算法

 Cluster 默認會對 key 值使用 crc16 算法進行 hash 得到一個整數值,然后用這個整數值對 16384 進行取模來得到具體槽位。

             HASH_SLOT = CRC16(key) % 16384 

跳轉重定位

   當客戶端向一個節點發出了指令,首先當前節點會計算指令的 key 得到槽位信息,判斷計算的槽位是否歸當前節點所管理;若槽位不歸當前節點管理,這時它會向客戶端發送一個特殊的跳轉指令攜帶目標操作的節點地址,告訴客戶端去連這個節點去獲取數據。客戶端收到指令后除了跳轉到正確的節點上去操作,還會同步更新糾正本地的槽位映射表緩存,后續所有 key 將使用新的槽位映射表。

 Redis集群節點之間的通信機制

維護集群的元數據有集中式和 gossip兩種方式,Redis 的集群節點之間的通信采取 gossip 協議進行通信

(1)集中式:

  優點:元數據的更新和讀取,時效性非常好,一旦元數據出現變更立即就會更新到集中式的存儲中,其他節點讀取的時候立即就可以立即感知到;

  缺點:所有的元數據的更新壓力全部集中在一個地方,可能導致元數據的存儲壓力。zookeeper使用該方式

(2)gossip:

  gossip協議包含多種消息,包括ping,pong,meet,fail等等。

  優點:元數據的更新比較分散,不是集中在一個地方,更新請求會陸陸續續,打到所有節點上去更新,有一定的延時,降低了壓力;

  缺點:元數據更新有延時可能導致集群的一些操作會有一些滯后。

  每個節點都有一個專門用於節點間通信的端口,就是自己提供服務的端口號+10000,比如7001,那么用於節點間通信的就是17001端口。 每個節點每隔一段時間都會往另外幾個節點發送ping消息,同時其他幾點接收到ping消息之后返回pong消息。

網絡抖動

  網絡抖動就是非常常見的一種現象,突然之間部分連接變得不可訪問,然后很快又恢復正常。
  為解決這種問題,Redis Cluster 提供了一種選項  cluster-­node-­timeout ,表示當某個節點失聯的時間超過了配置的 timeout時,才可以認定該節點出現故障,需要進行主從切換。如果沒有這個選項,網絡抖動會導致主從頻繁切換 (數據的重新復制)

Redis集群選舉原理 

當 slave 發現自己的 master 變為 fail 狀態時,便嘗試進行 FailOver,以期成為新的 master。由於掛掉的 master 有多個 slave,所以這些 slave 要去競爭成為 master 節點,過程如下:

(1)slave1,slave2都 發現自己連接的 master 狀態變為 Fail;

(2)它們將自己記錄的集群 currentEpoch(選舉周期) 加1,並使用 gossip 協議去廣播 FailOver_auth_request 信息;

(3)其他節點接收到slave1、salve2的消息(只有master響應),判斷請求的合法性,並給 slave1 或 slave2 發送 FailOver_auth_ack(對每個 epoch 只發一次ack);   在一個選舉周期中,一個master只會響應第一個給它發消息的slave;

(4)slave1 收集返回的 FailOver_auth_ack,它收到超過半數的 master 的 ack 后變成新 master; (這也是集群為什么至少需要3個master的原因,如果只有兩個master,其中一個掛了之后,只剩下一個主節點是不能選舉成功的) 

(5)新的master節點去廣播 Pong 消息通知其他集群節點,不需要再去選舉了。

  從節點並不是在主節點一進入 FAIL 狀態就馬上嘗試發起選舉,而是有一定延遲,一定的延遲確保我們等待FAIL狀態在集群中傳播,slave如果立即嘗試選舉,其它masters或許尚未意識到FAIL狀態,可能會拒絕投票。

  延遲計算公式:DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

  SLAVE_RANK:表示此slave已經從master復制數據的總量的rank。Rank越小代表已復制的數據越新。這種方式下,持有最新數據的slave將會首先發起選舉(理論上)

Redis集群為什么至少需要三個master節點,並且推薦節點數為奇數?
  因為新master的選舉需要大於半數的集群master節點同意才能選舉成功,如果只有兩個master節點,當其中一個掛了,是達不到選舉新master的條件的。
  奇數個master節點可以在滿足選舉該條件的基礎上節省一個節點,比如三個master節點和四個master節點的集群相比,大家如果都掛了一個master節點都能選舉新master節點,如果都掛了兩個master節點都沒法選舉新master節點了,所以奇數的master節點更多的是從節省機器資源角度出發說的。

集群是否完整才能對外提供服務
當redis.conf的配置cluster-require-full-coverage為no時,表示當負責一個插槽的主庫下線且沒有相應的從庫進行故障恢復時,集群仍然可用,如果為yes則集群不可用。

 5、Java 操作 Redis 集群

 方式一(Jedis):

(1)引入jedis的maven坐標

<dependency>
 <groupId>redis.clients</groupId>
 <artifactId>jedis</artifactId>
 <version>2.9.0</version>
</dependency>

(2)Java編寫的代碼,如下:

public class JedisClusterTest {
    public static void main(String[] args) throws IOException {
 
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(20);
        config.setMaxIdle(10);
        config.setMinIdle(5);

        Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
        jedisClusterNode.add(new HostAndPort("192.168.1.1", 8001));
        jedisClusterNode.add(new HostAndPort("192.168.1.1", 8002));
        jedisClusterNode.add(new HostAndPort("192.168.1.2", 8001));
        jedisClusterNode.add(new HostAndPort("192.168.1.2", 8002));
        jedisClusterNode.add(new HostAndPort("192.168.1.3", 8001));
        jedisClusterNode.add(new HostAndPort("192.168.1.3", 8002));
        JedisCluster jedisCluster = null;
        try {
             //connectionTimeout:指的是連接一個url的連接等待時間
             //soTimeout:指的是連接上一個url,獲取response的返回等待時間
             jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "redis-pw", config);
             System.out.println(jedisCluster.set("name", "zhangsan"));
             System.out.println(jedisCluster.get("name"));
         } catch (Exception e) {
            e.printStackTrace();
         } finally {
             if (jedisCluster != null) {
                jedisCluster.close();
             }
        }
    }
}

方式二:SpringBoot 整合 Redis 

(1)引入maven坐標

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring‐boot‐starter‐data‐redis</artifactId>
</dependency>
 
<dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons‐pool2</artifactId>
</dependency>

(2)配置文件

server: port: 8080 spring: redis: database: 0 timeout: 3000 password: redis-pw cluster: nodes: 192.168.0.61:8001,192.168.0.62:8002,192.168.0.63:8003,192.168.0.61:8004,192.168.0.62:8005,192.168.0.6
3:8006 lettuce: pool: max‐idle: 50 min‐idle: 10 max‐active: 100 max‐wait: 1000

(3)java代碼,如下:

@RestController public class TestController { private static final Logger logger = LoggerFactory.getLogger(TestController.class); @Autowired private StringRedisTemplate stringRedisTemplate; @RequestMapping("/test_cluster") public void testCluster() throws InterruptedException { stringRedisTemplate.opsForValue().set("user:name", "wangwu"); System.out.println(stringRedisTemplate.opsForValue().get("user:name")); } }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM