ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Shell脚本一键创建Redis Cluster集群实战指南

Shell脚本一键创建Redis Cluster集群实战指南 写这篇实战文章前先说个背景。我之前有段时间频繁搭建Redis Cluster一开始按官方文档手搓一次两次还行次数多了就发现整个流程里有大量重复劳动每台机器要写单独的配置文件、起服务、敲 create 命令、分配主从……期间只要手滑把某个 IP 打错、端口写错整个集群直接起不来排查起来特别磨人。后来我把整个流程固化成了一个 shell 脚本参数输对、回车一敲几分钟内自动完成从节点初始化到槽位分配的全过程。今天把脚本的设计思路、完整源码和踩坑记录全部分享出来希望对正在折腾 Redis Cluster 的同学有参考价值。这个标题“shell脚本一键创建redis cluster集群”对应的其实是运维自动化的一个经典场景特别适合这三类人看一是刚开始接触 Redis Cluster、想看完整搭建流程的新手二是需要在多台机器上反复部署集群、想把重复工作收敛掉的运维三是做本地测试环境、想快速起一套集群验证业务逻辑的开发。核心思路不复杂就是把手工操作翻译成变量、循环、函数让 shell 替我们做那些枯燥且容易出错的重复步骤。1. 为什么要把集群搭建脚本化1.1 手工部署 Redis Cluster 的痛点手工部署一个三主三从的集群至少要经历这些步骤准备六台机器或六个端口、为每个节点修改 redis.conf、逐个启动 redis-server、等全部节点就绪后用 redis-cli 执行 create 命令、再处理可能的告警和绑定问题。听起来不多但每一步都有隐蔽的坑。我见过最典型的翻车现场是 bind 配置。很多人拿着同一个配置模板去改结果漏改了某台机器的 bind 地址节点启动很顺利等到 create 的时候直接报[ERR] Node ... is not empty或者 connect 超时。这种错误定位非常费时因为问题不在语法而在节点之间的网络连通性。还有一类坑是端口规划混乱Redis Cluster 每个节点除了业务端口还有一个端口 10000 的集群总线端口如果业务端口从 7000 开始总线端口就是 17000。防火墙只放行了前者就会出现节点能启动但互相 meet 失败、一直处于waiting for the cluster to join的状态。手工操作多人就会疲劳疲劳就会出错。脚本本质上是把操作流程标准化让机器去执行那些确定性很高的动作同时通过 wait、检查、日志输出把过程中可能出现的异常提前暴露出来这比等 create 命令报错再去排查效率高得多。1.2 脚本化方案的预期目标我对这个脚本的定位是“一个命令、一段日志、一次成功”。具体来说它需要满足几个硬性指标只需要传入节点 IP 列表和一个起始端口脚本自动推导出所有端口包括业务端口和总线端口。每个节点的 redis.conf 由脚本动态生成避免手工复制模板导致的配置漂移。节点启动后等待端口就绪再做集群初始化而不是启动完立刻执行 create保证时序稳定。主从角色分配用 redis-cli 自带的 cluster replicas 机制让官方工具去决定谁是谁的从节点减少人为规划带来的主从不均匀问题。脚本执行过程输出清晰的日志生成了什么配置、启动哪个节点、当前状态是什么、最终 cluster info 的结果如何。这套设计目标看着简单实际写的时候要考虑很多细节比如端口推导、IP 去重、等待就绪的时间、失败时的退出策略。接下来的章节我会逐一展开。2. 脚本核心设计参数、拓扑与配置生成2.1 端口规划与拓扑推导Redis Cluster 至少需要三个主节点生产环境推荐三主三从。端口规划上我习惯从 7000 开始依次递增业务端口 7000-7005对应总线端口 17000-17005。脚本里我定义一个基础端口变量通过下标计算BASE_PORT7000 # 第 i 个节点的业务端口 BASE_PORT i # 第 i 个节点的总线端口 BASE_PORT i 10000为什么单独留出 10000 段这是 Redis Cluster 的硬性约定集群节点间通过总线端口做 gossip 通信端口偏移固定为 10000不能随便改。这也是很多新手部署失败的第一道槛脚本里我不需要用户管总线端口直接算出来省一个心智负担。节点 IP 列表我设计成从命令行传入支持任意数量。脚本内部会统计 IP 总数再决定怎么分配主从。比如传入 6 个 IP就是三主三从传入 3 个 IP默认就是三主无从测试阶段也够用。2.2 脚本框架与流程设计整体流程我用四个函数组织check_env # 检查 redis-server / redis-cli 是否存在、版本是否支持 cluster generate_conf # 为每个节点生成 redis.conf start_nodes # 逐个启动 redis-server 并等待端口就绪 create_cluster # 执行 redis-cli --cluster create 并输出集群状态四个函数顺序执行任何一步失败都直接退出避免带着错误状态继续往后跑。主流程末尾加一个 verify_cluster 函数通过CLUSTER INFO和CLUSTER NODES返回关键状态比如是否 all ok、槽位分配是否完整、主从是否一一对应。这个框架的好处是职责清晰每个函数都能独立测试。我在实际开发时是先把 start_nodes 跑通再调 create_cluster出了问题能快速定位是哪一段的锅。2.3 配置文件生成规则每个节点的 redis.conf 大同小异差异点主要是端口、节点 ID 文件、数据目录和 pid 文件。我在脚本里用一个模板函数输出配置内容generate_conf() { local port$1 local ip$2 local dir/data/redis/cluster_${port} mkdir -p ${dir} cat ${dir}/redis.conf EOF port ${port} bind ${ip} 127.0.0.1 cluster-enabled yes cluster-config-file nodes-${port}.conf cluster-node-timeout 5000 appendonly yes appendfilename appendonly-${port}.aof daemonize yes pidfile /var/run/redis_${port}.pid logfile ${dir}/redis_${port}.log dir ${dir} protected-mode no EOF }这里有一个需要解释的关键项bind为什么要写具体 IP 而不是0.0.0.0如果 bind 0.0.0.0所有接口都监听看起来省事但 Redis Cluster 在 meet 时会拿本机地址去报告给其他节点如果本机有多个网卡可能报错或者导致其他节点拿着错误的地址来连接。指定 IP 之后节点间地址发现就非常明确这也是官方推荐的做法。2.4 为什么不用 Docker 而是直接跑在宿主机上很多人会问为什么不直接用 docker-compose 起多个容器做集群两种方案各有适用场景我分享下自己的取舍。Docker 方案适合单机模拟多节点快速起测试环境隔离性好但存在几个问题一是跨主机时网络模式要额外配置host 网络 不同端口还好bridge 网络就要处理容器间 DNS 和端口映射二是数据目录在容器里日志排查要多一层 docker logs 的包装不够直接三是有时候需要模拟真实网络环境比如故意断掉某个节点的网络做故障演练容器方案反而不好控制。宿主机直接跑则保留了最原始的运维操作方式所有日志、数据文件、进程状态都是裸的问题定位直观。这个脚本的主要使用场景就是多台物理机或云主机的批量部署所以我选择宿主机方案这也是 shell 脚本最舒服的战场。3. 一键脚本实现与核心环节拆解3.1 完整脚本源码下面是我的完整实现为了便于阅读我加上行号并保留了详细注释。脚本开头有一段变量区域运行时只需要改 IP 列表。#!/bin/bash # # 一键创建 Redis Cluster 集群 # 用法: ./create_redis_cluster.sh 192.168.1.10 192.168.1.11 192.168.1.12 192.168.1.13 192.168.1.14 192.168.1.15 # set -e # ---------- 参数与配置区 ---------- IP_LIST$1 BASE_PORT${BASE_PORT:-7000} REDIS_HOME${REDIS_HOME:-/usr/local/redis} DATA_ROOT${DATA_ROOT:-/data/redis} START_INDEX0 if [ -z ${IP_LIST} ]; then echo [ERROR] 请提供节点 IP 列表 echo 示例: ./create_redis_cluster.sh \192.168.1.10 192.168.1.11 ...\ exit 1 fi # 将 IP 列表转为数组 IFS read -r -a NODES ${IP_LIST} NODE_COUNT${#NODES[]} echo [INFO] 共检测到 ${NODE_COUNT} 个节点 # ---------- 函数区 ---------- check_env() { echo [CHECK] 检查 redis-server / redis-cli 是否存在 if [ ! -x ${REDIS_HOME}/bin/redis-server ]; then echo [ERROR] 未找到 ${REDIS_HOME}/bin/redis-server请检查 REDIS_HOME 配置 exit 1 fi if [ ! -x ${REDIS_HOME}/bin/redis-cli ]; then echo [ERROR] 未找到 ${REDIS_HOME}/bin/redis-cli exit 1 fi local version version$(${REDIS_HOME}/bin/redis-server --version) echo [INFO] Redis 版本: ${version} local cluster_support cluster_support$(${REDIS_HOME}/bin/redis-cli --cluster help 21 | head -n 1) if [ -z ${cluster_support} ]; then echo [ERROR] redis-cli 可能不支持集群管理命令请升级 Redis 至 5.0 以上 exit 1 fi echo [INFO] redis-cli 集群命令可用 } check_port_free() { local port$1 if ss -lnt | grep -q :${port} ; then echo [ERROR] 端口 ${port} 已被占用请更换 BASE_PORT 或释放端口 exit 1 fi } generate_conf() { local ip$1 local port$2 local dir${DATA_ROOT}/cluster_${port} mkdir -p ${dir} echo [CONF] 生成 ${dir}/redis.conf cat ${dir}/redis.conf EOF port ${port} bind ${ip} 127.0.0.1 cluster-enabled yes cluster-config-file nodes-${port}.conf cluster-node-timeout 5000 appendonly yes appendfilename appendonly-${port}.aof daemonize yes pidfile /var/run/redis_${port}.pid logfile ${dir}/redis_${port}.log dir ${dir} protected-mode no EOF echo [CONF] 配置文件生成完成: port${port}, ip${ip} } start_nodes() { local idx0 for ip in ${NODES[]}; do local port$((BASE_PORT idx)) check_port_free ${port} echo [START] 启动节点 ${ip}:${port} ${REDIS_HOME}/bin/redis-server ${DATA_ROOT}/cluster_${port}/redis.conf idx$((idx 1)) done echo [WAIT] 等待所有节点端口就绪 idx0 for ip in ${NODES[]}; do local port$((BASE_PORT idx)) local retry0 while [ ${retry} -lt 30 ]; do if ss -lnt | grep -q :${port} ; then break fi retry$((retry 1)) sleep 1 done if [ ${retry} -ge 30 ]; then echo [ERROR] 节点 ${ip}:${port} 在 30 秒内未就绪请检查日志 exit 1 fi echo [OK] 节点 ${ip}:${port} 端口已监听 idx$((idx 1)) done } build_node_list() { local nodes_str local idx0 for ip in ${NODES[]}; do local port$((BASE_PORT idx)) nodes_str${nodes_str} ${ip}:${port} idx$((idx 1)) done echo ${nodes_str# } } create_cluster() { local node_list node_list$(build_node_list) echo [CREATE] 开始创建集群, 节点列表: ${node_list} local replica_count1 if [ ${NODE_COUNT} -lt 6 ]; then replica_count0 echo [INFO] 节点数少于 6创建无从集群 fi ${REDIS_HOME}/bin/redis-cli --cluster create \ ${node_list} \ --cluster-replicas ${replica_count} \ --cluster-yes echo [CREATE] 集群创建命令执行完毕 } verify_cluster() { local first_ip${NODES[0]} local first_port$((BASE_PORT 0)) echo [VERIFY] 集群状态检查: ${first_ip}:${first_port} ${REDIS_HOME}/bin/redis-cli -h ${first_ip} -p ${first_port} cluster info echo ------------------------------ ${REDIS_HOME}/bin/redis-cli -h ${first_ip} -p ${first_port} cluster nodes } # ---------- 主流程 ---------- check_env start_nodes create_cluster verify_cluster echo [DONE] 全部完成3.2 关键函数逐段解读check_env里我最看重的是版本检查。Redis 4.x 时代的集群创建要借助一个 ruby 脚本也就是网上各种教程里出现的redis-trib.rb非常折磨人。Redis 5.0 之后官方把创建集群的功能集成了redis-cli --cluster子命令里脚本最大的简化就来源于此。如果你的环境还是 4.x强烈建议直接升级别和旧方案纠缠。generate_conf里有一个容易被忽略的细节就是cluster-config-file的名字必须和端口对应。每个节点启动后会生成一个nodes-7000.conf之类的文件记录本节点视角下的集群拓扑。如果你复用同一个基础配置文件没改这个字段多个节点抢同一个文件会出现各种奇怪的互相覆盖问题。我在脚本里用nodes-${port}.conf保证文件唯一性这是实战出来的一条重要经验。start_nodes里的等待循环是很多人写脚本时不会想到的。Redis Server 是 daemonize 方式后台启动命令返回不代表端口已经正常监听。如果脚本立刻执行 create 命令很可能赶上某个节点还在初始化白白报一次连接失败。我用ss -lnt | grep -q :${port} 做轮询最多等 30 秒保证所有节点都准备好再进入下一步。注意 grep 的模式端口后面带着空格避免匹配到类似 70001 这种包含关系。build_node_list的作用是把 IP 数组和端口拼成 redis-cli 要求的格式。这地方还有一个细节从cat 的配置阶段到启动阶段idx必须保持一致否则会出现配置生成给了 7000 端口启动时却拿着 7001 端口去找配置文件直接失败。我脚本里两个循环都是独立的idx看起来没毛病但实际写代码时很容易复制粘贴后忘记重置。3.3 从执行到完成的完整过程实录我在一台测试环境里用三个 IP 模拟了一键建集群的完整输出$ ./create_redis_cluster.sh 192.168.1.31 192.168.1.32 192.168.1.33 [CHECK] 检查 redis-server / redis-cli 是否存在 [INFO] Redis 版本: Redis server v7.0.12 ... [INFO] redis-cli 集群命令可用 [CONF] 生成 /data/redis/cluster_7000/redis.conf [CONF] 生成 /data/redis/cluster_7001/redis.conf [CONF] 生成 /data/redis/cluster_7002/redis.conf [START] 启动节点 192.168.1.31:7000 [START] 启动节点 192.168.1.32:7001 [START] 启动节点 192.168.1.33:7002 [WAIT] 等待所有节点端口就绪 [OK] 节点 192.168.1.31:7000 端口已监听 [OK] 节点 192.168.1.32:7001 端口已监听 [OK] 节点 192.168.1.33:7002 端口已监听 [CREATE] 开始创建集群 ... [VERIFY] 集群状态检查 cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384三个节点创建出的就是最简单的 master-only 集群16384 个槽位刚好平均分配到三个主节点上每个主节点拿 5462 个左右。如果要高可用就传六个 IP 进去脚本会自动为每个主节点分配一个从节点。4. 高可用验证与常见问题速查4.1 怎么确认集群真的健康脚本输出的 verify 结果里最需要盯住的是两个字段cluster_state必须是okcluster_slots_ok必须等于 16384。这两个字段说明槽位完整没有丢失或重复分配。另外cluster_nodes输出的每一行左列是一个 40 位的节点 ID如果看到某些行开头是slave后面跟着master的 ID说明主从关系已经建立。我在实际运行后建议顺手做一次写入测试redis-cli -h 192.168.1.31 -p 7000 -c set foo bar # 返回 OK redis-cli -h 192.168.1.32 -p 7001 -c get foo # 返回 bar注意这里一定加-c参数redis-cli 在集群模式下会自动把 key 重定向到正确的槽位节点。不加-c的话连到非槽位节点会收到 MOVED 错误很多新手在这一步就误以为集群有问题。4.2 高可用切换测试创建三主三从后我建议顺手做一个主节点故障切换测试验证集群真的具备高可用能力。找到 7000 端口对应进程的 PID直接 kill 掉kill -9 $(cat /var/run/redis_7000.pid)等十几秒再查看集群状态redis-cli -h 192.168.1.31 -p 7001 cluster nodes正常情况下原来的 5682 端口的从节点经过选举会变成新的主节点cluster_state 依然 ok。这个测试验证的是 Redis Cluster 的自动故障转移机制脚本本身并没有参与这个过程——但如果你在脚本化部署后不做这个验证等到真正故障时才依赖这套机制风险就大了。4.3 从零清理环境开发测试环境里重建集群是常有的事。我的脚本没有内置清理命令因为清理操作风险较大不适合一键执行。我通常手动执行# 清理数据目录和进程 pkill -f redis-server.*cluster_7 rm -rf /data/redis/cluster_7*注意pkill的模式不要写太宽比如pkill redis-server可能误杀同一机器上其他业务的 Redis 实例。用cluster_7做模式匹配精确锁定这个脚本创建的节点。4.4 常见问题速查表现象可能原因排查与解决create 时报 connect 超时防火墙未放行端口检查业务端口和 10000 偏移总线端口是否都放行create 时报 Node is not empty数据目录里有旧 AOF/RDB 文件清空 /data/redis/cluster_7xxx 目录后重试一直 waiting for the cluster to join总线端口被封放行端口10000 的 TCP 规则cluster_state 显示 fail部分节点挂掉或网络隔离逐个检查节点进程、日志和网络连通性MOVED 错误客户端没有使用集群模式客户端连接加-c参数或启用集群模式配置文件权限报错Redis 拒绝以 root 启动用专用账号启动或者配置参数允许端口被占用上一次部署未清理干净用 ss -lnt 查占用进程并处理这张表里我重点加粗总线端口的排查。很多人只会放行业务端口Redis Cluster 节点间通信全部走总线端口它没放行时集群根本 join 不起来。5. 脚本层面的避坑与后续扩展5.1 对 bash 语法的防御性处理shell 脚本调试很痛苦几个关键点必须注意变量未定义默认是空字符不会直接报错但行为完全偏离预期。所以set -e必须有出现非零返回值直接退出避免一路错下去。数组处理上用read -r -a注意IFS必须在同一行设置如果写成分号后面的独立语句数组分割方式是错的会把整个 IP 列表当成一个元素。命令检测这块有一个细节redis-cli --cluster help在 Redis 5.0 以下的旧版本会返回非零值但我用了21把错误输出捕获并读第一行保证后续判断不会因为无输出而僵住。我实际在生产脚本里还会加一个对返回码的判断这里为了清晰没有展开但原理是一样的对任何依赖外部命令的环节都要假设它可能失败、可能输出非预期内容。5.2 为什么选择 shell 而不是其他自动化工具有人会问这种场景用 Ansible、SaltStack、Python 不是更合适吗我的回答是看场景。如果是几十台机器、复杂配置、需要定期变更和审计Ansible 这类工具天然合适它有 inventory、有变量、有幂等性。但如果只是三五台机器的一次性集群搭建为它单独维护一套 playbook 有点重。Shell 脚本的好处是零依赖、直接运行、可读性好而且 Redis Cluster 建集群的核心动作就那几条命令shell 完全能覆盖。另外shell 脚本能让你对底层动作保持感知生成了什么配置、起了什么进程、监听什么端口。如果是 Ansible黑盒感更强出了问题多一层 debug 成本。这不算优劣是取舍。我在本文给出的是 shell 方案如果你后续要大规模上生产把它翻译成 playbook 也很容易配置生成逻辑直接复用。5.3 这个脚本还能怎么扩展目前脚本能完成基础集群的创建但实际业务中通常还有更多需求按我的经验列出几个高价值的扩展方向一是把节点数参数化既然 IP 列表能动态传入节点数、副本数、槽位分配策略都可以从命令行读取。二是加上健康检查的循环监控比如每 10 秒跑一次 cluster info状态异常就输出告警这个很适合做验收脚本。三是把创建命令替换成渐进式拓扑变更先创建三个主节点再逐个把从节点 add-node 进去。这样可以控制槽位迁移节奏在不停服的前提下构建集群适合线上扩容场景。另外一个很实用的扩展是生成 redis-cli 命令的幂等判断。脚本重跑时检测到某节点已经有集群配置就提示“节点已加入集群是否强制重建”避免因为手滑执行了脚本把现有集群搞乱。5.4 最后的实测体会我写这个脚本最深的体会是复杂操作自动化的收益不在第一次运行而在第五次、第十次运行。第一次跑脚本和手工搭建消耗的时间差不多因为要调试、要处理环境差异。但跑通之后后续每次部署的时间基本就是输入 IP 加等待输出误差不超过两分钟。而且因为脚本把所有关键参数显式化团队里其他人接手时也不用再翻文档猜配置。我自己后续用的时候还会在这个脚本基础上加一段用于生成 .env 文件的输出把各个节点的 IP、端口、节点 ID 写到固定格式的文件里方便上层业务系统读取。如果你也把 Redis Cluster 当成基础设施在重复使用我建议尽快把这类流程脚本化收益远大于前期那点投入。
返回列表