性能优化-常见方案

索引,并行,异步,分片,批量,集合转map,池化,预先分配
  1. 批量

    为什么网络请求小包合并成大包会提高性能?主要原因有两个:

    1. 减少无谓的请求头,如果你每个请求只有几字节,而头却有几十字节,无疑效率非常低下。
    2. 减少回复的ack包个数。把请求合并后,ack包数量必然减少,确认和重发的成本就会降低。

    参考:消息队列设计精要

  2. 降低锁的粒度

  3. 降低事务范围,可以使用编程式事务

    非DB操作不放在事务中
    
  4. 非核心逻辑异步化

    区分出核心、非核心逻辑,将核心逻辑同步处理,非核心逻辑进行异步化,主要有以下两种方式
    1. 线程池异步化。如果失败采用延迟失败处理,失败一定次数可以发送消息到mq中处理,或者定时任务处理
    2. mq
    
  5. 都是核心业务

    CF并行处理
    
  6. 自动分页查询

    @Test
    public void test08() {
    
        ExecutorService executorService = Executors.newFixedThreadPool(10);
    
        // 每次查询条数
        int pageSize = 100;
        // 页码
        int pageNo = 1;
    
        List<TbUserInfo> tbUserInfos;
        while (true) {
    
            PageRequest pageRequest = new PageRequest();
            pageRequest.setPageNo(pageNo);
            pageRequest.setPageSize(pageSize);
            tbUserInfos = this.userInfoMapper.queryPagePhysics(pageRequest);
    
            if (CollectionUtils.isEmpty(tbUserInfos)) {
                break;
            }
    
            // 业务处理
            final List<TbUserInfo> finalTbUserInfos = tbUserInfos;
            executorService.execute(() -> {
                System.out.println("tbUserInfos = " + finalTbUserInfos);
            });
    
            // 如果当前页查询的数据没拉满,则表示是最后一页
            if (tbUserInfos.size() < pageSize) {
                break;
            }
    
            pageNo++;
        }
    
    }
    

    案例:

    导出订单查询结果的所有商品信息

    功能描述:

    订单查询是调用es接口查询的,可以理解为是透传的查询功能,分页查询很好实现。
    订单商品明细导出是导出符合条件查询的所有商品信息,这个不难导出,难点是如何优化导出大量数据时的阻塞问题
    
    针对导出商品明细功能优化
    使用分页配合CountDownLatch+线程池异步处理,优化查询结果慢的问题
    
    步骤如下
    1. 查询总条数,计算共多少页
    2. 使用CompletableFuture配合CountDownLatch将每次查到的数据放在List中,最后再将总结果集导出
    

    伪代码如下

    public static void main(String[] args) throws InterruptedException {
    
        ExecutorService executorService = Executors.newFixedThreadPool(100);
    
        // 总条数
        int total = 100_0000;
        // 分页大小
        int batchNum = 2000;
        // 总页数
        int totalPage = total % batchNum == 0 ? total / batchNum : total / batchNum + 1;
    
        CountDownLatch countDownLatch = new CountDownLatch(totalPage);
    
        for (long i = 0; i < totalPage; i++) {
            long currPage = i;
            executorService.execute(() -> {
                // 执行分页查询
                log.info(String.format("第%s页,每页%s条", currPage, batchNum));
                countDownLatch.countDown();
            });
        }
    
        // 主线程等待子线程执行完成
        countDownLatch.await();
        System.out.println("执行完成");
    }
    
  7. 异步+分片批处理

    public class PoolTest {
    
        private static ThreadPoolExecutor threadPoolExecutor;
    
        static {
            threadPoolExecutor = new ThreadPoolExecutor(10,
                    20,
                    1,
                    TimeUnit.MINUTES,
                    new LinkedBlockingQueue<>(100),
                    new CustomizableThreadFactory("某业务线程池"),
                    new ThreadPoolExecutor.CallerRunsPolicy()); 
            threadPoolExecutor.prestartAllCoreThreads();
            threadPoolExecutor.allowCoreThreadTimeOut(true);
        }
    
        public static void main(String[] args) {
    
            List<Integer> nums = Arrays.asList(1, 2, 2, 3, 4, 5, 6);
    
            List<List<Integer>> partition = Lists.partition(nums, 5);
    
            partition.stream().map(innerList -> {
                return CompletableFuture.runAsync(() -> {
                    System.out.println(innerList);
                }, threadPoolExecutor);
            }).collect(Collectors.toList()).forEach(CompletableFuture::join);
    
        }
    
    }
    

接口性能优化的 15 个技巧

从20s优化到500ms,我用了这三招

使用多线程的问题

  1. 大数据导入、导出优化

导入导出都使用EasyExcel处理,因为它重写了POI的对excel的解析,使用更少的内存能读到更多的数据。

导出优化:

方案一:异步化处理,即主线程响应请求,子线程处理具体的任务,让导出的文件上传文件服务器里或者oss里,去另外一个页面查询

方案一:自动分页查询,将每次分页查询的结果写到excel中

导入优化:

分批次 + MyBatis批量插入 + MyBatis批量插入异步化处理

百万数据的导入导出解决方案

MySQL优化

  • 慢查询与走不走索引没有必然关系,关键还要看扫描的行数rows,扫描行数越少那么效率肯定也会越高
  1. 串行转并行

    使用CompletableFuture.allOf(a,b,c).join();
    
  2. 空间换时间:

    针对for循环查询库,前置查询再通过toMap/groupingBy分组

    需求:

    将遍历查询改为一次查询转map
    比如:现要查询指定班级1、2、3下所有的学生
    班级表属性:班级id、班级名称
    学生表属性:学生id,学生姓名、归属班级id
    

    实现步骤:

    1. 先获取指定班级信息 List<Classes> classes
    2. 获取所有指定班级中的学生 List<Student> stus
    3. 对 List<Student> stus使用班级id进行toMap,得到各个班级下的所有的学生Map<String,List<Student>> students
    4. 将students中分组的数据塞到A中
    
  3. 业务条件限制,比如分页查询参数要求必填某项条件、限制只能查询最近几个月的

  4. 业务上限制-->索引优化-->多线程并行调用-->分批次多线程并行调用

    性能优化-如何爽玩多线程来开发本篇给读者带来切实可行的多线程代码套路,完整代码复制可用,流程图以及亮点详解,给你的项目增 - 掘金 (juejin.cn)

  5. 数据冗余

    将业务中需要从不同接口查到的数据放在同一个地方,放缓存中后边需要这些数据直接从缓存中拿数据
    

    弊端:存在数据不一致性问题

参考

田螺-用了这18种方案,接口性能提高了100倍!

两万字的性能优化指南!39个策略提升接口性能!

posted @ 2025-11-11 21:24  永无八哥  阅读(0)  评论(0)    收藏  举报