Redis集群部署实战:从单机迁移到高可用架构
最近正好接手一个数据量往上涨的业务系统单机Redis内存眼看着不够用读写瓶颈也越来越明显。借着这个机会我把从单机到Redis集群的完整迁移过程走了一遍顺便把踩过的坑、调过的参数、验证过的主从切换流程都记录了下来。这篇文章就以“Linux部署Redis集群”为主线从架构选型讲到配置落地再聊到日常运维里的坑尽量把关键决策背后的理由讲清楚而不是只丢一份配置让你照抄。适合正准备从单机切到集群、或者想自己搭一套Redis集群练手的同学参考。1. 部署前的架构思考与方案选型1.1 Redis集群到底解决了什么问题先聊聊为什么要上集群。单机Redis遇到的压力通常来自三个方向一是内存容量数据量超过单机物理内存后只能靠淘汰策略硬撑对业务影响很大二是CPU性能Redis虽然是单线程模型但高并发下CPU依然会满三是可用性单点挂了整个缓存层就断了后端数据库会直接被流量打穿。Redis集群方案提供的核心能力有三个数据分片、水平扩展、高可用。数据分片是通过哈希槽hash slot机制实现的整个集群有16384个槽位每个key经过CRC16校验后映射到某个槽再由槽分配到具体节点。写入时只需要连接任意一个节点命中哪个槽就在哪个节点上读写。水平扩展体现在集群可以动态增加节点把槽和对应数据迁移过去。高可用体现在每个主节点可以挂一个或多个从节点主节点挂了从节点自动提升。单机、哨兵模式、集群模式怎么选我的建议是数据量在几十GB以内、QPS压力可控、能接受秒级自动切换哨兵模式完全够用如果数据量会持续增长、需要在线扩容、或者对吞吐量有硬性要求直接上集群模式。集群模式虽然对多key操作有限制但带来的横向扩容能力是哨兵模式不具备的。1.2 集群规模与拓扑设计Redis集群要求至少3个主节点官方推荐每个主节点配1个从节点即3主3从是最小的高可用集群。生产环境我建议至少6台独立节点或者3台物理机上各跑2个Redis实例注意端口区分。如果资源紧张可以先3主3从起步后面在线扩容。关于节点分布有一点要特别注意从节点尽量不要和对应主节点放在同一台物理机上否则物理机宕机时主从一起挂高可用就成空话。我自己最初图省事把3主3从都塞在2台机器上后来做容灾演练时一拔电源直接有两个分片完全不可用教训很深刻。另一个要考虑的是cluster-node-timeout。这个参数决定了节点间心跳超时多久会被判定为故障。默认15000毫秒生产环境我通常设为5000到10000之间。设太短网络抖动就会触发无谓的主从切换设太长故障恢复时间变长。可以在部署后用ping来测量节点间的网络延迟再根据延迟倍数调整。2. 环境准备与Redis安装2.1 服务器规划与系统参数调优我先用6台虚拟机模拟6个独立节点每台分配2核CPU、4GB内存操作系统是常见的Linux发行版。用虚拟机部署有个好处可以随时快照回滚做故障演练也不用担心影响真实业务。安装Redis之前有几个系统参数必须先调否则集群跑起来会出各种诡异问题vm.overcommit_memory设置为1。Redis的持久化RDB和AOF涉及fork操作如果内存过度分配策略设置不当fork可能失败或者进程被系统杀掉。这个参数的含义是允许所有内存分配请求不检查实际可用内存避免Redis在fork时被OOM killer误杀。关闭透明大页THP。透明大页会导致Redis的写时复制copy-on-write机制下内存占用爆增延迟也会升高。需要修改内核参数或者直接设置启动命令禁用。net.core.somaxconn设置为1024以上。Redis默认监听511个连接请求队列如果系统默认的somaxconn小于511高并发下客户端连接会被内核直接丢弃表现就是偶尔连接超时。文件描述符上限调高。建议设置到65535以上否则大量客户端连接时会出现 Cant open so many files 之类的报错。调整方式直接修改/etc/sysctl.conf然后执行sysctl -p使其生效同时把/etc/security/limits.conf里的nofile限制调高。这几步看着繁琐但漏掉任何一个后边排查问题都可能花掉半天时间。2.2 Redis源码编译安装的几个细节我习惯使用官方源码编译安装而不是直接用系统自带的软件包因为版本比较灵活可以精确控制。Redis编译依赖gcc、make这些基础工具先安装环境再下载源码包解压编译# 安装编译依赖 yum install -y gcc make # 下载源码包版本号按需替换 wget http://download.redis.io/releases/redis-7.0.14.tar.gz tar xzf redis-7.0.14.tar.gz cd redis-7.0.14 # 编译安装到指定目录 make make install PREFIX/usr/local/redis编译时有个小坑如果gcc版本较老编译可能会报错提示malloc、backtrace相关的符号找不到。这时先执行make distclean清掉之前的环境再重新编译或者升级gcc版本。另外编译之前建议阅读源码包里的README它会明确列出当前版本要求的编译环境。安装完成后可执行文件会放在/usr/local/redis/bin下。我给每个节点建了独立的目录结构包括数据目录、日志目录和配置文件目录后面管理起来清晰很多mkdir -p /data/redis/{data,logs,conf,run}每个实例单独一套目录避免多实例时日志和数据混在一起。实际生产环境中我还建议用systemd来管理Redis实例写一个简单的service文件实现开机自启和异常重启。集群模式下节点的自动拉起很重要否则主从切换后如果节点一直离线整个集群的可用性还是受影响。3. 集群配置与逐节点部署3.1 redis.conf核心参数逐个拆解Redis集群的配置并不复杂但每个参数都值得仔细理解。下面是我在6个节点上使用的配置片段以节点一为例# 节点监听端口 port 7001 bind 0.0.0.0 protected-mode no # 以守护进程方式运行 daemonize yes pidfile /var/run/redis_7001.pid # 持久化配置 appendonly yes appendfilename appendonly-7001.aof appendfsync everysec dir /data/redis/data/7001 logfile /data/redis/logs/redis-7001.log # 集群开关这是最核心的参数 cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 5000 # 内存限制与淘汰策略 maxmemory 2gb maxmemory-policy allkeys-lru这里逐个解释关键参数cluster-enabled yes开启集群模式。如果不加这个redis-cli创建集群时会直接报错提示节点未启用集群。cluster-config-file nodes-7001.conf节点状态文件。Redis会自动维护这个文件记录本节点视角下的集群拓扑。千万不要手动编辑它节点启动时会自动更新它。一旦文件损坏删掉后重启它会重新从其他节点同步拓扑。bind 0.0.0.0监听所有网卡。如果有多网卡建议只绑定内网IP避免Redis直接暴露到不安全网络。protected-mode no在没有密码策略时受保护模式会拒绝非本机连接集群节点间通信会失败。这里设为no再通过防火墙限制访问来源更安全。maxmemory 2gb每个节点分配2GB内存。注意集群的容量是所以主节点内存之和从节点不占用额外分片容量。appendfsync everysec每秒同步一次AOF兼顾性能和数据安全。每个节点根据实际规划把端口、数据目录、日志文件名改成对应的序号然后就可以启动了。启动命令很简单/usr/local/redis/bin/redis-server /etc/redis/7001.conf3.2 节点启动后的状态自检启动后第一步不是急着创建集群而是先确认每个节点正常启动。看日志是最直接的启动成功后日志末尾会出现Ready to accept connections tcp如果集群开关是开启的日志里还会出现类似 Cluster state changed 的提示。我习惯在创建集群前先跑一次连通性测试用redis-cli连到每个节点执行ping/usr/local/redis/bin/redis-cli -p 7001 ping返回PONG说明节点正常。接着检查集群状态/usr/local/redis/bin/redis-cli -p 7001 cluster info /usr/local/redis/bin/redis-cli -p 7001 cluster nodes未创建集群时cluster_state显示为failcluster_nodes只有本节点信息这是正常的。如果cluster nodes能看到其他节点说明集群已经通过某种方式建立过需要重新初始化在测试环境可以直接删除nodes文件后重启。另外要注意防火墙配置。Redis集群除了业务使用的主端口还需要开放集群总线端口它是主端口10000。比如7001的集群总线端口是17001节点间心跳通信走的是这个端口。我第一次部署时只放开了7001-7006结果创建集群时一直报节点间无法通信排查半天才发现是总线端口被墙了。4. 集群创建与常见拓扑验证4.1 用自带的redis-cli一键创建集群Redis从5.0开始推荐使用redis-cli --cluster create这条命令创建集群很直观。在任意一个已经启动的节点上执行/usr/local/redis/bin/redis-cli --cluster create \ 192.168.10.11:7001 192.168.10.12:7002 \ 192.168.10.13:7003 192.168.10.14:7004 \ 192.168.10.15:7005 192.168.10.16:7006 \ --cluster-replicas 1这条命令的逻辑是前三个节点7001、7002、7003被分配为主节点后三个作为从节点同时为每个主节点指定一个从节点。--cluster-replicas 1表示每个主节点配1个从节点。命令执行过程中会让你确认槽位分配方案输入yes即可。创建完成后Redis会打印一段摘要包含每个主节点的槽位范围、从节点归属等信息。我习惯把这段输出留存后面扩容、迁移时方便对照。创建完之后用如下命令检查集群整体状态/usr/local/redis/bin/redis-cli -p 7001 cluster info正常情况下cluster_state显示为okcluster_slots_assigned为16384说明槽位全部覆盖。再看cluster_nodes可以确认6个节点的角色。主节点的role显示master从节点role显示slave并通过master字段指向对应的主节点ID。有个经验之谈创建集群时如果使用的是不同网段的IP建议统一写成内网IP。如果某台机器配置了多个IPRedis默认获取第一个很可能不是你期望的那个导致其他节点连接失败。4.2 槽位分布与数据读写验证集群搭好之后第一步验证就是写入读取。用redis-cli连接任意节点执行写入命令/usr/local/redis/bin/redis-cli -c -p 7001 set hello world注意一定要加-c选项也就是cluster模式。admin节点会根据key计算出槽位如果槽位不在当前节点-c会自动跳转到正确节点执行命令。如果忘了加-c会报错(error) MOVED 16384 192.168.10.15:7005这个MOVED重定向是正常的不是故障。集群客户端会自动处理但手动测试时容易误判。多key操作是个值得专门提醒的点。集群模式下不同key如果映射到不同槽位一条命令涉及多个key时会出现CROSSSLOT错误(error) CROSSSLOT Keys in request dont hash to the same slot解决办法使用哈希标签hash tag让相关key落在同一个槽位。方法是把key写成包含花括号的形式Redis只对花括号内的部分计算槽位。例如{user}:name和{user}:age花括号里的user决定了它们都落在同一个槽位。业务代码里如果频繁使用mget、del等多key操作在设计key时就要有意识地利用哈希标签。还有一个分片场景下的经典问题在大数据量时需要知道某个key落在哪个节点可以用cluster keyslot命令查询/usr/local/redis/bin/redis-cli -p 7001 cluster keyslot hello返回的是槽位编号然后通过cluster nodes输出对照就能定位到是哪个节点。4.3 主从切换与故障转移演练集群部署完成后高可用到底靠不靠谱建议做一次故障演练来验证。演练方法很简单直接停掉一个主节点的进程观察从节点是否自动提升为新的主节点。我用7001节点举例。执行/usr/local/redis/bin/redis-cli -p 7001 shutdown nosave等几秒后查看节点状态/usr/local/redis/bin/redis-cli -p 7002 cluster nodes可以看到7001对应的那个分片原本的从节点状态变成了masterrole字段变化cluster_state依然保持ok。这说明故障转移生效了。等到原7001节点重新启动后它会作为从节点加入集群自动从新的主节点同步数据回到高可用状态。一个容易被忽略的细节故障转移的触发条件不只是进程退出。内存超限、长时间阻塞、网络分区等都可能导致节点被判定故障。演练时除了kill进程还可以试试用防火墙阻断两个节点间的通信观察网络分区场景下的行为。我之前把集群总线端口断掉后个别节点进入了fail状态但主从切换比直接kill进程要慢很多原因是集群需要先收集足够多的投票才触发转移。关于投票机制简单说一下一个节点被标记为疑似故障pfail后集群中其他主节点会互相交换信息当超过半数的主节点过半原则认为该节点不可达它才会被判定为fail。这就是为什么集群至少需要3个主节点的原因2个主节点时如果其中一个挂了无法形成多数票。5. 常见问题与排查技巧实录5.1 集群状态异常的核心排查思路遇到cluster_state:fail最常见的两类原因一是槽位未完整覆盖二是部分节点失联导致无法形成多数派。排查时按顺序来先看cluster_info里的cluster_slots_assigned如果小于16384说明有槽位没有分配给任何节点。这种情况通常发生在扩容中途中止或者节点数据迁移异常之后。此时需要找出来哪些槽位未覆盖/usr/local/redis/bin/redis-cli -p 7001 cluster slots对比输出找到缺失的槽位区间然后手动迁移槽位或者重新补充节点。再看cluster_nodes中的节点状态。如果出现大量disconnected优先检查网络和防火墙。我遇到过一个典型场景迁移机房后节点间网络延迟从0.2ms飙到30mscluster-node-timeout还是默认的15000节点间频繁出现瞬间断连不断触发主从切换日志里全是切换记录。最后把timeout调到10000才稳定下来。跨机房部署时还要关注cluster-allow-emptyslots参数。默认情况下如果集群槽位全部被删除或者初始化不完整集群会拒绝服务这是安全机制。但如果是在做迁移演练想先把节点拉起来再慢慢填充槽位可能需要临时开启这个参数。生产环境不建议长期开启。5.2 日常运维需要盯紧的几个指标集群搭起来只是开始日常运维要盯的指标比单机多不少一是cluster_state是否稳定。写成监控脚本定期查询一旦变成fail立即告警。二是主从复制偏移量。在从节点执行info replication观察master_repl_offset和slave_repl_offset是否持续一致长期不一致说明从节点同步有延迟。三是内存增长趋势。每个节点单独设maxmemory但要预留buffer不要等内存满了再扩容。数据备份策略也要调整思路。集群模式的数据分布在多个节点上备份时需要对每个节点分别执行BGSAVE或BGREWRITEAOF然后统一拷贝RDB/AOF文件到一个集中位置。恢复时更麻烦要么全量恢复所有节点数据再重建集群要么在单个分片故障时用对应节点的备份文件恢复。我建议每周做一次全量备份每天的增量靠AOF。备份文件至少保留两周这是最基本的底线。日常小操作里还有个容易被忽略的点redis-cli --cluster reshard做在线扩容时数据迁移的单位是槽Redis会以slot为单位把key逐个搬移到新节点。迁移过程中key不会丢失但迁移期间访问正在迁移的key会多一次ASK重定向客户端需要支持处理。老牌的客户端库一般都有处理但如果你自己写了脚本直接用redis-cli遇到ASK要先请求目标节点然后再访问否则会失败。5.3 我踩过的三个实际坑第一个坑是透明大页导致的高延迟。部署初期线上请求p99偶尔会飙到几百毫秒检查一切正常后来才反应过来是THP没关闭。Redis官方文档里明确建议禁用THP否则写时复制时内存和CPU消耗都会异常。修改系统内核参数之后实测P99恢复了正常波动水平。第二个坑是节点时间不同步。集群节点间的时间偏差会影响故障检测和选举判断尤其是多个节点跨越不同物理机时。我部署时没注意集群里6台机器的时间同步偏差到了1秒以上平时看着没事故障切换时就会投票失败。后来统一配置了时间同步服务问题消失。第三个坑是数据目录写权限。Redis进程通常以普通用户运行如果数据目录的属主不对启动时会报Cant open the append-only file: Permission denied。手动启动时可能还好一旦配置了systemd自动拉起进程用户变了目录权限没跟上就会出现启动即失败然后又重启的循环。检查用户归属和目录权限是每条新机器上线前必须过一遍的。6. 写在最后的一点运维体会整套Redis集群部署下来我的感受是集群本身不难建难的是理解每个参数背后的原因以及建立起一套完整的运维习惯。redis-cli --cluster create这条命令一分钟就能建好集群但真正的挑战在后面的扩容、缩容、故障切换、备份恢复这些日常操作。生产环境里的集群迁移我坚持一个原则先小规模验证再逐步迁移流量。先在测试环境完整跑一遍扩容和故障切换记录每一步的输出和影响再在灰度环境重复一次最后才动线上。每个节点切流之前检查数据一致性确认cluster_state稳定观察一段时间再继续下一步。如果你现在正准备部署Redis集群可以把这篇文章里的配置作为初始模板但最终一定要根据你的业务请求特征、网络环境、机器配置去调优。特别是cluster-node-timeout、maxmemory-policy这些参数没有一套放之四海皆准的值能做的就是把原理搞清楚然后通过压测和演练找出最适合自己的配置。最后分享一个小技巧给每个节点的主目录和日志文件加上清晰命名比如带上机房、分片和角色信息。这个习惯帮我省过无数次排查时间看起来是小事等集群节点多起来价值就会体现出来。