news 2026/10/16 5:03:37

Redis使用第一弹:核心数据结构与缓存穿透、Spring Boot集成指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis使用第一弹:核心数据结构与缓存穿透、Spring Boot集成指南

大概五六年前我刚开始接触Redis的时候,真没觉得它有多少学问。那会儿项目里最常用的就是set、get两个命令,塞在接口里当缓存用,线上数据量也不大,感觉它就是一把"高级点的Map"。真正意识到Redis不是这么用的,是有一次在联调环境排查超时问题,整个服务链路绕来绕去,最后发现所有人都绕不开一个尴尬事实:缓存到底该怎么设计、怎么用,团队里其实没几个人说得清楚。

那次之后,我决定把Redis的使用好好梳理一遍,"Redis使用~第一弹"就是这么来的。这篇我不打算讲冷门高阶特性,先把高频、刚需、能直接落地的东西写透:Redis到底解决什么问题、五种核心数据结构怎么用得明白、如何和后端框架集成、以及几乎每个团队都会踩的缓存穿透、击穿、雪崩这些坑。适合刚用Redis不到半年、还处于"会敲命令但不太敢上生产"阶段的同学,当然,如果你已经写了不少缓存代码,也可以对照着检查一下自己的用法有没有跑偏。

1. 第一弹先聊清楚:Redis到底是来解决什么痛点的

1.1 一次超时排查带来的反思

那次联调时的现象很典型:一个查询商品详情的接口,说慢不慢,平均四五百毫秒,但压测一上来就直接飚到两三秒。看链路,调用关系其实不复杂,每个服务各自查了数据库,每个服务又都做了本地缓存。问题就出在"本地缓存"上——本地缓存是跟着应用进程走的,多个服务实例之间谁也不认识谁的数据。同一个商品ID,A实例缓存了旧价格,B实例缓存了新价格,用户来回刷新看到的价格都不一样。

Redis在当时最直接的吸引力就是:它是独立部署的内存服务,所有应用实例往同一个地方读写,天然解决了多实例数据一致性的问题。加上命令简单、客户端生态成熟,接进来几乎不费劲。

但等到真接进来,才意识到很多问题是"本地缓存时代"根本没有的:key过期了怎么办、热点key被击穿怎么办、Redis重启之后缓存里的数据要不要恢复、内存满了会怎样。这些不是Redis本身坏,而是定位没想清楚。Redis不是MySQL的替代品,它是给数据库挡子弹的。把这句话先放在心里,后面很多纠结都能少一半。

1.2 内存数据库到底快在哪里

很多人把Redis快归结为"它在内存里",这话只对了一半。同一台机器上,用本地HashMap读十万次,大概率比用Redis读十万次更快,因为Redis再快也过了一层网络。Redis的核心优势,是把"计算"搬到了离数据更近的地方,并且用一套很聪明的模型去支撑高并发读。

Redis是单线程模型,这句话经常被误解成"Redis性能不行,一个线程能干多少活"。恰恰相反,单线程让它省掉了锁竞争、省掉了线程切换的开销,所有命令按到达顺序排队执行,天然串行、天然原子。网络IO这块,Redis用IO多路复用让一个线程同时盯住成千上万个连接,哪个连接有请求就处理哪个。

所以Redis适合的场景有非常明显的画像:读多写少、单个操作足够轻量、数据需要被多个服务共享。反过来,如果你有一个重计算任务,或者一个几十MB的JSON要反复读写,那Redis不一定是最优解。

1.3 第一弹的内容边界

我打算把基础使用拆成两到三弹。这一弹只覆盖到"能正常用、用对路"的程度:本地环境怎么搭、五种核心数据类型怎么选、Spring Boot集成时最容易翻车的序列化和失效问题、三大缓存经典坑怎么防、持久化和内存淘汰怎么配。

下一弹大概率讲分布式锁、高可用部署和压测调优。看完这一弹,你至少应该能独立回答这几个问题:这个数据该不该进Redis?应该用哪种数据结构?过期时间设多久?如果Redis崩了,业务会不会跟着崩?

