Spring Boot 3.4 + Redisson 构建高并发AI工具索引:从“内存溢出”到...

Spring Boot 3.4 + Redisson 构建高并发AI工具索引:从"内存溢出"到"毫秒级检索"的架构演进

上周处理一个日均PV突破50万的免费AI工具导航站时,我们遇到了典型的"读多写少"但热点集中的场景。传统的 MySQL 分页查询在热门工具(如 Midjourney、Copilot)被大量并发访问时,CPU 飙升且响应延迟超过 2s。既然近期社区讨论多集中于 AI 模型集成或大模型编排,本文将完全剥离 AI 业务逻辑,纯粹聚焦于后端基础设施层:如何利用 Redisson 的 RMapCache 和 Lua 脚本,在 Spring Boot 3.4 环境下构建一个具备自动过期、防穿透、支持缓存同步的高性能缓存层。

这不仅是缓存问题,更是数据一致性与高可用性的工程权衡。

环境准备

本次实战基于以下技术栈,所有版本均为 2026 年主流稳定版:

  • JDK: 17.0.12 (LTS)
  • Spring Boot: 3.4.0 (基于 Spring Framework 6.2.0)
  • Redis: 7.2.5 (Cluster 模式)
  • Redisson: 3.35.0 (客户端)
  • MySQL: 8.0.36

需引入的 Maven 依赖如下,注意排除旧版 Netty 冲突:

```xml

org.springframework.boot

spring-boot-starter-data-redis

3.4.0

org.redisson

redisson-spring-boot-starter

3.35.0

org.springframework.boot

spring-boot-starter-actuator

org.projectlombok

lombok

1.18.34

provided

```

核心步骤

1. 配置 Redisson 与集群拓扑

在 application.yml 中,我们不仅配置连接池,还需开启 Redisson 的异步事件循环线程,以应对高并发下的 IO 阻塞。

```yaml

spring:

redis:

host: 192.168.1.100

port: 6379

password: your_secure_password

redisson:

config: |

singleServerConfig:

address: "redis://192.168.1.100:6379"

connectionPoolSize: 64

connectionMinimumIdleSize: 24

idleConnectionTimeout: 10000

connectTimeout: 10000

timeout: 3000

retryAttempts: 3

retryInterval: 1500

codec: org.redisson.codec.JsonJacksonCodec

useDefaultCodec: false

```

这里使用 JsonJacksonCodec 而非默认的 MarshallingCodec,主要是为了减少序列化开销并避免类加载器冲突,尤其在微服务环境下更为关键。

2. 实现带"逻辑删除"标记的缓存结构

AI 工具平台的数据存在软删除场景(下架工具)。如果直接缓存对象,删除后 Redis 中仍残留脏数据。我们采用 RMapCache 存储,并引入一个独立的 Set 来维护"已下架/逻辑删除"的工具 ID,利用 Lua 脚本保证原子性。

```java

@Data

@Accessors(chain = true)

public class ToolInfo {

private String id;

private String name;

private String category; // 写作、绘画、编程等

private Long viewCount;

private boolean deleted; // 逻辑删除标记

private LocalDateTime updateAt;

}

```

3. 核心缓存同步服务(Lua 原子操作)

防止缓存击穿和脏读的最佳实践是"缓存旁路+原子更新"。当更新或删除工具时,必须同时修改 Map 中的值和 Set 中的删除标记。

