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

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

背景

接口优化,在微服务中调用了另一个服务的接口,这个接口提供一个类似词典的基础数据服务,信息更新又不频繁,对实时性要求不高,如果每次直接访问都去调用一次性能很差,而接口的底层还是每次去 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:

相关文章
|
2月前
|
存储 缓存 自然语言处理
深入PHP内核:理解Opcode缓存对性能的影响
【4月更文挑战第25天】 在提升PHP应用性能的众多策略中,Opcode缓存技术因其显著的效果和较低的复杂度而备受开发者青睐。本文将深入探讨Opcode缓存机制,解析其对PHP执行效率的提升原理,并通过实验数据展示启用Opcode缓存前后的性能差异。我们还将讨论几种流行的Opcode缓存工具,如APC、OpCache与APCu,并评估它们的优劣及适用场景,帮助开发者根据不同的项目需求做出合适的选择。通过本文,读者不仅能够了解Opcode缓存的工作原理,还能学会如何在实际项目中应用这一技术以优化PHP应用程序的性能。
|
2月前
|
缓存 数据库 索引
如何优化Python Web应用的性能,包括静态资源加载、缓存策略等?
```markdown 提升Python Web应用性能的关键点:压缩合并静态资源,使用CDN,设置缓存头;应用和HTTP缓存,ETag配合If-None-Match;优化数据库索引和查询,利用数据库缓存;性能分析优化代码,避免冗余计算,使用异步处理;选择合适Web服务器并调整参数;部署负载均衡器进行横向扩展。每一步都影响整体性能,需按需调整。 ```
29 4
|
2月前
|
存储 缓存 算法
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
375 0
|
2月前
|
存储 缓存 前端开发
揭秘Web缓存:提升网站性能与用户体验
揭秘Web缓存:提升网站性能与用户体验
|
1月前
|
存储 缓存 对象存储
合理地处理不需要的缓存
【6月更文挑战第8天】本文介绍了管理缓存数据过期的重要性,以避免内存浪费和过时信息的使用。缓存系统通常允许设置默认过期策略或为每个对象指定绝对或滑动过期时间。缓存服务常使用LRU策略进行逐出,但过度使用可能导致内存超出异常。
33 10
合理地处理不需要的缓存
|
26天前
|
监控 Python 缓存
缓存系统提升Web应用性能
【6月更文挑战第22天】
22 0
|
2月前
|
存储 缓存 监控
中间件应用合理使用缓存和数据结构
【5月更文挑战第4天】中间件应用合理使用缓存和数据结构
42 3
中间件应用合理使用缓存和数据结构
|
2月前
|
缓存 JavaScript 前端开发
Vue 3的事件监听缓存如何优化性能?
【5月更文挑战第31天】Vue 3的事件监听缓存如何优化性能?
98 1
|
2月前
|
缓存 NoSQL Java
用好缓存,让你的接口速度飞起来
本文是关于接口性能优化,特别是通过缓存来提升接口响应速度的探讨。作者是一名有六年经验的Java后端开发者,分享了自己避免线上系统因代码崩溃造成资损的经验,主要归功于业务的简单性、遵循代码规约和积累的实用技巧。文章重点讲解了缓存的两个方面:缓存预热(包括定时任务和启动预热)和缓存层次化(多级缓存和热点数据缓存),并提供了如何用代码实现这些思路的示例。作者还介绍了自定义缓存处理器的设计和实现,包括接口和抽象类的定义,以及使用函数式编程实现的缓存查询模板。最后提醒,缓存虽有益但需谨慎使用,应根据业务需求和数据特征定制策略。
92 1
|
2月前
|
存储 缓存 Java
探秘MyBatis缓存原理:Cache接口与实现类源码分析
探秘MyBatis缓存原理:Cache接口与实现类源码分析
49 2
探秘MyBatis缓存原理:Cache接口与实现类源码分析