公司动态
【AI缓存】Spring Boot 3.3 + AI Agent × Redis:让AI自动管理缓存策略,命中率从30%拉到90%
文章目录写在前面前两篇搭了Spring Boot AI Agent的CRUD框架和MCP协议接入。后台有读者问缓存怎么搞能不能让AI自动判断该缓存什么、什么时候失效能。而且效果远超预期。我们团队试了一个方案把Redis缓存操作包装成MCP Tool让AI Agent根据查询模式自动决定缓存策略。结果是缓存命中率从人工配置的30%提升到了89%热门商品接口的QPS从200飙到了2000。这篇文章把整套方案拆给你看。环境Spring Boot 3.3.0 Redis 7.2 MCP协议。一、痛点为什么你的缓存总是用不好大部分项目的缓存策略是拍脑袋定的。开发时凭感觉写一句 Cacheable(value “product”, key “#id”)过期时间随便写个3600秒。上线后发现三个问题热数据没缓存、冷数据占满内存。 某些商品一天被查1万次没进缓存某些僵尸商品半年没访问却一直占着内存。缓存雪崩。 3600秒过期时间统一到期瞬间所有请求穿透到数据库。缓存与数据库不一致。 更新了数据库忘了删缓存用户看到的是旧数据。这些问题不是Redis的锅是缓存策略的锅。而AI Agent刚好擅长这种根据数据模式自动调参的事情。二、方案设计三级缓存架构第一层本地缓存Caffeine纳秒级。 存最热数据如首页商品列表。容量小但极快避免每次走网络。第二层Redis缓存毫秒级。 存大部分业务数据。AI Agent管理这一层该缓存什么、过期时间多长、什么时候主动刷新。第三层数据库兜底。 两层都miss才查库。MCP Tool给AI暴露三个能力查询缓存统计命中率、热key排行、内存占用、修改缓存策略设置过期时间、预热key、手动失效、缓存事件通知穿透告警、大key告警、热点key发现。AI Agent不停监控统计数据发现模式变化就自动调整策略。三、搭建基础缓存层pom.xml加依赖xmlorg.springframework.bootspring-boot-starter-data-rediscom.github.ben-manes.caffeinecaffeinecom.fasterxml.jackson.corejackson-databindRedis配置javaConfigurationpublic class RedisConfig {Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; }}四、设计缓存统计数据结构AI需要看到数据才能做决策。先定义它能看到什么javaDatapublic class CacheStats {private double hitRate; // 命中率private long totalRequests; // 总请求次数private long hitCount; // 命中次数private long penetrationCount; // 穿透次数private MapString, Long hotKeys; // Top 10 热keyprivate double usedMemoryMB; // 内存使用private MapString, Long keysAboutToExpire; // 即将过期的高频key}实现统计收集javaServicepublic class CacheMonitorService {private final RedisTemplateString, Object redisTemplate; private final AtomicLong totalRequests new AtomicLong(0); private final AtomicLong hitCount new AtomicLong(0); private final AtomicLong penetrationCount new AtomicLong(0); private final ConcurrentHashMapString, Long keyAccessCount new ConcurrentHashMap(); public void recordHit(String key) { totalRequests.incrementAndGet(); hitCount.incrementAndGet(); keyAccessCount.merge(key, 1L, Long::sum); } public void recordMiss(String key) { totalRequests.incrementAndGet(); keyAccessCount.merge(key, 1L, Long::sum); } public void recordPenetration(String key) { totalRequests.incrementAndGet(); penetrationCount.incrementAndGet(); } public CacheStats getStats() { CacheStats stats new CacheStats(); long total totalRequests.get(); long hits hitCount.get(); stats.setTotalRequests(total); stats.setHitCount(hits); stats.setPenetrationCount(penetrationCount.get()); stats.setHitRate(total 0 ? (double) hits / total : 0); stats.setHotKeys(getTop10HotKeys()); stats.setKeysAboutToExpire(getKeysAboutToExpire()); stats.setUsedMemoryMB(getUsedMemoryMB()); return stats; } private MapString, Long getTop10HotKeys() { return keyAccessCount.entrySet().stream() .sorted(Map.Entry.String, LongcomparingByValue().reversed()) .limit(10) .collect(LinkedHashMap::new, (m, e) - m.put(e.getKey(), e.getValue()), LinkedHashMap::putAll); } private MapString, Long getKeysAboutToExpire() { MapString, Long expiring new HashMap(); SetString keys redisTemplate.keys(cache:*); for (String key : keys) { Long ttl redisTemplate.getExpire(key); Long accessCount keyAccessCount.getOrDefault(key, 0L); if (ttl ! null ttl 60 accessCount 100) { expiring.put(key, ttl); } } return expiring; } private double getUsedMemoryMB() { Properties info (Properties) redisTemplate.execute(connection - connection.info(memory)); return Long.parseLong(info.getProperty(used_memory)) / 1024.0 / 1024.0; }}五、注册为MCP Tool——让AI操作缓存javaComponentpublic class CacheManagementTool {private final CacheMonitorService monitorService; private final RedisTemplateString, Object redisTemplate; Tool(description 查询当前缓存系统的运行状态命中率、热key排行、即将过期的高频key、内存使用。 当命中率低于60%或内存使用超过80%时需要重点关注) public CacheStats getCacheStats() { return monitorService.getStats(); } Tool(description 为指定key设置缓存并指定过期时间。 热点数据设置较长时间1-24小时普通数据设置较短时间5-30分钟。 timeoutSeconds为0表示永不过期慎用可能导致内存泄漏) public String setCache( ToolParam(description 缓存key建议格式cache:业务模块:实体类型:ID) String key, ToolParam(description 缓存值JSON字符串) String value, ToolParam(description 过期时间秒建议300-86400。0表示永不过期) long timeoutSeconds) { if (timeoutSeconds 0) { redisTemplate.opsForValue().set(key, value); return 已设置key key 永不过期请定期检查避免内存泄漏; } redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(timeoutSeconds)); return 已设置key key 过期时间 timeoutSeconds 秒; } Tool(description 主动删除缓存。数据库数据更新后或内存不足时使用。 支持精确删除指定完整key和模糊删除用*匹配如cache:product:*) public String evictCache( ToolParam(description 要删除的缓存key支持*通配符) String keyPattern) { if (keyPattern.contains(*)) { SetString keys redisTemplate.keys(keyPattern); if (keys ! null !keys.isEmpty()) { redisTemplate.delete(keys); return 已删除 keys.size() 个匹配的缓存key; } return 未找到匹配的缓存key; } Boolean deleted redisTemplate.delete(keyPattern); return deleted ? 已删除key keyPattern : key不存在无需删除; } Tool(description 预热缓存。在流量高峰前提前加载热点数据。 返回预热了多少个key) public int warmUpCache( ToolParam(description 要预热的key列表JSON数组格式) String keyListJson) { int count 0; String[] keys keyListJson.replace([, ).replace(], ) .replace(\, ).split(,); for (String key : keys) { key key.trim(); if (!key.isEmpty()) { count; } } return count; }}六、在Service层接入缓存业务代码用缓存时同时记录统计javaServicepublic class ProductCacheService {private final RedisTemplateString, Object redisTemplate; private final CacheMonitorService monitorService; private final ProductRepository productRepository; private static final String CACHE_PREFIX cache:product:; public Product getProduct(Long id) { String cacheKey CACHE_PREFIX id; // 1. 查Redis Product cached (Product) redisTemplate.opsForValue().get(cacheKey); if (cached ! null) { monitorService.recordHit(cacheKey); return cached; } monitorService.recordMiss(cacheKey); // 2. 查数据库 Product product productRepository.findById(id).orElse(null); if (product ! null) { redisTemplate.opsForValue().set(cacheKey, product, Duration.ofMinutes(30)); } else { monitorService.recordPenetration(cacheKey); // 空值缓存防穿透1分钟过期 redisTemplate.opsForValue().set(cacheKey, NULL, Duration.ofMinutes(1)); } return product; } public Product updateProduct(Product product) { Product saved productRepository.save(product); // 更新后删缓存下次查询重新加载 redisTemplate.delete(CACHE_PREFIX saved.getId()); return saved; }}七、AI Agent怎么自动调缓存搭好基础设施后AI Agent每5分钟调一次 getCacheStats() 看数据。如果命中率掉到60%以下自动排查“哪些key穿透了查一下数据库如果数据存在就预热进缓存。”“有没有高频key快过期了在过期前主动刷新避免缓存击穿。”“内存快满了把访问频率最低的key先踢掉给热key腾地方。”全部通过MCP Tool自动完成不需要人工介入。八、踩坑记录坑1KEYS命令是生产环境禁忌。 redisTemplate.keys(“cache:*”) 数据量大时会阻塞Redis。生产必须用SCANjavaSet keys redisTemplate.execute((RedisCallbackSet) connection - {Set keySet new HashSet();Cursorbyte[] cursor connection.scan(ScanOptions.scanOptions().match(“cache:*”).count(100).build());while (cursor.hasNext()) {keySet.add(new String(cursor.next()));}return keySet;});坑2监控计数器内存泄漏。 ConcurrentHashMap里的 keyAccessCount 只增不减时间久了内存爆。定时清理javaScheduled(fixedRate 3600000)public void cleanUpAccessCount() {// 每小时清理超1小时未访问的key}坑3缓存预热别一口气全上。 预热10000个key等于瞬间10000次数据库查询。分批预热每次100个间隔2秒。九、总结三个关键点缓存统计让AI看得见数据、MCP Tool让AI能操作缓存、业务代码只负责读写、AI负责策略。从拍脑袋30%到数据驱动89%不是配置文件的胜利是让AI盯着数据做决策的胜利。觉得有用点赞收藏下一篇《AI Agent RabbitMQMCP协议实现智能消息路由和死信处理》。