接口设计与优化

news2024/11/18 5:35:40

文章目录

  • 接口的注意事项
    • 获取对象的属性或方法,先 判断对象是否为空!
    • 修改老接口,思考接口的兼容性
    • 重点接口,考虑线程池隔离
    • 调用第三方接口考虑超时、重试
    • 接口的熔断、降级
    • 接口,需要考虑限流
    • 接口要打印好日志
    • 接口考虑热点数据隔离性
    • 多线程情况下,考虑线程安全问题
    • 考虑接口的幂等性
    • 接口要考虑异常处理
  • 优化接口
    • guava工具包
    • 考虑使用文件 / MQ等其他方式暂存数据,异步再落地DB
    • mysql优化
      • 优化索引
      • 选错索引
    • 查询数据库由单线程改成多线程(异步处理)
    • 分批调用接口、批量查询数据
    • 数据量比较大,批量操作数据入库
    • 压缩
    • 并行处理数据
    • 并行调用
    • 集群横向扩容,分摊每台服务器的请求量
    • 减少接口中的业务,非核心业务异步执行
    • 预取数据
    • 接口安全以及参数校验
    • 注意大文件、大事务、大对象
    • 分页处理
      • 同步调用
      • 异步调用
    • 使用缓存
    • 设计模式优化代码
    • 优化专栏

接口的注意事项

获取对象的属性或方法,先 判断对象是否为空!

if (list != null) {
    list.size();
}

修改老接口,思考接口的兼容性

// 老接口
void oldService(A, B) {
   // 兼容新接口,穿个null 代替C
   newService(A, B, null);
}

//新接口,暂时不能删除老接口,需要做兼容
void newService(A, B, C) {

}

重点接口,考虑线程池隔离

一些登陆、转账交易、下单等重要接口,考虑线程池隔离哈。如果你所有业务都共用一个线程池,有些业务出bug导致线程池阻塞打满的话,那就杯具了,所有业务都影响

在这里插入图片描述

调用第三方接口考虑超时、重试

  • 接口超时

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

  • 重试次数
    优雅实现接口重试

你的接口调失败,需不需要重试?重试几次?接口的幂等性处理?需要站在业务上角度思考这个问题

接口的熔断、降级

Hystrix 请求合并、请求隔离、优化

优化

分布式系统中经常会出现某个基础服务不可用,最终导致整个系统不可用的情况, 这种现象被称为服务雪崩效应

熔断和降级。最简单是加开关控制,当下游系统出问题时,开关降级,不再调用下游系统。还可以选用开源组件Hystrix

接口,需要考虑限流

高并发接口限流

Redis+Lua的分布式限流

sentinel限流

接口要打印好日志

打印日志注意事项

开始、结束、入参、异常

public void transfer(TransferDTO transferDTO) {
       log.debug("transfer begin");
       //打印入参,
       log.debug("transfer,parameters:{}", transferDTO);
       try {
           res = transferDTO.transfer(transferDTO);
       } catch (Exception e) {
           log.error("transfer fail,account:{}", transferDTO.getAccount());
           log.error("transfer fail,exception:{}", e);

       }
       log.debug("transfer end ");

   }

接口考虑热点数据隔离性

瞬时间的高并发,可能会打垮你的系统。可以做一些热点数据的隔离。比如业务隔离、系统隔离、用户隔离、数据隔离等

  • 业务隔离性,比如12306的分时段售票,将 热点数据分散处理,降低 系统负载压力
  • 系统隔离:比如把系统分成了用户、商品、社区三个板块。这三个块分别使用不同的域名、服务器和数据库,做到从接入层到应用层再到数据层三层完全隔离。
  • 用户隔离:重点用户请求 到配置更好的机器
  • 数据隔离:使用单独的缓存集群或者数据库服务热点数据

多线程情况下,考虑线程安全问题

高并发场景下,不能 使用HashMap会造成 死循环,可以使用ConcurrentHashMap 代替

HashMap、List、LinkedList、TreeMap都是线程不安全的

考虑接口的幂等性

查询类的请求,其实不用防重
转账类接口,并发不高的话,推荐使用数据库防重表,以唯一流水号作为主键或者唯一索引

接口幂等设计

接口要考虑异常处理

