全部產品
Search
文件中心

Elastic Container Instance:使用ECI搭建Redis叢集

更新時間:Jul 06, 2024

使用容器化方式搭建Redis叢集具有簡化部署、靈活擴充、資源隔離等優勢,可以提高Redis叢集的部署、管理、擴充和維護效率,降低營運成本。本文介紹如何基於ACK Serverless叢集,使用ECI來搭建Redis叢集。

背景資訊

Redis支援以叢集模式運行,在該模式下,Redis將所有儲存空間分為16384個雜湊槽,叢集中的每個Master節點負責N個雜湊槽(一個資料分區),當使用者寫入一條資料時,Redis計算其雜湊槽,然後將資料寫在負責該雜湊槽的節點上。且每個Master節點可以添加一個或多個Slave節點,當某個Master節點不可用時,其Slave節點自動代替Master節點繼續工作。

由此可見,在Redis叢集模式下,可以獲得更高的輸送量和一定程度的可用性。需要注意的是,在叢集模式下,Redis仍不能保證資料零丟失,更多資訊,請參見Redis官方文檔

前提條件

已建立ACK Serverless叢集,且叢集滿足以下條件:

  • 叢集具備公網環境,可以通過公網拉取鏡像。

  • 叢集中已部署儲存外掛程式。

    建議使用CSI外掛程式,確保叢集已部署阿里雲CSI-Provisioner組件。

  • 叢集中已部署CoreDNS。

搭建Redis叢集

本文以Redis官方鏡像6.0.8版本作為樣本,建立一個6個節點的Redis叢集,其中3個Master節點,3個Slave節點。因為每個節點有自己的狀態和標識,所以使用Statefulset來建立Pod,此外需要為每個節點掛載一個雲端硬碟,用於持久化儲存節點資料。

說明