2. 本地环境搭建:启动Redis前最容易忽视的几个细节

2.1 三种安装方式怎么选

开发机装Redis,我最推荐的方式是操作系统的包管理器。比如macOS下直接:

brew install redis

装完就能用redis-server启动,简单直接。Linux服务器上如果也想省事,Ubuntu和CentOS也都有官方仓库里的Redis包,但版本通常比较旧,而且不同发行版默认配置差异挺大。

生产服务器上我习惯用源码编译安装。官方源码包下载解压后,依次执行:

make make install PREFIX=/usr/local/redis

编译前需要确认机器上有gcc和make工具,Redis是基于C语言的,编译失败十有八九是缺工具链。编译成功后会生成redis-server和redis-cli两个主要二进制,直接放进PATH就能用。

如果实在不想碰环境,Docker其实是最省心的:

docker run -d --name redis-first \ -p 6379:6379 \ redis:7-alpine

这样启动的是默认配置,日志直接打到容器里,测试代码连一下就能跑。但要注意,容器部署时挂载数据卷这件事必须提前想好,否则容器一删数据就全没了。

2.2 redis.conf里值得提前改的几个参数

Redis默认配置其实"能用,但不适合直接用",尤其是跑在服务器上的时候。我每次装完新实例,第一时间会检查这几个配置:

  • daemonize:改成yes以后Redis会在后台运行,否则终端一关服务就停了。开发时跑前台也没问题,能看到即时日志。
  • bind:默认127.0.0.1只允许本机访问。如果有多台机器要连,得改成实际IP或者0.0.0.0,但要配合防火墙一起考虑。
  • protected-mode:这是Redis的自我保护机制,没有密码且只绑了本机时,会拒绝远程访问。改bind之后记得确认这个开关的状态。
  • requirepass:设置访问密码。生产环境必须设,哪怕只在内网跑也建议设。别嫌麻烦,扫描脚本最喜欢找没密码的6379端口。
  • logfile:默认日志打到标准输出,改成文件路径后排查问题会舒服很多。
  • maxmemory:不设的话,Redis会一直吃内存直到系统OOM。这个后面专门讲。

配置文件修改后,用指定配置文件启动:

redis-server /etc/redis/redis.conf

2.3 用命令行验证安装是否正常

启动完成之后,用一个命令验证:

redis-cli ping

返回PONG就说明服务活着。再体验一下最基础的读写:

redis-cli set hello world redis-cli get hello

如果设了密码,连接时需要带认证:

redis-cli -a 你的密码

命令行会提示密码暴露在进程列表里,仅限开发环境临时用,脚本里推荐用REDISCLI_AUTH环境变量代替。

3. 核心数据类型实操:从命令行到业务代码的思维转换

3.1 String:不只是缓存字符串,还能做计数器和分布式锁

String是Redis里最基础的类型。但很多人用了一两年,还只会set和get,其实String里藏着不少高频好用的命令。

缓存场景下的标准写法是:

SET product:1001 '{"name":"茶杯","price":199}' EX 3600

EX 3600表示3600秒后自动过期。这里有个容易忽略的点:set key value和set key value ex 3600是两回事,前者永不过期,后者到期自动删除。用Spring Boot里的opsForValue().set(key, value)时如果不带超时参数,就会写进去一个永不删除的key。

再说两个高频命令。SETNX只有在key不存在时才写入成功,这个特性经常用来做分布式锁的占位操作;INCR是做计数器最顺手的工具:

INCR product:1001:views

比如统计某个商品被查看了多少次,直接INCR就行,读出来再显示,走数据库计数还要担心并发扣减问题。

3.2 Hash:一个对象只占一个key的直观优势

大多数新手刚接触Hash时,会觉得它"不就是多了一级吗"。但真正在业务里,Hash能省掉非常多的key混乱。

举个例子,往Redis里存用户信息。用String的话可能会拆成:

SET user:1001:name 张三 SET user:1001:age 25

这样一个用户散落成好几个key,删也麻烦,查也麻烦。用Hash就清爽很多:

HSET user:1001 name 张三 age 25 HGET user:1001 name HGETALL user:1001

