Redis集羣容器化安裝

1. Redis集羣概述

Redis作爲當前非常熱門的內存型數據結構存儲,可用於數據存儲,緩存和消息代理等。本文將講解如何基於docker搭建Redis集羣,Redis的集羣設計包括兩個部分:主從複製和哈希Slot。

1.1. 主從複製

主從複製在數據庫中很常見,一般用來做讀寫分離,Redis中也是如此。要求只有1個Master(主節點),可以有N個slaver(從節點),而且Slaver也可以有自己的Slaver,由於這種主從的關係決定他們是在配置階段就要指定他們的上下級關係,而不是Zookeeper那種平行關係是自主推優出來的。

讀寫分離,Master只負責寫和同步數據給Slaver,Slaver承擔了被讀的任務,所以Slaver的擴容只能提高讀效率不能提高寫效率。

Slaver先將Master那邊獲取到的信息壓入磁盤,再load進內存,client端是從內存中讀取信息的。當一個新的Slaver加入到這個集羣時,會主動找Master來拜碼頭,Master發現新的小弟後將全量數據發送給新的Slaver,數據量越大性能消耗也就越大,所以儘量避免在運行時做Slaver的擴容。

  • 優點:讀寫分離,通過增加Slaver可以提高併發讀的能力。
  • 缺點:Master寫能力是瓶頸,維護Slaver開銷也總將會變成瓶頸。

1.2. 哈希Slot

哈希Slot名字上可能不好理解,其實就是數據庫中的“水平劃分”。如果你之前有了解過數據庫的表分區的話,就會發現下來對於哈希Slot的描述,就和數據庫表分區裏面的“HASH分區”原理上大致相同。

圖片描述

對象保存到Redis之前先經過CRC16哈希到一個指定的Node上,例如圖中Object4最終Hash到了Node1上。

 每個Node被平均分配了一個Slot段,對應着0-16384,Slot不能重複也不能缺失,否則會導致對象重複存儲或無法存儲。

 Node之間也互相監聽,一旦有Node退出或者加入,會按照Slot爲單位做數據的遷移。例如Node1如果掉線了,0-5640這些Slot將會平均分攤到Node2和Node3上,由於Node2和Node3本身維護的Slot還會在自己身上不會被重新分配,所以遷移過程中不會影響到 5641-16384 Slot段的使用。

  • 優點:將Redis的寫操作分攤到了多個節點上,提高寫的併發能力,擴容簡單。
  • 缺點:每個Node承擔着互相監聽、高併發數據寫入、高併發數據讀出,工作任務繁重。

1.3. 合二爲一

看到這裏大家也就發現了,主從和哈希的設計優缺點正好是相互彌補的,將二者結合在一起,就是Redis集羣的終極形態,先Hash分邏輯節點,然後每個邏輯節點內部是主從,如圖:

圖片描述

2. 集羣安裝

默認我們已經有了docker環境,現在開始基於docker安裝Redis集羣。redis 5.0 版本之前,網上有很多教程都是通過redis-trib.rb 來創建集羣,但是redis 5.0 版本以後,就只能通過 redis-cli 來實現。本文即通過 redis-cli 創建集羣,因爲redis集羣的節點選舉方式是需要半數以上的master通過,所以建議創建奇數個節點。本例中創建3個Master節點,併爲每個Master節點各分配1個Slave節點。

2.1. 宿主機環境

首先需要找一份原始的redis.conf文件,將其重命名爲:redis-cluster.tmpl,並配置如下幾個參數,此文件的目的是生成每一個redis實例的redis.conf:

[root@kerry2 redis]# wget https://raw.githubusercontent.com/antirez/redis/5.0/redis.conf
[root@kerry2 redis]# mv redis.conf redis-cluster.tmpl

vi redis-cluster.tmpl

# bind 127.0.0.1
protected-mode no
port ${PORT}
daemonize no
dir /data/redis
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000

然後執行下列腳本,給3個Master和3個Slave創建各自的掛載卷目錄