SpringBoot封装响应数据和异常处理

  1. 不用 e.printStackTrace(),而是使用log 打印出来
  2. catch 住异常时,建议打印出具体的 exception,方便定位
  3. finally块关闭资源或者 ·try-with-resource·–>实现AutoCloseable接口的 流可以自动关闭
  4. 优先捕获具体的异常
  5. 不要用一个Exception 捕捉所有的异常
  6. 尽量减少try....catch的使用
  7. 运行时异常RuntimeException不应该通过catch的方式来处理,而是先预检查,比如:NullPointerException处理

优化接口

guava工具包

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

考虑使用文件 / MQ等其他方式暂存数据,异步再落地DB

MQ专栏

如果数据太大,落地数据库实在是慢的话,可以考虑先用文件的方式保存,或者考虑MQ,先落地,再异步保存到数据库~

本次转账接口,如果是并发开启,10个并发度,每个批次1000笔数据,数据库插入会特别耗时,大概10秒左右,这个跟我们公司的数据库同步机制有关,并发情况下,因为优先保证同步,所以并行的插入变成串行啦,就很耗时

优化前:

优化前,1000笔先落地DB数据库,再异步转账,如下:

在这里插入图片描述

优化后:

先保存数据到文件,再异步下载下来,插入数据库,如下:
在这里插入图片描述
解析:

如果你的耗时瓶颈就在数据库插入操作这里了,那就 考虑文件保存或者MQ或者其他方式暂存吧,文件保存数据,对比一下耗时,有时候会有意想不到的效果哦。

mysql优化

mysql专栏

优化索引

单列索引 可以 使用 联合索引 进行优化

选错索引

为了防止选错索引force index 来 强制查询sql走某个索引

查询数据库由单线程改成多线程(异步处理)

但由于 该接口 是要将查询出的所有数据,都返回回去的,所以要获取查询结果
使用多线程调用,并且要获取返回值,这种场景使用 java8 中

要使用线程池!!!!

CompleteFuture 非常合适
代码调整为:

ThreadPoolConfig 线程池配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ThreadPoolConfig {
    /**
     * 核心线程数量,默认 1
     */
    private int corePoolSize = 8;
    /**
     * 最大线程数量,默认 Integer.MAX_VALUE 约 21亿;
     */
    private int maxPoolSize = 10;
    /**
     * 空闲线程 存活时间
     */
    private int keepAliveSeconds = 60;
    /**
     * 线程阻塞 队列容量,默认 Integer.MAX_VALUE
     */
    private int queueCapacity = 1;
    /**
     * 是否允许 核心线程超时
     */
    private boolean allowCoreThreadTimeOut = false;

    @Bean("asyncExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);
        // 设置拒绝策略,直接在 execute 方法的调用线程中运行被拒绝的任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 执行初始化
        executor.initialize();
        return executor;
    }

}

执行的代码

@Resource
private ThreadPoolTaskExecutor asyncExecutor;

public String handlerData(List<String> dataList) {
    CompletableFuture[] futureArray = dataList.stream()
            .map(data -> CompletableFuture
                    // query 查询数据库 和一些其他逻辑,使用 自定义的线程池
                    .supplyAsync(() -> query(data), asyncExecutor)
                    .whenComplete((result, th) -> {
                    }))
            .toArray(CompletableFuture[]::new);
    CompletableFuture.allOf(futureArray).join();
    return "";
}

分批调用接口、批量查询数据

限制 一次性查询的记录条数

比如:查询 500条数据,之前是 一次性查询完成,现在是分5次查询,一次查询100条记录,可以使用多线程处理!!

数据量比较大,批量操作数据入库

优化前:

//for循环单笔入库
for(TransDetail detail:list){
  insert(detail);  
}

优化后:

// 批量入库,mybatis demo实现

<insert id="insertBatch" parameterType="java.util.List">
insert into trans_detail( id,amount,payer,payee) values
 <foreach collection="list" item="item" index="index" separator=",">(
    #{item.id},	#{item.amount},
    #{item.payer},#{item.payee}
  )
</foreach>
</insert>

压缩

Nginx专栏

Nginx 配置

server {
        listen       80;
        server_name  localhost;

		gzip on;
		gzip_vary on;
		gzip_buffers 32 4K;
		gzip_min_length 1024;
		gzip_proxied expired no-cache no-store private auth;
		gzip_types text/plain text/css text/xml text/javascript application/xjavascript application/xml;
		gzip_disable "MSIE [1-6]\.";
		gzip_static on; #如果有压缩好的,直接使用
		location / {
           proxy_pass   http://127.0.0.1:8080;   
        }

     
       
}