建議選擇較新Redis版本,如果Redis版本低於5.0,則初始化叢集所用的命令可能會有所不同。

  1. 建立一個ConfigMap,用來儲存和管理Redis叢集的配置。

    kubectl create -f redis-config.yaml

    redis-config.yaml的內容樣本如下:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: redis-cluster
    data:
      redis.conf: |
        bind 0.0.0.0
        port 6379
        cluster-announce-bus-port 16379
        cluster-enabled yes
        appendonly yes
        cluster-node-timeout 5000
        dir /data
        cluster-config-file /data/nodes.conf
        requirepass pass123
        masterauth pass123
    • 欄位dir為Redis節點資料的持久化儲存目錄,所以Pod的/data目錄應當是一個持久化儲存目錄。

    • 欄位cluster-config-file是Redis叢集的節點資訊,由Redis節點自動產生和修改,該配置也應當是一個持久化儲存目錄,以便節點宕機恢複後能夠找到叢集中的其他節點,並繼續工作。

  2. 建立一個headless類型的Service。

    kubectl create -f redis-service.yaml

    redis-service.yaml的內容樣本如下:

    apiVersion: v1
    kind: Service
    metadata:
      name: redis-cluster-svc
    spec:
      clusterIP: None
      selector:
        app: redis-cluster
  3. 建立一個Statefulset,用於部署Redis。

    建立Statefulset時引用之前建立的Service,將ConfigMap掛載到每一個Pod的/config,並且為每一個Pod建立一個PVC,掛載到/data

    kubectl create -f redis.yaml

    redis.yaml的內容樣本如下:

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: redis-cluster
    spec:
      selector:
        matchLabels:
          app: redis-cluster
      serviceName: redis-cluster-svc
      replicas: 6
      template:
        metadata:
          labels:
            app: redis-cluster
            alibabacloud.com/eci: "true"
        spec:
          terminationGracePeriodSeconds: 10
          affinity:
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
              - podAffinityTerm:
                  labelSelector:
                    matchExpressions:
                    - key: app
                      operator: In
                      values:
                      - redis-cluster
                  topologyKey: kubernetes.io/hostname
                weight: 100
          containers:
          - name: redis
            image: redis:6.0.8
            command: ["redis-server", "/config/redis.conf"]
            ports:
            - name: redis
              containerPort: 6379
              protocol: TCP
            - name: election
              containerPort: 16379
              protocol: TCP
            volumeMounts:
            - name: redis-conf
              mountPath: /config
            - name: pvc-essd-redis-data
              mountPath: /data
          volumes:
          - name: redis-conf
            configMap:
              name: redis-cluster
              items:
              - key: redis.conf
                path: redis.conf
      volumeClaimTemplates:
      - metadata:
          name: pvc-essd-redis-data
        spec:
          accessModes: [ "ReadWriteOnce" ]
          storageClassName: alicloud-disk-essd
          resources:
            requests:
              storage: 20Gi

    建立後,等待Statefulset中的所有Pod達到Ready狀態。

    kubectl get statefulset redis-cluster -o wide

    預期返回:

    NAME            READY   AGE     CONTAINERS   IMAGES
    redis-cluster   6/6     8m52s   redis        redis:6.0.8
  4. 初始化叢集。

    1. 擷取每個節點的IP地址。

      目前Redis還不支援以hostname方式初始化叢集,所以需要先擷取每個節點的IP地址。

      kubectl get pods -l app=redis-cluster -o wide

      返回類似以下資訊:

      NAME              READY   STATUS    RESTARTS   AGE     IP             NODE                           NOMINATED NODE   READINESS GATES
      redis-cluster-0   1/1     Running   0          9m37s   172.16.55.6    virtual-kubelet-cn-beijing-k   <none>           <none>
      redis-cluster-1   1/1     Running   0          9m5s    172.16.55.7    virtual-kubelet-cn-beijing-k   <none>           <none>
      redis-cluster-2   1/1     Running   0          8m30s   172.16.55.8    virtual-kubelet-cn-beijing-k   <none>           <none>
      redis-cluster-3   1/1     Running   0          7m46s   172.16.55.9    virtual-kubelet-cn-beijing-k   <none>           <none>
      redis-cluster-4   1/1     Running   0          7m7s    172.16.55.10   virtual-kubelet-cn-beijing-k   <none>           <none>
      redis-cluster-5   1/1     Running   0          6m30s   172.16.55.11   virtual-kubelet-cn-beijing-k   <none>           <none>
    2. 登入到其中一個Redis節點。

      kubectl exec -ti redis-cluster-0 bash

      為6個節點執行初始化命令,當選項--cluster-replicas指定為1時,表示為每個Master節點分配一個Slave節點,這樣叢集中剛好3個Master節點和3個Slave節點。

      redis-cli -a pass123 --cluster create 172.16.55.6:6379 172.16.55.7:6379 172.16.55.8:6379 172.16.55.9:6379 172.16.55.10:6379 172.16.55.11:6379 --cluster-replicas 1

      返回類似以下資訊表示初始化成功。

      [OK] All nodes agree about slots configuration.
      >>> Check for open slots...
      >>> Check slots coverage...
      [OK] All 16384 slots covered.
  5. 使用Redis,

    由於之前建立了一個headless類型的Service,kubernetes叢集會為該服務分配一個DNS A記錄,格式為{service name}.{service namespace}.svc.{domain},可以映射到後端Pod IP列表,即每次訪問該服務名時,將隨機解析到其中一個Redis節點上。

    因此,進入叢集中的任意一個Pod中均可以訪問Redis服務。

    redis-cli -a pass123 -c -h redis-cluster-svc.default.svc.cluster.local -p 6379

    登入後的測試樣本如下:

    172.16.55.8> set k1 v1
    OK
    172.16.55.8> get k1
    "v1"

擴容Redis叢集

目前的部署方式不支援動態擴容叢集,主要的問題是每次新增節點都需要為叢集中的所有節點重新分配雜湊槽,此時可以在Redis鏡像中添加指令碼,以便每個Pod啟動時自動完成該操作,但是當叢集中的資料非常多時,連續的重新分區會導致擴容操作非常緩慢,並且有可能在重新分區期間因為耗盡Redis叢集頻寬而導致依賴此服務的所有用戶端逾時。另一個問題是沒有好的策略確定新啟動的Pod應該作為Master節點還是Slave節點。

