Kotlin + Spring Boot 如何为Map缓存添加TTL自动过期机制
Kotlin 实现带TTL自动过期的缓存Map方案
根据你的需求,分为两种常见场景提供对应实现:全局定时清空缓存、单条记录按写入时间自动过期。
场景1:全局每小时统一清空所有缓存
如果你只需要整份缓存每小时重置一次,实现最简单,不需要额外封装:
import java.util.concurrent.Executors import java.util.concurrent.TimeUnit private var labelsForGroupId: Map<GroupId, Label> = emptyMap() init { // 初始化定时任务,首次延迟1小时执行,之后每隔1小时执行一次清空 Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate( { labelsForGroupId = emptyMap() }, 1, 1, TimeUnit.HOURS ) }
场景2:单条记录写入后1小时单独过期
如果需要每条缓存按自己的写入时间计算过期时间,推荐以下三种实现:
方案A:无第三方依赖的轻量实现
不需要引入额外库,兼顾惰性清理(访问时判断过期)和定期清理(避免冷数据占内存):
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors import java.util.concurrent.TimeUnit class TTLCache<K, V>(private val ttlMillis: Long) { // 存储结构:键 -> (值, 过期时间戳) private val cache = ConcurrentHashMap<K, Pair<V, Long>>() init { // 启动定时清理任务,间隔为TTL时长 Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate( ::clearExpired, ttlMillis, ttlMillis, TimeUnit.MILLISECONDS ) } // 写入缓存 fun put(key: K, value: V) { cache[key] = value to (System.currentTimeMillis() + ttlMillis) } // 读取缓存,过期返回null fun get(key: K): V? { val (value, expireAt) = cache[key] ?: return null return if (System.currentTimeMillis() > expireAt) { cache.remove(key) null } else { value } } // 清理所有过期缓存 private fun clearExpired() { val now = System.currentTimeMillis() cache.entries.removeIf { now > it.value.second } } // 手动清空所有缓存 fun clearAll() { cache.clear() } }
使用方式:
// 初始化1小时TTL的缓存 private val labelsForGroupId = TTLCache<GroupId, Label>(3600 * 1000) // 写入缓存 labelsForGroupId.put(groupId, label) // 读取缓存 val label = labelsForGroupId.get(groupId)
方案B:JVM环境用Guava Cache(生产级实现)
如果允许引入第三方依赖,Guava Cache是经过工业验证的成熟缓存实现,自带TTL、容量限制、LRU淘汰等能力,不需要自己造轮子:
import com.google.common.cache.CacheBuilder import java.util.concurrent.TimeUnit private val labelsForGroupId = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) // 写入后1小时过期 .maximumSize(1000) // 可选:配置最大缓存容量,避免内存溢出 .build<GroupId, Label>()
使用方式:
// 写入 labelsForGroupId.put(groupId, label) // 读取,过期返回null val label = labelsForGroupId.getIfPresent(groupId)
方案C:Kotlin协程栈适配实现
如果你的项目用了Kotlin协程,可以用协程实现定时任务,更符合协程的生命周期管理:
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.concurrent.ConcurrentHashMap class CoroutineTTLCache<K, V>( private val ttlMillis: Long, private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO) ) { private val cache = ConcurrentHashMap<K, Pair<V, Long>>() init { scope.launch { while (true) { delay(ttlMillis) clearExpired() } } } fun put(key: K, value: V) { cache[key] = value to (System.currentTimeMillis() + ttlMillis) } fun get(key: K): V? { val (value, expireAt) = cache[key] ?: return null return if (System.currentTimeMillis() > expireAt) { cache.remove(key) null } else { value } } private fun clearExpired() { val now = System.currentTimeMillis() cache.entries.removeIf { now > it.value.second } } }
注意事项
- Android端开发建议关闭全局定时清理逻辑,仅保留访问时的惰性过期判断,避免后台线程长期运行导致耗电
- 如果对数据一致性要求高,可以在每次读取接口前判断缓存是否过期,过期后主动拉取新数据
- 高并发场景下推荐使用Guava Cache等成熟实现,避免自写缓存的并发安全问题
内容的提问来源于stack exchange,提问作者vukojevicf
相关产品推荐
相关产品推荐

