开发中接口优化方向,持续更新

1.重复调用

  • mysql
        // 反例:表连接ids.size次
        List<Project> projects = CollUtil.newArrayList();
        for (Long id : ids) {
            Project project = projectService.getById(id);
            projects.add(project);
            // 业务处理
        }

        // 正例:用in或者or批量查询,交互一次
        List<Project> projects1 = projectService.listByIds(ids);
  • redis
        // keys
        List<Object> totalPowerMeasIds = allInvCalcAttrMeasIdListMap.get("totalPMeasIds");

        // 反例:单个key查询
        List<Object> objectList = CollUtil.newArrayList();
        totalPowerMeasIds.forEach(e->{
            Object obj = redisTemplate.opsForHash().get(Constants.REAL_TIME_TABLE, e);
            objectList.add(obj);
            // 业务处理
        });
        
        // 正例:key集合批量查询
        List<Object> totalPowerList = redisTemplate.opsForHash().multiGet(Constants.REAL_TIME_TABLE, totalPowerMeasIds);
  • 死循环
  • 递归

2.异步

有个用户请求接口中,需要做业务操作,发站内通知,和记录操作日志。为了实现起来比较方便,通常我们会将这些逻辑放在接口中同步执行,势必会对接口性能造成一定的影响。

  1. SpringBoot 注解 @Async;
  2. JDK1.8 中,Java 提供了 CompletableFuture 类,它是基于异步函数式编程。相对阻塞式等待返回结果,CompletableFuture 可以通过回调的方式来处理计算结果,实现了异步非阻塞,性能更优:
    CompletableFuture 中常用api:
  • 依赖关系
  thenApply():把前面任务的执行结果,交给后面的Function
  thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回
  • and集合关系

thenCombine():合并任务,有返回值
thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理,无返回值
runAfterBoth():两个任务都执行完成后,执行下一步操作(Runnable类型任务)

  • 聚合关系

applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)

  • 并行执行

allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture

  • 结果处理

whenComplete:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作
exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成

  • 获取结果(join&get)

join()和get()方法都是用来获取CompletableFuture异步之后的返回值。join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。get()方法抛出的是经过检查的异常,ExecutionException,
InterruptedException 需要用户手动处理(抛出或者 try catch)

实例1:

    public static void main(String[] args) {
        System.out.println("小白进入餐厅");
        System.out.println("小白点了 番茄炒蛋 + 一碗米饭");

        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("厨师炒菜");
           ThreadUtil.sleep(200);
            return "番茄炒蛋";
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println("服务员蒸饭");
           ThreadUtil.sleep(300);
            return "米饭";
        }), (dish, rice) -> {
            System.out.println("服务员打饭");
           ThreadUtil.sleep(100);
            return String.format("%s + %s 好了", dish, rice);
        });
        System.out.println("小白在打王者");
        System.out.println(String.format("%s ,小白开吃", cf1.join()));
    }

实例2:

		 // 线程池
	     @Autowired
   		 private ThreadPoolTaskExecutor taskExecutor;
		 // 对象VO
        Load load = loadService.getByProjectId(projectId);
     		// 查询对象中属性
            CompletableFuture<ElecLoad> cf1 = CompletableFuture.supplyAsync(() -> elecLoadService.getByLoadId(load.getId(), load.getCycle()), taskExecutor);
            CompletableFuture<ColdLoad> cf2 = CompletableFuture.supplyAsync(() -> coldLoadService.getByLoadId(load.getId(), load.getCycle()), taskExecutor);
            CompletableFuture<HeatLoad> cf3 = CompletableFuture.supplyAsync(() -> heatLoadService.getByLoadId(load.getId(), load.getCycle()), taskExecutor);
            CompletableFuture<List<Select>> cf4 = CompletableFuture.supplyAsync(() -> industryService.select(Constants.ProjectIndustryType.LOAD_BASE), taskExecutor);
            CompletableFuture<List<Select>> cf5 = CompletableFuture.supplyAsync(() -> industryService.select(Constants.ProjectIndustryType.LOAD_INDUSTRY), taskExecutor);
            cf1.thenAccept(load::setElecLoad).join();
            cf2.thenAccept(load::setColdLoad).join();
            cf3.thenAccept(load::setHeatLoad).join();
            cf4.thenAccept(load::setLoadBaseList).join();
            cf5.thenAccept(load::setLoadIndustryTypeList).join();