基於上述問題,接下來以手動分區示範叢集擴容。

  1. 修改Statefulset副本,將Statefulset的副本數從6改為8。

    kubectl scale statefulsets redis-cluster --replicas=8
  2. 擷取新節點的IP。

    等待所有Pod達到Ready狀態後,執行以下命令擷取新節點的IP地址。

    kubectl get pods -l app=redis-cluster -o wide

    預期返回如下,其中redis-cluster-6和redis-cluster-7為新增加的節點。

    NAME              READY   STATUS    RESTARTS   AGE   IP             NODE                           NOMINATED NODE   READINESS GATES
    redis-cluster-0   1/1     Running   0          88m   172.16.55.6    virtual-kubelet-cn-beijing-k   <none>           <none>
    redis-cluster-1   1/1     Running   0          88m   172.16.55.7    virtual-kubelet-cn-beijing-k   <none>           <none>
    redis-cluster-2   1/1     Running   0          87m   172.16.55.8    virtual-kubelet-cn-beijing-k   <none>           <none>
    redis-cluster-3   1/1     Running   0          87m   172.16.55.9    virtual-kubelet-cn-beijing-k   <none>           <none>
    redis-cluster-4   1/1     Running   0          86m   172.16.55.10   virtual-kubelet-cn-beijing-k   <none>           <none>
    redis-cluster-5   1/1     Running   0          85m   172.16.55.11   virtual-kubelet-cn-beijing-k   <none>           <none>
    redis-cluster-6   1/1     Running   0          52s   172.16.55.16   virtual-kubelet-cn-beijing-k   <none>           <none>
    redis-cluster-7   1/1     Running   0          32s   172.16.55.17   virtual-kubelet-cn-beijing-k   <none>           <none>
  3. 添加Master節點。

    登入到其中一個節點(以redis-cluster-0節點為例),執行以下命令將redis-cluster-6添加為叢集的Master節點。

    kubectl exec -ti redis-cluster-0 bash
    redis-cli -a pass123 --cluster add-node 172.16.55.16:6379 172.16.55.6:6379
    說明

    172.16.55.6:6379為任意一箇舊節點的訪問地址,串連成功後,redis-cli將自動擷取其他所有節點。

    查看redis-cluster-6節點的ID。

    redis-cli -a pass123 -c cluster nodes | grep 172.16.55.16:6379 | awk '{print $1}'

    預期返回:

    47879390ecc7635f5c57d3e324e2134b24******
  4. 重新分配雜湊槽。

    現在共有4個Master節點,共同分擔16384個雜湊槽,平均每個節點分擔的雜湊槽數量為16384 / 4 = 4096,即需要從之前的3個Master分出4096個雜湊槽給新節點。

    執行以下命令開始分配雜湊槽,其中172.16.55.6:6379為任意一箇舊節點的訪問地址,串連成功後,redis-cli將自動擷取其他所有節點。

    redis-cli -a pass123 --cluster reshard 172.16.55.6:6379

    以上命令是互動,請根據提示依次輸入資訊:

    How many slots do you want to move (from 1 to 16384)? 4096
    What is the receiving node ID? 47879390ecc7635f5c57d3e324e2134b24******
    Source node #1: all
    Do you want to proceed with the proposed reshard plan (yes/no)? yes
    • 準備為新節點(redis-cluster-6)分配的雜湊槽數量:4096

    • 接收節點的ID,即新節點的ID:47879390ecc7635f5c57d3e324e2134b24******

    • 來源節點的ID,從所有節點平均抽取雜湊槽給新節點:all

    • 確認分配:yes

    操作後,需等待分配完畢,在此期間Redis叢集可以正常提供服務。

  5. 添加Slave節點。

    執行以下命令將redis-cluster-7添加為redis-cluster-6的Slave節點。

    redis-cli -a pass123 --cluster add-node 172.16.55.17:6379 172.16.55.6:6379 --cluster-slave --cluster-master-id 47879390ecc7635f5c57d3e324e2134b24******
    說明
    • 172.16.55.6:6379為任意一箇舊節點的訪問地址,串連成功後,redis-cli將自動擷取其他所有節點。

    • 47879390ecc7635f5c57d3e324e2134b24******為該Slave節點要跟隨的Master節點ID,即redis-cluster-6節點的ID。

    添加後可以看到有4個Master節點,並且每個Master有一個副本節點。

    redis-cli -a pass123 -c cluster nodes

    返回樣本如下:

    47879390ecc7635f5c57d3e324e2134b24****** 172.16.55.16:6379@16379 master - 0 1667383406000 7 connected 0-1364 5461-6826 10923-12287
    75a7398a45f9696066eaa6ac7968b13a47****** 172.16.55.11:6379@16379 slave b8f6b826241b47f29a4bdde14104b28fe8****** 0 1667383406514 2 connected
    b7849f8577e43d5a6da51bc78ae809bbb1****** 172.16.55.17:6379@16379 slave 47879390ecc7635f5c57d3e324e2134b24****** 0 1667383406314 7 connected
    b8f6b826241b47f29a4bdde14104b28fe8****** 172.16.55.7:6379@16379 master - 0 1667383406815 2 connected 6827-10922
    ffce547186e0be179830cb0dca47c203f6****** 172.16.55.6:6379@16379 myself,master - 0 1667383406000 1 connected 1365-5460
    833a939cde93991c8a16c41fb2568b8642****** 172.16.55.8:6379@16379 master - 0 1667383406514 3 connected 12288-16383
    a4df1a26394bfcb833914278744219db01****** 172.16.55.9:6379@16379 slave 833a939cde93991c8a16c41fb2568b8642****** 0 1667383407515 3 connected
    c4234d71343ca4dbe07822e17f7572ac30****** 172.16.55.10:6379@16379 slave ffce547186e0be179830cb0dca47c203f6****** 0 1667383405813 1 connected