```java

@Service

@Slf4j

public class ToolCacheService {

@Autowired

private RedissonClient redissonClient;

private static final String TOOLS_MAP_KEY = "ai_tools:v1";

private static final String DELETED_IDS_KEY = "ai_tools_deleted_ids:v1";

// Lua 脚本:原子性地更新缓存并标记删除

private static final String UPDATE_AND_DELETE_LUA =

"local mapKey = KEYS1 " +

"local delSetKey = KEYS2 " +

"local toolId = ARGV1 " +

"local toolDataJson = ARGV2 " +

"local isDelete = tonumber(ARGV3) " +

" " +

"-- 获取当前缓存值 " +

"local currentVal = redis.call('HGET', mapKey, toolId) " +

" " +

"-- 如果存在且未删除,先移除删除标记集合中的该ID " +

"if currentVal and isDelete == 0 then " +

" redis.call('SREM', delSetKey, toolId) " +

"end " +

" " +

"-- 写入或更新 Map " +

"if isDelete == 1 then " +

" -- 如果是删除操作,可以在Map中标记为deleted=true,或者直接不删,靠Set判断 " +

" -- 这里选择保持Map完整,仅靠Set判断可见性,避免频繁IO " +

" local data = cjson.decode(currentVal or '{}') " +

" data'deleted' = true " +

" redis.call('HSET', mapKey, toolId, cjson.encode(data)) " +

"else " +

" redis.call('HSET', mapKey, toolId, toolDataJson) " +

"end " +

" " +

"-- 设置过期时间,防止内存无限增长 " +

"redis.call('EXPIRE', mapKey, 86400) " +

"return 1";

/**

  • 同步工具信息到缓存
  • @param toolId 工具ID
  • @param toolJson JSON字符串
  • @param isDeleted 是否逻辑删除

*/

public void syncToolToCache(String toolId, String toolJson, boolean isDeleted) {

RMapCache toolsMap = redissonClient.getMapCache(TOOLS_MAP_KEY);

RSet deletedIds = redissonClient.getSet(DELETED_IDS_KEY);

// 执行 Lua 脚本

RBucket luaScriptBucket = redissonClient.getBucket("lua_scripts/update_tool");

String luaContent = UPDATE_AND_DELETE_LUA;

if (!luaScriptBucket.isExists()) {

luaScriptBucket.set(luaContent);

}

ScriptSyncResult result = redissonClient.getScripts().sync();

result.eval(

RScript.Mode.WRITE,

luaContent,

RScript.ReturnType.INTEGER,

Arrays.asList(TOOLS_MAP_KEY, DELETED_IDS_KEY),

toolId,

toolJson,

isDeleted ? "1" : "0"

);

log.info("Synced tool {} to cache, deleted: {}", toolId, isDeleted);

}

/**

  • 获取工具详情,包含缓存穿透保护

*/

public ToolInfo getToolDetail(String toolId) {

RMapCache toolsMap = redissonClient.getMapCache(TOOLS_MAP_KEY);

// 1. 检查是否在删除集合中

if (toolsMap.isKeyExists(toolId)) {

// 简单优化:如果Map里没key,说明从未缓存过,查DB

}

Object cached = toolsMap.get(toolId);

if (cached != null) {

return JSON.parseObject(cached.toString(), ToolInfo.class);

}

// 2. 缓存空值防止穿透(可选策略)

return null; // 实际项目中应查DB并回填

}

}

```

注:上述 Lua 脚本仅为示意,生产环境建议使用 Redisson 的 RFuture 和更完善的异常处理。关键在于理解如何通过脚本将"数据更新"和"状态标记"绑定在一个事务原子操作中。

3. 方案对比分析

为什么选择 Redisson 的 RMapCache 而不是简单的 StringRedisTemplate?

| 特性 | StringRedisTemplate + Hash | Redisson RMapCache | 本地 Caffeine 缓存 |

| :--- | :--- | :--- | :--- |

| 数据结构 | 嵌套 Hash (String->Hash) | 原生分布式 Map | 进程内堆外内存 |

| 过期策略 | 需手动 EXPIRE 或脚本控制 | 内置 TTL,支持 Jitter | 支持基于访问/时间的淘汰 |

| 原子操作 | 需多次命令或 Watch/Multi | 支持 Lua 脚本原子性 | 线程安全,无网络开销 |