# 創建 master 和 slave 文件夾
for port in `seq 7700 7705`; do
    ms="master"
    if [ $port -ge 7703 ]; then
        ms="slave"
    fi
    mkdir -p ./$ms/$port/ && mkdir -p ./$ms/$port/data \
    && PORT=$port envsubst < ./redis-cluster.tmpl > ./$ms/$port/redis.conf;
done

當前目錄結構爲

[root@kerry2 redis]# tree
.
├── master
│   ├── 7700
│   │   ├── data
│   │   └── redis.conf
│   ├── 7701
│   │   ├── data
│   │   └── redis.conf
│   └── 7702
│       ├── data
│       └── redis.conf
├── redis-cluster.tmpl
├── slave
     ├── 7703
     │   ├── data
     │   └── redis.conf
     ├── 7704
     │   ├── data
     │   └── redis.conf
     └── 7705
         ├── data
         └── redis.conf

2.2. 創建Redis節點

假設我們只考慮單純的docker環境,並無docker-compose和k8s之類的服務編排,每個redis容器之間必須要保證通訊,可以通過創建docker network。(使用微服務編排的情況,後續再討論)

[root@kerry2 redis]# docker network create redis-cluster-net

現在我們就可以運行docker redis 的 master 和 slave 實例了

# 運行docker redis 的 master 和 slave 實例
for port in `seq 7700 7705`; do
    ms="master"
    if [ $port -ge 7703 ]; then
        ms="slave"
    fi
    docker run -d -p $port:$port -p 1$port:1$port \
    -v $PWD/$ms/$port/redis.conf:/data/redis.conf \
    -v $PWD/$ms/$port/data:/data/redis \
    --restart always --name redis-$ms-$port --net redis-cluster-net \
    redis redis-server /data/redis.conf;
done

查看已創建的redis容器

[root@kerry2 redis]# docker ps |grep redis
010f295922e3        redis                                                                                                  "docker-entrypoint..."   41 seconds ago       Up 36 seconds       0.0.0.0:7705->7705/tcp, 6379/tcp, 0.0.0.0:17705->17705/tcp   redis-slave-7705
b5d89f0469ee        redis                                                                                                  "docker-entrypoint..."   45 seconds ago       Up 40 seconds       0.0.0.0:7704->7704/tcp, 6379/tcp, 0.0.0.0:17704->17704/tcp   redis-slave-7704
f710e805fe96        redis                                                                                                  "docker-entrypoint..."   50 seconds ago       Up 45 seconds       0.0.0.0:7703->7703/tcp, 6379/tcp, 0.0.0.0:17703->17703/tcp   redis-slave-7703
b187603aec65        redis                                                                                                  "docker-entrypoint..."   55 seconds ago       Up 50 seconds       0.0.0.0:7702->7702/tcp, 6379/tcp, 0.0.0.0:17702->17702/tcp   redis-master-7702
ea635bd8b3dc        redis                                                                                                  "docker-entrypoint..."   About a minute ago   Up 55 seconds       0.0.0.0:7701->7701/tcp, 6379/tcp, 0.0.0.0:17701->17701/tcp   redis-master-7701
f02a468572ca        redis                                                                                                  "docker-entrypoint..."   About a minute ago   Up About a minute   0.0.0.0:7700->7700/tcp, 6379/tcp, 0.0.0.0:17700->17700/tcp   redis-master-7700

2.3. 構建集羣

通過redis-cli 命令構建集羣,隨便找一個redis容器,運行redis-cli --cluster create --cluster-replicas 1 ip:port 命令即可

[root@kerry2 redis]# docker exec -it redis-master-7700 redis-cli --cluster create 宿主ip:7700 宿主ip:7701 宿主ip:7702 宿主ip:7703 宿主ip:7704 宿主ip:7705 --cluster-replicas 1

# 提示輸入yes後,構建集羣成功

記住構建集羣時,要保證節點redis數據爲空,否則會出現下列錯誤。

[ERR] Node 172.18.0.2:7700 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.

2.4. 集羣驗證

集羣搭建完成後,我們通過 redis-cli 命令連接集羣節點驗證一下。redis 集羣節點的連接命令是通過 redis-cli -c -h ${ip} -p ${port}

