合理的使用缓存提升接口性能

简介: 合理的使用缓存提升接口性能

背景

接口优化,在微服务中调用了另一个服务的接口,这个接口提供一个类似词典的基础数据服务,信息更新又不频繁,对实时性要求不高,如果每次直接访问都去调用一次性能很差,而接口的底层还是每次去 DB 捞一次数据(理论上应该对这个接口进行优化,考虑到要怀疑第三方的态度,还是需要这种方法保护自己的服务不要因为依赖外部资源而导致的宕机),所以考虑对这个接口做一个 cache,理论上就可以大幅度提升接口性能了。

要解决的问题

  • 当缓存结果到期后,如果同时多个并发请求过来,如何避免这些请求都会重新去调用远程服务(读取 DB )来刷新缓存?如何确保只有一个请求线程会去真实的去调用远程服务(读取 DB),其他请求直接返回老值或等待返回结果?
  • 另一个问题就是更新线程还是会被阻塞,可能还会使响应时间变得不满足要求。
  • 当缓存 key 集体过期时,可能还会使响应时间变得不满足要求。

解决方案

缓存框架 guava cache 针对以上问题有相应的解决方案,分三步:

  1. 设置 refreshAfterWrite 缓存过期策略,在 guava cache 中设置缓存过期的策略有 refreshAfterWriteexpireAfterWrite ,两者的区别是 expireAfterWrite 到期会直接删除缓存,如果同时多个并发请求过来,这些请求都会重新去刷新缓存,会造成线程的阻塞。而 refreshAfterWrite,则不会删除缓存,只有一个请求线程会去执行缓存更新,其他请求直接返回老值。这样可以避免同时过期时大量请求被阻塞,从而提升性能。
  2. 当刷新过期时,开启一个新线程异步刷新,请求直接返回旧值,防止耗时过长。具体做法就是在 reload 方法里提交一个新的线程,就可以用这个线程来刷新 cache 了,如果刷新 cache 没有完成的时候有其他线程来请求该 key,则会直接返回旧值。
  3. 上面两步完成了不阻塞刷新缓存的功能,如果项目刚启动的时候,所有的缓存都是不存在,这个时候如果处理大批量请求过来,同样会被阻塞,因为没有旧的值供返回,都得等待缓存的第一次执行 load 完毕。所以,解决这个问题的方法就是在项目启动的过程中,将所有的缓存预先初始化完成,这样用户请求直接读缓存,不用等待缓存的第一次执行 load

总结:设置 refreshAfterWrite 缓存过期策略 ——> 后台线程异步刷新 ——> 初始化缓存

代码示例

package com.gemantic.finance.dw.repository;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * @author Yezhiwei
 * 2020/11/10
 */
@Component
@Slf4j
public class DictColumnWithCache {
  // 外部服务 
    @Resource
    private MetadataThemeRepository metadataThemeRepository;
 /**
 * 对外暴露的方法
 */
    public List<DictColumn> getDictColumn() throws ExecutionException {
        return cache.get("DictColumn");
    }
    protected List<DictColumn> getDictColumnFromDb() {
        ResponseEntity<Response<List<DictColumn>>> responseResponseEntity = metadataThemeRepository.selectAllColumn();
        List<DictColumn> data = responseResponseEntity.getBody().getData();
        return data;
    }
    ListeningExecutorService refreshPools =
            MoreExecutors.listeningDecorator(new ThreadPoolExecutor(10, 10,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>()));
    LoadingCache<String, List<DictColumn>> cache = CacheBuilder.newBuilder()
            .refreshAfterWrite(30, TimeUnit.MINUTES)
            .build(new CacheLoader<String, List<DictColumn>>() {
                @Override
                // 当本地缓存命没有中时,调用load方法获取结果并将结果缓存
                public List<DictColumn> load(String appKey) {
                    log.info("------------------- load newValue");
                    return getDictColumnFromDb();
                }
                @Override
    // 后台线程刷新,开启一个新线程异步刷新,老请求直接返回旧值,防止耗时过长
                public ListenableFuture<List<DictColumn>> reload(String key, List<DictColumn> oldValue) throws Exception {
                    log.info("=================== return oldValue");
                    refreshPools.submit(() -> getDictColumnFromDb());
                    return Futures.immediateFuture(oldValue);
                }
            });
    @PreDestroy
    public void destroy() {
        try {
            refreshPools.shutdown();
        } catch (Exception e) {
            log.error("thread pool showdown error ", e);
        }
    }
 /**
 * 初始化缓存
 */
    @PostConstruct
    public void initDictColumnWithCache() {
        try {
            getDictColumn();
        } catch (Exception e) {
            log.error("init dictColumn with cache error ", e);
        }
    }
}