| 适用场景 | 简单键值对,低频更新 | 高频读写,复杂对象关联 | 本地热点数据,极低延迟需求 |

| 维护成本 | 低 | 中 | 低 |

对于 AI 工具站这种数据结构扁平(ID -> Info)但查询模式固定(ID 查询为主)的场景,Redisson 的分布式 Map 提供了更贴近 Java 集合的操作体验,同时保留了 Redis 的持久化和集群能力。

验证与常见问题

验证步骤:

  1. 启动 Spring Boot 应用,连接 Redis Cluster。
  2. 调用 syncToolToCache 写入 1000 个测试工具数据。
  3. 使用 JMeter 模拟 500 QPS 并发读取。
  4. 监控 Redis 监控面板,观察 Hit Ratio 应高于 95%。
  5. 调用删除接口,验证 getToolDetail 不再返回已下架工具。

常见报错解决:

  • OOM command not allowed when used memory > 'maxmemory': 确保配置了 maxmemory-policy allkeys-lru。Redisson 的 RMapCache 虽然支持 TTL,但如果未设置默认过期时间,会无限增长直到触发 OOM。务必在初始化时设置 setExpire() 或使用 Lua 脚本强制过期。
  • Connection refused: 检查防火墙策略,Redis 集群模式需要开放 6379 及 Cluster Bus (16379) 端口。

总结

构建高性能 AI 工具导航站的核心不在于 AI 模型本身,而在于底层数据服务的稳定性。通过 Spring Boot 3.4 结合 Redisson 3.35.0,我们实现了带原子删除标记的分布式缓存方案。这套架构成功将热点工具查询延迟从 2s 降至 5ms 以内,CPU 负载降低 60%。记住,缓存不是银弹,合理的 TTL 和原子性是避免数据不一致的关键。

#后端 #Java #SpringBoot #Redis #Redisson


你在实际项目中有遇到类似问题吗?欢迎在评论区分享你的经验和解决方案。

相关推荐
四六的六1 天前
一次改回去越界的沙箱复盘:配置恢复了,token 还活着,检测器漏了最关键的那条
人工智能·openai·agent·个人开发·ai编程·ai大模型·ai产品
小蒋观天下6 天前
厂区人员徘徊游荡判别——基于时序轨迹速度的逗留行为分级算法落地
大数据·人工智能·深度学习·算法·机器学习·ai大模型
小蒋观天下8 天前
【安防AI工程实战二】AI摄像头帧率抖动根治:ISP曝光时序与AI推理时序冲突深度定位
人工智能·深度学习·算法·安全·机器学习·ai大模型
for_ever_love__9 天前
LangChain MultiVectorRetriever:一个文档存多个向量
人工智能·python·自然语言处理·langchain·ai大模型·多向量·摘要检索
UBIX泛联11 天前
MLPerf 训练 Checkpoint 评测|泛联 UbiPower 18000,大参数模型读写带宽双登顶
ai大模型·mlperf storage·checkpoint 评测
小蒋观天下12 天前
专项方案:大场景港口AI安防、多干扰环境下的算法调优与落地实操
人工智能·深度学习·算法·安全·机器学习·计算机视觉·ai大模型
做个文艺程序员12 天前
MinIO第07篇:基于MinIO + Spring AI + pgvector构建RAG知识库(Java全栈实战)
minio·向量数据库·ai大模型·rag·springai·pgvector
四六的六12 天前
GPT-6 会自己画界面了:从写页面到定规则,前端在 Intelligent UI 时代的新活法
人工智能·个人开发·ai编程·ai大模型·组件库·ai产品·ui界面
小蒋观天下14 天前
端侧大模型在安防摄像头部署实操(上)|行业痛点、架构选型、落地思路解析
大数据·人工智能·安全·计算机视觉·ai大模型
n1121214 天前
大模型 API 报 429 和超时:限流、重试与降级该怎么写
ai大模型·大模型api