☰
Redis单机与三主三从集群搭建实战:从安装配置到投票后端开发
2026/10/23 5:09:41 网站建设 项目流程

简介:本资源为南京邮电大学大数据存储与管理课程实验一的完整报告文档,面向大数据、云计算方向的学生及Redis初学者,帮助读者在Ubuntu环境下完成Redis单机与集群的安装配置,并动手实现一个文章投票网站后端。压缩包内仅含1个doc文件,约933KB,内容涵盖实验目的、环境配置、详细操作步骤、关键代码与结果分析,结构完整、可直接参考。实验涉及Redis官方源安装、配置文件修改、六节点集群搭建、rediscluster库使用,以及基于String和Sorted Set实现文章发布、投票与评分排序等核心操作,并附有连接报错等常见问题的排错思路。目前已有324人学习,适合需要提交实验报告或系统练习Redis集群与Python实战的读者参考借鉴。

1. 从一份实验报告说起:Redis 单机、集群与投票后端到底怎么落地

很多人第一次接触 Redis,都是从一份课程实验报告开始的。你手里这份“大数据存储与管理实验一”就是典型:在 Ubuntu 16.04 上装 Redis、搭三主三从集群、再用 Python 写一个文章投票网站的后端。听起来像教学作业,但它覆盖的东西一点都不玩具——单机安装、配置文件调参、集群槽位分配、客户端连接、哈希与有序集合建模,全是生产环境里天天要碰的。我见过太多人装完 Redis 就停在redis-cli ping返回 PONG 那一步,真到集群和业务代码就翻车。这篇笔记就按这份实验的路径走一遍,把每一步为什么这么做、参数怎么改、报错怎么查讲清楚。适合刚上手 Redis 的开发者,也适合想复习集群搭建细节的老手。

2. 单机 Redis 安装与配置:从 apt 源到远程连接

2.1 为什么不用 apt 默认源,而要走官方仓库

Ubuntu 自带的apt install redis-server能装,但版本通常偏旧,而且配置文件路径和官方包有差异。实验里用的是 Redis 官方 APT 仓库,好处是版本新、后续升级方便,配置文件统一在/etc/redis/redis.conf。这套流程在 Ubuntu 16.04 到 22.04 上都通用,只是lsb_release -cs会自动识别代号,不用手动改。

先装依赖,再导入 GPG 密钥,然后写源列表。注意gpg --dearmor那一步是把 ASCII 密钥转成二进制格式,新版 apt 要求这种格式,少了它后面apt update会报签名错误。

# 安装基础依赖,lsb-release 用来识别系统代号 sudo apt install lsb-release curl gpg # 导入 Redis 官方 GPG 密钥,--dearmor 转成 apt 可识别的二进制格式 curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg # 写入 APT 源,$(lsb_release -cs) 会自动替换成 xenial/focal/jammy 等代号 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 # 更新索引并安装 sudo apt-get update sudo apt-get install redis

装完之后 Redis 默认不会自动启动,实验里用的是/etc/init.d/redis-server start。这是 SysV 风格的启动脚本,在 systemd 系统上也能用,但更推荐systemctl start redis-server。启动后先redis-cli ping,返回 PONG 说明单机通了。

2.2 配置文件里必须改的几个参数

/etc/redis/redis.conf是核心。默认配置只监听127.0.0.1,外部客户端连不上。实验里用 Another Redis Desktop Manager 连接虚拟机,报ECONNREFUSED,根因就是绑定地址和 protected-mode。

需要改的项:

参数默认值建议值作用
bind127.0.0.10.0.0.0 或虚拟机 IP允许外部连接
protected-modeyesno(内网环境)关闭保护模式
daemonizenoyes后台运行
requirepass无按需设置连接密码

改完重启:/etc/init.d/redis-server restart。然后用ifconfig查虚拟机 IP,客户端里填这个 IP 而不是127.0.0.1。实验报告里特别提到“网上说关 protected-mode 没解决,最后改 IP 才通”,其实两个都要做——protected-mode 关掉是前提,IP 填对是连接目标正确。

提示:生产环境不要关 protected-mode 裸奔,至少配requirepass加防火墙白名单。实验环境图方便可以关,但要知道风险在哪。

2.3 用 Another Redis Desktop Manager 验证单机

客户端连接时填虚拟机 IP、端口 6379,如果设了密码就填密码。连上后能看到默认 16 个数据库(db0-db15)。实验里后续投票网站的数据就存在 db0。这一步的意义是:命令行验证的是服务本身,GUI 验证的是网络可达性和客户端兼容性,两个都过才算单机环境真正可用。

3. 三主三从集群搭建:配置文件、槽位与节点握手

3.1 集群模式的核心参数

单机 Redis 默认关闭集群支持,直接执行cluster nodes会报ERR This instance has cluster support disabled。要开集群,每个节点的配置文件里必须加:

# 开启集群模式 cluster-enabled yes # 集群配置文件,每个节点独立,不能共用 cluster-config-file nodes-6379.conf # 节点超时时间,毫秒 cluster-node-timeout 15000 # 开启后台运行 daemonize yes # 绑定地址 bind 0.0.0.0 # 端口,每个节点不同 port 6379 # 日志文件,方便排查 logfile /opt/redis/cluster/log/redis_6379.log # 数据目录,每个节点独立 dir /var/lib/redis/6379

实验里建了 6 个配置文件:6379、6380、6381 作为主节点,6389、6390、6391 作为从节点。端口规划没有强制规则,但习惯上主从端口错开一段,方便记忆。每个节点的cluster-config-file和dir必须不同,否则启动时会互相覆盖状态。

3.2 启动节点与创建集群

先建目录、复制配置模板,再逐个改端口和路径。启动命令用绝对路径/usr/bin/redis-server,后面跟配置文件路径:

# 创建集群目录和日志目录 mkdir -p /opt/redis/cluster/log # 复制默认配置作为模板 cp /etc/redis/redis.conf /opt/redis/cluster/ # 启动 6 个节点 /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 # 确认 6 个进程都在 ps -ef | grep redis

6 个节点都起来后,用redis-cli --cluster create合成集群。--cluster-replicas 1表示每个主节点配一个从节点,6 个节点正好三主三从。命令里前三个是主候选,后三个是从候选,Redis 会自动分配槽位:

/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

执行后会提示确认槽位分配方案,输入yes。成功后用redis-cli -c -p 6379进入集群模式,cluster nodes能看到 6 个节点和各自角色,cluster info看cluster_state:ok和槽位覆盖情况。

3.3 槽位冲突与集群重置

实验报告里提到ERR Slot 671 is already busy,这是最典型的集群搭建翻车现场。原因是之前跑过集群,nodes.conf和槽位分配残留,新集群创建时发现槽位被占。解决办法是登录每个节点执行:

# 清空当前节点所有数据 redis-cli -p 6379 flushall # 重置集群状态,清除槽位分配和节点信息 redis-cli -p 6379 cluster reset

6 个节点都要执行一遍,然后重新--cluster create。注意flushall会清数据,生产环境慎用;实验环境无所谓。另外cluster reset默认是软重置,如果还不行加hard参数。

注意:集群创建前确保每个节点的cluster-config-file指向的文件不存在或已清理,否则旧状态会被加载。

4. 文章投票后端:哈希、有序集合与投票逻辑

4.1 数据模型设计

实验参考的是《Redis in Action》1.3 节的投票网站。核心数据结构两个:

  • 哈希(Hash):存文章详情,key 是article:<id>,field 是 title、link、poster、time、votes、score。
  • 有序集合(ZSet):存排序索引。time集合按发布时间排序,score集合按评分排序。

文章 ID 用INCR article生成,保证唯一递增。发布时间用time.time()存浮点秒数,ZSet 的 score 就是它,这样ZRANGE time 0 -1就是按时间倒序或正序取文章。

4.2 发布文章与初始投票

发布文章时,先INCR拿 ID,拼出article:<id>,用HSET批量写字段,再用ZADD把文章加到时间索引。然后给发布者自己投一票,最后算一次评分。

import time import redis # 单机连接;集群用 rediscluster.RedisCluster rc = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True) def post(articleData, redis=rc): # 文章计数器加 1,作为唯一 ID articleId = str(redis.incr('article')) article = 'article' + articleId # 写入文章哈希 for k, v in articleData.items(): redis.hset(article, k, v) # 记录发布时间,用于时间排序 post_time = time.time() redis.zadd('time', {article: post_time}) # 发布者自动投一票 vote(article, articleData['poster'], True) # 计算初始评分 score(article, redis) return article

decode_responses=True让返回结果是字符串而不是 bytes,省得每次手动 decode。hset在 redis-py 3.x 之后支持多字段写法,但实验里是循环单字段写,兼容性更好。

4.3 投票与评分计算

投票逻辑要防重复投票。常见做法是用 Set 记录每个文章的投票用户,投票前SADD返回 1 表示新投票,返回 0 表示已投过。投票后更新文章的 votes 字段,并重新计算评分。

def vote(article, user, is_poster=False): # 用 Set 记录投票用户,SADD 返回 1 表示首次投票 if rc.sadd(article + ':votes', user): rc.hincrby(article, 'votes', 1) # 发布者初始票权重更高,这里简单加 1 if is_poster: rc.hincrby(article, 'votes', 1) score(article, rc) def score(article, redis=rc): # 评分 = 投票数 + 发布时间因子,简单模拟热度衰减 votes = int(redis.hget(article, 'votes') or 0) post_time = float(redis.hget(article, 'time') or 0) # 时间越新加分越多,这里用当前时间减发布时间 current = time.time() hot = votes + max(0, (current - post_time) / 1000) redis.hset(article, 'score', hot) # 同步到评分有序集合 redis.zadd('score', {article: hot})