合理的使用缓存提升接口性能。

如果觉得还有帮助的话,你的关注和转发是对我最大的支持,O(∩_∩)O:

相关文章
|
3月前
|
缓存 Java
Java本地高性能缓存实践问题之使用Caffeine的Cache接口来查找一个缓存元素的问题如何解决
Java本地高性能缓存实践问题之使用Caffeine的Cache接口来查找一个缓存元素的问题如何解决
|
3月前
|
SQL 缓存 开发框架
分享一个 .NET EF6 应用二级缓存提高性能的方法
分享一个 .NET EF6 应用二级缓存提高性能的方法
|
18天前
|
缓存 监控 测试技术
如何利用浏览器的缓存来优化网站性能?
【10月更文挑战第23天】通过以上多种方法合理利用浏览器缓存,可以显著提高网站的性能,减少网络请求,加快资源加载速度,提升用户的访问体验。同时,要根据网站的具体情况和资源的特点,不断优化和调整缓存策略,以适应不断变化的业务需求和用户访问模式。
61 7
|
1月前
|
缓存 JavaScript 前端开发
Vue 3的事件监听缓存如何优化性能?
【10月更文挑战第5天】随着前端应用复杂度的增加,性能优化变得至关重要。Vue 3 通过引入事件监听缓存等新特性提升了应用性能。本文通过具体示例介绍这一特性,解释其工作原理及如何利用它优化性能。与 Vue 2 相比,Vue 3 可在首次渲染时注册事件监听器并在后续渲染时重用,避免重复注册导致的资源浪费和潜在内存泄漏问题。通过使用 `watchEffect` 或 `watch` 监听状态变化并更新监听器,进一步提升应用性能。事件监听缓存有助于减少浏览器负担,特别在大型应用中效果显著,使应用更加流畅和响应迅速。
77 1
|
2月前
|
缓存 JavaScript 中间件
优化Express.js应用程序性能:缓存策略、请求压缩和路由匹配
在开发Express.js应用时,采用合理的缓存策略、请求压缩及优化路由匹配可大幅提升性能。本文介绍如何利用`express.static`实现缓存、`compression`中间件压缩响应数据,并通过精确匹配、模块化路由及参数化路由提高路由处理效率,从而打造高效应用。
153 10
|
2月前
|
缓存 监控 负载均衡
在使用CDN时,如何配置缓存规则以优化性能
在使用CDN时,如何配置缓存规则以优化性能
|
2月前
|
缓存 运维 NoSQL
二级缓存架构极致提升系统性能
本文详细阐述了如何通过二级缓存架构设计提升高并发下的系统性能。
123 12
|
2月前
|
缓存 NoSQL Java
揭秘性能提升的超级武器:掌握Hibernate二级缓存策略!
【9月更文挑战第3天】在软件开发中,性能优化至关重要。使用Hibernate进行数据持久化的应用可通过二级缓存提升数据访问速度。一级缓存随Session生命周期变化,而二级缓存是SessionFactory级别的全局缓存,能显著减少数据库访问次数,提高性能。要启用二级缓存,需在映射文件或实体类上添加相应配置。然而,并非所有场景都适合使用二级缓存,需根据业务需求和数据变更频率决定。此外,还可与EhCache、Redis等第三方缓存集成,进一步增强缓存效果。合理运用二级缓存策略,有助于大幅提升应用性能。
87 5
|
3月前
|
存储 缓存 分布式计算
如何在 PySpark 中缓存数据以提高性能?
【8月更文挑战第13天】
162 8
|
3月前
|
缓存 NoSQL Redis
一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)
这篇文章是关于Java面试中可能会遇到的五个问题,包括MySQL索引类型及其对数据库性能的影响、Redis的RDB和AOF持久化机制、Redis的过期键删除策略、Redis的单线程模型为何高效,以及缓存雪崩、缓存穿透和缓存击穿的概念及其解决方案。