Hash适合存储那些"对象整体读取、偶尔改某个字段"的数据。比如购物车、订单摘要、商品详情。改年龄就HINCRBY user:1001 age 1,完全不碰其他字段。唯一要注意的是HGETALL在field非常多时会有一次性返回大量数据的开销,没必要全取时就别全取,用HMGET指定字段更稳。

3.3 List、Set、ZSet:不同业务场景下怎么选

List可以做简单队列。LPUSH往左边塞,RPOP从右边取,或者反过来,天然先进先出。比如异步任务列表的轻量版,可以用:

LPUSH task:queue task-1001 RPOP task:queue

Set适合去重和集合运算。SADD添加成员是自动去重的,SINTER能取两个集合的交集。典型的场景是"我和某位用户有哪些共同关注",或者标签系统里取拥有多个标签的商品。

ZSet这个类型我特别想多说一句,它是在Set的基础上给每个元素加了一个分数(score),按分数排序。排行榜业务几乎绕不开它:

ZADD rank:hot 100 商品A ZADD rank:hot 80 商品B ZINCRBY rank:hot 20 商品A ZREVRANGE rank:hot 0 9 WITHSCORES

ZREVRANGE ... WITHSCORES取的是按分数从高到低的前十名。ZSet底层用的跳表结构,范围查询性能稳定。代价是内存占用比普通类型大,别什么数据都往ZSet里塞。

数据类型典型场景核心命令
String缓存JSON、token、计数器SET、GET、SETNX、INCR
Hash对象存储,购物车、用户信息HSET、HGET、HGETALL、HINCRBY
List简单队列、时间线LPUSH、RPOP、LRANGE
Set去重、共同好友、抽奖SADD、SMEMBERS、SINTER
ZSet排行榜、热力值排名ZADD、ZREVRANGE、ZINCRBY

4. 与Spring Boot集成:从配置文件到缓存使用的完整链路

4.1 依赖引入与配置里最坑的版本差异

Spring Boot项目接Redis,官方starter就够了:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

配置文件里,不同Spring Boot版本的写法有变化。2.x版本通常是这样:

spring: redis: host: 127.0.0.1 port: 6379 password: "" timeout: 3s

但到了Spring Boot 3.x,配置前缀改成了spring.data.redis:

spring: data: redis: host: 127.0.0.1 port: 6379 password: "" timeout: 3s

这个变化很容易被忽略。不少人从网上复制旧配置到新项目里,发现Redis连接参数完全不生效,卡一个小时最后发现只是前缀写错了。

4.2 序列化问题的根因与一次到位配置

新手第一次用RedisTemplate存数据后,打开客户端看到一长串类似\xac\xed\x00\x05t...的二进制乱码,这几乎是必经之路。原因是Spring默认用JDK序列化,把对象序列化成了一长串Java专属字节流,不光看着恶心,其他语言也读不懂。

解决方案是显式指定序列化器。我的固定写法是这样:

@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); StringRedisSerializer keySerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashKeySerializer(keySerializer); template.setHashValueSerializer(valueSerializer); template.afterPropertiesSet(); return template; } }

这样key是普通字符串,value是可读的JSON。有一点必须理解:GenericJackson2JsonRedisSerializer会在JSON里带上@class类型信息,否则反序列化时拿不到原始类型。代价是缓存里会多几个字段,但对大多数业务来说完全值得。

4.3 RedisTemplate的常用操作与TTL设计

我习惯把缓存操作包一层自己的Service,不直接在业务代码里到处出现RedisTemplate。简单几个方法就够用:

public void set(String key, Object value, long ttlSeconds) { redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(ttlSeconds)); } public <T> T get(String key, Class<T> clazz) { Object value = redisTemplate.opsForValue().get(key); return value == null ? null : objectMapper.convertValue(value, clazz); } public void delete(String key) { redisTemplate.delete(key); }