score函数里用发布时间差做热度因子,是简化版。真实场景会用votes / (time + 常数)做衰减,实验里不要求那么精确。关键是理解 ZSet 的 score 可以动态更新,ZADD对已存在成员会覆盖分数。

4.4 分组与排序查询

实验里还做了文章分组,用 Set 存分组关系:SADD group:<name> article:<id>。查询某分组文章时,先SMEMBERS拿文章列表,再逐个HGETALL取详情。按评分排序则直接ZREVRANGE score 0 -1,拿到文章 key 后再取哈希。

# 把文章加入分组 rc.sadd('group:tech', 'article1') rc.sadd('group:tech', 'article2') # 取分组内文章 articles = rc.smembers('group:tech') # 按评分降序取前 10 篇 top_articles = rc.zrevrange('score', 0, 9) for a in top_articles: print(rc.hgetall(a))

集群模式下smembers和zrevrange跨槽位会报 CROSSSLOT 错误,需要用 hash tag 把相关 key 绑到同一槽,比如group:{tech}和article:{tech}:1。实验里是单机或简单集群,没踩到这个坑,但生产环境必须注意。

5. 避坑与排查:连接、集群、客户端那些坑

5.1 客户端连不上:ECONNREFUSED

现象:Another Redis Desktop Manager 报connect ECONNREFUSED 127.0.0.1:6379。

原因:客户端填的是本地回环地址,但 Redis 跑在虚拟机里,回环地址指向客户端本机。另外bind和protected-mode没改也会拒绝外部连接。

解决:ifconfig查虚拟机 IP,客户端填这个 IP;配置文件里bind 0.0.0.0、protected-mode no,重启 Redis。两步都做,缺一不可。

5.2 集群支持被禁用

现象:cluster nodes报ERR This instance has cluster support disabled。

原因:Redis 默认cluster-enabled no,单机模式不支持集群命令。

解决:每个节点的配置文件里把cluster-enabled改成yes,去掉行首#,重启节点。注意是每个节点都要改,漏一个就组不成集群。

5.3 槽位已被占用

现象:--cluster create时报ERR Slot 671 is already busy。

原因:旧集群的nodes.conf和槽位分配残留,新集群创建时冲突。

解决:每个节点执行flushall和cluster reset,删掉旧的nodes-*.conf文件,再重新创建集群。顺序是先重置所有节点,再执行 create。

5.4 集群客户端连接断开

现象:Another Redis Desktop Manager 连集群报stream on error: connection is close或cluster is ended。

原因:普通单机连接方式不支持集群的 MOVED/ASK 重定向,客户端需要集群模式。

解决:客户端里选集群连接类型,或者用 SSH 隧道加集群模式。Python 端用redis-py-cluster的RedisCluster类,它会自动处理重定向。

5.5 Python 客户端选型混淆

现象:用redis.Redis连集群,部分 key 读写报 MOVED 错误。

原因:redis.Redis是单机客户端,不处理集群重定向。

解决:集群用rediscluster.RedisCluster(startup_nodes=[...]),单机用redis.Redis。实验里pip install redis-py-cluster就是为集群准备的,但投票代码如果跑在单机上,用redis.Redis更简单。

6. 进阶技巧:用 pipeline 和 Lua 脚本把投票做稳

实验里的投票逻辑是多次独立命令,网络往返多,并发下还可能超卖。我一般会做两件事:批量操作用 pipeline 打包,原子逻辑用 Lua 脚本。

pipeline 把多个命令一次发出去,减少 RTT:

# 用 pipeline 批量写文章和索引 pipe = rc.pipeline() pipe.hset('article:1', mapping={'title': 'Redis实战', 'poster': 'A'}) pipe.zadd('time', {'article:1': time.time()}) pipe.execute()

Lua 脚本保证投票的“检查+写入”原子性,避免并发重复投票:

-- vote.lua:原子投票,返回 1 表示成功,0 表示已投过 local voted = redis.call('SADD', KEYS[1], ARGV[1]) if voted == 1 then redis.call('HINCRBY', KEYS[2], 'votes', 1) return 1 else return 0 end

Python 里用rc.eval或register_script调用。集群模式下 Lua 脚本涉及的所有 key 必须在同一槽,所以 key 命名要用 hash tag,比如{article1}:votes和{article1}:detail。

验证集群是否健康,我习惯跑一遍:

# 查看集群状态 redis-cli -c -p 6379 cluster info | grep cluster_state # 查看槽位覆盖 redis-cli -c -p 6379 cluster slots # 模拟故障转移:停掉一个主节点,看从节点是否升主 redis-cli -p 6379 shutdown sleep 5 redis-cli -c -p 6389 cluster nodes

最后这条shutdown会真的停掉节点,实验环境可以玩,生产别乱来。从那以后我每次搭完集群,都强制走一遍“停主看从”的验证,确认故障转移真的生效,而不是只看cluster_state:ok就放心。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询