在开发一个复杂的业务计算需求时,遇到如下问题:业务计算过程很复杂,需要拆分为很多步骤,每个步骤又需要拆分为多个不同职责的类,计算时,这些类之间需要共享很多数据。如果通过方法参数传递,则参数会很多,非常繁琐。如果通过中间数据结构传递,则每个类所需的共享数据不同,中间数据结构不好统一。
解决思路:不传递共享数据,而是每个类自己去获取,需要什么就获取什么,参数只保留最必要的,这样每个类的独立性大大增强,更简洁、清晰。显然,这里有个问题,大量重复获取同一个数据,会严重降低效率。我们可以用缓存解决这个问题,对于同一个数据,只在第一次获取时查库,后续直接用缓存。
在我们的业务场景中,数据库的数据可能被多个来源修改,那么,缓存淘汰策略就是问题。如果修改全部在单一项目进行,那么,可以采用修改时主动淘汰的策略,这样效率最高,显然我们的业务场景不满足。另一种常用的策略是基于超时,但超时时间设多长不好定,短了影响性能,长了影响一致性,每种数据的修改频率也不同,单独设置很繁琐。
考虑到,一次请求的时间非常短(通常几十~几百毫秒),在此过程中,相同的参数几乎100%会返回相同的数据。因此,我们可以实现请求级缓存机制。在请求级缓存机制中,一个缓存的生命周期为,从第一次查询开始,到请求执行结束。
基于这种机制去实现复杂的业务计算需求,可以同时保证代码简洁性(减少共享数据传递)、高效率(只查一次,效率与传递共享数据相同)和数据一致性(极短时间内认为数据不变化)。
参考代码:
// 缓存管理器
public class RequestLevelCacheManager implements CacheManager {
private final ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
/**
* 获取指定请求线程中指定名称的缓存
* @param thread 请求线程
* @param name 缓存名称
* @return 缓存
*/
public Cache getCache(Thread thread, String name) {
return cacheManager.getCache(thread.threadId() + ":" + name);
}
/**
* 获取当前请求线程中指定名称的缓存
* @param name 缓存名称
* @return 缓存
*/
@Override
public Cache getCache(String name) {
return getCache(Thread.currentThread(), name);
}
/**
* 获取缓存管理器中的所有缓存名称
* @return 缓存名称集合
*/
@Override
public Collection<String> getCacheNames() {
return cacheManager.getCacheNames();
}
/**
* 清除指定请求线程的缓存
* @param thread 请求线程
*/
public void clearCache(Thread thread) {
// 缓存管理器中的所有缓存名称
cacheManager.getCacheNames().stream()
// 筛选出当前请求线程的所有缓存名称
.filter(name -> name.startsWith(thread.threadId() + ":"))
// 获取缓存名称对应的缓存
.map(cacheManager::getCache)
// 筛选出非null的缓存
.filter(Objects::nonNull)
// 对缓存执行清除
.forEach(Cache::clear);
}
/** 清除当前请求线程的缓存 */
public void clearCache() {
clearCache(Thread.currentThread());
}
}
// 缓存管理器配置
@Configuration
public class CacheConfig {
// 将RequestLevelCacheManager设为默认的缓存管理器
@Bean
@Primary
public RequestLevelCacheManager requestLevelCacheManager() {
return new RequestLevelCacheManager();
}
}
// 像其他类型的缓存一样使用
@Cacheable("cacheName")
// 请求拦截器,需要注册到`/**`路径
@Component
public class RequestLevelCacheInterceptor implements HandlerInterceptor {
@Autowired
private RequestLevelCacheManager requestLevelCacheManager;
/** 在请求执行结束时,清除请求级缓存 */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
requestLevelCacheManager.clearCache();
}
}