先丢数据,再中病毒,我终究还是从redis-sentinel的火坑中爬了出来

905 阅读5分钟

在第一次使用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自然成了我应该去理解和使用的目标。

中病毒?

这也是我第一次中病毒,这个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 :)