目录
[二、Redis 介绍](#二、Redis 介绍)
[(三)Redis 适用场景](#(三)Redis 适用场景)
[(四)Redis 不适用场景](#(四)Redis 不适用场景)
前言
- 前面的博客详细介绍了MySQL的基本使用和优化,可在主页查看相关的MySQL博客进行了解。
- 本文介绍Redis及缓存的三大问题缓存穿透、缓存击穿、缓存雪崩和Redis集群的搭建,下一篇介绍Redis的主从配置和Redis的哨兵 及Redis的数据持久化。
注 :如果想进一步了解Redis的使用的可以参考Redis中文文档教程
一、非关系数据库介绍
(一)定义
- 非关系型数据库泛指不遵循传统关系模型(表、行、列、SQL)的数据存储系统。
- 它们通过更灵活的数据模型 、横向扩展架构 以及针对特定场景的优化 ,解决高并发、海量数据、高可用与多活等问题。
(二)分类
数据模型 | 代表系统 | 典型场景 | 关键特性与注意事项 |
---|---|---|---|
键值对 | Redis | 缓存、会话、分布式锁 | 全内存、单线程命令执行、RDB/AOF 持久化、主从+哨兵/集群高可用 |
文档 | MongoDB | 内容管理、订单详情 | BSON 存储、动态 Schema、副本集、分片集群 |
检索 | ElasticSearch | 全文检索、日志分析 | 倒排索引、近实时搜索、分片+副本、DSL 查询 |
列族 | HBase | 写密集型、宽表存储 | LSM-Tree、Rowkey 设计决定性能、依赖 HDFS |
图 | Neo4j | 关系网络、推荐 | ACID 事务、Cypher 查询、节点-边-属性模型 |
时序 | Prometheus | 监控指标 | 拉模式采集、PromQL、本地 TSDB、Alertmanager 告警 |
向量 | Milvus / Qdrant | 语义检索、推荐 | 支持 IVF、HNSW 索引、GPU 加速、高维向量近似搜索 |
二、Redis 介绍
(一)简介
Redis(Remote Dictionary Server)是开源的、基于内存的键值存储系统,支持多种数据结构(String、Hash、List、Set、Sorted Set、Bitmap、HyperLogLog、Stream、Geo 等)。
(二)核心特点
特性维度 | 技术实现 | 优点 |
---|---|---|
高性能 | 全内存存储 + 单线程网络模型 + 非阻塞 I/O | 微秒级响应,缓存层 QPS 10 万级,读写极快,显著降低后端负载 |
多数据结构 | 原生支持 String、Hash、List、Set、Sorted Set、Bitmap、HyperLogLog、Geo、Stream | 同一实例即可覆盖缓存、计数、队列、排行榜、去重、位置服务等场景,减少组件数量 |
原子操作 | 单线程顺序执行命令,无锁竞争 | 无需显式加锁即可实现 INCR、SET NX、Lua 脚本等业务级原子逻辑,避免并发问题 |
高可用 | 主从复制 +Sentinel 自动故障转移+ Cluster 分片 | 故障秒级切换,水平扩展无业务侵入,确保服务持续在线 |
双持久化 | RDB 快照(快速恢复)+AOF 追加日志(低丢数) | 宕机后分钟级恢复,数据丢失控制在秒级,兼顾速度与可靠性 |
轻量消息 | Pub/Sub、Stream 消费组、Lua 原子脚本 | 无需引入 Kafka即可完成低延迟消息与事件驱动,架构更简洁 |
(三)Redis 适用场景
- 缓存 :将热点查询结果、会话状态或配置信息缓存在内存,降低后端数据库压力。
- 排行榜 :使用有序集合(ZSET)按分数排序,实现实时积分、销量或热度排行。
- 计数器:利用 INCR、DECR 等原子指令,支持高并发 UV、PV、库存扣减。
- 社交网络 :以集合(SET)或**哈希(HASH)**存储好友关系、共同关注、点赞列表。
- 消息队列 :通过LIST 的LPUSH/BRPOP 或 STREAM 实现轻量级任务队列与事件流。
- 地理位置 :基于 GEO命令计算两点距离、附近人搜索。
- 分布式锁 :SET key value NX PX ttl保证跨节点互斥。
- 发布/订阅:频道广播实现实时通知、配置推送。
(四)Redis 不适用场景
- 超大规模冷数据:单实例内存有限,亿级冷日志建议落盘到HDFS/S3。
- 复杂事务:不支持跨键 ACID事务,金融转账等场景需关系型数据库。
- 大对象存储:单值超过 512 MB 时需拆片或使用对象存储。
- 长时间持久化:RDB/AOF 极端宕机仍可能丢数,关键订单需双写。
三、Redis的容错机制
机制 | 作用 | 实现要点 |
---|---|---|
哈希槽分片 | 将16384个槽均匀分布到各主节点,实现水平扩展;迁移期间新旧节点均可用,ASK 重定向保障读写不中断 | 节点增减时自动迁移槽,客户端按 CRC16(key) mod 16384 定位槽 |
主从复制 | 每个主节点拥有 ≥1 个从节点,异步复制数据,支持读写分离;主故障时复制偏移量最大的从节点晋升 | 全量 RDB + 增量 AOF,可配置 min-replicas-max-lag 控制复制延迟 |
故障检测 | 每秒心跳探测,主观下线 → 客观下线;仅主节点投票,避免脑裂 | 基于半数以上主节点投票,cluster-node-timeout 定义超时阈值 |
故障转移 | 哨兵或集群自动选主,<1 秒完成切换;切换后通过 CLUSTER SETSLOT 广播新拓扑 | 从节点按复制偏移量与节点 ID选举,重新分配槽 并更新客户端路由表 |
Gossip 协议 | 节点间周期性交换状态 ,维护无中心集群视图;网络分区时依赖多数派收敛 | 每秒随机选 3 个节点交换 ping/pong 消息,消息体含节点 ID、槽位、故障标记 |
客户端重定向 | 节点返回 MOVED/ASK响应,客户端重发请求;主流客户端内置缓存,无需停机 | MOVED 指示槽位已迁移,ASK 临时转发请求,客户端缓存槽位映射 |
四、缓存策略与优化
(一)缓存穿透
问题
原因 | 目标 |
---|---|
大量非法请求查询数据库不存在的数据 → 既无法命中缓存 ,也查不到 DB → 每次请求都穿透缓存直达数据库。 | 让"空结果"也具备缓存能力,堵住流量洪峰。 |
解决方案
- 缓存空结果
- 对于查询结果为空的数据,在缓存中记录短时间的空值标记 (如
##
),后续相同查询直接返回空值,避免重复回源。 - 作用:阻断针对不存在数据的恶意或异常高频请求。
- 建议 :空值标记设置 30--120 秒过期,既防止缓存穿透 ,又避免长期占用内存。
- 对于查询结果为空的数据,在缓存中记录短时间的空值标记 (如
- 参数合法性校验
- 在网关或业务入口层对查询参数进行范围、格式、签名等校验,非法请求直接拒绝。
- 作用:在缓存层之前拦截无效流量,降低缓存与数据库压力。
- 建议:将 ID 范围、正则规则、签名密钥配置化,支持热更新,提升灵活性。
- 使用布隆过滤器
- 将所有可能存在的数据键写入布隆过滤器 ;查询先过过滤器,不存在则立即返回,存在再走缓存与数据库。
- 作用:以极小内存代价拦截 100% 不存在的数据请求,提升缓存命中率。
- 建议:过滤器采用位图或 Redis 模块实现,定期异步重建,保持假阳性率低于 1%。
(二)缓存击穿
问题
| 原因 | 目标 |
热点 Key 失效瞬间,大量并发请求同时回源 → DB 瞬时 QPS 飙升,甚至崩溃。 | 保证同一时刻仅一个线程回源,其余线程等待。 |
---|
解决方案
- 互斥锁
- 在缓存失效瞬间,通过分布式锁(如 Redis SET NX PX)仅允许一个线程回源数据库,其余线程阻塞等待。
- 作用 :避免并发回源穿透数据库,有效防止击穿风险。
- 建议:注意加锁的开销,可能会导致大量线程阻塞等待锁,形成锁竞争,降低并发性能。
- 软过期+互斥锁
- 在缓存 value 中写入逻辑过期时间 T1(T1 < 实际 TTL T2)。
- 读取时先检查 T1:若未过期直接返回;若已过期,仅让一个线程获取分布式锁回源更新 ,其余线程仍返回旧值。
- 作用:相比单纯的互斥锁方案,能进一步减少读请求线程的阻塞时间。
- 建议:合理设置逻辑过期时间和互斥锁的过期时间,结合重试机制,可以提高系统在高并发场景下的稳定性和性能。
- 静态数据+Lazy Expiration
- 在 Redis 中不设置 TTL,使 key 表面"永不过期";在 value 头部写入 8-byte 逻辑过期时间戳。读取时先比对当前时间:
- 未过期 :直接返回;
- 已过期 :仅让一个线程抢分布式锁,后台异步线程执行回源 并写回新值,其余线程仍读旧值 ,无阻塞。
- 作用 :性能最好,避免了频繁的缓存失效和重建,系统吞吐最稳定。
- 建议 :
- 逻辑过期时间:在缓存值中设置一个逻辑过期时间,当取值时判断过期时间是否已经到达。
- 异步更新:如果逻辑过期时间已过,则启动一个异步线程来更新缓存,而不是阻塞当前请求。
- 互斥锁:避免多线程同时更新缓存,使用互斥锁来保证只有一个线程能进行更新操作。
- 在 Redis 中不设置 TTL,使 key 表面"永不过期";在 value 头部写入 8-byte 逻辑过期时间戳。读取时先比对当前时间:
- 自动续期
- 为热点 key 预设 30 min TTL,并启动周期任务 :在 key 剩余 10 min 时回源刷新数据,成功后重置 TTL 为 30 min。
- 作用 :避免缓存频繁失效,减少对数据库的压力。
- 建议 :
- 续期任务使用 Lua 脚本保证 GET + SET PX 原子操作,避免并发回源。
- 采用分布式定时框架(如 Quartz + Redis 分布式锁)单实例执行,防止多节点重复刷新。
- 暂时缓存不失效
- 活动开始前,通过预热脚本把热点数据一次性加载到 Redis 并 不设置 TTL;活动结束后,统一脚本或消息触发 批量删除。
- 作用:避免热点数据频繁失效,提升系统的性能。
- 建议 :
- 预热阶段
- 使用预热任务灰度执行,写入完成后通过
CLUSTER NODES
确认所有槽位同步成功,再开放流量。 - 写入命令:
SET key value
(无 PX/EX 参数)。
- 使用预热任务灰度执行,写入完成后通过
- 活动结束清理
- 采用
SCAN
+UNLINK
分批删除,避免一次性DEL
大 key 导致节点阻塞; - 或把热点 key 统一放在
hot:{bizId}:*
命名空间,活动后用FLUSHDB
/UNLINK
通配删除。
- 采用
- 兜底策略
- 预热失败或数据变更时,通过后台补偿任务以 Lazy Expiration 方式异步修正,保证最终一致。
- 预热阶段
(三)缓存雪崩
问题
| 原因 | 目标 |
大批 Key 同时失效 或 缓存集群整体故障 → 流量瞬间压垮 DB。 | 让失效分散化 + 集群高可用 + 下游限流降级。 |
---|
解决方案
- 分散过期时间
- 在原始 TTL 上叠加 0-300 秒随机偏移,使用纳秒级随机避免伪随机聚集;对同一业务批次 key 采用滑动窗口方式错峰。
- 作用 :避免缓存key在同一时间失效,防止大量 key同时回源。
- 提前演练压测
- 上线前模拟缓存全量失效、节点宕机、网络延迟三种场景;输出 QPS-RT 曲线与数据库安全阈值,据此调整连接池、线程池及分片数。
- 作用 :提前暴露瓶颈,给出容量基线。
- 缓存高可用+后端数据库限流
- 双缓存热备:主备集群跨机架部署,使用客户端双写或异步复制;
- 数据库限流:Hystrix 线程池隔离,熔断阈值按压测峰值 80% 设置,熔断后降级本地缓存或默认值。
- 作用:缓存故障时仍保证核心链路可用。
- 服务降级
- 全局开关:30 秒内 Redis 失败 ≥5 次即开启,请求直接返回配置中心兜底数据;
- 自愈探针:后台任务每 30 秒探测 Redis,连续两次成功即关闭开关,恢复实时数据。
- 作用:缓存连续异常时快速兜底,保护下游。
五、搭建Redis集群
- 步骤 :基本环境准备 → 创建集群 →查看集群信息→测试集群
(一)Redis集群介绍
- Redis 集群通过数据分片与复制机制,实现了高可用性和水平扩展。
- 集群将 16384个哈希槽(Hash Slot)均匀分布到各个主节点上,每个主节点负责一部分槽的数据。
- 每个槽通常会有多个副本存储在不同的从节点 上,以防止节点故障导致数据丢失。
- 当有新的节点加入或节点移除时,槽会自动迁移 ,确保数据分布的均衡性。
(二)基本环境准备
主机准备
主机名 | IP地址 | 角色 |
---|---|---|
Redis50 | 192.168.88.50 | Master |
Redis51 | 192.168.88.51 | Slave |
Redis52 | 192.168.88.52 | Master |
Redis53 | 192.168.88.53 | Slave |
Redis54 | 192.168.88.54 | Master |
Redis55 | 192.168.88.55 | Slave |
Nginx56 | 192.168.88.56 | Web服务器 |

- 注:用于构建集群的主机不应存储任何数据,并且不应设置连接密码,配置SELINUX和关闭防火墙。
集群环境准备
-
注:所有主机都需要执行以下步骤,仅将bind之后的IP地址修改为对应主机的IP地址。
yum -y install redis #安装Redis服务 vim /etc/redis.conf #修改配置文件 #69行 bind 192.168.88.50 #指定 Redis 服务监听的 IP 地址 #92行 port 6379 #指定 Redis 服务监听的端口号 #838行 cluster-enabled yes #启用 Redis 集群模式 #846行 cluster-config-file nodes-6379.conf #指定集群配置文件的路径和名称 #852行 cluster-node-timeout 5000 #设置集群节点间通信的超时时间(单位:毫秒) systemctl start redis #启动Redis服务 ss -antlup | grep redis #检查Redis服务的端口信息 客户端端口(6379)处理客户端请求 集群总线端口(16379)Redis节点之间的内部通信
(三)创建集群
-
注:在任意一台主机执行一次即可。
redis-cli --cluster create \ 192.168.88.50:6379 192.168.88.52:6379 192.168.88.54:6379 \ 192.168.88.51:6379 192.168.88.53:6379 192.168.88.55:6379 \ --cluster-replicas 1 #解释 #redis-cli --cluster create 创建 Redis 集群,自动分配哈希槽(16384 个槽)并建立主从关系 #IP地址 指定节点列表 #-cluster-replicas 1 指定每个主节点的从节点数量 #Redis集群的创建工具会严格按照节点列表的顺序分配主从角色,比如节点列表中的前三个作为主节点,后三个作为从节点
(四)查看集群
redis-cli --cluster info 192.168.88.50:6379
#连接到指定的 Redis 集群节点(192.168.88.50:6379),并输出该节点所在集群的整体状态信息
(五)测试集群
验证数据分布
-
目的 :确认写入的三条测试键已根据 CRC16 算法均匀落入三个主节点的哈希槽,且可通过任意节点透明访问。
redis-cli -c -h 192.168.88.51 -p 6379 #-c 启用集群模式 -h <host> 指定Redis服务器的IP地址 -p <port> 指定Redis服务器的端口号 set test1 t1 #设置测试用的键值对 set test2 t2 set test3 t3 redis-cli --cluster info 192.168.88.50:6379 #查看集群状态信息,预期数据均匀分布在三个主节点上 #比如分布在50、52、54主机上
测试数据自动备份
-
目的 :验证 Slave 实时同步 Master 数据,实现零人工干预的备份。
#通过命令行连接Slave节点查看数据,预期能看到之前存储的test1和test2和test3键 redis-cli -c -h 192.168.88.51 -p 6379 192.168.88.51:6379> keys * redis-cli -c -h 192.168.88.53 -p 6379 192.168.88.53:6379> keys * redis-cli -c -h 192.168.88.55 -p 6379 192.168.88.55:6379> keys *
测试服务高可用(故障转移)
-
目的 :验证 Master 宕机 → Slave 自动升主 → 恢复后自动降级为从 的完整流程。
-
当前集群拓扑
Master Slot Range Slave Redis50 0-5460 Redis51 Redis52 5461-10922 Redis53 Redis54 10923-16383 Redis55
-
模拟 Redis50 Master 宕机
#Redis52执行 systemctl stop redis
-
查看故障转移结果
#Redis50主机执行 #查看集群状态信息 redis-cli --cluster info 192.168.88.50:6379 #预期能看到Redis53节点上升为主节点
-
原 Master 恢复并自动降级
#Redis52执行 systemctl start redis #Redis50执行 redis-cli --cluster info 192.168.88.50:6379 #预期能看到Redis52自动成为Redis53的从节点
搭建Web服务器并连接Redis集群
-
目的 :在 Nginx56 主机上完成 LNMP + Redis-Cluster 的端到端集成,实现 高并发缓存读写、水平扩展、故障自愈 三大目标。
-
注:在Nginx56主机上搭建LNMP平台,php-fpm以非sock的方式 运行,搭建可以参考Nginx技术笔记-从LNMP架构到反向代理、负载均衡、灰度发布的全攻略
-
配置Redis PHP扩展
tar -xf redis-cluster-4.3.0.tgz #解压 Redis 集群的源代码包(redis-cluster-4.3.0.tgz)到当前目录,需要提前下载 cd redis-4.3.0 phpize #生成 PHP 扩展的配置脚本(configure),为后续编译做准备 ./configure --with-php-config=/usr/bin/php-config #配置 PHP 扩展的编译参数,指定 PHP 的配置路径(php-config) make && make install #make 根据 Makefile 编译 Redis 扩展 #make install 将编译后的扩展文件(如 redis.so)安装到指定目录(如 /usr/lib64/php/modules/) ls /usr/lib64/php/modules/redis.so #验证 redis.so 文件是否已成功生成 vim /etc/php.ini #修改 PHP 的全局配置文件,启用 Redis 扩展 #737行 extension_dir = "/usr/lib64/php/modules/" #指定 PHP 扩展文件的存放路径 #739行 extension = "redis.so" #启用 Redis 扩展 systemctl restart php-fpm #重启 PHP-FPM 服务,使配置生效 php -m | grep redis #验证 Redis 扩展是否已成功加载
-
编写存储脚本
vim /usr/local/nginx/html/set.php <?php #定义一个包含 Redis 集群节点地址和端口的数组 $redis_list = [ '192.168.88.50:6379','192.168.88.51:6379','192.168.88.52:6379', '192.168.88.53:6379','192.168.88.54:6379','192.168.88.55:6379' ]; #初始化一个 Redis 集群客户端对象 #NULL 表示不使用密码认证 #$redis_list 传入的 Redis 节点列表 $client = new RedisCluster(NULL, $redis_list); #向 Redis 集群中写入三个键值对 $client->set("test50","t50"); $client->set("test52","t52"); $client->set("test54","t54C"); echo "SAVE OK\n"; ?>
-
编写查看脚本
vim /usr/local/nginx/html/get.php <?php #定义Redis集群节点列表,用于建立 RedisCluster 客户端连接 $redis_list = [ '192.168.88.50:6379','192.168.88.51:6379','192.168.88.52:6379', '192.168.88.53:6379','192.168.88.54:6379','192.168.88.55:6379' ]; #初始化一个 Redis 集群客户端对象 $client = new RedisCluster(NULL, $redis_list); #从 Redis 集群中读取三个键的值,并输出到客户端 echo $client->get("test50"); echo $client->get("test52"); echo $client->get("test54"); ?>
-
访问测试
curl http://192.168.88.56/set.php #返回结果 SAVE OK curl http://192.168.88.56/get.php #返回结果 t50 t52 t54
-
命令行验证数据分布
# 以集群模式连接到 Redis 节点并查看数据 redis-cli -c -h 192.168.88.50 -p 6379 192.168.88.50:6379> keys * #列出当前节点上所有的键 redis-cli -c -h 192.168.88.52 -p 6379 192.168.88.52:6379> keys * redis-cli -c -h 192.168.88.54 -p 6379 192.168.88.54:6379> keys *