简介:南邮大数据存储与管理实验一配套文档面向大数据、存储管理课程学生及Redis初学者,聚焦在Ubuntu 16.04下完成Redis单机与集群环境安装配置,并基于Redis构建简单文章投票网站后端,用于课程实验复现与实验报告撰写参考。包内仅含1个doc文件,压缩包约933KB,体量轻便,内容围绕实验目的、环境、原理、操作步骤、结果分析与小结展开,涵盖依赖安装、APT源配置、服务启停、集群节点配置与创建,以及投票数据模型、发布文章、投票排序等关键实现,并对连接异常等常见问题给出排查思路。现有324人学习,说明其具备一定参考价值;读者可据此梳理Redis集群搭建流程,理解String与Sorted Set在投票场景中的用法,并借鉴实验记录与总结写法,适合需要完成同类实验或快速上手Redis基础应用的人群。
1. 连不上的 6379:这套 Redis 实验环境到底卡在哪
第一次在 Ubuntu 16.04 上搭这套环境,卡住我的不是安装,是连接:虚拟机里redis-cli敲ping能回 PONG,宿主机上的图形客户端却一直抛ECONNREFUSED 127.0.0.1:6379。这类问题在 redis 大数据 相关实验里几乎人人要过一遍——单机只算热身,后面还有三个主三个从的六节点集群,和拿 Redis 当唯一存储的文章投票后端。版本选错、bind和protected-mode没改、上一轮实验残留的 slot 没清干净,都会让后面每一步白做。下面按单机能跑、集群能连、投票后端能出结果的顺序写,命令和参数都摊开,有一台 Ubuntu 16.04 虚拟机就能照着走。
2. 单机 Redis 安装:Ubuntu 16.04 上的依赖、APT 源与启动方式
单机这一段是整个实验的地基,但它的坑不在“装不上”,而在“装上了却连不上、装成了两份”。Ubuntu 16.04 是个老发行版,系统源里本来就带一份 Redis,官方 APT 仓库里又是另一份,两条路混着走,后面redis-server --version和which redis-server对不上,集群配置怎么写都不生效。我的习惯是先花三分钟把版本和来源确认清楚,再决定用哪种安装方式,而不是拿到命令就往上贴。
2.1 先用三条命令确认发行版代号和已有版本
Ubuntu 16.04 的发行版代号是xenial,官方 APT 仓库的软件源路径就是用它拼出来的,代号错一位,apt-get update直接 404。所以第一件事不是装,是看清楚系统底细:
lsb_release -cs # 输出 xenial,说明系统是 Ubuntu 16.04 which redis-server # 有输出说明系统里已经存在一份 redis-server redis-server --version # 看清楚是 3.x 还是 6.x 以上lsb_release -cs的-c是取代号、-s是只输出值不带标签,拼起来才是一段能直接塞进sources.list的字符串。which redis-server非空时要留意:如果显示/usr/bin/redis-server且版本号是 3.x,那它是系统自带的老包,而实验里要用的redis-cli --cluster create子命令是 4.0 之后才并入redis-cli的,3.x 时代集群合成靠另一个脚本,命令完全不同。这就是为什么后面的安装步骤要绕开系统源,走官方仓库。
提示:如果
lsb_release命令不存在,先装lsb-release这个包,别用cat /etc/issue硬猜代号,那玩意儿输出的是给人看的字符串,塞进源地址里会拼出错误的 URL。
2.2 加官方仓库并装包:四条命令的逐条解释
依赖、公钥、源、安装这四步,原文给的是官方教程里的原样命令,我在虚拟机上跑过多次,顺序不能乱:
# 1. 装三个前置工具:lsb-release 取代号,curl 下载公钥,gpg 做密钥去封装 sudo apt install -y lsb-release curl gpg # 2. 下载官方公钥并转成 apt 认识的 keyring 格式(--dearmor 是二进制化解封装) curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg # 3. 写软件源,$(lsb_release -cs) 会被替换成 xenial;signed-by 让 apt 只认这把钥匙 echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list # 4. 刷新索引并安装 sudo apt-get update sudo apt-get install -y redis第 2 条里的-fsSL是四个短选项拼起来的:-f让 curl 在 HTTP 状态码异常时直接失败而不是把错误页当公钥写进去,-s静默不打印进度条,-S出错时仍然报错,-L跟随重定向。这四个少一个,都可能把一个 HTML 错误页写进 keyring,之后apt-get update会报签名校验失败,而且报错信息里看不出根因,属于典型的黑匣子型故障。
第 3 条里的signed-by是关键:它把信任范围限定死,只有这把钥匙签的包才被接受,不会影响系统里其他源。tee之所以要sudo,是因为重定向发生在当前 shell,sudo echo >这种写法提权的是 echo 不是重定向,会报权限不足——这是很多人第一次配源就翻车的地方。
注意:
apt-get update如果在这一步报404 Not Found或does not have a Release file,说明官方仓库已经不再为这个代号提供构建。此时别硬改代号伪装成新系统,装出来的包和系统库不匹配。常见做法是改走源码编译,或者干脆用系统自带的那份,然后接受redis-cli --cluster不可用、改用旧脚本合成集群的事实。
2.3 redis.conf 里必须动手的四个参数
装完不要急着启动,配置文件先改。安装包默认的/etc/redis/redis.conf是给“只允许本机访问”的场景准备的,而实验要求宿主机上的图形客户端能连进虚拟机,必须放开两处,再顺手确认两处:
# /etc/redis/redis.conf bind 0.0.0.0 # 默认是 127.0.0.1,只监听回环,改 0.0.0.0 才能被外部网卡接进来 protected-mode no # 默认 yes,没有密码又非回环访问时直接拒绝命令 daemonize yes # 后台运行,不加这个前台一退出服务就断 port 6379 # 单机保持 6379,集群节点在后面单独改 dir /var/lib/redis # RDB/AOF 落地目录,必须存在且 redis 用户可写bind和protected-mode是一对:只改bind不改protected-mode,连接能建立但一发命令就被拒;只改protected-mode不改bind,外面根本连不进来。原文里那条“关闭安全模式却没解决问题”的记录,根因就是连接的目标地址还写着127.0.0.1——客户端在宿主机上跑,127.0.0.1指的是宿主机自己,那里当然没有 Redis。
修改方式用vim /etc/redis/redis.conf,改完一定要重启服务,Redis 不会热加载这种启动参数:
# 重启并确认 /etc/init.d/redis-server restart redis-cli ping # 期望输出 PONG2.4 启动与开机自启:三种启动方式别混用
同一个服务有三种启动方式,混着用最容易出现“我明明启动了,进程却不在”:
| 方式 | 命令 | 适用场景 |
|---|---|---|
| init 脚本 | /etc/init.d/redis-server start | 老系统、实验环境,启动脚本位置固定 |
| systemd | systemctl start redis-server | 现代发行版,能看status和日志 |
| 手动指定配置 | /usr/bin/redis-server /path/to/xxx.conf | 集群多实例,必须用这种 |
单机阶段用前两种都行,Ubuntu 16.04 上两者其实是同一套东西的两层皮。但到了集群阶段必须切到第三种:六个节点共用同一个二进制,靠的是启动时传入不同的配置文件,/etc/init.d/那套脚本只认/etc/redis/redis.conf,改不了。
查看虚拟机 IP 用ifconfig,记下ens33或eth0那张网卡的inet addr,后面填客户端连接地址时要用它,不是127.0.0.1。同时确认端口是通的:
ss -lntp | grep 6379 # 看到 0.0.0.0:6379 说明监听正确;看到 127.0.0.1:6379 说明 bind 没生效如果ss显示监听地址还是回环,别怀疑客户端,回去看bind那一行是不是改完没保存,或者redis-server启动时用了另一个配置文件。
3. 六节点集群搭建:目录规划、配置文件与 cluster create
集群这一步是整套实验里最容易反复推倒重来的部分。六个实例、六份配置文件、六个数据目录、六个日志文件,任何一处命名冲突或者目录缺失,表现都是“进程起不来但没提示”或者“起来了但集群合不成”。我一般的做法是先把目录和端口在纸上排好,再一次性把六份配置生成出来,避免手抄配置抄错一个字符排查半小时。
3.1 目录与端口规划:先定死再动手
端口分配规则很朴素:三个主节点用 6379、6380、6381,三个从节点用 6389、6390、6391,保持“主节点端口 + 10”的对应关系,出问题时一眼能看出谁是备谁。目录统一放在/opt/redis/cluster下,数据、日志、pid 各自分目录:
# 一条 -p 建完整棵目录树,避免逐层 mkdir mkdir -p /opt/redis/cluster/{log,data/{6379,6380,6381,6389,6390,6391}} cp /etc/redis/redis.conf /opt/redis/cluster/redis.conf.bak # 留一份模板做对照data/<port>这一层是必须的。集群模式下每个节点会往dir里写nodes-<port>.conf和 RDB 文件,六个节点共用一个dir会互相覆盖,最典型的现象就是集群明明建好了,重启一轮之后cluster nodes显示节点 ID 全变了。
| 节点角色 | 端口 | 配置文件 | 数据目录 |
|---|---|---|---|
| master1 | 6379 | redis-6379.conf | data/6379 |
| master2 | 6380 | redis-6380.conf | data/6380 |
| master3 | 6381 | redis-6381.conf | data/6381 |
| slave1 | 6389 | redis-6389.conf | data/6389 |
| slave2 | 6390 | redis-6390.conf | data/6390 |
| slave3 | 6391 | redis-6391.conf | data/6391 |
3.2 六个节点的配置文件:最小可用集合
原文里redis-6379.conf的具体内容被截断了,按集群的实际要求,一份能用的节点配置至少要有下面这些项。以 6379 为例:
# /opt/redis/cluster/redis-6379.conf port 6379 bind 0.0.0.0 protected-mode no daemonize yes pidfile /opt/redis/cluster/redis-6379.pid logfile /opt/redis/cluster/log/redis-6379.log dir /opt/redis/cluster/data/6379 cluster-enabled yes # 开启集群模式,不写这行后面所有命令都白搭 cluster-config-file nodes-6379.conf # 由 Redis 自己维护,文件名必须每个节点唯一 cluster-node-timeout 15000 # 节点失联判定毫秒数,本地虚拟机 15 秒够用 appendonly yes # 开 AOF,断电后比 RDB 少丢数据四个容易写错的地方:cluster-enabled yes是集群的开关,默认是注释掉的,不改这里,redis-cli --cluster create会直接回ERR This instance has cluster support disabled;cluster-config-file必须带端口号做区分,六个节点写成同一个名字就等于共享了集群状态文件;dir指向的目录必须先建好,Redis 不会帮你创建,目录不存在时daemonize yes会让进程静默退出,日志里只有一行含糊的错误;logfile同理,路径里的log目录漏建,报错会直接打到标准输出而被daemonize吞掉。
其余五个文件用sed批量改比手抄稳:
cd /opt/redis/cluster for p in 6380 6381 6389 6390 6391; do # 以上面的 6379 配置为模板,把所有出现 6379 的位置整体替换成目标端口 sed "s/6379/$p/g" redis-6379.conf > redis-$p.conf done ls redis-63*.conf # 应该看到 6 个文件sed "s/6379/$p/g"里的g是关键,因为一份配置里 6379 出现四次(port、pidfile、logfile、dir、cluster-config-file),不加g只替换第一个,剩下的还是旧端口。
3.3 启动六个节点并确认真的起来了
六个节点逐个启动,路径写绝对路径,不要依赖PATH:
/usr/bin/redis-server /opt/redis/cluster/redis-6379.conf /usr/bin/redis-server /opt/redis/cluster/redis-6380.conf /usr/bin/redis-server /opt/redis/cluster/redis-6381.conf /usr/bin/redis-server /opt/redis/cluster/redis-6389.conf /usr/bin/redis-server /opt/redis/cluster/redis-6390.conf /usr/bin/redis-server /opt/redis/cluster/redis-6391.conf ps -ef | grep redis | grep -v grep # 期望看到 6 行,每行末尾是各自的配置文件路径 ss -lntp | grep -E '63(79|80|81|89|90|91)' # 期望 6 个端口都在监听ps -ef | grep redis后面那个grep -v grep是必加的,否则总有一行是自己的 grep 进程,让人误以为多起了一个实例。数量不对时先看日志,日志路径就是配置文件里logfile那行写的,tail -n 50基本能看到原因:端口被占、目录无权限、配置文件语法错。
还有一种情况:ps显示 6 个进程都在,但ss只看到 5 个端口。这通常是某个节点的端口和系统里别的服务撞了,或者上一次的 Redis 实例还挂在那个端口上。判断办法是redis-cli -p 6379 info server | grep tcp_port,能看到实际监听端口和预期是否一致。
3.4 用一条命令合成集群
六个节点各自独立运行还不算集群,得让它们互相认识、把 16384 个槽分配下去。集群合成只需要一条命令:
/usr/bin/redis-cli --cluster create \ --cluster-replicas 1 \ 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 \ 127.0.0.1:6389 127.0.0.1:6390 127.0.0.1:6391--cluster-replicas 1的意思是每个主节点配一个从节点,命令会按传入顺序把前三个当主、后三个自动配对成从,并各自分到约 5461 个槽。执行过程中会打印一份分配方案,要求输入yes确认,不输就一直挂着,很多人以为卡死,其实是等着你回话。
合成之后立刻验证两件事:
redis-cli -c -p 6379 cluster info # cluster_state:ok 且 cluster_known_nodes:6 才算真的成了 redis-cli -c -p 6379 cluster nodes # 6 行,3 个 master 后面各自挂 1 个 slave-c参数是集群模式,作用是遇到不属于本节点的 key 时自动跟随后续重定向,不加这个参数,往 6379 写一个落在 6380 槽上的 key 会直接报MOVED。cluster_state:ok之前如果看到cluster_state:fail,多半是有节点没起来或者槽没分完,回头看 3.3 的进程和端口检查。
3.5 客户端接入:Another Redis Desktop Manager 的连接方式
图形客户端连单机和连集群是两套参数。单机连接填虚拟机的实际 IP(不是127.0.0.1)、端口 6379,如果配置文件里设了requirepass就一起填上,测试连接通了再保存。连集群时不能只填一个地址,要把 6 个节点全列进去,并且勾选 Cluster 模式,否则客户端只认一个节点,遇到 MOVED 重定向就断,表现就是连接状态来回跳。
宿主机和虚拟机网络不通的时候,还有一招是 SSH 方式:客户端里选 SSH 连接,填虚拟机的 SSH 地址和账号,再在隧道里填 Redis 的地址端口。原文里“ssh 加 cluster 连接后问题解决”记录的就是这条路,它绕开的是网络可达性和端口放行,不是 Redis 本身的配置问题——这个区分很重要,别把它当成 Redis 的玄学。
4. 文章投票后端:redis-py-cluster 与三个核心函数
单机和集群都通了之后,实验的第三块才是真正吃理解的部分:用 Redis 做唯一存储,实现一个能发文章、能投票、能按时间或评分排序的文章投票后端。它没有 MySQL 兜底,所有数据模型都得自己在 key 上设计出来,一旦 key 设计错了,后面排序、分组全得推倒。
4.1 装库与环境隔离:版本对上再谈代码
# 建议先建虚拟环境,避免污染系统 python python3 -m venv ~/venv-redis && source ~/venv-redis/bin/activate pip install redis-py-cluster pip install "redis<4.0" # redis-py-cluster 依赖旧版 redis,单独升级会冲突redis-py-cluster这个库对redis客户端的版本有硬要求,2.x 版本依赖redis3.x,直接pip install redis装上最新版会报ImportError: cannot import name 'StrictRedis'。我一般装完先跑一句pip show redis redis-py-cluster看两者的版本组合,确认没问题再动代码,省得后面在代码里找半天语法错误。
4.2 key 设计:一张表定生死
| key | 类型 | 用途 |
|---|---|---|
article | string | 自增计数器,用INCR生成文章 ID |
article:<id> | hash | 文章正文,字段 title、link、poster、time、votes |
time | zset | 成员是article:<id>,分值是发布时间戳,用于按时间排序 |
score | zset | 成员同上,分值是评分,用于按热度排序 |
voted:<id> | set | 记录给这篇投过票的用户,防重复投 |
group:<name> | set | 分组,成员是文章 key |
hash 存字段、zset 做排序、set 做去重,这三种结构各自解决一个问题,组合起来就是一个最小的内容系统。这里有个集群下必须注意的点:time和score是全局唯一的 zset,所有文章都往里塞,单 key 命令没问题,但一旦用到ZINTERSTORE这种多 key 操作,Redis 会要求这些 key 落在同一个槽上,跨槽直接报CROSSSLOT。所以分组排序那段代码,我改成了客户端算,见 4.4。
4.3 post 与 vote:发布和投票的完整实现
import time from rediscluster import RedisCluster # startup_nodes 列集群里的任意几个节点即可,客户端会自动发现完整拓扑 rc = RedisCluster(startup_nodes=[ {"host": "192.168.1.100", "port": "6379"}, {"host": "192.168.1.100", "port": "6380"}, ], decode_responses=True) ONE_WEEK = 7 * 24 * 3600 VOTE_SCORE = 432 # 一票 432 分 ≈ 一天的评分衰减量(86400/200) def post(article_data, redis=rc): # 1. 自增拿 ID,INCR 在集群里落在单 key 'article' 上,天然原子 article_id = str(redis.incr("article")) article = "article" + article_id # 2. 正文写 hash,字段名来自传入的字典 for k, v in article_data.items(): redis.hset(article, k, v) # 3. 发布时间进 zset,成员名就是 hash 的 key,后面排序直接取成员 redis.zadd("time", {article: time.time()}) # 4. 作者给自己投一票,占住 voted 集合的初始位置 vote(article, article_data["poster"], redis) return article def vote(article, user, redis=rc): # 文章发布超过一周就不能再投,时间从 time 这个 zset 里取 if redis.zscore("time", article) < time.time() - ONE_WEEK: return False voted_key = "voted:" + article # sadd 返回 1 表示新投,0 表示重复投,去重靠 Redis 保证 if redis.sadd(voted_key, user): redis.expire(voted_key, ONE_WEEK) # 一周后自动清理投票记录 redis.zincrby("score", VOTE_SCORE, article) # 评分累加 redis.hincrby(article, "votes", 1) # hash 里的票数同步加一 return True def score(article, redis=rc): return redis.zscore("score", article)post里的redis.zadd("time", {article: time.time()})用的是新版传字典的写法,老版本是zadd("time", article, time.time()),参数顺序反过来,升级客户端时报错多半是这一处。vote里先用zscore查发布时间再判断是否过期,zscore对不存在的成员返回None,和数字比较在 Python 3 里会抛TypeError,所以调用方必须保证文章存在。
VOTE_SCORE取 432 不是随手写的:一天 86400 秒除以一页 200 篇,大约每篇文章的评分每天要衰减 432 分,这样一票的分量刚好抵消一天的“老化”,新文章不至于因为发布时间晚而永远排在后面。要调整排序倾向,只改这一个常量就够了,别去改公式。
4.4 分组与排序:为什么不能直接在集群上用 zinterstore
分组就是把文章 key 塞进一个 set,然后按分组名取出来排序:
def add_to_group(article, group, redis=rc): redis.sadd("group:" + group, article) # 一个分组一个 set,成员是文章 key def get_group_articles(group, order, page, redis=rc): key = f"score:{order}:{group}" # 理论上的排序缓存 key if not redis.exists(key): # 集群下 ZINTERSTORE 要求两个 zset 同槽,group 集合不是 zset,这里改用客户端算 members = redis.smembers("group:" + group) scored = [(m, redis.zscore("score" if order == "score" else "time", m)) for m in members] scored.sort(key=lambda x: x[1], reverse=True) for m, s in scored: redis.zadd(key, {m: s}) start = (page - 1) * 25 return redis.zrevrange(key, start, start + 24)zrevrange的start和end都是闭区间下标,分页公式是(page-1)*25到start+24,写成start+25会多出一条。这里之所以不直接ZINTERSTORE,是因为集群模式下多 key 操作必须同槽,group:xxx和score这两个 key 的槽位基本不可能一样,跑一次报一次CROSSSLOT Keys in request don't hash to the same slot。客户端排序多走几次网络,但能跑通,实验阶段这样写足够。
5. 避坑与排查:五个把实验卡死的报错
这一节全是现象、原因、解决三段式的记录,都是我或者身边的人真实撞过的。有些报错看着玄学,其实是配置里某一行没生效。
5.1 客户端报 ECONNREFUSED 127.0.0.1:6379
现象:图形客户端测试连接失败,提示RedisClient On Error: Error: connect ECONNREFUSED 127.0.0.1:6379 Config right?,但虚拟机里redis-cli ping正常返回 PONG。
原因:客户端跑在宿主机上,连接地址填的是127.0.0.1,这个地址在宿主机上指的是宿主机自己,那儿没有 Redis。另一个叠加原因是redis.conf里bind还写着127.0.0.1,服务只监听回环,就算填对 IP 也进不来。
解决:先用ifconfig查虚拟机实际 IP,把客户端地址改成它;再把bind改成0.0.0.0、protected-mode改成no,重启服务;最后用ss -lntp | grep 6379确认监听地址不是回环。原文里“关掉安全模式没解决、改 IP 后成功”的经验,前半段说的就是这个叠加原因。
5.2 合成集群时报 cluster support disabled
现象:执行redis-cli --cluster create后每个节点都回(error) ERR This instance has cluster support disabled,命令直接退出。
原因:配置文件里cluster-enabled这一行被注释掉了,或者写成了no。Redis 默认不支持集群,这是个需要显式打开的开关。
解决:六个配置文件全部把cluster-enabled yes前面的#去掉,确认值确实是yes,然后逐个重启节点。重启后可以redis-cli -p 6379 info cluster | grep cluster_enabled看返回值,1才是开的。只改一个节点不够,合成时任何一个节点没开都会失败。
5.3 合成集群时报 Slot 671 is already busy
现象:--cluster create跑到一半报(error) ERR Slot 671 is already busy,或者提示某些槽已经被占用。
原因:上一次实验的数据和集群状态没清干净。槽的归属记录在cluster-config-file指向的nodes-*.conf里,旧文件还在,Redis 启动后就把自己当成旧集群的成员,槽自然被占。
解决:先登录每个节点执行flushall清数据,再执行cluster reset清集群状态:
for p in 6379 6380 6381 6389 6390 6391; do redis-cli -p $p flushall # 清空当前节点数据 redis-cli -p $p cluster reset # 硬重置集群状态,注意会解绑所有节点 done如果重置后仍然报占用,说明nodes-*.conf是持久化到磁盘的,需要停掉进程、删掉对应dir下的nodes-*.conf再启动。这一步做完再重新--cluster create,基本就干净了。
5.4 连集群时报 connection is close / cluster is ended
现象:单机能连,切到集群模式后客户端状态栏闪一下stream on error : connection is close、cluster is ended,然后断开。
原因:客户端只填了一个节点地址。集群模式下客户端需要知道全部拓扑,只给一个入口时,遇到重定向或者拓扑刷新就当成连接异常断开了。
解决:把六个节点地址全部加进连接列表,并显式勾选 Cluster 模式。宿主机和虚拟机网络受限时,改用 SSH 隧道方式连接,在 SSH 配置里填虚拟机地址,在 Redis 配置里填127.0.0.1:6379加 Cluster 模式,隧道由客户端自己转发,绕开端口不可达的问题。
5.5 分组排序报 CROSSSLOT
现象:调用分组排序接口时抛redis.exceptions.ResponseError: CROSSSLOT Keys in request don't hash to the same slot。
原因:用了ZINTERSTORE或者MULTI这类一次操作多个 key 的命令,而集群把不同 key 哈希到了不同槽,服务端直接拒绝执行。
解决:两条路,一是给相关 key 加同样的 hash tag,比如都写成{group:reading}:score这种形式,花括号里的内容参与哈希,能强制同槽;二是放弃服务端多 key 操作,改成smembers拿成员、客户端逐个zscore再排序,4.4 那段代码走的是第二条。实验规模下客户端排几十条数据,性能完全够。
6. 装完怎么验证:从 ping 到投票链路跑通
装完不等于跑通。我习惯按“进程 → 端口 → 单机命令 → 集群状态 → 业务链路”这个顺序验,每一步都有明确的通过标准,不通过就停在当层排查,不要跳到下一层。
先验进程和端口,这一步只证明服务活着:
ps -ef | grep redis | grep -v grep | wc -l # 期望 7:1 个单机 + 6 个集群节点(或 6,取决于是否保留单机) ss -lntp | grep -cE ':(6379|6380|6381|6389|6390|6391)' # 期望 6再验单机和集群的状态,用一张表对照着看,比一条条试快:
| 检查项 | 命令 | 通过标准 |
|---|---|---|
| 单机存活 | redis-cli ping | 返回PONG |
| 集群状态 | redis-cli -c -p 6379 cluster info | cluster_state:ok |
| 节点数量 | redis-cli -c -p 6379 cluster nodes | 6 行,3 主 3 从 |
| 槽覆盖 | cluster info里的cluster_slots_assigned | 16384 |
| 读写跳转 | redis-cli -c -p 6379 set k1 v1 | 不报CLUSTERDOWN |
槽那个数字特别值得看:cluster_slots_assigned如果是 16383 而不是 16384,说明有一个槽没分出去,集群状态会一直卡在fail,业务代码全报错。这种情况用redis-cli --cluster fix 127.0.0.1:6379让它自己补齐,比手动迁移省事。
最后验业务链路,直接跑一段冒烟脚本,比在交互式客户端里敲命令靠谱:
# smoke_test.py —— 发布、投票、排序三步串起来跑一遍 from vote_app import rc, post, vote a = post({"title": "冒烟测试", "link": "http://example.com", "poster": "u1"}, rc) print("新文章 key:", a) print("重复投票返回:", vote(a, "u1", rc)) # 同一用户再投,sadd 返回 0,分数不应变化 print("换人投票后评分:", rc.zscore("score", a)) # 应该比刚才多 432 top = rc.zrevrange("score", 0, 4, withscores=True) print("评分前五:", top)通过标准很直观:post返回的 key 是articleN形式且 N 递增;同一用户重复投票评分不变,换个用户投一次分数正好加 432;zrevrange拿到的第一条就是刚发的文章。这三条都过,说明 hash、zset、set 三种结构串起来了,集群的槽路由也没问题。
我一般还会顺手打开图形客户端,把article这个计数器和score这个 zset 各点开看一眼,肉眼确认成员名和分值对得上。这一步看着多余,但能发现“代码写进去了、连的却是另一个实例”这类问题——集群里六个节点,客户端连错一个,数据就落到别的槽上,光看代码输出是看不出来的。
从那以后我每次重建这套环境,都会强制先跑一遍槽数检查:cluster_slots_assigned不是 16384 就不往下走,宁可多花两分钟重置,也不在业务代码里找一小时的CLUSTERDOWN。这套单机加六节点集群的配置和投票后端的代码,我整理成了可直接复现的版本,照着上面几章的顺序走,第一次搭也能少绕几个弯。希望帮到你。
本文还有配套的精品资源,点击获取