做大数据架构这几年,我被问得最多的一个问题就是:HDFS、HBase、ClickHouse 都能存数据,为什么还要专门引一套 Redis?每次我都要花不少时间解释——在大数据链路里,缓存工具不是“数据库的备胎”,而是真正决定接口响应和任务稳定性的关键一环。今天就从我实际接触过的离线数仓、实时计算和数据服务化项目出发,把 Redis 和 Memcached、Caffeine、Ehcache、Hazelcast、Ignite、Tair、Aerospike 这些常被放在一起比较的缓存工具,放到大数据场景里逐项拆解,讲讲各自的能力边界和选型逻辑。
这篇文章不是教科书式的功能列表,而是一份实战侧的对比笔记。适合正在搭数据平台、做实时接口服务、或者被面试官问到“为什么用 Redis 不用 XXX”的同学参考。
1. 大数据缓存之问:为什么数据都有了,还要再快一层
1.1 大数据链路里缓存的三个使命
很多人理解缓存,第一反应是“让查询变快”。这个理解没错,但在大数据场景里不够完整。数据链路上游是 Hadoop、Spark、Flink,下游是报表、大屏、推荐接口、运营后台。上游计算结果是批量的、分钟级甚至小时级产出的,下游消费方却期望毫秒级拿到数据。这中间的落差,靠硬调存储性能很难解决,必须在中间加一层“记忆体”。
我习惯把大数据场景下缓存的使命分成三类,这样遇到选型问题就不容易跑偏。
第一类是热点加速。比如实时大屏每秒刷新,每次刷新都去查 ClickHouse 的聚合表,QPS 一高,ClickHouse 的查询队列就开始堆积,接口超时是家常便饭。但如果把最近 5 分钟的聚合结果丢进 Redis,大屏直接读缓存,ClickHouse 的负载能降一个数量级。
第二类是结果复用。数仓里一张指标宽表可能同时被七八个服务消费,每个服务都自己跑一遍 Hive SQL 或者查一次 HBase,资源浪费极严重。把计算结果写一份到缓存,所有服务共享同一份数据,既省计算资源,又能保证各端看到的口径一致。
第三类是状态协调。大数据任务天然是多节点并行,任务之间经常需要“谁先跑完谁拿锁”“这批数据里哪些 ID 已经处理过”这类协调动作。Redis 的分布式锁、Set 去重、进度标记,在这一层几乎是标配。
1.2 缓存失效的代价:从命中率到雪崩
我见过不少团队,上线缓存时只关注命中率,命中率一低就加 TTL,TTL 一加长数据又容易过期不新鲜。这种调优思路一开始就偏了。大数据场景里缓存失效真正可怕的不是命中率下降,而是“整片失效”。
原因很典型:很多批量任务在每天同一时刻跑完,往缓存里写数据时习惯性把 TTL 设成一样的值。结果就是所有 key 在同一分钟集中过期,缓存被打穿,所有请求同时回源打向下游数据库,数据库连接池瞬间被拉满,然后缓存被重建,又因为流量太大重建失败,形成恶性循环。
我在一个项目里就踩过这个坑。定时任务每天凌晨 2 点写一批缓存,TTL 统一设 2 小时,凌晨 4 点到 6 点之间缓存几乎全部过期,而那时候正好是运营后台的访问高峰。后来改成 TTL 加随机偏移,比如 7200 秒基础值加 0 到 900 秒随机数,让过期时间分散开,情况立刻缓解。
| 失效原因 | 典型影响 | 规避手段 |
|---|---|---|
| 批量写入 TTL 相同 | 缓存集中过期,回源流量打爆数据库 | TTL 增加随机偏移,错峰过期 |
| 热点 key 过期瞬间 | 单 key 大量请求同时回源 | 热点 key 不设过期,靠主动更新 |
| 缓存穿透 | 查询不存在的数据打到数据库 | 布隆过滤器或空值缓存 |
大数据场景里,缓存失效不是“等它慢慢恢复”就行的事。一次雪崩可能把上游任务也拖死,恢复时间以小时计。所以做缓存方案之前,先想清楚三个问题:数据能不能丢?丢了影响多大?回源能不能扛住?这三个问题没想明白,后面所有优化都是空中楼阁。
2. Redis 的看家本领:从数据类型到原子操作,它不只是快
2.1 五种基础类型在大数据链路里的典型用法
Redis 常被拿来和其他缓存工具比“谁更快”,但真正让它在数据领域站稳脚跟的,其实是数据结构。同样是缓存,Memcached 只能存字符串,而 Redis 的 String、Hash、List、Set、ZSet 五种基础类型,几乎覆盖了数据链路里所有常见的临时数据形态。
拿我做过的一个网约车项目举例。司机维表存在 MySQL 里,每次接口查询都要关联司机信息,如果每次都回 MySQL 查,数据库压力很大。我把维表按城市维度灌进 Redis Hash,key 是“city:driver:10001”,field 是司机 ID,value 是司机信息的 JSON,一次 HGETALL 就能拿到整个城市的司机集合,接口延迟从 200 毫秒降到 2 毫秒。
Hash 之外,另外几种类型也是高频工具。
String 用于计数器,比如实时计算里统计某订单的累计金额,INCRBY 一把梭。List 在轻量场景里可以做消息队列,LPUSH 生产、BRPOP 消费,虽然比不上 Kafka 可靠,但配合短任务列表绰绰有余。Set 用来做去重集合,比如“这批数据里哪些用户已经领过优惠券”,SADD 之后 SCARD 看数量,SISMEMBER 判断是否已存在,跨任务共享同一个集合非常方便。ZSet 做排行榜和 TopN 是杀手级用法,一个 ZADD 下去,按 score 排序的需求立刻满足,ZREVRANGE 直接取前 100,不用在应用层排序。
在大数据任务里,这些数据结构的价值在于“服务端计算”。数据不用拉回客户端,Redis 内部就把交集、并集、排名算好了。这对网络带宽的节省非常可观。
2.2 容易被忽略的高级结构:Bitmap、HyperLogLog、Stream
基础类型之外,Redis 还有三个高级结构,在大数据场景里经常比基础类型更出彩,但很多同学没用到。
第一个是 Bitmap。做日活统计时,一个用户占 1 bit,一亿用户也就 12MB 左右内存。每天一个 key,用户 ID 作为 offset 置 1,BITCOUNT 直接给出当日活跃数。更难得的是可以做跨天位的“与”“或”运算,比如要算“昨天和今天都活跃的用户是多少”,一个 BITOP AND 就出来了,不用去数仓做 join。这个操作放在 Hive 里跑,规模一大就是几百亿行扫描,而 Bitmap 在 Redis 里是纯内存位运算,毫秒级返回。
第二个是 HyperLogLog。如果你只需要知道“大概有多少”,不需要精确值,它极其好用。比如实时大屏的 UV 展示,误差 0.81%,但一个 HyperLogLog key 最多占 12KB 内存。我见过有人硬用 Set 存几百万用户 ID 来做 DAU,几个亿的数据量把 Redis 内存顶爆,换成 HyperLogLog 之后内存占用从 GB 级降到 KB 级,查询还是毫秒级。要注意的是 PFCOUNT 是近似值,不能用于对账,只能用于看板展示。
第三个是 Stream。Redis 5.0 引入的 Stream 数据结构,支持消费组、消息回溯、Pending 列表,在很多轻量级大数据组件之间可以充当缓冲层。比如 Flink 任务产出的实时告警事件,不想再引入一套 Kafka,直接写到 Redis Stream,告警服务用消费组读取,比 List 模拟的队列可靠得多。
2.3 单线程模型、多线程 IO 与持久化取舍
Redis 为什么快,这个问题面试里几乎必问。核心就三点:纯内存操作、单线程避免锁竞争、I/O 多路复用。单线程带来的副作用也很直接——一个慢命令会阻塞后面所有命令。网上一搜“Redis 慢查询”能出来一堆案例,大部分是有人在生产环境执行了 KEYS,或者对一个超大集合执行了 SMEMBERS,结果整个 Redis 卡了几秒。
需要注意,Redis 6.x 之后引入了多线程 I/O,但只是把网络读写分散到多个线程,核心命令执行依然是单线程。所以瓶颈在网络时多线程有帮助,瓶颈在慢命令时换了也白换。
持久化方面,RDB 是定期快照,恢复快但可能丢数据;AOF 是追加日志,可以做到每秒刷盘甚至每次写都刷盘,但文件大、恢复慢。大数据场景我的实践建议是:如果 Redis 只做缓存,关掉持久化最省事;如果 Redis 里放着不能丢的分布式锁状态或任务进度,开 AOF 且 everysec 刷盘足够;如果数据真的重要到一分一秒都不能丢,别依赖 Redis 的持久化,数据应该直接写真正的存储系统。Redis 持久化是兜底,不是主存储方案。
3. Redis 与 Memcached 的分水岭:在大数据侧谁更容易踩坑
Memcached 是 Redis 出现之前最流行的分布式缓存,今天依然有不少老系统在用。两者对比,网上能查到一堆表格,但那些表格大多停留在“Redis 支持丰富数据结构,Memcached 只支持 KV”这个表面。在大数据场景里,这个差异会带来非常具体的工程问题。
3.1 数据结构与原子操作:get/set 之外的能力差距
Memcached 的模型非常简单:key-value,value 是字符串,API 主要是 get、set、add、delete、incr/decr。在大数据侧,这个简单模型很快就碰到天花板。
举个例子。数据分析师要圈选“过去 7 天活跃且消费过的人群”,这个集合可能有几百万个用户 ID。如果放在 Redis 里,用 Set 存每天的活跃用户,然后 SINTERSTORE 取 7 天交集,结果直接在服务端算完返回。如果用 Memcached,只能把每一天的用户 ID 列表全部拉回应用服务器,在内存里做交集,几百万字符串的传输量会瞬间吃掉网络带宽。
再比如分布式锁。Redis 可以用 SET key value NX EX 一条命令完成加锁,配合 Lua 脚本保证判断和删除的原子性。Memcached 虽然有 add 命令可以做“只有 key 不存在时才写入”的原子操作,但后续怎么判断持有者、怎么续期、怎么安全释放,协议层面几乎没有帮忙。
我还有一次血的教训。项目的缓存层用 Memcached 存了一个 JSON 字符串的配置,两个服务同时读改写同一份配置,因为没有 CAS 或者 Lua 这种原子操作,最终一个服务覆盖了另一个服务的修改,配置丢失。这类问题在 Redis 里可以用 WATCH 配合 MULTI,或者干脆用 Lua 脚本在服务端执行,Memcached 则只能靠客户端加锁,复杂度高不少。
3.2 内存管理与淘汰策略的差异
Memcached 的内存管理走 slab 分配机制,内存按固定大小分成一系列 slab class,每个 class 里存放相近大小的对象。它的好处是内存碎片少、分配效率高,坏处是如果 value 大小分布不均匀,会出现内存浪费。比如一个 slab class 的 chunk 是 512KB,但实际存的是 400KB 的 value,剩下的空间就浪费了。
Redis 没有 slab 这种预分配机制,但它的过期删除是惰性删除加定期删除的组合,淘汰策略支持 LRU、LFU 以及 noeviction。在大数据场景里,我更关注的是淘汰策略的选择:如果 Redis 被当作用户维度缓存的临时结果,用 allkeys-lru 比较合适;如果是计数器或者分布式锁,一旦被淘汰就会出问题,建议用 noeviction + 明确的过期时间,宁可报错也不要默默丢数据。
内存对比上,Memcached 的 value 上限是 1MB,Redis 的 value 理论上可以到 512MB。大数据场景里经常有超过 1MB 的缓存对象,比如一次要返回几万行的维表结果,Memcached 直接写不进去。这也是选型时容易被忽略的点。
3.3 什么场景依然值得用 Memcached
说了这么多 Redis 的优势,Memcached 也不是一无是处。它的多线程模型让它在高并发纯 KV 场景下扩展性很强,CPU 多核利用比单线程 Redis 更有优势。如果业务形态极其简单——就是存一个几 KB 的 JSON 或者 HTML 片段,读多写少,TTL 短,那么 Memcached 更轻、更稳、更好维护。
我现在的选型标准很简单:缓存对象是否需要数据结构操作?是,选 Redis;否,但 value 很小且 QPS 要求极高,Memcached 可以纳入考察。大数据项目里确实遇到过这类场景:广告点击日志的透传缓存,key 是请求 ID,value 是一小段原始日志,两小时过期,QPS 峰值几十万。这种场景 Memcached 完全能胜任,而且比 Redis 省内存。
但如果你在做的是数据服务化、实时大屏、分布式任务协调这类偏复杂链路,Memcached 的能力缺口会随着业务发展越来越大。我的经验是:新项目不要选 Memcached,老项目如果没有数据结构需求,继续用着也没问题,不必为了换而换。
4. 本地缓存双雄:Caffeine 与 Ehcache 在大数据链路中的位置
说到缓存,很多人只想到 Redis,忽略了“本地缓存”这一层。大数据服务端经常是 JVM 应用,进程内缓存用得非常多。Caffeine 和 Ehcache 是 Java 生态里两个绕不开的身影,它们的定位和 Redis 完全不同,但又互补。
4.1 本地缓存与分布式缓存的边界
本地缓存活在应用进程里,Redis 是独立的分布式缓存服务。这个区别直接决定了一致性模型。
本地缓存的查询路径最短——内存里直接拿,没有网络开销,所以它一定是所有缓存里最快的。但它有天然缺陷:进程重启数据就没了,多实例部署时各节点数据不一致,更新某个 key 时无法通知其他节点。Redis 正好相反,一次写入全局可见,进程重启缓存还在(只要配置了持久化),缺点是每次读写都有一次网络 RTT。
在大数据链路里,这两类缓存的分工很清晰。Redis 管跨节点共享的数据,比如实时榜单、全局配置、分布式锁;本地缓存管单个实例内部的复用,比如维表查询结果、服务间调用的响应体、反复使用的配置对象。
4.2 Caffeine 与 Ehcache 的选型要点
Caffeine 是 Java 界目前公认最强的本地缓存库,基于 Window TinyLFU 算法,命中率高,API 现代,Spring Boot 里默认的本地缓存就是 Caffeine。它适合高并发、低延迟的接口层,热点数据几百毫秒内重复读取的情况,命中率能到 99% 以上。
Ehcache 则是老牌选手,功能更重。它支持堆外内存、磁盘存储、JTA 事务、分布式集群同步,缓存的持久化和恢复机制比较完整。代价是 API 风格偏老,性能上限比 Caffeine 低。我一般只在需要“本地缓存还要能落盘、重启不丢”的场景里才考虑它,普通接口服务用 Caffeine 就好。
两者的典型结合方式是二级缓存:Caffeine 做 L1 一级缓存,Redis 做 L2 二级缓存。读路径是 L1 未命中读 L2,L2 未命中回源数据库,再层层写回;写路径是更新数据库后先删 Redis,再在本地缓存里也做失效。这个模式能大幅降低 Redis 的读压力,但要注意一致性,我通常会给数据加版本号,本地缓存里带上版本,后台更新时通过版本号主动失效。
4.3 大数据算子内缓存和 Redis 的配合模式
很多人写 Spark 或 Flink 任务时,直接在 map 或 flatMap 里逐条查 Redis,遇到几个亿条数据就悲剧了。每条数据一次网络 RTT,哪怕 Redis 再快,几十万条并发的网络回环延迟也能把任务拖死。
正确做法是在算子内部用 Caffeine 做批内缓存。比如数据流中的每个用户 ID 都要关联城市维表,一百个字段里可能只有五六个城市在变,完全可以在 Executor 内部先查一次 Redis,把城市维表加载到本地缓存,后续数据直接读本地,Redis 查询次数能降 90% 以上。
这个模式有两个坑需要注意。一是内存,Executor 本地缓存如果无限膨胀,会挤占 Spark 的执行内存,必须设置 maximumSize 和 expireAfterWrite。二是数据新鲜度,本地缓存的 TTL 不能设太长,否则上游维表更新了,下游任务还在用旧数据,建议 TTL 设在 5 到 10 分钟,配合 Redis 主动版本失效机制。
5. 分布式内存网格:Hazelcast 与 Apache Ignite 是缓存还是计算引擎
Hazelcast 和 Apache Ignite 经常被归入“分布式缓存工具”的类别,和 Redis 放在一起对比。但从大数据工程的角度看,我更愿意把它们称作“内存数据网格”。这个定位差异,会直接影响你对它们的期望值。
5.1 数据网格和缓存的本质区别
Redis 的核心能力是数据结构操作,它的计算是在数据很小、逻辑很简单的场景下做的,比如集合运算、Lua 脚本。Hazelcast 和 Ignite 走的是另一条路:数据分布到集群各节点后,计算可以在数据所在的节点本地执行,再把结果汇总,也就是“移动计算而非移动数据”。
Ignite 支持标准 SQL、分布式 Join、分布式聚合,还能把一部分计算下推到数据侧。这意味着如果你有一份很大的数据在内存网格里,可以直接写一条 SQL 做 count、group by、join,获取聚合结果,不用像 Redis 那样把几千万条 key 全量拉回客户端再自己算。
Hazelcast 的 EntryProcessor 机制类似,可以在缓存条目所在的节点上执行一段 Java 代码做更新。比如某个字段需要累加值,Redis 会建议你用 INCR;Hazelcast 则允许你传一个函数进去,在数据节点上执行并返回结果,灵活性高很多,但也意味着你需要写业务逻辑部署到缓存节点上。
5.2 什么时候选择数据网格而不是 Redis
我总结过选型判断标准。如果只是“热点结果缓存”,比如把聚合结果存储起来按 key 查询,Redis 足够;如果你希望在缓存层直接做实时查询,比如“查询最近一小时所有订单中金额大于 1000 的订单,按城市分组统计”,并且数据量上千万,Ignite 这类工具会让 Redis 舒服得多。
Ignite 还有一个和 Redis 差异极大的能力:原生持久化。它可以把数据从内存落到磁盘,支持 SQL 实现“缓存即存储”,而 Redis 持久化更像备份机制,不能支撑复杂查询。在需要“快速写入+实时查询+可靠存储”三合一的场景里,Ignite 是强有力的竞争者,Redis 则需要配一个真的数据库才能达到同等效果。
但这不代表 Ignite 是随便就能上手的。我见过一个团队把 Ignite 用成了“二号 HBase”,最后集群内存水位一直下不来,分区再平衡常常阻塞,运维同学苦不堪言。数据网格的复杂度、集群拓扑、内存管理、故障转移都比 Redis 重得多。如果团队只有两三个人管系统,建议先用 Redis,真扛不住时再考虑上数据网格,不要一上来就为了“将来可能用到 SQL”而自找麻烦。
5.3 Redis Cluster 与数据网格的高可用对比
部署形态上,Redis Cluster 是分片 + 主从,16384 个 slot 按节点分配,扩容时要做 slot 迁移,故障转移靠主从自动晋升。整体机制成熟,但槽位迁移期间会有性能抖动,需要提前测。
Ignite 和 Hazelcast 的分区模型更“自然”,数据分片自动平衡,节点加入离开后会自动重新分布副本,不需要管理员手动指定槽位。它们的客户端可以感知拓扑变化,某个节点挂掉后请求自动转向副本节点。从高可用能力看,数据网格比 Redis Cluster 更灵活,但代价是网络开销更大,节点间通信也更多,集群规模一大,管理复杂度上升明显。
在超大规模场景下,Redis 的优势反而突出:协议简单、运维习惯成熟、监控指标丰富,数据网格的各种能力是要用复杂运维来换的。所以我的建议依然是:先用 Redis,让系统先跑起来,等真的遇到计算下推、SQL 需求时再评估是不是该换。
6. 大规模场景下的补位者:Tair、Aerospike 与 Redis 生态
6.1 Redis Cluster 撑不住时的几种方向
Redis 虽好,但也会碰到两个硬性问题。一是内存有限,数据量过了几百 GB 后,纯内存方案的成本陡增;二是单线程模型,极端高并发下 CPU 会成为瓶颈。Redis Cluster 能解决一部分扩展问题,但内存成本没办法靠集群本身解决。
这时候常见方向有几种。一是用 Tair 这类兼容 Redis 协议的分布式系统,它在阿里内部经历过大规模生产检验,支持持久化和多副本,客户端生态可以无缝迁移。二是用 Aerospike 这类内置 SSD 持久化方案的高并发 KV 存储,延迟仍然很低,但单机容量可以做到 TB 级,成本比纯内存低很多。三是用 Pika 这种兼容 Redis 协议的磁盘存储,把冷数据换到 SSD。还有一些方案是干脆把数据放回 HBase 或 ClickHouse,让 Redis 只承载热点,这其实是最朴素也最稳妥的做法。
我在一个广告推荐项目里见过内存预算卡死的案例。Redis 集群存了几十亿条用户特征 KV,双副本后单机内存开销巨大,每加一台机器都像在烧钱。后来把数据切到 Aerospike,内存集群缩到几台专门放热数据,整体成本降了一个量级,接口延迟还更稳了。
6.2 Aerospike 在高并发 KV 场景的特别之处
Aerospike 是广告、推荐系统里的常客,它的核心优势不是单机 QPS 比 Redis 高多少,而是“内存 + SSD 混合持久化”的架构。对大多数 KV 而言,热点数据在内存里,冷数据自动落到 SSD,但读取延迟依然只有 1 毫秒上下,不会像 MySQL 那样把磁盘当主力存储。
它的数据分片机制也比 Redis Cluster 更自动,集群节点变化后会自动做数据重新分布。比较有意思的是,Aerospike 还支持在服务端做简单的表达式过滤和聚合,虽然不是 SQL,但在画像筛选场景里能省不少网络传输。
缺点也很明显。一是生态远不如 Redis,客户端语言的成熟度、工具链、文档都差一个身位;二是它把数据当成存储管理,运维口径和缓存差别很大,不能像 Redis 那样随手 FLUSHALL;三是一些高级操作如 Lua 脚本、复杂数据结构完全没有。所以 Aerospike 适合的是“KV 量大、要持久化、QPS 极高”的场景,而不是普通缓存的替代品。
6.3 Tair 与 Redis 生态的兼容与增强
Tair 是阿里巴巴开源的分布式 KV 系统,重点在于生态兼容。很多组件直接兼容 Redis 协议,业务把连接地址一换就能从 Redis 迁移过去。它比较好的点是支持强一致的多副本同步、主备切换、持久化,解决了 Redis 主从异步复制可能丢数据的问题。
对于已经深度使用 Redis 的团队,如果遇到“Redis 主从切换丢了分布式锁导致任务重复执行”这类问题,又不想把系统改成另一套存储,走 Tair 兼容版是比较平滑的路。但要提醒的是,Tair 的版本、部署形态、变体很多,有的模块开源,有的能力依赖云厂商内部版本,选型前要看清楚到底是自建开源版还是云服务版,两者功能差异不小。
工具生态在落地时的作用经常被低估。Redis 有雷打不动的 redis-cli,有 RedisInsight、Another Redis Desktop Manager 这类可视化工具,很多运维脚本、监控面板、SDK 都是现成的,团队的迁移成本和学习成本都很低。这一整套生态,是 Redis 面对 Tair、Aerospike 们时最值得重视的隐性资产。
7. 实战选型与治理:从集群部署到缓存治理的经验记录
7.1 集群部署:主从、哨兵、Cluster 怎么选
实战中部署形态没有标准答案,我一般按三个档次做决策。
缓存不重要且数据量小:单机 Redis 够用,顶多配一个从节点做备份。高可用要求上来后,用主从加哨兵。哨兵负责自动故障转移,应用层通过哨兵拿主节点地址,挂掉时哨兵会推举新的主节点,切换期间可能有秒级不可用,但大多数大数据服务可以容忍。数据量大、QPS 高的时候,直接用 Redis Cluster,让数据分片到多节点,官方方案虽然没有哨兵那套独立监控,但故障转移和分片是内置的。
部署环境如果是 Docker,我推荐用 docker-compose 快速搭一套实验环境。以下是我常用的一键主从示例:
version: '3.8' services: redis-master: image: redis:7.2 container_name: redis-master command: ["redis-server", "--appendonly", "yes"] ports: - "6379:6379" volumes: - ./master-data:/data redis-replica: image: redis:7.2 container_name: redis-replica command: ["redis-server", "--slaveof", "redis-master", "6379", "--appendonly", "yes"] depends_on: - redis-master ports: - "6380:6379" volumes: - ./replica-data:/datadns 解析用 docker compose 内置网络,replica 会自动从 master 同步数据。需要提醒的是,这只是实验环境,生产环境不要这样裸跑,至少把密码、持久化策略、内存限制补上。
7.2 缓存治理:Key 设计、过期策略、Big Key 与热点 Key
Key 设计是第一道治理关卡。我见过最乱的 Redis 是那种一个业务一种风格,有的用冒号,有的用下划线,前缀完全没有统一,出了问题排查半天不知道 key 是哪个业务写的。实践上推荐“业务:领域:ID”的三段式,比如“order:paid:20250101:12345”,既方便人工识别,也方便用 SCAN 按前缀统计。
过期策略要按业务类型分开。接口结果缓存建议 1 到 10 分钟,加随机偏移;维表缓存可以半小时到一小时,但要配合版本号主动刷新;分布式锁不建议设很短的 TTL,锁的过期时间应该根据任务最大执行时长留足余量,同时用看门狗机制自动续期。
Big Key 和热点 Key 是大数据场景里的高频事故源。一个 Hash 里塞了几十万个字段,每次 HGETALL 都造成大流量,这是典型的 Big Key 问题,应该拆成多个小 Hash 分片。热点 Key 是某个 key 被大量请求同时打到,比如双十一大屏上的“总成交额”计数器,几万个客户端同时读一个 key,Redis 单实例 CPU 被打满。解决方案是本地缓存加 Redis 读取合并,把命中热点 key 的请求在服务端合并成一次或几次 Redis 调用。
还有两个命令层面的铁律:生产环境禁用 KEYS,用 SCAN 代替;线上慎用 MONITOR,它会让 Redis 变慢。慢查询日志打开,慢命令阈值建议设在 100 毫秒以下,定期分析。可以使用如下配置:
slowlog-log-slower-than 10000 slowlog-max-len 128单位是微秒,10000 微秒等于 10 毫秒,阈值可以按业务实际情况调整。
7.3 分布式锁在实时任务与调度里的坑
大数据任务调度里分布式锁用得极多。典型场景是多个定时任务同时跑完,都要写同一张统计结果表,不加锁就会重复写入甚至数据错乱。
最原始的实现是 SETNX 加 DEL,但释放锁时容易误删别人的锁。更稳的写法是 SET key value NX EX 加 Lua 脚本保证原子性,value 放一个唯一请求 ID,释放时先比对再删除。代码大概是这样:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end这个实现能解决绝大多数场景。但要注意,分布式锁不是万能的。锁过期会导致任务并发执行,加锁只能做互斥,不能做幂等。我的习惯是大数据任务里加锁之外,再配一个数据库唯一键或者版本号,双保险才敢放心。
Redisson 的分布式锁内置了看门狗续期逻辑,能降低锁过期问题,但 RedLock 这种多节点红锁方案在工程界一直有争议,我的个人观点是:大数据集群内同一机房的 Master-Slave 结构,用 Redisson 的普通锁足够,不需要过度设计。
8. 一次离线数仓项目里,Redis 到底承担了哪几个角色
前面讲了很多理论,最后用一个我做过的网约车离线数仓项目收尾,这样对 Redis 在大数据链路中的真实位置会有更直观的感觉。
这个项目的技术栈是 Hive 数仓加 Spark 计算加数据服务层。一开始 Redis 的角色只有一个,就是给实时大屏缓存聚合结果。后来业务复杂起来,Redis 的用法越来越多,最终承担了至少五个角色。
第一个是维表缓存。司机、城市、订单状态这些维度数据如果每次都从 MySQL 查,服务接口根本扛不住。我把热门的维度表预加载到 Redis Hash,应用服务一次 HGETALL 拿全量再本地 Caffeine 存副本,MySQL 的压力几乎降为零。
第二个是结果缓存。报表系统每天凌晨产出指标,运营后台白天读。结果直接写 Redis String,后台接口读不到缓存再回源 Hive 或 MySQL。大部分查询都是毫秒级返回。
第三个是 UV 去重。活动运营要看实时参与人数,百万级用户,用 Set 做去重内存会爆,我用 HyperLogLog,误差在可接受范围,内存开销不到 12KB。
第四个是实时排行。司机完成订单数、用户邀请排行,这些都是 ZSet 的天然场景,ZADD 更新成员分数,ZREVRANGE 取 Top100,几十毫秒出结果。
第五个是分布式锁。凌晨批量任务同时跑,多个任务都要写同一天的汇总表,我用任务日期作为锁粒度,在 Redis 里 setnx 加锁,配合 Lua 释放,确保同一时间只有一个任务在写。
踩过的坑也值得一提。大屏接口的缓存 TTL 最初设成 2 小时,结果每天凌晨任务写完后 2 小时整点,所有请求同时回源,HDFS 源表查询又慢,接口一度 5 秒超时。后来改成 30 分钟基础值加随机 0 到 15 分钟偏移,加上 Caffeine 本地兜底,这个问题再没出现过。
做大数据项目的这些年,我越来越认同一个观点:Redis 能成为默认缓存,不是因为它在某个维度上碾压所有对手,而是因为数据结构和分布式场景配合得最顺手。如果整篇文章只留一条经验,那就是先定义清楚“缓存里的数据可不可以丢、过期了回源能不能扛住”,再决定要不要接 Redis、要不要开持久化、要不要上集群。这个顺序一定不能反。