接口优化【缓冲,异步】

需求

在150万数据的表中(日增50万),按照不同维度进行字段统计数据。接口返回。
接口响应时间差不多6秒以上。
在这里插入图片描述
惨不忍睹
在这里插入图片描述

分析优化步骤

分析

分析从两个方面来分析接口

  • 查询个数
  • 数据实时性要求
  • SQL索引使用

查询接口性能优化

一个接口里尽量将数据查询减少到最低,最好就1个或者2个查询,不超过4个。这个查询是指DB查询或者第三方依赖接口查询。减少这样的查询,无非就是为了减少接口因为过多的查询,造成接口响应时间过大。那么有以下两种场景,根据实际场景选择合适的解决方案。
场景1:跨库查询多,即依赖于其他项目的接口多,可以选择使用并发查询,也就是使用线程池来减少接口总共所消耗的时间。原因在于,不涉及自己项目数据库连接池资源的竞争,并发查询可以有效地提高接口的响应能力。
场景2:本库查询多,可以选择使用缓存查询。比如将变动小的数据存放在redis缓存中;而变动较为频繁的数据,根据评估风险,可以选择将删除缓存和查询存放缓存进行配套使用,即在数据更新的时候对应删除缓存,保证查询的时候是最新的数据;原因在于,本库的查询较多,涉及到数据库连接池资源的竞争,如果使用并发查询也必须有空闲的数据库连接可用,否则并发查询也无太大效果。因此存放进缓存是最有效也是最合适的方式。
————————————————
版权声明:本文为CSDN博主「隐者自怡悦」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/YYQ_QYY/article/details/105996347

SQL索引使用

明确使用我们想要使用的SQL索引。数据库层面当作SQL最优处理。
在这里插入图片描述

查询个数

代码逻辑中,分为按维度查询,和总计查询。使用同步的方式调用SQL查询。分别用时2秒,3秒。
在这里插入图片描述

数据实时性要求

数据是查询前一天的数据。在不同类型查询全量数据的时候,数据的实时性要求很低。而且反馈接口慢的情况也只是查询全量数据数据。

优化

  1. 查询个数,同步耗时时间长。
    这里采用异步的方式调用两个查询。接口响应时间也差不多缩短了1/2.如果单纯只改变为异步调用的情况。响应时间为3秒左右,还是没办法接受。
  2. 数据缓冲
    由于全量数据查询(前一天数据已经落库,一般不进行修改),数据实时性要求并不高。所以在进行全量数据查询的时候进行缓冲处理。

目前的状态做到以上
第一次查询的时候肯定还是很慢(3s,异步处理之后的结果),之后全量查询查询全是走缓冲。

异步处理之后的结果【第一次查询】
在这里插入图片描述
缓冲处理之后结果
在这里插入图片描述

Mark

并发查询数据库连接池资源的竞争问题。第一次查询慢的问题。
都可以通过建立定时调度的。在每天数据库更新最新数据之后,调度全量查询接口,将数据进行缓冲。解决以上两个问题。

代码

异步调用

配置异步线程池 ExecutorConfig

package com.fang.industry.service.config;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@EnableAsync // 开启对异步任务的支持
@Configuration
public class ExecutorConfig implements AsyncConfigurer {

    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);

    @Autowired
    private BeanFactory beanFactory;

    @Override
    @Bean
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        threadPool.setCorePoolSize(20);
        // 设置最大线程数
        threadPool.setMaxPoolSize(1000);
        // 线程池所使用的缓冲队列
        threadPool.setQueueCapacity(10);
        // 等待任务在关机时完成--表明等待所有线程执行完
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
        threadPool.setAwaitTerminationSeconds(60);
        // 线程名称前缀
        threadPool.setThreadNamePrefix("MyAsync-");
        // 初始化线程
        threadPool.initialize();
        return new LazyTraceExecutor(beanFactory, threadPool);
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {

        return new MyAsyncExceptionHandler();
    }

    /**
     * 自定义异常处理类
     * 
     * @author hry
     *
     */
    class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

        // 手动处理捕获的异常
        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
            if (!"E.api.response.50001".equals(throwable.getMessage())) {
                System.out.println("-------------》》》捕获线程异常信息");
                logger.info("Exception message - " + throwable.getMessage());
                logger.info("Method name - " + method.getName());
                for (Object param : obj) {
                    logger.info("Parameter value - " + param);
                }
            }

        }
    }
}

注入异步线程池

  @Autowired
    private Executor executor;

