简介:这份资源提供了一套基于 Docker 的 Redis 集群一键部署方案,面向需要在 CentOS 7.x 环境下快速搭建 Redis 集群的运维与后端开发人员。使用者只需按说明将参数传递给安装脚本,即可自动完成镜像加载、容器编排与集群初始化,省去手工配置节点与槽位的繁琐过程,适合具备基础 Linux 与 Docker 操作能力、希望快速验证或落地集群环境的技术人员。资源包共 6 个文件,包含 3 个 shell 脚本、1 个镜像 tar 包、1 个 conf 配置文件与 1 个 md 说明文档,压缩包约 22.46MB,脚本负责自动化部署与卸载,配置文件定义集群参数,镜像包则免去联网拉取步骤。目前已有 1055 人学习下载。整体方案亲测可用,读者可借此获得一套可直接复用的部署脚本与排错参考,快速理解 Redis 集群在容器环境下的搭建流程与参数组织方式。
1. 为什么我宁愿写 200 行 shell,也不手动搭一次 Redis 集群
在 CentOS 7.x 上手工搭一套 Redis 集群,我干过不止一次,每次都要重复这几步:装 docker、拉镜像、写 6 份 redis.conf、改 bind 和 cluster-announce-ip、逐个redis-server起容器、redis-cli --cluster create分配槽位、再手动验证主从。中间任何一步 IP 写错、端口冲突、防火墙没放行,就得从头排查。所以后来我把整套流程压成了一个 shell 脚本,配合 docker 一条命令拉起 3 主 3 从的 Redis 集群,专门适配 CentOS 7.x 这套老环境。这篇笔记讲的就是这个脚本怎么写、参数怎么定、哪些坑我踩过。适合手上还有一批 CentOS 7 机器、想用 docker 快速起 Redis 集群做缓存或做 Spring Cloud 这类微服务中间件的同学,也适合刚学 shell 脚本、想找一个真实可跑的项目练手的人。脚本不依赖任何外部编排工具,纯 bash + docker,能复现。
2. 集群拓扑与脚本整体设计:先想清楚再动手
2.1 为什么选 3 主 3 从,而不是 6 主
Redis Cluster 官方推荐的最小生产拓扑是 3 主 3 从,共 6 个节点。原因是集群做故障转移需要「多数派」投票:3 个主节点里挂掉 1 个,剩下 2 个仍占多数,能把它的从节点提升为主,集群继续可用。如果只起 3 个主节点不带从,一旦某个主挂了,它负责的 16384 个哈希槽里那部分就没人管,整个集群进入 fail 状态,写请求直接报CLUSTERDOWN。
那为什么不上 6 主?因为主节点越多,槽位分片越细,客户端维护的连接和重定向开销越大,而单机 docker 环境下 6 个主节点全挤在一台机器上,故障域根本没隔离,多出来的主只是徒增内存和端口占用。3 主 3 从在单机演示和中小规模缓存场景里是性价比最高的选择。常见做法是每个节点分配 512MB 到 1GB 内存上限,6 个节点加起来对一台 8G 内存的 CentOS 7 机器压力可控。
端口规划上,我一般用 7001 到 7006 这 6 个端口,奇数端口做 master,偶数端口做对应的 slave,这样看端口号就能猜到角色。每个节点还需要一个「集群总线端口」,规则是服务端口 + 10000,也就是 17001 到 17006,这个端口用于节点间 gossip 通信,防火墙必须一起放行,很多人只开了 7001 忘了 17001,结果cluster create一直卡在Waiting for the cluster to join。
2.2 脚本要解决的四个核心问题
一个能用的部署脚本,本质要处理四件事,缺一个都跑不起来。
第一是环境准备:CentOS 7.x 默认的 yum 源里 docker 版本很老,需要换源、装 docker-ce、启动并设置开机自启。第二是配置生成:6 个节点不能共用一份 redis.conf,每个节点的端口、集群总线端口、节点自身 IP 都要单独写,否则节点之间互相找不到。第三是集群初始化:容器起来只是 6 个孤立的 Redis 实例,必须用redis-cli --cluster create把它们组成集群并分配槽位。第四是幂等与清理:脚本重复执行时不能因为容器已存在就报错退出,要能先清理旧容器再重建。
下面这张表是我脚本里用到的核心变量,先列出来,后面代码会引用:
| 变量名 | 含义 | 示例值 |
|---|---|---|
| REDIS_PORT_START | 起始端口 | 7001 |
| NODE_COUNT | 节点总数 | 6 |
| MASTER_COUNT | 主节点数 | 3 |
| REDIS_PASSWORD | 集群密码 | 自定义 |
| HOST_IP | 宿主机内网 IP | 192.168.1.100 |
| DATA_DIR | 数据挂载目录 | /data/redis-cluster |
| REDIS_IMAGE | 镜像 | redis:6.2 |
HOST_IP这个变量是整份脚本里最容易翻车的地方。集群模式下,节点向其他节点宣告自己的地址用的是cluster-announce-ip,如果你填了127.0.0.1,那么其他容器拿到这个地址后根本连不上,集群永远组建不成功。必须填宿主机在局域网里的真实 IP,用ip addr或hostname -I取。
2.3 目录结构与数据持久化设计
我习惯把每个节点的配置和数据分开存放,目录结构长这样:
/data/redis-cluster/ ├── 7001/ │ ├── redis.conf │ └── data/ ├── 7002/ │ ├── redis.conf │ └── data/ ... └── 7006/每个节点一个目录,redis.conf和data都在里面,容器启动时把这两个路径挂载进去。这样做的好处是:删容器不删数据,重建集群时数据还在;排查问题时直接进对应目录看配置和日志,不用docker exec进去翻。数据目录一定要挂载出来,否则容器一删,RDB 和 AOF 全没了,这在生产里是灾难。
配置生成用 shell 的 for 循环配合 heredoc 写,比用 sed 去改模板文件更直观,也不容易因为转义问题出错。下面这段就是生成单个节点配置的核心逻辑:
# 循环生成 6 个节点的 redis.conf for i in $(seq 0 $((NODE_COUNT - 1))); do PORT=$((REDIS_PORT_START + i)) BUS_PORT=$((PORT + 10000)) NODE_DIR="${DATA_DIR}/${PORT}" mkdir -p "${NODE_DIR}/data" cat > "${NODE_DIR}/redis.conf" <<EOF port ${PORT} bind 0.0.0.0 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-announce-ip ${HOST_IP} cluster-announce-port ${PORT} cluster-announce-bus-port ${BUS_PORT} appendonly yes requirepass ${REDIS_PASSWORD} masterauth ${REDIS_PASSWORD} daemonize no dir /data EOF done这段代码里几个参数值得单独说。bind 0.0.0.0配合protected-mode no是为了让容器外部能连进来,生产环境如果只在 docker 内网通信,可以收紧成具体网段。cluster-announce-ip填宿主机 IP,这是集群能组起来的关键。requirepass和masterauth必须同时设,前者是客户端连进来的密码,后者是主从之间同步用的密码,只设一个会导致从节点连不上主节点,日志里报NOAUTH。appendonly yes开启 AOF 持久化,集群场景下比纯 RDB 更稳,重启后数据丢失窗口更小。
cluster-node-timeout 5000是节点失联判定时间,单位毫秒。设太小,网络抖动就触发故障转移,集群频繁切主;设太大,真挂了半天不切换。5000 是官方给的常见起点,内网环境够用。如果机器负载高、GC 停顿明显,可以调到 10000 到 15000。
3. 从零到集群可用:脚本分步实现与执行
3.1 环境准备:CentOS 7 装 docker 与换源
CentOS 7.x 自带的 yum 源里 docker 叫docker,版本停在 1.13,太老,很多新镜像的语法不支持。我一般直接换官方 docker-ce 源。先装依赖工具,再添加仓库,最后装 docker-ce。
# 安装基础依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加 docker-ce 官方仓库 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装 docker-ce 并启动 yum install -y docker-ce systemctl start docker systemctl enable docker # 验证 docker version如果服务器访问官方源慢,可以把仓库地址换成国内镜像站,这一步属于 CentOS 7 换源的常规操作,不影响脚本逻辑。装完后docker version能看到 Client 和 Server 两段输出才算成功。有个常见现象是只显示 Client 不显示 Server,那说明 docker 守护进程没起来,systemctl status docker看报错,多半是 SELinux 或存储驱动的问题。
CentOS 7 默认开着 firewalld,集群节点之间要通信,必须放行 7001-7006 和 17001-17006 这两段端口。我一般直接按范围放行:
# 放行集群服务端口和总线端口 firewall-cmd --zone=public --add-port=7001-7006/tcp --permanent firewall-cmd --zone=public --add-port=17001-17006/tcp --permanent firewall-cmd --reload提示:如果测试环境图省事,可以临时
systemctl stop firewalld,但生产环境别这么干,老老实实放行端口。另外有些云主机外层还有安全组,记得同步放行,否则本机通了外面还是连不上。
3.2 拉镜像与启动 6 个容器
镜像我选redis:6.2,这个版本对集群支持成熟,体积也适中。先拉下来:
docker pull redis:6.2启动容器时,每个节点用--net host模式,让容器直接共享宿主机网络栈。这样做的好处是省去端口映射的麻烦,集群总线端口也能正常通信。如果用默认 bridge 网络加-p映射,17001 这类总线端口很容易漏映射,导致集群组建失败。--net host下容器里的 Redis 直接监听宿主机端口,配置里的cluster-announce-ip填宿主机 IP 就完全对得上。
# 启动 6 个 Redis 节点容器 for i in $(seq 0 $((NODE_COUNT - 1))); do PORT=$((REDIS_PORT_START + i)) NODE_DIR="${DATA_DIR}/${PORT}" # 先清理同名旧容器,保证脚本可重复执行 docker rm -f "redis-${PORT}" >/dev/null 2>&1 docker run -d \ --name "redis-${PORT}" \ --net host \ --restart always \ -v "${NODE_DIR}/redis.conf:/usr/local/etc/redis/redis.conf" \ -v "${NODE_DIR}/data:/data" \ redis:6.2 \ redis-server /usr/local/etc/redis/redis.conf donedocker rm -f那行是幂等的关键,脚本重复跑不会因为容器名冲突中断。--restart always保证宿主机重启后容器自动拉起,配合appendonly yes,数据和服务都能恢复。挂载配置时注意容器内路径是/usr/local/etc/redis/redis.conf,这是 redis 官方镜像约定的配置目录,别写错。启动后可以用docker ps看 6 个容器是不是都 Up 状态,有 Exited 的用docker logs redis-7001看日志。
3.3 用 redis-cli 组建集群并分配槽位
6 个容器起来后,它们还是各自独立的实例,cluster-enabled yes只是让它们具备集群能力,还没组成集群。组建集群用redis-cli --cluster create,把 3 个主节点和 3 个从节点按顺序传进去,--cluster-replicas 1表示每个主节点配 1 个从节点。
# 组装节点列表,前 3 个是 master,后 3 个是 slave NODES="" for i in $(seq 0 $((NODE_COUNT - 1))); do PORT=$((REDIS_PORT_START + i)) NODES="${NODES} ${HOST_IP}:${PORT}" done # 创建集群,--cluster-yes 跳过交互确认 docker run --rm --net host redis:6.2 \ redis-cli -a "${REDIS_PASSWORD}" --cluster create \ ${NODES} \ --cluster-replicas 1 \ --cluster-yes这里我用了一个临时容器来跑redis-cli,因为宿主机上不一定装了 redis 客户端,用镜像里的最省事。--cluster-yes让脚本非交互执行,适合放进自动化流程。执行成功后输出里会看到All 16384 slots covered,这句话是集群健康的标志,说明 16384 个槽位全部分配完毕。
如果卡在Waiting for the cluster to join,八成是总线端口没通或者cluster-announce-ip填错了。排查顺序是:先docker exec -it redis-7001 redis-cli -a 密码 -p 7001 cluster nodes看节点有没有互相发现,再检查防火墙 17001-17006 是否放行,最后确认配置里的 IP 是不是宿主机真实 IP。
集群建好后,验证一下状态:
# 查看集群信息 docker exec -it redis-7001 redis-cli -a "${REDIS_PASSWORD}" -p 7001 cluster info # 查看节点角色和槽位分配 docker exec -it redis-7001 redis-cli -a "${REDIS_PASSWORD}" -p 7001 cluster nodescluster info里cluster_state:ok表示集群正常,cluster_slots_assigned:16384表示槽位分配完整。cluster nodes会列出 6 个节点,前面带master的是主,带slave的是从,每个主节点后面能看到它负责的槽位区间。
3.4 写入测试与主从切换验证
集群可用不代表主从复制正常,得实际写数据验证。用-c参数让 redis-cli 跟随重定向,往集群里写 key:
# 写入测试,-c 表示集群模式 docker exec -it redis-7001 redis-cli -a "${REDIS_PASSWORD}" -c -p 7001 set testkey hello docker exec -it redis-7001 redis-cli -a "${REDIS_PASSWORD}" -c -p 7001 get testkey写入成功会返回OK,读取返回hello。如果报MOVED错误,说明没加-c,客户端不知道要跳到负责该槽位的节点。生产里用 Jedis、Lettuce 这类客户端会自动处理重定向,不用手动加。
验证主从复制,可以往某个主节点写,然后去它的从节点读。先通过cluster nodes找到某个 master 的 slave 端口,直接连从节点:
# 假设 7002 是 7001 的从节点 docker exec -it redis-7002 redis-cli -a "${REDIS_PASSWORD}" -p 7002 get testkey能读到同样的值,说明主从同步正常。想验证故障转移,可以手动停掉一个主节点容器,等几秒后看它的从节点有没有被提升为主:
docker stop redis-7001 sleep 10 docker exec -it redis-7002 redis-cli -a "${REDIS_PASSWORD}" -p 7002 cluster nodes如果 7002 的角色从slave变成了master,说明故障转移成功。测完记得把 7001 重新启动,它会以从节点身份重新加入集群。这个验证过程建议在正式上线前跑一遍,心里有底。
4. 避坑与排查:那些让我熬夜的集群故障
4.1 集群一直卡在 Waiting for the cluster to join
现象是执行cluster create后长时间无响应,最后超时失败。原因通常是两类:一是集群总线端口 17001-17006 没放行,节点之间 gossip 通信被防火墙拦了;二是cluster-announce-ip填了127.0.0.1或容器内网 IP,其他节点拿到这个地址后连不上。解决办法是先确认防火墙放行了总线端口,再把配置里的 IP 改成宿主机局域网真实 IP,重建容器。用docker exec进容器ping一下其他节点的 IP,能通才说明网络层没问题。
4.2 从节点连不上主节点,日志报 NOAUTH
现象是cluster nodes里从节点状态一直是fail或者connecting,docker logs里能看到NOAUTH Authentication required。原因是配置里只设了requirepass没设masterauth,主从同步时从节点拿不到密码。解决很简单,两个参数都写上,值保持一致,然后重建容器。这个坑很隐蔽,因为客户端连进来是正常的,只有主从复制这条链路受影响。
4.3 容器重启后集群状态丢失
现象是宿主机重启或容器重建后,cluster info显示cluster_state:fail,槽位分配不全。原因是cluster-config-file nodes.conf没持久化,或者数据目录没挂载出来。nodes.conf记录着集群的拓扑和槽位分配,丢了就得重新cluster create。解决办法是确保dir /data指向的目录挂载到宿主机,nodes.conf会生成在里面。我一般把整个节点目录都挂出来,配置和数据一起持久化。
4.4 内存超限导致节点被 OOM 杀掉
现象是某个 Redis 容器突然 Exited,docker logs里看到OOM command not allowed或者直接被系统 kill。原因是没设maxmemory,Redis 把宿主机内存吃满,被内核 OOM Killer 干掉。解决办法是在配置里加maxmemory 512mb和maxmemory-policy allkeys-lru,给每个节点设上限。6 个节点各 512MB,一台 8G 机器留足余量。集群模式下每个节点内存别设太大,否则故障转移时数据迁移会很慢。
4.5 客户端报 CLUSTERDOWN 但集群看着正常
现象是应用连集群报CLUSTERDOWN The cluster is down,但cluster info显示cluster_state:ok。原因多半是客户端连的节点和实际集群不在一个网络,或者客户端缓存了旧的拓扑。还有一种情况是集群里某个主节点挂了且没有从节点接管,部分槽位不可用,整体进入 fail。排查时先用cluster info确认cluster_slots_assigned是不是 16384,再用cluster nodes看有没有节点处于fail状态。如果是客户端拓扑缓存问题,重启客户端连接池即可。
5. 把脚本打磨成能反复用的工具:几个进阶技巧
脚本能跑通只是第一步,真正省心的是让它能反复用、能自检、能应对不同环境。我后来在这份脚本上加了三样东西,分享给你。
第一是执行前的环境自检。在脚本开头加一段检查,docker 没装就提示,端口被占用就报错,避免跑到一半才失败:
# 检查 docker 是否可用 if ! command -v docker >/dev/null 2>&1; then echo "docker 未安装,请先安装 docker-ce" exit 1 fi # 检查起始端口是否被占用 if ss -tlnp | grep -q ":${REDIS_PORT_START} "; then echo "端口 ${REDIS_PORT_START} 已被占用,请修改 REDIS_PORT_START" exit 1 fi第二是集群健康检查函数。部署完自动跑一遍验证,把结果打印出来,不用手动一条条敲:
# 集群健康检查 check_cluster() { local state state=$(docker exec "redis-${REDIS_PORT_START}" \ redis-cli -a "${REDIS_PASSWORD}" -p "${REDIS_PORT_START}" \ cluster info | grep cluster_state | tr -d '\r') echo "集群状态: ${state}" if echo "${state}" | grep -q "ok"; then echo "集群健康" else echo "集群异常,请检查日志" fi }第三是参数外置。把HOST_IP、REDIS_PASSWORD、端口范围这些放到脚本顶部的变量区,或者用一个单独的cluster.env文件 source 进来。这样同一份脚本换台机器只改变量,不用动逻辑。我一般还会加一个--clean参数,传了就先把旧容器和数据目录清掉再重建,方便测试环境反复折腾。
| 进阶能力 | 解决什么问题 | 实现要点 |
|---|---|---|
| 环境自检 | 跑到一半才报错 | 检查 docker、端口占用 |
| 健康检查 | 部署完不知道成没成 | 解析 cluster info 输出 |
| 参数外置 | 换环境要改脚本 | 变量区或 env 文件 |
| clean 模式 | 测试环境反复重建 | 参数控制清理逻辑 |
最后说个我自己的习惯:每次改完脚本,我一定先在一台干净的 CentOS 7 虚拟机上从零跑一遍,而不是在已经装过 docker、跑过集群的机器上测。因为老环境里残留的容器、端口、数据目录会掩盖很多问题,只有干净环境跑通了,才敢拿到别的机器上用。这个习惯帮我省了无数次「在我这明明能跑」的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取