news 2026/10/13 18:00:31

Spring Boot整合Redis 4.x实战:配置、序列化与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot整合Redis 4.x实战:配置、序列化与避坑指南

说实话,Redis 这个组件在 Spring Boot 项目里已经快成标配了。这篇是我 Spring Boot 学习笔记系列的第十一篇,主题是整合 Redis 4.x。标题里的 4.x 指的是 Redis 服务端版本,不是 Spring Boot 版本,这个得先说明白,免得有人对不上号。

为什么单独拿一篇写 Redis 4.x?因为不少老项目到现在还跑在 4.x 上,它不像 5.0 之后的 Stream 那样花哨,但模块系统、PSYNC2 部分重同步、LFU 淘汰策略这些改动,对日常业务开发的影响其实不小。尤其当你需要在 Spring Boot 里做缓存、分布式锁、接口限流、热点数据存储时,Redis 4.x 配合 Spring Data Redis 这条链路,搭起来快,坑也相对少。

这篇内容偏实战。适合两类人看:一是刚把 Spring Boot 基础跑通的初学者,想弄清楚 Redis 到底怎么接进来、序列化器为什么要改;二是已经在用 RedisTemplate 但总遇到连接池报错、缓存乱码问题的老哥。我会从环境准备、配置、代码实战、问题排查四个角度把整条链路过一次,写的都是我自己反复用过的方案。

1. 整合前先搞明白:Redis 4.x 能带来什么

1.1 Redis 4.x 和旧版本的关键差异

很多教程上来就贴依赖、写配置,完全不解释为什么。我觉得先搞清楚手里这个 Redis 4.x 跟以前用的 2.8、3.2 有什么本质区别,后面出问题时才知道该往哪个方向排查。

Redis 4.0 第一个大变化是模块系统。你可以往 Redis 里加载第三方模块,比如搜索、JSON 支持等。对普通业务开发来说可能用不上,但它意味着 Redis 不再是单纯的 kv 缓存,而是往“数据平台”的方向走。

第二个变化是 PSYNC2,也叫部分重同步的优化。以前的版本里,从节点一旦断开重连,主节点可能被迫做全量同步,数据量大时非常痛苦。4.x 优化了复制缓冲区的处理逻辑,从节点重启后有机会只同步缺失部分,对主从架构的稳定性提升很明显。

第三个变化是内存淘汰策略增加了 LFU。以前我们最常用的是 LRU,也就是淘汰最近最少使用的 key。LFU 则是统计访问频率,对那种“一段时间被猛刷、后面不再用”的冷热数据场景更准确。配置上可以直接写 allkeys-lfu 或 volatile-lfu。这个特性在缓存容量受限时很实用。

另外还有几个工程上特别有价值的命令。UNLINK 是异步删除大 key 的,如果你要清理一个几十 MB 的字符串或者超大 Hash,用 DEL 会阻塞 Redis 主线程,可能造成服务抖动;UNLINK 只标记删除,后台线程慢慢回收。MEMORY USAGE 命令可以查某个 key 到底占多少内存,排查大 key 比用 keys 遍历高效得多。FLUSHALL 和 FLUSHDB 也支持 ASYNC 参数,清库不会卡死在线请求。

这些特性和 Spring Boot 整合没直接关系,但它们决定了你在项目里怎么用 Redis。比如后来我在清理过期用户会话时,就特意用 UNLINK 而不是 delete。

1.2 客户端选型:Lettuce 还是 Jedis

Spring Boot 2.x 之后,spring-boot-starter-data-redis 默认整合的是 Lettuce。早期很多老项目用的是 Jedis,如果你维护过这类工程,应该见过 JedisPool 相关配置。这里我强烈建议新项目直接用 Lettuce,不要动默认依赖。

两者最大的区别在连接模型。Lettuce 基于 Netty,一个连接可以被多个线程共享,实例是线程安全的。换句话说,你可以定义一个全局的 RedisTemplate,随手往里面扔请求,不用担心连接被并发搞乱。Jedis 不一样,Jedis 实例不是线程安全的,同一个连接在多个线程里用会出问题,只能靠连接池来保证每次拿到独立连接。

Spring Boot 默认方案已经是 Lettuce,如果你还需要连接池,额外引入 commons-pool2 依赖并配置 lettuce.pool。大多数场景下,一个应用实例保持几条连接就够用,因为 Netty 多路复用把网络连接压得很低。