[root@kerry2 ~]# docker exec -it redis-master-7700 redis-cli -c -h 宿主機ip  -p 7700
宿主機ip :7700> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:23838
cluster_stats_messages_pong_sent:24283
cluster_stats_messages_sent:48121
cluster_stats_messages_ping_received:24278
cluster_stats_messages_pong_received:23838
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:48121
宿主機ip :7700> cluster nodes
056b99fe5993510c264e3e9a1fd1a04144da6a7b 172.18.0.2:7700@17700 myself,master - 0 1558578383000 1 connected 0-5460
73376f00b2837309d77b82d98984715f44eb2dcf 宿主機ip:7704@17704 slave 056b99fe5993510c264e3e9a1fd1a04144da6a7b 0 1558578388562 5 connected
20e4b509a54fb17ed8d0f6c21bbc8693ab715ee7 宿主機ip:7705@17705 slave 1bcb0a6ac770e261c5b0de21cfe26b0bd614590e 0 1558578386658 6 connected
1bcb0a6ac770e261c5b0de21cfe26b0bd614590e 宿主機ip:7701@17701 master - 0 1558578386579 2 connected 5461-10922
07a4c19848d578ac339bfaf741e1edfd0b010b08 宿主機ip:7702@17702 master - 0 1558578388661 3 connected 10923-16383
506271ed3f0657f05f439108d9372b638d2c4571 宿主機ip:7703@17703 slave 07a4c19848d578ac339bfaf741e1edfd0b010b08 0 1558578386000 4 connected

可以看到通過 "cluster info"命令看到集羣的基本信息,所有的slot (16384) 都分配完畢。然後通過 "cluster nodes" 命令查看到每個master節點的slot分配的區域。至此,redis集羣基本安裝成功。

3. 後期運維

3.1. 基本命令

集羣

cluster info :打印集羣的信息
cluster nodes :列出集羣當前已知的所有節點( node),以及這些節點的相關信息。

節點

cluster meet <ip> <port> :將 ip 和 port 所指定的節點添加到集羣當中,讓它成爲集羣的一份子。
cluster forget <node_id> :從集羣中移除 node_id 指定的節點。
cluster replicate <node_id> :將當前節點設置爲 node_id 指定的節點的從節點。
cluster saveconfig :將節點的配置文件保存到硬盤裏面。

槽(slot)

cluster addslots <slot> [slot ...] :將一個或多個槽( slot)指派( assign)給當前節點。
cluster delslots <slot> [slot ...] :移除一個或多個槽對當前節點的指派。
cluster flushslots :移除指派給當前節點的所有槽,讓當前節點變成一個沒有指派任何槽的節點。
cluster setslot <slot> node <node_id> :將槽 slot 指派給 node_id 指定的節點,如果槽已經指派給
另一個節點,那麼先讓另一個節點刪除該槽>,然後再進行指派。
cluster setslot <slot> migrating <node_id> :將本節點的槽 slot 遷移到 node_id 指定的節點中。
cluster setslot <slot> importing <node_id> :從 node_id 指定的節點中導入槽 slot 到本節點。
cluster setslot <slot> stable :取消對槽 slot 的導入( import)或者遷移( migrate)。
鍵
cluster keyslot <key> :計算鍵 key 應該被放置在哪個槽上。
cluster countkeysinslot <slot> :返回槽 slot 目前包含的鍵值對數量。
cluster getkeysinslot <slot> <count> :返回 count 個 slot 槽中的鍵  

3.2. 常見問題

(1)redis-cluster 把所有的物理節點映射到[ 0 ~ 16383 ]個slot(哈希槽)上,cluster負責維護 node<->slot<->value。

(2)集羣任意一個節點中,如果master掛掉,但是還有slaver,slave將自動升爲 master,系統正常。

(3)集羣任意一個節點中,如果master掛掉,並且沒有slaver,集羣將進入fail狀態。

(4)如果集羣超過半數以上節點的master掛掉,不管是否有slaver,集羣都將進入fail狀態。

(5)節點判斷是否失效的選舉,是集羣中所有的master參與的,如果半數以上的master節點與當前被檢測的master節點通訊檢測超時(cluster-node-timerout),就認爲當前master節點掛掉了。

4. 腳本和yaml

參考文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章