先丟資料,再中病毒,我終究還是從redis-sentinel的火坑中爬了出來

wwayne21發表於2020-04-05

在第一次使用redis sentinel模式的時候,我是通過大量的google和別人的分享完成了我的第一版搭建並上線,這之後我不僅丟過資料,還中了挖礦病毒...而後也只能通過別人零星的分享加上不斷的嘗試頭痛醫頭腳痛醫腳地一步步升級,所以我希望藉此可以分享一些我的實踐,提供一個生產環境可用的模板,未來有機會幫助那些第一次搭建redis sentinel的人一步到位。

我主要是使用docker-compose來組建我的服務,所以我的redis sentinel也是針對docker-compose來配置的。

為什麼要使用redis sentinel?

我之前自己開發的產品都有用到redis做一些快取的資料,但我其實都只是使用一個redis例項,它掛了以後redis裡的資料就無法被讀取到了。這一次我是作為供應商為一個客戶開發產品,這裡面剛好涉及到一些諸如每週下載排行之類的東西,這類資料我都是讓cronjob在夜深人靜時觸發計算然後將結果存入redis中,而讓一個孤獨的redis例項去保障這些資料總顯得有些形單影薄。

於是我google了一些redis避免單點失敗,可持續之類的東西,發現了sentinel和cluster。簡單理解就是sentinel如其名字所示就是一個哨兵,它會保障在你的一個redis例項掛了以後,第一時間讓另一個候補選手來替換這個redis例項;而cluster主要是當你有多個機器多個redis例項時,幫你分割槽管理資料。而我只有一個伺服器,完全不涉及什麼分散式,sentinel自然成了我應該去理解和使用的目標。

中病毒?

先丟資料,再中病毒,我終究還是從redis-sentinel的火坑中爬了出來
這也是我第一次中病毒,這個cpu使用率實在刺眼,查了一下才知道是比較常見的挖礦病毒,可能是通過在docker啟動時,從不設定密碼的redis傳入進來。但我一直有設定防火牆限制埠,所以我也不確定這病毒到底是怎麼進入我的伺服器,總之我清理完這個病毒後就給sentinel加了密碼。

那接下來我就介紹下各種配置的內容:

檔案目錄

sentinel/
   Dockerfile
   sentinel-entrypoint.sh
   sentinel.conf
.env
docker-compose.yml
複製程式碼

docker-compose.yml

在這裡我們啟動1個redis master節點,2個redis slave後補節點以及3個sentinel哨兵做監督,為什麼這裡是3個sentinel呢?因為在master節點掛了以後,sentinel們會投票決定是否選擇候補節點來接班,只要有兩票就可以決定哪一個後補節點來上位,所以這時候假設其中一個sentinel剛好掛了也不會影響整套機制的運作。

在這裡我們不僅要通過docker-compose的volumes來保持redis的資料持久化,也需要通過redis的command來加入密碼保護

version: '3.2'
services:
  api:
    ...
    environment:
      ...
      - REDIS_SENTINEL_HOSTS=redis-sentinel1,redis-sentinel2,redis-sentinel3
      - REDIS_SENTINEL_NAME=${REDIS_SENTINEL_NAME}
      - REDIS_PASSWORD=${REDIS_PASSWORD}
      ...

  redis-master:
    image: 'redis:5-alpine'
    container_name: xxx-master
    command: redis-server --port 6379 --requirepass ${REDIS_PASSWORD}
    ports:
      - '6379:6379'
    volumes:
      - "${REDIS_DATA}:/data"

  redis-slave1:
    image: 'redis:5-alpine'
    container_name: xxx-slave1
    ports:
      - '6380:6380'
    volumes:
      - "${REDIS_DATA}/slave1:/data"
    command: redis-server --port 6380 --slaveof redis-master 6379 --requirepass ${REDIS_PASSWORD} --masterauth ${REDIS_PASSWORD}
    depends_on:
      - redis-master

  redis-slave2:
    image: 'redis:5-alpine'
    container_name: xxx-slave2
    ports:
      - '6381:6381'
    volumes:
      - "${REDIS_DATA}/slave2:/data"
    command: redis-server --port 6381 --slaveof redis-master 6379 --requirepass ${REDIS_PASSWORD} --masterauth ${REDIS_PASSWORD}
    depends_on:
      - redis-master

  redis-sentinel1:
    build: ./sentinel
    container_name: xxx-sentinel1
    ports:
      - '26379:26379'
    environment:
      - SENTINEL_NAME=${REDIS_SENTINEL_NAME}
      - MASTER_HOST=redis-master
      - MASTER_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD}
    depends_on:
      - redis-master

  redis-sentinel2:
    build: ./sentinel
    container_name: xxx-sentinel2
    ports:
      - '26380:26379'
    environment:
      - SENTINEL_NAME=${REDIS_SENTINEL_NAME}
      - MASTER_HOST=redis-master
      - MASTER_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD}
    depends_on:
      - redis-master

  redis-sentinel3:
    build: ./sentinel
    container_name: xxx-sentinel3
    ports:
      - '26381:26379'
    environment:
      - SENTINEL_NAME=${REDIS_SENTINEL_NAME}
      - MASTER_HOST=redis-master
      - MASTER_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD}
    depends_on:
      - redis-master
