← 返回文章列表

backend项目架构设计 - 请求级缓存

分类:计算机/架构/backend项目架构设计

在开发一个复杂的业务计算需求时,遇到如下问题:业务计算过程很复杂,需要拆分为很多步骤,每个步骤又需要拆分为多个不同职责的类,计算时,这些类之间需要共享很多数据。如果通过方法参数传递,则参数会很多,非常繁琐。如果通过中间数据结构传递,则每个类所需的共享数据不同,中间数据结构不好统一。

解决思路:不传递共享数据,而是每个类自己去获取,需要什么就获取什么,参数只保留最必要的,这样每个类的独立性大大增强,更简洁、清晰。显然,这里有个问题,大量重复获取同一个数据,会严重降低效率。我们可以用缓存解决这个问题,对于同一个数据,只在第一次获取时查库,后续直接用缓存。

在我们的业务场景中,数据库的数据可能被多个来源修改,那么,缓存淘汰策略就是问题。如果修改全部在单一项目进行,那么,可以采用修改时主动淘汰的策略,这样效率最高,显然我们的业务场景不满足。另一种常用的策略是基于超时,但超时时间设多长不好定,短了影响性能,长了影响一致性,每种数据的修改频率也不同,单独设置很繁琐。

考虑到,一次请求的时间非常短(通常几十~几百毫秒),在此过程中,相同的参数几乎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();
    }
}