开启服务端配置:

server:
  port: 8888
  compression:
    enabled: true
    min-response-size: 1024
    mime-types: [ "text/html","text/xml","application/xml","application/json","application/octet-stream" ]

开启客户端配置:

feign:
  okhttp:
    enabled: true
  httpclient:
    enabled: false

并行处理数据

CompletableFuture优化代码

我们可以 把 这个接口,拆分成 顺序执行的两部分,在 某个部分都 可以并行的获取数据
那就按照这种分析结果改造试试吧,使用 concurrent 包里的 CountDownLatch

实现了并取功能

CountDownLatch latch = new CountDownLatch(jobSize);
//submit job
executor.execute(() -> {
//job code,countDown方法 让计数器 -1
	latch.countDown();
});
executor.execute(() -> {
	latch.countDown();
});
...
//end submit,让主线程等待
latch.await(timeout, TimeUnit.MILLISECONDS);

结果非常让人满意,我们的接口耗时,又减少了接近一半!此时,接口耗时已经降低到 2 秒以下
并发编程一定要小心,尤其是在业务代码中的并发编程。我们构造了专用的线程池,来支撑这个并发获取的功能。

final ThreadPoolExecutor executor = new ThreadPoolExecutor(
100, 
200, 
1,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(100));

并行调用

上面说到,既然串行调用多个远程接口性能很差,为什么不改成并行呢?
如下图所示:
在这里插入图片描述