複製程式碼

sentinel/Dockerfile

這個檔案是用來啟動redis sentinel,參考自Redis Sentinel Docker

FROM redis:5-alpine

EXPOSE 26379
COPY sentinel.conf /etc/redis/sentinel.conf
RUN chown redis:redis /etc/redis/sentinel.conf
ENV SENTINEL_QUORUM 2
ENV SENTINEL_DOWN_AFTER 30000
ENV SENTINEL_FAILOVER 180000
COPY sentinel-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["sentinel-entrypoint.sh"]
複製程式碼

sentinel/sentinel-entrypoint.sh

這個檔案是將來自docker-compose的一些環境變數傳入redis sentinel的配置檔案中,參考自Redis Sentinel Docker

#!/bin/sh

sed -i "s/\$SENTINEL_QUORUM/$SENTINEL_QUORUM/g" /etc/redis/sentinel.conf
sed -i "s/\$SENTINEL_DOWN_AFTER/$SENTINEL_DOWN_AFTER/g" /etc/redis/sentinel.conf
sed -i "s/\$SENTINEL_FAILOVER/$SENTINEL_FAILOVER/g" /etc/redis/sentinel.conf
sed -i "s/\$SENTINEL_NAME/$SENTINEL_NAME/g" /etc/redis/sentinel.conf
sed -i "s/\$MASTER_HOST/$MASTER_HOST/g" /etc/redis/sentinel.conf
sed -i "s/\$MASTER_PORT/$MASTER_PORT/g" /etc/redis/sentinel.conf
sed -i "s/\$REDIS_PASSWORD/$REDIS_PASSWORD/g" /etc/redis/sentinel.conf
exec docker-entrypoint.sh redis-server /etc/redis/sentinel.conf --sentinel
sentinel/sentinel.conf
複製程式碼

redis sentinel的配置檔案,參考自Redis Sentinel Docker

port 26379

dir /tmp

sentinel monitor $SENTINEL_NAME $MASTER_HOST $MASTER_PORT $SENTINEL_QUORUM
sentinel down-after-milliseconds $SENTINEL_NAME $SENTINEL_DOWN_AFTER
sentinel parallel-syncs $SENTINEL_NAME 1
sentinel auth-pass $SENTINEL_NAME $REDIS_PASSWORD
sentinel failover-timeout $SENTINEL_NAME $SENTINEL_FAILOVER
複製程式碼

.env

通過env檔案將一些敏感的資訊通過環境變數的方式寫入,可以將這個檔案只存放在伺服器中從而保證資訊保安

REDIS_SENTINEL_NAME=這個是sentinel的名字,隨便取一個就好
REDIS_PASSWORD=這個是redis的密碼
REDIS_DATA=這個是redis儲存資料的路徑,比如./data/redis這樣
複製程式碼

最後我們從客戶端接入進來就可以,因為我使用的是node.js以及ioredis,所以這裡以此為例

/*
 * redisSentinelHosts = process.env.REDIS_SENTINEL_HOSTS.split(/,\W*/))
 * redisSentinelName = process.env.REDIS_SENTINEL_NAME
 * redisPassword = process.env.REDIS_PASSWORD
*/

const Redis = require("ioredis");
return new Redis({
    sentinels: redisSentinelHosts.map(host => ({ host, port: "26379" })),
    name: redisSentinelName,
    password: redisPassword
});
複製程式碼

測試一下

當一切都執行起來時,你可以嘗試先給redis的master節點寫入一個資料,然後使用 docker stop來關掉這個節點,這個時候如果你用docker logs來觀察任何一個sentinel節點的話,都可以看到它們如何投票廢棄當前master節點,推舉另一個salve節點上位的過程,當這一過程完成後,你再通過客戶端請求來看自己之前儲存的redis資料是否依舊能被訪問到就可以了。

開發環境中啟動redis-sentinel

以上都是都是針對生產環境的docker-compose,而通常在開發中我們也會用docker來啟動一些需要的服務諸如redis以及其它database等等,如果你和我一樣也是在mac環境下開發,可以參考這篇文章來搭建開發環境中的redis-sentinel,還是挺便利的。

Happy coding :)

相關文章