使用方式

        /**
         * 异步调用在3秒左右
         */
        CompletableFuture<List<LandSupplyStatisticsDto>> listCompletableFuture = CompletableFuture.supplyAsync(() -> {
            // 指标获取的语句
            return landDataDao.getLandSupplyStatisticsAll(landSupplyStatistics);
        }, executor);


        CompletableFuture<List<LandSupplyStatisticsDto>> listCompletableFuture1 = CompletableFuture.supplyAsync(() -> {
            // 指标获取的语句
            return landDataDao.getLandSupplyStatistics(landSupplyStatistics);
        }, executor);
        try {
        //阻塞等待返回结果方式
            List<LandSupplyStatisticsDto> landSupplyStatisticsAll = listCompletableFuture.get();
            List<LandSupplyStatisticsDto> dtos = listCompletableFuture1.get();
            landSupplyStatisticsAll.addAll(dtos);
            return landSupplyStatisticsAll;
        } catch (InterruptedException e) {

        } catch (ExecutionException e) {

        }
        return null;

存在子线程太慢情况

使用join等待所有子线程运行结束

在这里插入图片描述

// join等待所有线程执行完成
CompletableFuture.allOf(uCompletableFuture, mapCompletableFuture, mapCompletableFuture1,mapCompletableFuture2,mapCompletableFuture3).join();

缓冲数据

注入缓冲

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

写入缓冲

/**
     * 全量数据按QueryType存缓存【支持1,3,4】
     * 第二天早晨9点过期
     * @author guochao.bj@fang.com
     * @date 2021/8/12
     */
    private void addCache(LandSupplyStatisticsBo bo,List<LandSupplyStatisticsDto> landSupplyStatisticsAll){
        String key="industry-effect-api-LandSupplyStatistics-"+bo.getQueryType();
        redisTemplate.opsForValue().set(key,landSupplyStatisticsAll,getSecondsNextEarlyMorning(), TimeUnit.SECONDS);
    }

从缓冲中取

/**
     * 根据类型从缓冲中获取全量数据
     * @author guochao.bj@fang.com
     * @date 2021/8/12
     */
    private List<LandSupplyStatisticsDto> getDataFromCache(LandSupplyStatisticsBo bo){
        String key="industry-effect-api-LandSupplyStatistics-"+bo.getQueryType();
        if (redisTemplate.opsForValue().get(key) == null) {
            return null;
        }else {
            List<LandSupplyStatisticsDto> landSupplyStatisticsDtos = (List<LandSupplyStatisticsDto>) redisTemplate.opsForValue().get(key);
            return landSupplyStatisticsDtos;
        }
    }

整体调用逻辑

 public List<LandSupplyStatisticsDto> getLandSupplyStatistics(LandSupplyStatisticsBo bo) {
        if (isFulfillCache(bo)){ //是否是全量数据缓冲
            List<LandSupplyStatisticsDto> dataFromCache = getDataFromCache(bo);     //从缓冲取
            if (dataFromCache!=null){   //缓冲中有,返回
                return dataFromCache; 
            }else {     //缓冲中没有,使用数据库查询,并存入缓冲
                List<LandSupplyStatisticsDto> landSupplyStatisticsFromDB = getLandSupplyStatisticsFromDB(bo);
                addCache(bo,landSupplyStatisticsFromDB);
                return landSupplyStatisticsFromDB;
            }
        }else {
            List<LandSupplyStatisticsDto> landSupplyStatisticsFromDB = getLandSupplyStatisticsFromDB(bo);
            return landSupplyStatisticsFromDB;
        }
    }

工具代码

  • 获取当前时间到第二天9点的秒数
    private static long getSecondsNextEarlyMorning(){
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DAY_OF_YEAR, 1);
        cal.set(Calendar.HOUR_OF_DAY, 9);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.MILLISECOND, 0);
        /**
        *验证使用,https://www.baidu.com/s?ie=UTF-8&wd=%E7%A7%92%E8%BD%AC%E6%97%B6
        */
        Date time = cal.getTime();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(simpleDateFormat.format(time));
        return (cal.getTimeInMillis() - System.currentTimeMillis()) / 1000;
    }
  • 判断字符串是否数字
/**
     * 判断字符串是否数字
     * @param str
     * @return boolean
     * @author guochao.bj@fang.com
     * @date 2021/8/12
     */
    public boolean isNumeric(String str) {
        Pattern pattern = Pattern.compile("^[-+]?(([0-9]+)([.]([0-9]+))?|([.]([0-9]+))?)$");
        Matcher isNum  = pattern.matcher(str);
        return isNum.matches();
    }
  • 获取前一天日期
/**
     * 获取前一天日期
     * @return java.lang.String
     * @author guochao.bj@fang.com
     * @date 2021/8/12
     */
    private String getYesterdayStr(){
        Date dNow = new Date();
        Date dBefore = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(dNow);
        calendar.add(Calendar.DAY_OF_MONTH, -1);
        dBefore = calendar.getTime();
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(dBefore);
    }

参考博客

Java8新的异步编程方式 CompletableFuture(一)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Abner G

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值