对比项LettuceJedis
线程安全性线程安全,可共享连接非线程安全,需独立连接
连接模型Netty 多路复用每实例一个 socket 连接
异步支持原生支持需要自己封装
Spring Boot 默认2.x / 3.x 默认早期版本默认
适用场景大多数 Spring Boot 项目轻量工具、脚本

如果你是初学者,我的建议很直接:别纠结,用默认的 Lettuce。你真正需要花时间的是序列化器选型,这才是日常开发里最容易出问题的地方。

2. 从零开始:环境准备与项目核心配置

2.1 Redis 服务端准备

先把服务端搞起来。我自己的习惯是开发环境用本地编译安装,或者用包管理工具直接装。Linux 上装 4.x 不难,但要注意系统源里的版本可能比较老,如果源里是 3.x,建议直接用官方源码编译安装。

启动方式很简单:

redis-server

如果不想占用当前终端窗口,就用后台模式启动:

redis-server --daemonize yes

启动之后一定要验证一下能不能连上:

redis-cli ping

正常情况下会返回 PONG。如果你配置了密码,连接和验证要分开写:

redis-cli -a yourpassword ping

这里我要额外强调一个安全习惯。开发环境哪怕只是本机跑,也建议设置密码。不要把 Redis 裸奔在一个公网上,尤其是 6379 端口默认就是公开的,扫描器天天都在扫。设置密码很简单,在 redis.conf 里改一行:

requirepass yourpassword

另外注意 bind 和 protected-mode 的配置。开发机上 bind 127.0.0.1 就够了;如果是局域网环境要给其他机器连,再改成对应内网 IP。protected-mode 默认开启的情况下,如果 Redis 没有密码且没有配置 bind,它会拒绝外部连接。这个设计本来是为了安全,但很多人不清楚,导致远程连不上,后面排查时会讲到。

2.2 Spring Boot 工程依赖与配置

项目里引入依赖。如果你是 Spring Boot 2.x,直接用官方 starter,版本号不需要自己写,交给 Spring Boot 的依赖管理就行:

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

第二个依赖是给 Lettuce 连接池用的。如果你不想用连接池,其实可以不引,但我不建议你省这一步。虽然 Lettuce 本身支持共享连接,压测下来连接池能帮你控制资源水位,避免极端流量下连接数无限上涨。

然后是配置文件。这是我最常用的配置模板,直接贴在这里:

spring: redis: host: 127.0.0.1 port: 6379 password: 123456 database: 0 timeout: 3000ms lettuce: pool: max-active: 16 max-idle: 8 min-idle: 0 max-wait: 3000ms

逐项解释一下:host 和 port 是连接地址;password 对应服务端 requirepass,没有就留空;database 是 Redis 逻辑库编号,默认 0,Redis 最多支持 16 个库。

timeout 要注意,这个值在 Lettuce 下表示命令读写超时时间。如果是 3 秒,一次大的批量操作超过 3 秒就会抛超时异常。所以这个值不要设得太激进,尤其是后面讲 Pipeline 和大 key 操作时会遇到。

连接池参数里面,max-active 是最大连接数,max-idle 是最大空闲连接,min-idle 是最小空闲连接,max-wait 是拿连接的最大等待时间。这组参数不建议照搬,需要结合你的并发量调。并发不高 16 够用,QPS 很高就往上加,同时配合压测观察线程等待时间。

2.3 RedisTemplate 与序列化器配置

服务端和客户端都通了你就会发现,直接用默认 RedisTemplate 存数据,Redis 客户端里看到的 key 会变成一串乱码,比如 \xAC\xED\x00\x05t... 之类的东西。第一次遇到时我真的一脸懵,以为数据坏了。

其实原因是 Spring Boot 默认的 RedisTemplate 使用 JdkSerializationRedisSerializer,也就是 Java 对象序列化。存进去的内容是一大段二进制,key 和 value 都是这样。这种序列化方式有几个问题:可读性差、体积大、没法跨语言。比如你用 Java 存的数据,用 Python 或其他语言读就很麻烦。

所以我做任何项目,第一件事就是自定义 RedisTemplate 的序列化器。核心配置如下:

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