TTL的设计我认为是缓存设计里最重要的一件事,没有之一。太长,数据陈旧;太短,缓存形同虚设。我一般先看业务对新鲜度的容忍度:商品库存变化频繁,一两分钟就够;用户昵称这种修改不频繁的,半小时甚至一小时都没问题。还有就是,写缓存时一定要顺手写TTL,防止某次异常写入把永远不过期的脏数据带进去。我就见过一次同步任务写坏key导致旧数据展示了快一个月的现场,而那个key根本不需要长时间存活。

4.4 缓存注解与CacheManager的配合

Spring还提供了一套声明式缓存注解,@Cacheable用得最多:

@Cacheable(cacheNames = "product", key = "#id") public Product getProductById(Long id) { return productMapper.selectById(id); }

第一次调用会执行方法并把结果放入缓存,后续相同key直接返回缓存。配套的还有@CacheEvict用于删除缓存,@CachePut用于更新缓存。

注解用起来确实方便,但有两个坑要提前知道。一是默认的RedisCacheManager没有设置合理的TTL,所有缓存都不会过期,必须在配置里覆盖默认值:

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(config) .build();

二是注解缓存遇到Redis故障时默认会抛异常,接口直接跟着失败。如果你的业务不能接受这种强依赖,可以加sync = true,或者干脆不用注解、走自己封装的双写逻辑。没有银弹,只有适合当前业务的方案。

5. 缓存三大经典坑:穿透、击穿、雪崩的判断与处理

5.1 缓存穿透:查询一个根本不存在的key

形象点说,正常缓存是盾牌,数据库是城。大部分请求被盾牌挡下了。可如果有人故意或者不小心,一直查询一个缓存和数据库里都不存在的ID,那每次请求都会绕过缓存直接砸到数据库上。一次两次无所谓,几千几万次并发压缩,数据库CPU就开始告警了。

高频解法有两种。第一种是缓存空值:

