尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

没有高并发就不用Redis?一文讲透Redis真实价值与选型落地要点

没有高并发就不用Redis?一文讲透Redis真实价值与选型落地要点 先说明一下我的态度“认为系统没有高并发就不用 Redis 的都是初学者”这句话虽然说得有点绝对但它真正想纠正的不是“你必须上高并发架构”这件事而是很多人对 Redis 的理解还停留在“缓存工具”和“高并发神器”这两个标签上。仿佛只要日活不高、QPS 没过万Redis 就完全派不上用场。我见过不少项目用户量确实不大但业务里的分布式锁、接口限流、登录状态共享、排行榜、异步任务队列、热点榜单、幂等控制桩桩件件都跟 Redis 有关。这些东西并不需要你服务多少量级而是只要你的系统是多实例部署、有分布式协作、有状态共享、有热点数据Redis 就是那个把“本地内存做不到的事”变成“基础设施短板”的关键角色。这篇文章不绕到书面上讲一大堆概念我会直接按实际项目里做过的内容来拆Redis 到底解决哪些“不依赖高并发也会遇到”的问题怎么判断一个项目究竟需不需要 Redis哪些场景用了 Redis 反而更难受以及你在第一次上手时要注意哪些最容易踩的坑。换句话说这不是一篇“Redis 高并发调优指南”而是一篇“Redis 该不该用、怎么判断、怎么落地”的实战笔记。1. 先拆穿“没高并发就不用 Redis”这个误区1.1 很多人把“高并发”理解成了 Redis 存在的唯一理由先说为什么会有这种误区。多数初学者接触 Redis是从高并发缓存场景开始的数据库扛不住读请求于是把热点数据提前放到 Redis 里用内存读取替代磁盘查询把 QPS 从几百撑到几万。这个场景太经典了经典到大家下意识把 Redis 和“为了高并发而存在”画上等号。但如果你只从这个角度去理解 Redis会漏掉它真正的基础设施属性。Redis 不只是一个缓存数据库它本质上是一个高性能的、支持多种数据结构的、可以跨进程共享状态的存储服务。你可以把它当缓存也可以把它当分布式锁存储当速率限制器当消息队列当排行榜引擎当布隆过滤器当分布式 ID 生成器。举个例子。你有两个应用实例部署在两台机器上用户登录之后生成了 session。因为负载均衡第一次请求打到 A 实例第二次请求打到 B 实例。如果 session 只存在本地内存里B 实例根本不认识这个用户。这时候你系统的并发量可能只有 10但问题依然存在。解决方案有很多其中一个常规做法就是把 session 集中存到 Redis 里。这个场景跟高并发一点关系都没有但它足够真实。所以那句“觉得系统没有高并发就不用 Redis都是初学者”真正想说的是你的系统只要是多实例部署只要存在多个服务之间共享状态或协作的需求Redis 很多时候就是绕不开的选择。高并发只是放大了 Redis 的价值但 Redis 的价值从来不只是高并发。1.2 初学者容易犯的判断错误我再总结一下初学者最容易有的几个判断错误只看 QPS不看分布式需求。单机部署可能确实不需要 Redis但你一旦扩容到两台以上本地缓存、本地 session、本地锁都会开始出问题。只看缓存提速不看功能能力。Redis 的分布式锁、限流计数器、滑动窗口、延迟队列、附近的人、排行榜这些能力跟“并发高不高”没有必然关系。只看 Redis 能做什么不看 Redis 不能做什么。以为引入 Redis 一劳永逸结果遇到持久化丢数据、内存淘汰导致缓存击穿、集群脑裂、大 Key 阻塞等问题又反过来怪 Redis 不稳定。注意判断一个项目是否该用 Redis不能只看“并发高不高”而是要看“有没有多个进程需要共享状态、有没有需要低延迟访问的数据结构、有没有不想直接落库但又需要临时存储的数据”。2. Redis 到底解决了哪些“不靠高并发也会遇到”的问题这一章重点讲五个高频场景。其实这些场景很多都跟“系统规模”关系不大而是跟“业务结构”关系更大。2.1 登录态和会话共享多实例部署后的第一道坎网上关于单体应用的 session 存储方案往往默认你只有一个实例。但真实项目部署时除了非常小的玩具项目之外多数都会至少挂两台服务器或者在 K8s 里跑多个副本。这个时候 session 不共享用户就会“莫名其妙掉线”。用 Redis 存 session 的好处在于Redis 本身就是独立部署的跨进程存储所有实例访问同一个 Redissession 自然就共享了。Redis 支持给 key 设置过期时间天然适合 session 这种有时间限制的数据。数据结构上可以用 string 存 session 序列化后的内容也可以用 hash 按字段存读取和更新都比较灵活。如果你不想直接依赖哪个 session 框架可以自己写一个很简单的逻辑用户登录成功后生成一个 token以 token 为 key用户信息序列化为 value写进 Redis过期时间设为 7 天。以后前端每次请求都带上 token后端先从 Redis 里查一遍查到就代表登录有效。这里不适合用什么“session 必须存 Redis”的说法来绑架项目但如果你的系统已经是多实例部署我建议优先考虑这种方案成本很低收益很明确用户状态全局统一不再依赖负载均衡策略。2.2 接口幂等与分布式锁没锁住就等着出重复数据吧这个场景在“管理后台 前端双击提交”的情况下特别常见。一个创建订单的接口用户手一抖点了两次如果没有任何幂等控制数据库里就会出现两条一模一样的订单。并发不高不低但问题完全不受并发控制影响。解决方案之一就是“分布式锁 业务唯一标识”。流程写起来大概是这样客户端生成一个唯一请求标识requestId提交时把 requestId 带上来。服务端拿到 requestId尝试向 Redis 写入一个临时 key比如order:submit:{requestId}并设置 30 秒过期时间。如果写入成功说明这是第一次请求继续执行业务执行完删除 key。如果写入失败说明之前已经有相同请求在处理直接返回“重复提交”。这个逻辑里 Redis 扮演的是“跨进程的互斥存储”靠的是SET key value NX EX seconds这个命令的原子性。同样的思路也适用于更复杂一点的分布式锁场景多个应用实例同时抢一个资源比如“定时任务只能有一个实例执行”“两个人同时点击同一件商品的抢购按钮”。用 Redis 做分布式锁本身不难难的是锁的释放、过期时间设置、误删别人锁的问题这些可以在初始版本的代码里留好扩展点再用 Redisson 这类客户端库来封装。说到分布式锁有几个点要特别留意不要用setnx 单独expire两条命令来实现因为这个操作不是原子的如果客户端在setnx之后就崩了key 永远不过期锁就变成死锁。应该用带NX和EX参数的原子命令。释放锁之前要确认 value 是否还是自己的避免 A 线程的锁过期被自动删了B 线程拿到同一把锁然后 A 把 B 的锁释放掉了。如果业务执行时间超过锁的过期时间可以考虑“看门狗”机制自动续期或者把过期时间设大一点但代价是发生异常时锁被占用的时间更长。2.3 接口限流扛不扛得住是其次先防止被刷限流也不一定是被高并发逼出来的。很多业务场景比如短信验证码发送、AI 接口调用、批量导出、发票查询都是因为有人用脚本刷或者调用方逻辑写错导致长时间重试才需要限流。限流最常见的实现方式之一就是 Redis 计数器固定窗口用INCR给某个 key 计数比如sms:code:ip:1.2.3.4第一次执行INCR如果结果是 1就顺便设置过期时间比如 60 秒后续每次先INCR再判断是否超过阈值。滑动窗口用 ZSET 记录每个请求的时间戳移除窗口之外的数据再统计窗口内数量。这个对 Redis 操作次数更多但限流结果更平滑。令牌桶或漏桶可以用 Redis 的 Lua 脚本实现适合对突发流量有一定容忍的场景。对于“没有高并发但是想控制风险”的项目最简单的固定窗口应用最广因为实现只要几行代码。如果你用的不是 Redis而是单纯在应用本地内存里计数那限流只对单实例有效一旦后面扩容A 和 B 各自计各的限制形同虚设。这里我给一个非常通用的参考判断标准如果你的系统是单体单机本地内存限流完全够如果你的系统是多实例、容器化部署或者未来大概率会多实例那直接一步到位用 Redis 限流。不然到时候上线前一天才意识到限流只限了一半真的难受。2.4 热点数据与排行榜别把所有“计算结果”都硬塞给数据库“热点数据”不一定只有并发高的系统才有。很多业务里反复读取的数据比如商品详情、配置项、首页聚合结果、热搜列表本身就存在频繁读、偶尔写的特性。频繁到什么程度呢可能只是几个用户一直刷新同一个页面或者后台运营改配置后所有接口都要重新拉一遍。用 Redis 做热点数据读取缓存命中率上升之后数据库压力会显著下降。这个判断标准就是“重复读的命中率”。如果一个接口每次拿到的数据都一样或者变化频率很低就可以考虑加缓存。排行榜也是一样。很多系统比如答题活动、运动排行、积分商城、社区热帖榜用户量可能只有几千但排序逻辑跑在数据库里并不舒服你既需要按分数倒序又要看前 100 名还要支持某个用户随时查询自己的排名。如果用 Redis 的有序集合 ZSET这个需求实现起来非常直接# 给用户 uid 增加分数 ZINCRBY rank:game:202501 100 user:10001 # 查看排名前十 ZREVRANGE rank:game:202501 0 9 WITHSCORES # 查看某个用户当前排名 ZREVRANK rank:game:202501 user:10001这种场景并不要求你有多少 QPS相反它更像是一种“数据结构带来的编程便利性”。如果选型时只看 Redis 是“高并发缓存”你就容易错过这个功能。2.5 消息队列与任务暂存临时存储等消费端去处理Redis 可以用 List、Stream 等数据结构实现简单的消息队列。这个能力更多被用在“异步处理”而不是“高并发削峰”上。比如用户上传文件后先生成一条任务记录丢到 Redis 的 List 里工作线程从另一端BRPOP取出任务处理。这种场景下Redis 承担的是“跨进程传递任务”的职责对于“没有高并发但需要异步”的小项目比引入 RabbitMQ、Kafka 要轻量得多。需要注意的点Redis 作为消息队列可靠性和消息回溯能力比专业的消息队列弱。如果任务处理失败你要自己负责重试和补偿。用 List 实现LPUSHBRPOP不具备消费组的概念多个消费者是竞争消费不是广播消费。Redis Stream 提供了消费者组、消息确认、未消费消息查看等能力比 List 更像一个正经消息队列但仍然没有 Kafka 那样完善的数据保留策略。因此我建议这样判断如果你的队列场景只是“后台任务延迟处理处理失败最多重试几次消息丢了影响也不大”那 Redis 足够用。如果你的业务要求消息必须可靠、顺序性强、支持消费组复杂路由那就要选专业消息队列。3. 不是所有项目都该上 Redis哪些情况下先缓缓前面讲了很多 Redis 能干的事但我也要说一句公道话Redis 不是银弹。有些项目确实不该急着引入引入之后反而增加运维成本、代码复杂度和出故障的概率。3.1 单机单体、无状态共享需求的小项目如果你的项目就是单个应用跑在一台电脑或者一台云服务器上用户量小、没有多实例共享 session 的需求、没有多个服务调用同一个数据的场景那直接用本地内存就行。Java 用 CaffeinePython 用内建的 lru_cacheGo 用 sync.Map效果也许没 Redis 那么强但对于几乎没有缓存压力的系统已经足够。这种项目强行引入 Redis 会带来这些额外成本需要单独部署和维护一个 Redis 进程。代码里多了缓存更新和过期策略。缓存与数据库的数据一致性靠人肉保证容易出脏数据。一旦 Redis 没启动应用启动或运行时会抛连接异常。3.2 数据一致性要求极高的金融核心链路Redis 本身是 AP 型系统主从模式下存在数据同步延迟如果主节点故障且没有开启持久化或持久化配置不当极端情况下可能丢数据。对于账务、支付清结算这类“一条记录都不能丢”的核心交易链路把关键状态直接放 Redis 并不合适。正确做法是把 Redis 放在“辅助”位置比如用 Redis 做前置风控、热点数据加速、限流等非核心判断最终资金和订单状态还是以数据库事务为准。这里不是抹黑 Redis它有 RDB 和 AOF 两种持久化机制理论上也能配置成相对安全的模式但“安全”和“高性能”本身就是有取舍的。真正要保数据的场景数据库仍然是你最依赖的事实来源。3.3 团队不具备 Redis 运维经验时如果团队中没人懂 Redis 的基础运维比如内存淘汰策略、持久化配置、主从同步、集群槽位、慢查询排查那建议先不要把它引入核心链路。因为 Redis 一旦用起来问题不只是“装个软件”还包括内存突然涨了怎么分析哪些 key 占空间最大。缓存雪崩、缓存击穿、缓存穿透怎么提前防护。大 key 删除导致 Redis 阻塞怎么避免。主从故障自动切换怎么确保数据基本不丢。很多初学者第一次用 Redis连CONFIG SET maxmemory和maxmemory-policy都没配结果内存被占满之后 key 一直被淘汰缓存形同虚设。这种事很常见不是 Redis 的问题是“用了但没用好”的问题。我的建议是引入 Redis 前先确认团队有能力处理“缓存穿透、缓存击穿、缓存雪崩、大 key、热 key、持久化、集群分片”至少这 7 类问题。能处理就可以放心引入不能处理先花时间补基础。4. 第一次使用 Redis先跑通的最小闭环如果你看了上面的分析确定项目里确实需要 Redis那么不要一上来就搞集群、哨兵、高级用法先把最小闭环跑通。最小闭环指的是能连上 Redis能写入数据能读出来能设置过期时间能处理连接异常。4.1 Windows 和 Linux 环境下的安装与启动我默认你用的是 Linux 服务器因为生产环境基本都以 Linux 为主。Windows 上的 Redis 更多用于本地开发学习比如用 Memurai 或 WSL 里的 Redis。LinuxCentOS / Ubuntu 等常见安装方式# Ubuntu / Debian sudo apt update sudo apt install redis-server # CentOS / RHEL sudo yum install redis安装完后启动服务并检查状态systemctl start redis systemctl enable redis redis-cli ping如果返回PONG说明 Redis 已经启动并响应命令。redis-cli ping是最简单的连通性测试比写代码测试还快。Windows 上学习的话官方从某个时间点开始没有提供原生 Windows 安装包。常见做法是启用 WSL然后安装 Linux 版 Redis或者使用第三方提供的 Windows 移植版。这里我建议直接用 WSL 或 Docker理由很简单原生 Windows 版 Redis 的版本不一定跟线上一致容易出现本地和线上行为不一致的问题。4.2 用 Docker 快速启动一个 Redis 实例如果你已经熟练使用 Docker会更快一些docker run -d --name redis-dev -p 6379:6379 redis:7 docker exec -it redis-dev redis-cli ping这里说明几点-p 6379:6379把容器里的 6379 映射到宿主机这样宿主机上其他程序也能访问。redis:7表示使用 Redis 7 镜像。具体版本号建议启动前查看一下当前镜像标签不要盲目假定。开发环境用这种简单方式没问题生产环境不要用裸容器直接跑要配置持久化、限制内存、做监控。4.3 第一次写入和读取用 redis-cli 验证基本操作redis-cli SET user:10001 zhangsan GET user:10001 EXPIRE user:10001 600 TTL user:10001在这个最小闭环里你只需要理解三件事SET和GET是最基本的 key-value 读写。EXPIRE设置过期时间TTL查看剩余存活时间。Redis 的过期特性是很多业务逻辑的基础。key 的命名要有业务前缀比如user:10001里的user:。不要用无意义的名字否则后面排查问题会非常痛苦。第一次用 Redis 的常见错误是不设置过期时间又没有淘汰策略导致 Redis 内存持续增长最后 OOM 或触发淘汰。学习阶段宁可显式给临时测试 key 都加上过期时间也不要让脏 key 一直堆积。5. 从单机到生产化你需要关注的配置和参数跑通最小闭环之后要开始考虑生产化。不要等线上出问题了再补配置可以先从下面这些参数入手。5.1 内存上限与淘汰策略Redis 无限占用内存是灾难。首先要设置上限CONFIG SET maxmemory 1gb CONFIG SET maxmemory-policy allkeys-lrumaxmemory表示 Redis 可以使用的最大内存超过这个值后会根据maxmemory-policy进行淘汰。常见策略策略作用noeviction不淘汰内存满了之后写操作直接报错allkeys-lru对所有 key 按 LRU 近似算法淘汰volatile-lru仅对设置过过期时间的 key 按 LRU 淘汰allkeys-random随机淘汰任意 keyvolatile-ttl淘汰即将过期的 key如果你主要把 Redis 当缓存用allkeys-lru或者allkeys-lfu用的比较多。如果你在 Redis 里存了不希望能被淘汰的业务数据比如分布式锁、限流计数、临时状态那就要设置noeviction并监控内存或者在 key 设计上把这类数据单独隔离。一个常见误操作是“不设置 maxmemory”然后 Redis 把机器内存吃满操作系统开始 swapRedis 测出来响应速度突然变得极慢。这种情况不是你写的代码有问题而是资源管理没做。5.2 持久化RDB 和 AOF 怎么选Redis 是内存数据库如果不做持久化重启之后内存数据全丢。根据业务要求选择RDB快照把某个时间点的全部数据写进磁盘文件紧凑恢复快但两次快照之间的数据可能丢失。AOF追加日志记录每次写命令数据更安全但文件体积更大恢复速度更慢。混合持久化Redis 4.0 之后支持 RDB AOF 混合使用兼顾灵活性和数据安全。如果只是做缓存很多团队甚至不开启持久化因为缓存数据丢了之后可以重新从数据库加载。但如果你用 Redis 存了分布式锁、限流计数、消息队列任务持久化策略就要谨慎。尤其是一旦 Redis 重启导致锁信息丢失可能造成锁失效进而引发并发安全问题。5.3 慢查询日志与监控Redis 本身有慢查询日志功能通过配置slowlog-log-slower-than指定一个耗时阈值单位微秒例如CONFIG SET slowlog-log-slower-than 10000 CONFIG SET slowlog-max-len 128然后查看慢查询SLOWLOG GET生产实践中很多看似“Redis 卡死”的问题其实是出现了大 key、热 key、复杂 Lua 脚本、阻塞命令比如KEYS *等。慢查询日志能帮你快速定位是哪个命令在执行时拖慢了整体性能。注意KEYS *在数据量大的生产环境尽量不要用它会扫描所有 key造成 Redis 阻塞。需要按规则匹配 key 时可以使用SCAN命令或者根据业务维护一个 key 索引集合。5.4 主从复制和哨兵的取舍单机 Redis 有单点风险。最简单的容错方式是部署主从复制主节点负责写从节点负责读或作为备份。哨兵Sentinel可以监控主节点状态在主节点挂掉时自动把从节点提升为新的主节点。选型建议只有 1 个实例数据丢了能接受直接用单节点。需要读写分离或基本高可用可以用主从 哨兵。数据量极大、单机内存放不下或者写并发极高才需要认真考虑 Redis Cluster。不要一开始就上 Cluster。Cluster 的槽位规划、多 key 操作限制、客户端协议变化、重定向处理都容易劝退新手。先保证“正常运行、能备份、能恢复”比什么都重要。6. 高频业务场景从代码样例看 Redis 的实际用法这一章给出 4 个常见业务场景的最小代码片段方便你理解“为什么这么设计”以及“落地时改哪里”。6.1 缓存用户信息伪代码如下import redis import json r redis.Redis(host127.0.0.1, port6379, db0) def get_user_info(user_id): cache_key fuser:info:{user_id} data r.get(cache_key) if data: return json.loads(data) user query_mysql(fSELECT * FROM user WHERE id {user_id}) if user: # 通常设置 5-10 分钟的过期时间就可以 r.setex(cache_key, 300, json.dumps(user)) return user这段代码里最核心的是setex把 key、过期时间、value 一起设置比分开调SET和EXPIRE更安全。这里你还要提前考虑几个问题如果数据库里根本没有这个用户缓存里也不会有那每次都穿透到数据库。解决方案可以是缓存空值也可以使用布隆过滤器。如果用户信息变了怎么办可以在更新数据库时同步删除 Redis key或使用带版本号的 key。如果缓存过期瞬间有大量请求同时访问可能出现缓存击穿。可以在查询缓存没命中时加一个临时互斥锁来控制回源。6.2 分布式锁import redis import uuid r redis.Redis(host127.0.0.1, port6379, db0) def acquire_lock(lock_key, expire_time10): request_id str(uuid.uuid4()) # SET NX EX只有 key 不存在时才设置成功 ok r.set(lock_key, request_id, nxTrue, exexpire_time) if ok: return request_id return None def release_lock(lock_key, request_id): # 用 Lua 脚本确保比较和删除是原子操作 script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end return r.eval(script, 1, lock_key, request_id)这段代码的重点不是“把锁加上”而是“释放锁时必须确认 value 是不是自己的”。如果你没有这个判断只凭DELETE lock_key就结束等于把一个带隐患的锁放到了生产环境。等出现事故的时候排查起来特别被动。6.3 接口限流import time import redis r redis.Redis(host127.0.0.1, port6379, db0) def is_allowed(user_id, limit5, window60): key frate:user:{user_id}:{int(time.time()) // window} count r.incr(key) if count 1: r.expire(key, window 1) return count limit这个实现是固定窗口限流。time.time() // window把当前时间按窗口大小切分同一窗口内 key 相同每次请求incr加一。第二行的if count 1给 key 设置过期时间避免窗口结束后 key 残留。这个方案的问题是在窗口切换的瞬间可能产生两倍请求量的突发。如果业务上能接受这个实现就够用如果不能接受再考虑滑动窗口或令牌桶。6.4 消息队列用 List 实现简单任务队列import redis import time r redis.Redis(host127.0.0.1, port6379, db0) # 生产者 r.lpush(task:email, json.dumps({user: aexample.com, message: hello})) # 消费者 while True: task_data r.brpop(task:email, timeout5) if task_data: task json.loads(task_data[1]) send_email(task[user], task[message]) else: time.sleep(1)消费者用brpop阻塞读取可以避免空轮询带来的 CPU 浪费。问题在于这个队列没有可靠消费机制如果消费者把任务取走之后发送邮件失败任务就丢了。因此任务要配置重试机制或者把任务状态写进数据库再异步处理。7. 实战中的坑和排查思路最后这个章节我按自己踩过的坑来写。有些问题一眼看不出跟 Redis 有关但排查到最后往往都是这些原因。7.1 报错Could not connect to Redis或Connection refused优先排查顺序Redis 进程是否在运行ps -ef | grep redis。端口是否监听netstat -tunlp | grep 6379。配置文件中bind是否允许你的应用 IP 连接。安全组、防火墙、云服务商白名单是否放行 6379 或自定义端口。应用配置里使用的密码与 Redis 实际requirepass是否一致。自己本机测试用redis-cli -h 你的IP -p 6379 -a 你的密码直接连一下看能否连通。我用一句话总结很多 Redis 连接问题不是 Redis 挂了而是网络、bind、密码和防火墙问题。先把它当网络问题排查比一上来就重装 Redis 高效。7.2 缓存和数据库数据不一致数据不一致的根源往往不是 Redis而是更新顺序混乱。比如数据库更新成功Redis 删除失败或者先删 Redis数据库更新失败导致旧缓存被重建。这里给一个相对稳妥的策略读操作先查 Redis未命中则查数据库再回写 Redis。写操作先更新数据库再删除缓存。对强一致要求不高的场景可以接受短时间不一致但尽量在更新后异步删除或更新缓存。如果想更稳可以加“延迟双删”更新数据库后先删缓存间隔几百毫秒再删一次防止并发读线程把旧数据重新写回缓存。不要太迷信“Redis 和数据库强一致”这种说法。真有严格强一致需求应该考虑从架构上避免缓存而不是让 Redis 去背锅。7.3 大 key、热 key大 key 指的是单个 key 存储的 value 过大比如一个 List 里有几百万条记录或者一个 Hash 里字段特别多。大 key 的问题在于执行删除或读取时可能导致 Redis 阻塞主从同步时对大 key 做快照也会拖慢性能。排查方式可以用redis-cli --bigkeys扫描或者自己写脚本按STRLEN、HLEN、LLEN等命令分析。发现大 key 之后可以做拆分例如把一个大 List 拆成多个带编号的小 List或者改用多个 key 分散存储。热 key 指的是某个 key 被高频访问比如用户量多时某个“全局配置”被所有请求读取又比如抢购时某个“秒杀商品库存”被反复请求。热 key 会导致单个 Redis 节点 CPU 或网络打满。缓解方式通常有本地缓存 Redis 二级缓存把部分流量挡在应用内。把热 key 复制成多份比如hotkey:1、hotkey:2写入时同步写多份读取时随机取一份。这个方式要谨慎处理“一致性”。7.4 缓存穿透、缓存击穿、缓存雪崩这三个概念在 Redis 面试题里很常见但在真实项目里也经常碰到只是形态不一样缓存穿透查询一个必然不存在的 key比如“负数的订单 ID”所有请求都直接打到数据库。解决方案缓存空值、布隆过滤器。缓存击穿某个 key 过期瞬间大量请求同时打到数据库。解决方案互斥锁、逻辑过期。缓存雪崩大量 key 同时过期导致数据库瞬间压力上升。解决方案过期时间加随机偏移量比如30 分钟 rand(0, 5分钟)。这三类问题本质上是“缓存没有命中时如何保护后端”。学习阶段用“小流量压测”来模拟并行请求同一个不存在的 key最容易看到效果。7.5 Redis 日志和客户端连接数异常遇到 Redis 突然停止响应除了慢查询还要看客户端连接数。默认 Redis 最多接受 10000 个连接如果应用侧连接池没设置好或者出现连接泄漏就可能把连接数打满。排查方法redis-cli INFO clients看connected_clients是否接近maxclients。如果连接数异常高优先检查应用里的连接池配置和代码是否在每个请求里都创建了新连接。这是非常经典的问题不是你连接 Redis 的姿势不够高级而是资源释放思路不对。8. 项目选型决策建议一个小型检查清单要不要引入 Redis我建议用下面这个清单做快速判断而不是只凭“并发高不高”来决定。8.1 满足任何一个Redis 就值得考虑系统是多实例部署存在登录状态、配置、权限等需要跨实例共享的数据。存在热点数据重复读远大于写数据库压力明显。需要分布式环境下做互斥操作比如定时任务防重、接口幂等、资源抢占。需要限流或防刷且希望限制对多实例全局生效。有排行榜、计数器、附近的人、布隆过滤器等特殊数据结构需求。需要一个轻量可靠的任务队列或延迟队列暂时不想引入专业 MQ。8.2 满足大多数下面条件也许可以先不引入 Redis单实例单体部署几乎没有扩展计划。没有特殊数据结构需求普通数据库查询加本地缓存就够。没有跨进程共享状态的需求session 可以存在应用本地。团队没有运维 Redis 的经验也没有监控手段。数据一致性要求极高且不允许异步缓存方案。8.3 如果确定要引入落地顺序建议先装单机 Redis跑通最小读写闭环。配置密码、限制内存、开启持久化。根据业务选择第一个场景比如 session 共享或接口限流。在测试环境压一下缓存命中和异常场景比如 Redis 挂掉后系统是否能优雅降级。再加监控看连接数、内存、慢查询、命中率。考虑主从或哨兵。9. 回到开头那个标题“认为系统没有高并发就不用 Redis 的都是初学者” 这句话的真正价值是逼你从“Redis 是缓存”这个框架里跳出来真正去理解分布式系统里“跨进程共享状态”这件事。很多系统不是被高并发打死的而是被“状态不同步”“重复提交”“限流失效”“数据库被不算高的请求压力拖垮”这些问题折磨死的。这些问题的共性就是你没有一种跨进程、低延迟、支持复杂数据结构的共享存储来支撑业务逻辑。Redis 恰好是最常用、最容易上手的那个选择。当然我也反对任何项目都强行用 Redis。正确姿势应该是先确认自己是否真的有跨实例共享状态、热点数据、分布式协作、异步任务暂存等需求再评估团队是否有能力维护好 Redis最后才是考虑高并发优化。如果你的系统目前是小项目、单机单体那你确实可以暂时不用 Redis。但要记住不用 Redis 不是因为你永远不需要而是因为现阶段收益不明显。等系统开始多实例部署、业务开始出现并发协作问题的时候Redis 大概率就是你落地的第一步。个人建议是学习阶段多花半天时间把 Redis 的基本安装、命令、过期策略、持久化配置和连接池用熟练。真正用上的时候你会感谢当初没有只在面试题里背过 Redis。
返回列表