注意: 实际开发中要用线程池去执行,线程池使用不合理会导致很多问题,文档后面会有线程池使用以及注意问题

  1. 京东零售的工具:asyncTool

3.事务

使用@Transactional注解这种声明式事务的方式提供事务功能,确实能少写很多代码,提升开发效率,也容易造成大事务(运行时间长的事务,由于事务一直不提交,就会导致数据库连接被占用,即并发场景下,数据库连接池被占满,影响到别的请求访问数据库),会导致整个业务方法都在同一个事务中,粒度太粗,不好控制事务范围;
在这里插入图片描述

1.复杂的业务逻辑或者执行时间长的业务不用声明式事务,采可以用编程式事务;
2.将查询(select)方法放到事务外,这里需要注意:@Transactional注解的声明式事务是通过spring aop起作用的,而spring aop需要生成代理对象,直接方法调 用使用的还是原始对象(新建service、自己获取代理对象)
3.可以根据业务情况,不使用事务

4.锁

在某些业务场景中,为了防止多个线程并发修改某个共享数据,造成数据异常。为了解决并发场景下,多个线程同时修改数据,造成数据不一致的情况。通常情况下,我们会加锁。JUC

  • 锁的选取
    Lock 类:手动释放;可重入锁,可以 判断锁,非公平(可以自己设置);适合锁大量的代码
    synchronized 内置关键字;自动释放;无法判断获取锁的状态;可重入锁,不可以中断的,非公平;
    分布式锁 :zk、redis

  • 锁粒度
    如果锁加得不好,导致锁的粒度太粗,也会非常影响接口性能。

    public synchronized void save(){
        // 执行业务1需要加锁
        System.out.println("执行业务1");
        System.out.println("执行业务2");
        System.out.println("执行业务3");
    }

例子中只有【执行业务1】需要加锁,现在会导致整个方法加锁;需要改为代码块的方式

    public void save(){
        // 执行业务1需要加锁
        synchronized(this) {
            System.out.println("执行业务1");
        }
        System.out.println("执行业务2");
        System.out.println("执行业务3");
    }

5.缓存

Redis,读多写少且数据时效要求越低的场景,缓存用得好,可以承载更多的请求,提升查询效率,减少数据库的压力,比如一些平时变动很小或者说几乎不会变的商品信息,可以放到缓存,请求过来时,先查询缓存,如果没有再查数据库,并且把数据库的数据更新到缓存。需要考虑: 缓存和数据库一致性如何保证、集群(主备、哨兵)、缓存击穿、缓存雪崩、缓存穿透等问题。

6.远程调用

  • 异常处理

比如,你调别人的接口,如果异常了,怎么处理,是重试还是当做失败还是告警处理。

  • 接口超时

没法预估对方接口多久返回,一般设置个超时断开时间,以保护你的接口。如果http 调用不设置超时时间,最后响应方进程假死,请求一直占着线程不释放,拖垮线程池。

  • 重试次数

你的接口调失败,需不需要重试?重试几次?根据业务情况。

  • 幂等操作

网络抖动多次点击或者发请求,比如下单、付款等操作;文档详细说明

7 .辅助功能

  1. 开启慢查询日志

通常情况下,为了定位sql的性能瓶颈,我们需要开启mysql的慢查询日志。把超过指定时间的sql语句,单独记录下来,方面以后分析和定位问题。
开启慢查询日志需要重点关注三个参数:

  • slow_query_log 慢查询开关
  • slow_query_log_file 慢查询日志存放的路径
  • long_query_time 超过多少秒才会记录日志

通过mysql的set命令可以设置:

set global slow_query_log='ON'; 
set global slow_query_log_file='/usr/local/mysql/data/slow.log';
set global long_query_time=2;

设置完之后,如果某条sql的执行时间超过了2秒,会被自动记录到slow.log文件中。
当然也可以直接修改配置文件my.cnf

[mysqld]
slow_query_log = ON
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 2

可以用mq或者其他方式发送信息或者邮件的方式定时发送给开发人员,开发人员可以优化sql

2.druid的SQL监控

3.java性能分析

  • jconsole
  • jvisualvm
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青朽_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值