Product product = getFromCache(key); if (product == null) { product = productMapper.selectById(id); if (product == null) { // 空result也缓存,但TTL要短,比如60秒 cacheService.set(key, "", 60); return null; } cacheService.set(key, product, 3600); }

这个方案简单有效,但空值key如果太多会占内存,所以TTL必须短。第二种是布隆过滤器,把所有存在的ID先过滤一遍,不在过滤器里的请求直接返回,根本不给数据库压力。布隆过滤器的缺点是有误判率,且ID集合变化频繁时要小心更新。我的建议是:大部分中小业务,缓存空值就够了;只有ID量大、又持续遭遇恶意遍历时才上布隆过滤器。

5.2 缓存击穿:热点key在过期瞬间的高并发压力

击穿和穿透名字像,问题完全不同。击穿打的是"一个热点key刚好过期"的时间窗口:比如一个爆款商品的详情缓存,平时几千QPS全靠缓存扛着,突然缓存过期了,那一瞬间几千个请求同时去查数据库,数据库很容易被打崩。

处理思路也很直接:别让多个请求同时去查数据库。最常用的手段是互斥锁:

String key = "product:detail:" + id; String lockKey = "lock:product:detail:" + id; Object value = cacheService.get(key); if (value != null) { return value; } Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", Duration.ofSeconds(3)); if (Boolean.TRUE.equals(locked)) { try { value = cacheService.get(key); if (value == null) { value = productMapper.selectById(id); cacheService.set(key, value, 3600); } return value; } finally { redisTemplate.delete(lockKey); } } else { // 拿不到锁的请求,可以短暂休眠后重试,或者返回旧数据 Thread.sleep(100); return cacheService.get(key); }

这里有个容易踩的点:锁一定要带超时时间,否则拿到锁的线程还没干完就崩了,锁永远不会释放,后面所有请求全部卡死。如果业务场景对极端情况特别敏感,还可以用逻辑过期方案:缓存里只存旧数据和一个过期时间标记,后台异步去刷新,用户永远拿到旧数据直到新的缓存就绪。这个方案更复杂,但对用户体验最友好。

5.3 缓存雪崩:大面积过期和宕机的区分

雪崩这个词有点被用滥了,我习惯把雪崩拆成两大类。

第一类是数据层面的大面积过期:比如我们把一批商品统一设置成3600秒TTL,那么每天早上整点,这一批key同时过期,数据库瞬间涌入一整片热点。解决方式很朴素,给TTL加上随机偏移:

long ttl = 3600 + new Random().nextInt(300); cacheService.set(key, value, ttl);

让过期时间错开,数据库就不会同时收到一大波查询请求。

第二类是Redis节点宕机导致整个缓存层失效。这个比数据过期严重得多,因为不是散开几个请求,而是所有请求全部打到数据库。处理思路不能只靠Redis本身,得在架构层面做准备:多级缓存(本地缓存扛一阵)、熔断降级(限制数据库流量)、快速切换到从节点等。顺便提醒一句,Redis做主从复制时,从节点的数据是异步同步的,故障切换时可能丢少量写入,对缓存来说通常可接受,但如果缓存里存了业务强一致数据,就需要额外考虑了。

5.4 别上来就上复杂方案

我把这三个坑放在一起写,不代表每个都一定要全套方案上齐。现实中见过不少团队,明明只有几十QPS,却为了"防穿透防击穿防雪崩"引入布隆过滤器、多级缓存、逻辑过期,最后困在维护复杂度里动弹不得。

我的经验是先做判断:压测压出真实QPS,看数据库在峰值时能不能接住;再分场景看,是热点key问题还是大批key问题。小体量业务,做好空值缓存、TTL随机化、限流降级,通常已经够了。复杂的方案是给足够大的压力准备的。

6. 持久化与内存淘汰:别把Redis当成永不丢失的内存

6.1 RDB和AOF到底选哪个,实际怎么配

Redis数据默认存在内存里,进程一退出数据就没了,这本身不是问题,问题是有些场景下数据不能丢。如果Redis里只存可重建的热点缓存,不做持久化问题不大;但如果存了计数器、延时任务、业务配置这类数据,就要认真考虑持久化。

RDB是定期生成全量快照,文件紧凑,恢复速度快。缺点是在两次快照之间,数据丢了就丢了。AOF是每一条写命令追加到文件里,数据丢失窗口小,但文件一直涨,比RDB大很多,重启时的恢复速度也慢。

我日常推荐的组合是AOF + 每秒刷盘 + 混合持久化。Redis 4.0以后支持混合持久化,AOF重写时生成一份RDB格式的基础文件,再在上面追加重写之后的增量命令,合并了两者的优点:

appendonly yes appendfsync everysec

要说清楚的是,round everysec意味着极端情况下可能丢最近一秒的写入,但对绝大多数业务来说完全可接受。而RDB也有自己的用途:做周期性备份很省空间,比如每天凌晨执行一次BGSAVE,把生成的dump文件拷到备份服务器去。

6.2 maxmemory和淘汰策略:内存满了会发生什么

Redis内存是无上限地用吗?不是,它默认情况下给你一种"能用很多"的感觉,但真用满了,写命令会直接报错:

OOM command not allowed when used memory > 'maxmemory'.

所以生产环境一定要给Redis设内存上限:

maxmemory 4gb maxmemory-policy allkeys-lru

maxmemory-policy就是内存满了之后的淘汰策略。几种主要策略全部都列在这里:

策略含义适用场景
noeviction不淘汰,写命令直接报错明确不能丢数据的场景
allkeys-lru从所有key里淘汰最久没访问的最常用的通用缓存场景
volatile-lru只从设置了过期时间的key里淘汰最久没访问的缓存数据大部分必须有过期时间
allkeys-random从所有key里随机淘汰访问非常均匀的场景
volatile-ttl从有过期时间的key里淘汰剩余时间最短的优先保新鲜数据
volatile-random从有过期时间的key里随机淘汰和volatile-lru类似但更随机

大多数业务缓存场景直接选allkeys-lru就够了。但要特别记住:如果Redis里还存了未过期的业务数据,比如临时票据或者分布式锁,那么allkeys-lru可能会把没设置TTL的重要key淘汰掉。这种情况下用volatile-lru更安全,只淘汰那些本来就会过期的缓存key。

我一直强调这一点,因为线上真的出过问题:有人把短信验证码存在Redis里,验证码key没设过期时间(因为代码bug),内存一满,allkeys-lru把验证码key淘汰了,用户收不到验证码、或者收到的验证码永远提示无效,排查了半天才定位到内存淘汰上。

6.3 运维排查时最常用的几条命令

最后再分享几个平时排查问题很有用的命令。

INFO memory能快速看到内存使用情况,重点关注used_memory_human和maxmemory_human:

redis-cli INFO memory

如果想知道某个key具体占了多少内存:

redis-cli MEMORY USAGE product:1001

如果Redis突然变慢,优先查慢日志:

redis-cli SLOWLOG GET 10

慢日志里能看到哪些命令执行时间过长,通常容易出问题的是KEYS全量扫描、HGETALL大hash、SMEMBERS大set这类命令。排查Redis内存分布时也可以跑redis-cli --bigkeys,它会扫描并统计大key分布的概况,但注意这个扫描本身可能造成一定阻塞,最好在低峰期执行。

6.4 第一弹结束前的一点个人体会

Redis用得越久,我越觉得它最大的门槛不在命令,而在"边界感"。哪些数据值得进Redis,哪些数据绝不应该放进去,TTL设置成多久,内存淘汰策略怎么选,每件事说大不大,但合起来就决定了一次线上事故发生还是不发生。第一弹先写到这里,下一篇准备聊聊分布式锁的可靠性设计和主从哨兵架构,等第二弹吧。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 5:03:35

Apollo-11:模块化个人知识管理与自动化工作流实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 5:03:29

Hermes Agent 接入 MCP 工具:从 dashboard 查询到 agent-toolkit 验证全流程

1. 为什么要在 Hermes Agent 上接入 MCP 工具Hermes Agent 本身是一个任务编排与执行框架&#xff0c;它擅长把复杂流程拆成可调度的步骤&#xff0c;但默认能力集是有限的。当业务需要访问外部数据源、调用第三方服务、或者执行一些框架本身不内置的操作时&#xff0c;就需要一…

作者头像 李华
网站建设 2026/10/16 5:03:27

JS对象从创建到原理:原型链、属性描述符与深浅拷贝实战

作为一个整天跟 JavaScript 打交道的前端开发&#xff0c;我越来越觉得,对象这东西就是 JS 的命根子。你要是能把对象搞透&#xff0c;什么框架、源码、工程化&#xff0c;看着都跟玩拼图似的。但我也发现&#xff0c;很多写了两年多的开发者&#xff0c;对象还停留在“{ } 存东…

作者头像 李华
网站建设 2026/10/16 5:02:48

EndNote中文文献双语引用:重建字段与样式模板,解决乱码混排

简介&#xff1a;资源包为EndNote用户提供中文文献管理与中英双语引用的一站式解决方案&#xff0c;主要面向经常撰写学术论文、需要同时处理中文期刊、书籍和学位论文引用格式的研究生、科研人员及期刊投稿作者。包内共4个文件&#xff0c;包含3个xml样式模板与1个ens输出格式…

作者头像 李华
网站建设 2026/10/16 5:02:07

七类爬虫采集工具实用指南:从数据源判断到三分钟跑通

上周一个朋友让我帮她整理一份行业名单&#xff0c;输入框一行行粘过去&#xff0c;弄到晚上十点还剩一半。我过去看了一眼&#xff0c;那份数据其实就躺在同一个网页列表里&#xff0c;结构规规整整&#xff0c;十几分钟就能全部下载完。她听完愣了几秒。这不是她手慢&#xf…

作者头像 李华
网站建设 2026/10/16 5:01:12

Spark Standalone与Yarn运行模式深度对比与生产选型指南

1. 项目概述&#xff1a;为什么必须搞懂 Standalone 和 Yarn 这两种 Spark 运行模式在某高校大数据实验室带学生做实时日志分析项目时&#xff0c;我遇到过一个典型场景&#xff1a;团队用同一份 Spark Streaming 代码&#xff0c;在本地开发环境跑得飞快&#xff0c;一上生产集…

作者头像 李华