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

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

背景

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

相关文章
|
6天前
|
存储 缓存 自然语言处理
深入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服务器并调整参数;部署负载均衡器进行横向扩展。每一步都影响整体性能,需按需调整。 ```
21 4
|
2月前
|
存储 缓存 算法
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
120 0
|
3月前
|
存储 缓存 UED
缓存策略与Apollo:优化网络请求性能
缓存策略与Apollo:优化网络请求性能
|
8月前
|
存储 缓存 数据库
优化性能与减少资源浪费:深入了解缓存策略
缓存策略是现代Web开发中关键的优化技术之一,它可以显著提高网站性能,降低服务器负载,并减少用户等待时间。在本博客中,我们将深入研究缓存策略的概念、不同类型的缓存和如何在项目中实施它们。
71 0
|
6天前
|
SQL 缓存 数据库
在Python Web开发过程中:数据库与缓存,如何使用ORM(例如Django ORM)执行查询并优化查询性能?
在Python Web开发中,使用ORM如Django ORM能简化数据库操作。为了优化查询性能,可以:选择合适索引,避免N+1查询(利用`select_related`和`prefetch_related`),批量读取数据(`iterator()`),使用缓存,分页查询,适时使用原生SQL,优化数据库配置,定期优化数据库并监控性能。这些策略能提升响应速度和用户体验。
8 0
|
18天前
|
存储 缓存 自动驾驶
缓存策略与Apollo:优化网络请求性能
缓存策略与Apollo:优化网络请求性能
|
2月前
|
存储 缓存 监控
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(场景问题分析+性能影响因素)
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(场景问题分析+性能影响因素)
36 0
|
2月前
|
缓存 NoSQL Java
手撸的 SpringBoot缓存系统,性能杠杠的
手撸的 SpringBoot缓存系统,性能杠杠的
28 0
|
2月前
|
存储 缓存 算法
深入探究LRU缓存机制:优化内存利用与提升性能
深入探究LRU缓存机制:优化内存利用与提升性能
159 1