You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Boot中选择性清理Redis缓存?

解决方案建议

针对你遇到的问题,这里提供几种高效且可行的方案,避免遍历全量缓存的开销:

1. 维护反向索引缓存(推荐)

核心思路是额外维护一个**<MyPojoId, Set>**的缓存,记录每个MyPojo对应的所有用户ID。这样当MyPojo的code字段修改时,直接通过MyPojoId取出关联的UserId集合,批量清除对应的用户缓存即可。

实现示例:

首先定义两个缓存空间:

  • userPojos:存储<UserId, MyPojo>的主缓存
  • pojoToUsers:存储<MyPojoId, Set>的反向索引缓存

主缓存查询与反向索引同步

@Cacheable(value = "userPojos", key = "#userId")
public MyPojo getPojoForUser(String userId) {
    MyPojo pojo = // 从数据库/其他数据源获取数据
    // 同步更新反向索引
    updatePojoToUsers(userId, pojo);
    return pojo;
}

@CachePut(value = "pojoToUsers", key = "#pojo.id")
public Set<String> updatePojoToUsers(String userId, MyPojo pojo) {
    Cache cache = cacheManager.getCache("pojoToUsers");
    Set<String> userIds = cache.get(pojo.getId(), Set.class);
    if (userIds == null) {
        userIds = new HashSet<>();
    }
    userIds.add(userId);
    return userIds;
}

处理code修改时的缓存清除

public void handlePojoCodeUpdate(MyPojo updatedPojo) {
    Cache pojoCache = cacheManager.getCache("pojoToUsers");
    Set<String> userIds = pojoCache.get(updatedPojo.getId(), Set.class);
    
    if (userIds != null) {
        // 批量清除用户缓存
        Cache userCache = cacheManager.getCache("userPojos");
        userIds.forEach(userCache::evict);
        // 清除该MyPojo对应的反向索引(可选,根据业务是否需要保留)
        pojoCache.evict(updatedPojo.getId());
    }
}

缓存一致性补充

如果用户缓存可能被主动清除或过期,需要同步更新反向索引:可以通过监听Spring的CacheEvictEvent事件,当userPojos缓存的键被清除时,取出对应的MyPojo,从pojoToUsers中移除该UserId。

2. 利用缓存原生特性(以Redis为例)

如果使用Redis作为缓存实现,可以通过Lua脚本配合SCAN命令,渐进式地查找并删除符合条件的缓存键,避免全量遍历内存的开销。

Lua脚本示例(evict_pojo_users.lua)

local cursor = "0"
repeat
    -- 渐进式扫描userPojos前缀的缓存键
    local result = redis.call('SCAN', cursor, 'MATCH', 'userPojos::*', 'COUNT', 100)
    cursor = result[1]
    local keys = result[2]
    
    for _, key in ipairs(keys) do
        local pojoStr = redis.call('GET', key)
        local pojo = cjson.decode(pojoStr)
        -- 匹配目标MyPojoId
        if pojo.id == ARGV[1] then
            redis.call('DEL', key)
        end
    end
until cursor == "0"

Spring中调用脚本

@Autowired
private StringRedisTemplate redisTemplate;

public void evictUsersByPojoId(Long pojoId) {
    DefaultRedisScript<Void> evictScript = new DefaultRedisScript<>();
    evictScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("evict_pojo_users.lua")));
    // 执行Lua脚本,传入目标MyPojoId
    redisTemplate.execute(evictScript, Collections.emptyList(), pojoId.toString());
}

3. 调整缓存键设计(业务允许时)

如果业务场景允许,可以将缓存键设计为**#{userId}::#{pojo.id}**,这样当MyPojo的code修改时,直接通过通配符清除所有以*::#{pojo.id}结尾的键(部分缓存支持通配符删除,如Redis)。但这种方案会改变原有的缓存键结构,需要评估对现有业务的影响。


内容的提问来源于stack exchange,提问作者ooomid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 07:10:47