縮容Redis叢集

叢集縮容時,由於StatefulSet只能以和建立Pod時相反的順序逐個刪除Pod,即如果要刪除一個Master節和一個Slave節點,就只能刪除redis-cluster-7和redis-cluster-6。其次,刪除這兩個節點前必須先把該節點上所有雜湊槽移動到其他節點,否則將會有資料丟失,並且Redis叢集將拒絕服務。

  1. 重新分配雜湊槽。

    為了避免剩下的3個Master節點出現負載傾斜的情況,需要將redis-cluster-6的雜湊槽平均分配給其它Master節點,即需要執行三次重新分區的操作,操作方法與擴容時執行的分區操作類似,但接收節點需變更為其它Master節點中的一個,來源節點為redis-cluster-6的節點ID。樣本如下:

    redis-cli -a pass123 --cluster reshard 172.16.55.6:6379

    以上命令是互動,請根據提示依次輸入資訊:

    How many slots do you want to move (from 1 to 16384)? 4096
    What is the receiving node ID? ffce547186e0be179830cb0dca47c203f6******
    Source node #1: 47879390ecc7635f5c57d3e324e2134b24******
    Source node #2: done
    Do you want to proceed with the proposed reshard plan (yes/no)? yes
    • 準備給舊節點分配的雜湊槽數量:4096

    • 接收節點的ID,即舊節點的ID:ffce547186e0be179830cb0dca47c203f6******

    • 來源節點的ID,從redis-cluster-6節點抽取雜湊槽給舊節點:47879390ecc7635f5c57d3e324e2134b24******

    • 確認分配:yes

  2. 修改Statefulset副本,將Statefulset的副本數從8改為6。

    kubectl scale statefulsets redis-cluster --replicas=6

    查看Redis叢集的Pod資訊,可以看到叢集縮容回6個節點。

    kubectl get pods -l app=redis-cluster -o wide

刪除Redis叢集

刪除Redis叢集時需要刪除StatefulSet,Service和ConfigMap。

kubectl delete statefulset redis-cluster
kubectl delete svc redis-cluster-svc
kubectl delete cm redis-cluster

需要注意的是,StatefulSet刪除後,相關PVC並不會被自動刪除,您需要單獨刪除PVC。PVC刪除後,相應的雲端硬碟和資料也將被刪除。

kubectl delete pvc -l app=redis-cluster