调用远程接口总耗时 200ms = 200ms(即 耗时最长的那次远程接口调用
在 java8 之前可以通过实现 Callable 接口,获取线程返回结果。
java8 以后通 过 CompleteFuture 类实现该功能

CompleteFuture 为例:

public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException {
      final UserInfo userInfo = new UserInfo ();
      CompletableFuture userFuture = CompletableFuture.supplyAsync (() -> {
          getRemoteUserAndFill (id, userInfo);
          return Boolean.TRUE;
      }, executor);
      CompletableFuture bonusFuture = CompletableFuture.supplyAsync (() -> {
          getRemoteBonusAndFill (id, userInfo);
          return Boolean.TRUE;
      }, executor);
      CompletableFuture growthFuture = CompletableFuture.supplyAsync (() -> {
          getRemoteGrowthAndFill (id, userInfo);
          return Boolean.TRUE;
      }, executor);
      CompletableFuture.allOf (userFuture, bonusFuture, growthFuture).join ();
      userFuture.get ();
      bonusFuture.get ();
      growthFuture.get ();
      return userInfo;
  }

温馨提醒:这两种方式别忘了使用线程池。示例中我用到了 executor,表示自定义的线程池,为了防止高并发场景下,出现线程过多的问题

集群横向扩容,分摊每台服务器的请求量

减少接口中的业务,非核心业务异步执行

预取数据

在哪些实际场景会用呢?

  1. 视频或直播类网站

播放前 先缓冲一小段时间, 是预取数据。有的在播放时不仅预取这一条数据,甚至还会预测下一个要看的其他内容,提前把数据取到本地;

  1. 热点资源 提前预分配到各个实例

比如:秒杀、售票的库存性质的数据;分布式唯一 ID 等

接口安全以及参数校验

接口不对外暴露,仅内部使用

接口安全

入参是否为空,长度符合预期,范围符合预期

入参 出参校验是每个程序员必备的基本素养。你设计的接口,必须先校验参数。比如入参是否允许为,入参长度是否符合你的预期长度

比如你的数据库表字段设置为varchar(16),对方传了一个32位的字符串过来,如果你不校验参数,插入数据库直接异常

出参也是,比如你定义的接口报文,参数是不为空的,但是你的接口返回参数,没有做校验,因为程序某些原因,直返回别人一个null

参数校验

注意大文件、大事务、大对象

  • 读取大文件时,不要Files.readAllBytes直接读取到内存,这样会OOM的,建议使用BufferedReader一行一行来
  • 大事务可能 导致死锁、回滚时间长、主从延迟等问题,开发中尽量避免大事务。
  • 注意一些大对象的使用,因为大对象是直接进入老年代的,可能会触发fullGC

大事务引发的问题
在这里插入图片描述
事务回调编程,在事务提交后 执行代码,可以优化代码逻辑
事务回调编程

我们该如何优化大事务呢?

  1. 少用@Transactional 注解
  2. 将查询(select)方法放到事务外
  3. 事务中避免远程调用
  4. 事务中避免一次性处理太多数据
  5. 有些功能可以非事务执行
  6. 有些功能可以异步处理

分页处理

比如:通过用户 id 批量查询出用户信息,然后给这些用户送积分
但如果你一次性查询的用户数量太多了,比如一次查询 2000 个用户的数据。参数中传入了 2000 个用户的 id,远程调用接口,会发现该用户
查询接口经常超时

同步调用

具体示例代码如下:

// guava工具类
List<List<Long>> allIds = Lists.partition (ids, 200);
for (List<Long> batchIds : allIds) {

    List<User> users = remoteCallUser (batchIds);
    
}

代码中我用的 google 的 guava 工具中的 Lists.partition() 方法,用它来做分页

异步调用

除了需要考虑远程调用接口的耗时之外,还需要考虑该接口本身的总耗时,也不能超时 500ms

List<List<Long>> allIds = Lists.partition (ids, 200);
final List<User> result = Lists.newArrayList ();
   allIds.stream ().forEach ((batchIds) -> {
       CompletableFuture.supplyAsync (() -> {
           result.addAll (remoteCallUser (batchIds));
           return Boolean.TRUE;
       }, executor);
   });

使用 CompletableFuture 类,多个线程异步调用远程接口,最后汇总结果统一返回

使用缓存

哪些场景适合使用缓存?读多写少 且 数据时效要求越低的场景

redis专栏

Redis+Caffeine两级缓存,让访问速度纵享丝滑!

设计模式优化代码

设计模式专栏

优化专栏

优化专栏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/29958.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ETF动量轮动+RSRS择时,RSRS修正标准分,回撤降至16%

原创文章第113篇&#xff0c;专注“个人成长与财富自由、世界运作的逻辑&#xff0c; AI量化投资”。 昨天的策略我是比较满意的&#xff0c;沿着进化的方向在迭代我们的策略。ETF轮动RSRS择时&#xff0c;加上卡曼滤波&#xff1a;年化48.41%&#xff0c;夏普比1.89 我们会持…

Linux从入门到精通(十)——进程管理

文章篇幅较长&#xff0c;建议先收藏&#xff0c;防止迷路 文章跳转Linux从入门到精通&#xff08;八&#xff09;——Linux磁盘管理goLinux从入门到精通&#xff08;九&#xff09;——Linux编程goLinux从入门到精通&#xff08;十&#xff09;——进程管理goLinux从入门到精…

Go Web项目 接口开发全流程

风离不摆烂学习日志 Day5 — Go Web项目 接口开发全流程 接上篇地址 Web项目学习之项目结构 routes包分析 InitRoutes package routesimport ("fmt""github.com/gin-gonic/gin""go-web-mini/common""go-web-mini/config""go-we…

Charles断点

1、断点测试的含义 1.1、断点&#xff08;英语&#xff1a;Breakpoint&#xff09;是程序中为了调试而故意停止或者暂停的地方。 调试设置断点&#xff1a;可以让程序运行到该行程序时停住&#xff0c;借此观察程序到断点位置时&#xff0c;其变量、寄存器、I/O等相关的变量内…

mysql InnoDB 索引结构

目录 前言 1. InnoDB常见的索引 2. B树索引 2.1 二分查找法 2.2 二叉查找树 2.3 平衡二叉树 2.4 B树索引 2.5 B树索引 2.5.1 聚集索引 2.5.2 非聚集索引 2.5.3 聚集索引与非聚集索引区别 前言 索引的本质是让mysql以最高效、扫描行数最少的方式找到需要的数据。索引…

Paper写作怎么了解题目方面的重要性?

我们常听到&#xff1a;Paper的好坏&#xff0c;从选择题目开始&#xff01;可见选择Paper题目的重要性。这正是我们今天要探讨的内容。 We often hear that the quality of paper starts from choosing the topic!This shows the importance of selecting paper topics.This i…

数字信号处理-9-离散余弦变换

1 波形合成 假定给一系列振幅和一系列频率&#xff0c;要求构建一个信号&#xff0c;此信号是这些频率元素的和。这样的操作就是合成 def synthesize(amps, fs, ts):"""amps 振幅数组fs 频率数组ts 采样时间点"""# ts 和 fs 的外积&#xff0c…

Spring Cloud(十三):Spring 扩展

Spring扩展点 Bean的生命周期中的BeanPostProcessor扩展点Spring扩展点梳理 Spring扩展点应用场景 整合Nacos 服务注册 ApplicationListener扩展场景——监听容器中发布的事件Lifecycle Nacos 发布订阅 & Eureka 服务启动、同步、剔除Lifecycle扩展场景——管理具有启动、停…

JSP快速入门

目录 1、jsp简述 2、JSP快速入门 2.1、搭建环境 2.2、导入JSP页面 2.3、编写代码 2.4、启动测试 3、JSP原理 4、JSP脚本 4.1、JSP脚本的分类 4.2、案例 4.2.1、需求 4.2.2、实现 ​编辑 4.2.3、测试 ​编辑4.3、JSP缺点 5、JSP语法 5.1、JSP页面的基本结构…

【每日一题Day36】LC1742盒子中小球的最大数量 | 哈希表 找规律

盒子中小球的最大数量【LC1742】 You are working in a ball factory where you have n balls numbered from lowLimit up to highLimit inclusive (i.e., n highLimit - lowLimit 1), and an infinite number of boxes numbered from 1 to infinity. Your job at this facto…

[附源码]java毕业设计养老院管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

C++ 语言学习 day10 复习(2)

1.友元 的三种形式 /*********** 友元 ************ * ①全局函数做友元 * ②类做友元 * ③类成员函数做友元 * **************************/ 代码&#xff1a; #include <iostream> #include <string> using namespace std;/* ③类函数友元 : 程序规则{ 自上…

Charles下载抓包基本流程

一、Charles官网下载链接&#xff1a; https://www.charlesproxy.com/download/ 二、抓包步骤&#xff1a; 1、安装Charles&#xff0c;并打开 2、电脑设置代理端口&#xff1a; 打开charles->Proxy->Proxy Settings,设置代理端口&#xff0c;如图所示 3、手机设置代…

Day10--配置uni-app的开发环境

1.啥子是ui-app呢&#xff1f; 1》官方介绍&#xff1a; 2》博主介绍 ************************************************************************************************************** 2.开发工具建议使用HBuilder X *************************************************…

BSA/HSA表面修饰二甘醇酐,人血清白蛋白HSA、牛血清白蛋白BSA偶联二甘醇酐

BSA作用&#xff1a; BSA一般做为稳定剂被用于限制酶或者修饰酶的保存溶液和反应液中&#xff0c;因为有些酶在低浓度下不稳定或活性低。加入BSA后&#xff0c;它可能起到“保护”或“载体”作用&#xff0c;不少酶类添加 BSA后能使其活性大幅度提高。不需要加BSA的酶加入BSA一…

【时序预测-SVM】基于鲸鱼算法优化支持向量机SVM实现时序数据预测附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

【数据结构】二叉树详解(上篇)

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《数据结构》 &#x1f466;个人简介&#xff1a;一名双非编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; 二叉树&…

18 【Redux Toolkit】

18 【Redux Toolkit】 上边的案例我们一直在使用Redux核心库来使用Redux&#xff0c;除了Redux核心库外Redux还为我们提供了一种使用Redux的方式——Redux Toolkit。它的名字起的非常直白&#xff0c;Redux工具包&#xff0c;简称RTK。RTK可以帮助我们处理使用Redux过程中的重…

ABTest样本量计算

A/B 测试一般是比较实验组和对照组在某些指标上是否存在差异&#xff0c;当然更多时候是看实验组相比对照组某个指标表现是否更好。 这样的对比在统计学上叫做两样本假设检验&#xff0c;即实验组和对照组为两样本&#xff0c;假设检验的原假设Ho&#xff1a;实验组和对照组无…

Springboot 整合 JWT + Redis 实现双Token 校验Demo(简单实现)

一、新建一个SpringBoot 项目&#xff0c;springboot项目创建过程详见 mac idea 创建 springboot 项目_JAVA&#xff24;WangJing的博客-CSDN博客_mac idea创建springboot项目 二、SpringBoot 整合使用 Rdis SpringBoot 项目 添加 redis配置_JAVA&#xff24;WangJing的博客…