这里的思路是:key 用 String 序列化,保证可读性;value 用 JSON 序列化,存到 Redis 里是一段 JSON,其他语言也能读。为什么用 GenericJackson2JsonRedisSerializer 而不是 Jackson2JsonRedisSerializer?区别在于前者会在 JSON 里保留 @class 类型信息,反序列化时不需要你手动指定目标类型,灵活很多。代价是 JSON 里多一个类型字段,数据体积稍微大一点。

这一节配置看起来简单,但直接影响后面所有缓存代码。如果你在旧项目里看到 key 是乱码,多半就是这里没改。配置完之后,我建议顺手把缓存管理器也配好,不然用 @Cacheable 注解时又是默认 Java 序列化,和 RedisTemplate 的行为不一致:

@Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) .disableCachingNullValues(); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(config) .build(); }

注意这个配置里写了 disableCachingNullValues,含义是方法返回 null 时不缓存。这个选项在部分场景下要让位给缓存穿透的空值缓存策略,后面问题排查那节我会细说。

3. 实操:把 Redis 用起来

3.1 最常用的 String 和 Hash 操作

配好了 RedisTemplate,第一个落地的场景通常是缓存。字符串操作最典型的就是验证码、计数、临时状态。

我一般直接用 StringRedisTemplate,因为这类场景 key 和 value 都是字符串,写起来非常干净:

@Autowired private StringRedisTemplate stringRedisTemplate; // 写验证码,5分钟有效 stringRedisTemplate.opsForValue().set("captcha:user:1001", "482913", Duration.ofMinutes(5)); // 读验证码 String code = stringRedisTemplate.opsForValue().get("captcha:user:1001"); // 删除验证码 stringRedisTemplate.delete("captcha:user:1001");

如果要存对象,常规做法是把对象转成 JSON 字符串再塞进去。但如果你用的是前面配置的 RedisTemplate<String, Object>,可以直接塞对象:

@Autowired private RedisTemplate<String, Object> redisTemplate; User user = new User(); user.setId(1001L); user.setName("张三"); redisTemplate.opsForValue().set("user:1001", user, Duration.ofMinutes(30)); User cacheUser = (User) redisTemplate.opsForValue().get("user:1001");

Hash 结构在业务开发里也经常用。它的优势是可以单独读写一个对象里的某个字段,不用每次整个序列化和反序列化。比如用户信息缓存的场景:

HashOperations<String, Object, Object> hash = stringRedisTemplate.opsForHash(); hash.put("user:1001", "name", "张三"); hash.put("user:1001", "age", "18"); Map<Object, Object> userMap = hash.entries("user:1001");

使用字符串值的时候有个小坑:从 Hash 里读出来的 age 是字符串 "18",如果你需要数字类型,必须手动转换。我见过不少同事在这里直接强转,运行时报错才反应过来。

3.2 缓存注解:@Cacheable、@CachePut、@CacheEvict

如果说 RedisTemplate 是手动挡,那 Spring Cache 注解就是自动挡。日常开发里我大量使用注解做方法级缓存,代码层面几乎不需要关心 Redis 操作细节。

首先在主配置类或者启动类上加上:

@EnableCaching

然后在 Service 方法上声明缓存行为:

@Cacheable(cacheNames = "goods", key = "#id") public Goods getGoodsById(Long id) { // 查询数据库 return goodsMapper.selectById(id); } @CachePut(cacheNames = "goods", key = "#goods.id") public Goods updateGoods(Goods goods) { goodsMapper.updateById(goods); return goods; } @CacheEvict(cacheNames = "goods", key = "#id") public void deleteGoods(Long id) { goodsMapper.deleteById(id); }

这三个注解的区别:@Cacheable 先查缓存,缓存没有才执行方法,并把返回值放缓存;@CachePut 每次都会执行方法,然后把结果更新到缓存,适合更新操作;@CacheEvict 只做缓存删除,适合删除操作和失效操作。

key 的写法是 SpEL 表达式,比如 #id 取方法参数 id 的值,#goods.id 取参数对象里的 id 字段。cacheNames 加 key 最终会拼成 Redis 的 key,默认是 goods::1001 这样两个冒号分隔的格式。

组合使用时要特别小心。如果更新操作只做了数据库更新,忘了加 @CachePut 或 @CacheEvict,那缓存里的数据一直是旧的,就会产生数据不一致。这个问题在改需求时尤其容易踩到,我的习惯是对所有写操作做一次全局检查。

3.3 分布式锁:一个能用但别得意的实现

多实例部署时,Java 自身的 synchronized 锁不到别的机器上。最常见的解决办法就是在 Redis 里做分布式锁。基础思路是利用 SET NX EX 的原子语义:只有 key 不存在时才设置成功。

我的实现模板如下:

String lockKey = "lock:stock:1001"; String requestId = UUID.randomUUID().toString(); Boolean locked = stringRedisTemplate.opsForValue() .setIfAbsent(lockKey, requestId, Duration.ofSeconds(30)); if (Boolean.TRUE.equals(locked)) { try { // 扣减库存等业务逻辑 } finally { // 释放锁 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); stringRedisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId); } }

两个关键细节必须理解到位。

第一,value 为什么要用唯一标识,比如 UUID。因为如果直接 set 一个固定值,任何线程都能删除这把锁,会出现线程 A 的锁被线程 B 释放的严重问题。用请求唯一标识后,释放前先判断持有者是不是自己,防止误删。

第二,释放锁为什么要用 Lua 脚本。因为“判断值是否相等”和“删除 key”是两个操作,如果不放在同一个原子脚本里,中间可能被其他线程插进来,又出现误删。Lua 脚本保证这两步操作原子执行。

这套实现能应付大多数内部系统,但它不是满分的分布式锁。比如锁超时后业务还没执行完,锁会被自动释放,其他线程就能进来,导致并发问题。要解决得做锁续期,也就是“看门狗”机制,每过一段时间自动延长过期时间。这个逻辑自己写比较繁琐,如果项目里允许引入额外依赖,我会建议直接用 Redisson 的分布式锁,它对续期、重入、失败重试都处理得更完善。

3.4 过期监听:让 Redis 帮你做延迟通知

Redis 的 key 过期事件是个很实用的能力。比如订单超时未支付自动取消、定时任务提醒,这类场景如果不希望单独起一个后台线程扫描数据库,就可以考虑 Redis 过期监听。

首先需要在 Redis 服务端开启键空间通知,Redis 4.x 支持这个特性。可以在配置里加:

notify-keyspace-events Ex

或者运行时执行命令:

CONFIG SET notify-keyspace-events Ex

然后写一个监听器,继承 Spring Data Redis 提供的 KeyExpirationEventMessageListener:

@Component public class RedisKeyExpiredListener extends KeyExpirationEventMessageListener { public RedisKeyExpiredListener(RedisConnectionFactory connectionFactory) { super(connectionFactory); } @Override public void onMessage(Message message, byte[] pattern) { String expiredKey = message.toString(); // 根据 key 前缀做业务处理,比如 order:expire:123456 } }

这里要注意,onMessage 收到的内容只是过期的 key,value 已经拿不到了。所以如果需要关联业务数据,要把业务信息编进 key 里,比如 order:expire:123456。

Redis 过期事件有个天然局限:它不是严格准时的事件,消息也可能因为内部清理机制延迟。实际上 Redis 的过期 key 是被动清理和定期清理结合完成的,可能 key 已经到点,但事件过一会儿才出来。所以不能把它当作强一致消息系统用。如果业务要求“几分钟内处理”这种宽松时序,它很合适;如果要求“必须精确处理”,还得配合定时扫描兜底。

3.5 Pipeline 批量操作:把网络开销拉下来

批量操作是 Redis 性能优化的重头戏。业务上经常遇到要写几千个 key 的情况,如果写一个 for 循环,每条命令都走一次网络往返,耗时成倍增长。Pipeline 可以把一批命令打包发到 Redis,一次往返搞定。

Spring Data Redis 里用 executePipelined 实现:

stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> { for (int i = 0; i < 10000; i++) { byte[] key = ("batch:key:" + i).getBytes(StandardCharsets.UTF_8); byte[] value = ("batch:value:" + i).getBytes(StandardCharsets.UTF_8); connection.stringCommands().set(key, value); } return null; });

我实际测下来的感受是,本地环境下 1 万次普通 set 和 1 万次 Pipeline 的耗时差距能达到几十倍。网络越慢、命令越多,Pipeline 的优势越明显。所以只要是批量初始化缓存、批量写入临时数据,我第一反应就是用 Pipeline。

Pipeline 有几个注意点:如果返回结果不为 null,executePipelined 会收集每条命令的返回值,记得按顺序对应。并且 Pipeline 中命令是在同一个连接上顺序执行的,如果是在 Redis 集群环境,key 必须落在同一个 slot,否则会报跨 slot 错误。集群模式下想用 Pipeline 批量操作,需要先按 slot 分组或者用 hash tag 强制 key 落到同一个槽位。

4. 真实项目中的问题排查与避坑手册

4.1 连接池报错 Could not get a resource from pool?

这个报错几乎是 Redis 集成最常见的拦路虎,关键信息在后面半句,一般会跟着 Connection refused 或者 Read timed out。

第一步,确认 Redis 到底启动没有。不要猜,直接敲命令:

redis-cli -h 127.0.0.1 -p 6379 -a 123456 ping

能看到 PONG 说明服务没问题。连接拒绝,要么是端口不对,要么是 bind 配置没包含你当前访问的 IP。如果 Redis 只 bind 了 127.0.0.1,你从另一台服务器连,它一定拒绝。

第二步,确认密码是否正确。密码错误时 Spring Boot 日志会提示 NOAUTH Authentication required,但如果你把异常直接吞了,可能只看到连接失败。

第三步,考虑连接池本身。如果流量很大,max-active 设置太小,或者 max-wait 设置太短,短时间内连接被借完就会出现 Could not get a resource from pool。处理办法不是一味调大连接数,而是先看 Redis 服务端的连接数和耗时,再结合业务并发量调整。

连接池参数我给过一个经验值:常规 Web 项目 max-active 20 到 50,max-wait 3 秒比较合理。压测时观察到超时再去微调,不要一上来就堆几百个连接,Redis 本身处理能力有限,连接太多反而增加上下文切换开销。

4.2 缓存 key 出现乱码,怎么办

如果你在 Redis 客户端看到 key 长这样:\xAC\xED\x00\x05t\x00\x06key:1,说明用的还是 JdkSerializationRedisSerializer,也就是默认序列化器。

解决办法就是我前面给的 RedisConfig,把 key 序列化器改成 StringRedisSerializer,value 序列化器改成 GenericJackson2JsonRedisSerializer。改了之后,新写入的数据就是可读字符串,之前写入的乱码数据只能删掉,没法自动解析回来。

这里提醒一点:如果你的业务里大量使用了 @Cacheable 注解,而且没配置 RedisCacheManager,那缓存 key 和 value 还是可能乱码。很多人只改了 RedisTemplate 忘记改 CacheManager,结果 RedisTemplate 操作正常,注解缓存还是有问题。这才是真正容易忽略的地方。

4.3 LocalDateTime 反序列化失败

用 JSON 序列化缓存对象,常见问题之一是对象里有 LocalDateTime、LocalDate 这类时间字段。反序列化时报错 java.time.LocalDateTime cannot be cast to... 或者 InvalidDefinitionException。

原因是 Jackson 默认没有注册 Java 8 时间模块,LocalDateTime 会被序列化成数组或数字格式,反序列化时无法自动转回。解决思路是给序列化器配一个自定义 ObjectMapper,注册 JavaTimeModule:

ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);

然后把 RedisConfig 里的 value 序列化器换成这个自定义实例。如果还要处理 LocalDate、LocalTime,同样在 JavaTimeModule 覆盖范围内。这个坑在 Spring Data Redis 较新版本里有所改善,但如果你遇到时间类型问题,第一反应就应该是 JavaTimeModule。

4.4 缓存穿透、击穿、雪崩的应对思路

这三个词是 Redis 缓存面试必问题,实战里也确实会遇到。

缓存穿透是查一个肯定不存在的数据。缓存没有,数据库也没有,每个请求都会打到数据库。简单的应对是缓存空值,即使查出来是 null,也往缓存里写一个空对象或者特殊标记,过期时间设短一点,比如 30 秒。但我前面配置里写了 disableCachingNullValues,两者冲突,如果选择缓存空值方案就要把这个配置去掉。

缓存击穿是某个热点 key 正好过期,大量并发同时去查数据库。可以用互斥锁,只让一个线程去查库,其他线程等待。另一种思路是逻辑过期,把 key 的过期时间放在 value 里,不真正设 Redis TTL,后台异步刷新。实现复杂度高一点,但能避免锁带来的等待。

缓存雪崩是大量 key 在同一时间集体过期。解决方式是在设置缓存过期时间时加一个随机值,比如 30 到 60 分钟之间随机,让过期时间错开。也可以用多级缓存或者加多副本,但最简单实用的还是随机过期时间。

这三大问题的核心都指向同一个原则:别让请求全打到数据库,削峰、错峰、直接挡住。

4.5 排查和验证缓存的小技巧

实际开发中验证 Redis 缓存到底有没有生效,我一般先用 SLOWLOG 和 MONITOR。

Redis 4.x 的慢查询日志查看方法:

SLOWLOG GET 10 SLOWLOG RESET

慢查询里能看到耗时高的命令,能帮你快速定位是不是有某个大 key 或批量操作拖慢了 Redis。

MONITOR 命令可以实时打印 Redis 收到的所有命令,上线环境下不要随便开,影响性能。开发联调时可以用它确认某个请求到底有没有打到 Redis。

可视化工具方面,我自己的习惯是先用命令行把逻辑全部跑通,再打开图形工具观察 key 的变化和 TTL。如果你用的是默认序列化器,图形工具里看到一堆二进制,那问题就肉眼可见了。

5. 一些我后来才想明白的体会

最后说一个我自己的习惯。很多人做 Spring Boot 整合 Redis,第一步就是抄一个配置类,然后开开心心跑通 demo,后面遇到问题就全靠搜索引擎。其实整合 Redis 这件事,最值得花时间的不是配置,而是想清楚你项目里每个场景应该用哪种数据结构、哪种序列化策略、怎样设置过期时间。

序列化器选型是我最想让你记住的内容。一个合理的配置能让你从开发到运维都舒服,Redis 里的数据清楚可读,排查问题时一眼就能看到 key 和 value;一个偷懒的默认配置,后面要改必须清数据,那才是真正难受。

Redis 4.x 的 UNLINK、LFU、模块系统这些东西,看着不像 Spring Boot 直接相关,但它们在系统稳定性、容量规划、扩展能力上都能帮上忙。用熟基础功能之后,我建议你回来看这些特性,会发现自己能解决的问题又多了一圈。

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

QT框架下旅行模拟查询系统的开发与优化实践

1. 项目概述与核心价值这个旅行模拟查询系统本质上是一个集成了地图可视化、路径规划和信息查询的桌面端应用。不同于简单的网页版工具&#xff0c;基于QT框架的C实现让它具备了本地化运行的高效性&#xff0c;特别适合需要处理大量地理数据或复杂计算场景的旅行规划需求。我去…

作者头像 李华
网站建设 2026/10/13 17:57:29

Python第二次作业深度解析:数据流、面向对象与异常处理实战

1. 作业概览&#xff1a;这绝不是一次简单的“课后练习”如果你只把这份“Python第二次作业”当成一次常规的语法巩固训练&#xff0c;那可能真的低估它了。我在实际批改和复现这类作业时发现&#xff0c;很多同学的卡点并不在“会不会写代码”&#xff0c;而在“能不能把学过的…

作者头像 李华
网站建设 2026/10/13 17:54:06

AVSG稀疏TopK Cache:大模型推理显存优化实战指南

1. 这不是普通缓存优化&#xff1a;AVSG稀疏TopK Cache背后的真实战场“AVSG稀疏TopK Cache收集”——光看这个标题&#xff0c;很多人第一反应是&#xff1a;又一个论文里蹦出来的术语组合&#xff1f;缩写堆叠、概念嵌套、动词模糊&#xff08;“收集”到底指采集、构建、还是…

作者头像 李华
网站建设 2026/10/13 17:52:39

AI辅助研发设计:五大实战场景补齐团队设计短板

设计能力这东西&#xff0c;在研发团队里长期处于一种“谁都觉得该有、但谁都不想碰”的状态。我做了快十年研发管理&#xff0c;从带小组到带整个技术中心&#xff0c;最深的感受之一就是&#xff1a;很多技术方案本身没问题&#xff0c;却死在了呈现方式上——架构图画得像蜘…

作者头像 李华
网站建设 2026/10/13 17:50:57

用户行为画像体系搭建实战:从标签设计到落地应用

用户网络行为画像这个话题&#xff0c;这几年在互联网行业里被反复提起。无论是做推荐系统、广告投放、用户增长还是产品迭代&#xff0c;最终都会落到一个核心问题上&#xff1a;我们到底有多了解正在使用产品的这群人。所谓画像&#xff0c;本质上就是把用户在网络上留下的行…

作者头像 李华