高性能API设计

背景

设计出一个高性能的API,需要综合网络、业务、数据库的优化。以下是我在实际的开发过程中总结的优化思想和一些效率提升的技巧。

批量思想

很多的数据库操作都含有batch或者bulk的api,如我最近常使用的mybatismybatis plus以及 elastic Search 的数据操作API。从单条循环插入到批量插入,减少了session 的链接,更高的效率。

ini 复制代码
 # 优化前
 for (User user: UserList) {
   mapper.insert(user);
 }
 ​
 # 优化后
 mapper.batchInsert(UserList);

对应的实际sql为:

scss 复制代码
 insert into user(id, name) values(1,2) 
 insert into user(id, name) values(1,2) 
 insert into user(id, name) values(1,2) ,(3,4), (5,6)

所以java的开发也有一种规范:禁止在循环中操作数据库

异步思想

对于服务的调用链路特别长的情况,接口的响应时间也特别的长。如果前端再去控制timeout的时间,直接出现接口超时的异常。于是异步的思想就出来了,允许耗时长的操作异步的执行。这类一般见于电商服务的业务流程中。

空间换时间

提到这个有点像算法了,空间复杂度和时间复杂度之间的一个权衡。这里的空间,指的是类似于redis的缓存 中间件。以查询数据为例:

池化思想

在这里不得不提到线程池了,这是多少人的噩梦!面试要问,项目要用,用不好服务直接搞挂!

我们常见的线程池类型有:数据库连接池、线程池、redis连接池

总结下来的功能有:

  • 避免线程的频繁创建和销毁
ini 复制代码
  Thread t = new Thread(() -> {System.out.println("hello world")});
  t.start();

瞧瞧这new的多恐怖。spring bean都交给容器管理了,线程还要单独new?来看看一段优雅的代码:

scss 复制代码
     @Override
     public int executeNotify() throws InterruptedException {
         // 获得需要通知的任务
         List<PayNotifyTaskDO> tasks = payNotifyTaskMapper.selectListByNotify();
         if (CollUtil.isEmpty(tasks)) {
             return 0;
         }
 ​
         // 遍历,逐个通知
         CountDownLatch latch = new CountDownLatch(tasks.size());
         tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> {
             try {
                 executeNotifySync(task);
             } finally {
                 latch.countDown();
             }
         }));
         // 等待完成
         awaitExecuteNotify(latch);
         // 返回执行完成的任务数(成功 + 失败)
         return tasks.size();
     }

优雅在任务直接往池子里塞,具体的什么时候完成一直等着就好了。

  • 预分配
  • 循环使用

在这里,我也必须补充一下线程池的构造方法:

kotlin 复制代码
     /**
      * 用给定的初始参数创建一个新的ThreadPoolExecutor。
      */
     public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                               int maximumPoolSize,//线程池的最大线程数
                               long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
                               TimeUnit unit,//时间单位
                               BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
                               ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
                               RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
                                ) {
         if (corePoolSize < 0 ||
             maximumPoolSize <= 0 ||
             maximumPoolSize < corePoolSize ||
             keepAliveTime < 0)
             throw new IllegalArgumentException();
         if (workQueue == null || threadFactory == null || handler == null)
             throw new NullPointerException();
         this.corePoolSize = corePoolSize;
         this.maximumPoolSize = maximumPoolSize;
         this.workQueue = workQueue;
         this.keepAliveTime = unit.toNanos(keepAliveTime);
         this.threadFactory = threadFactory;
         this.handler = handler;
     }

线程池的逻辑如下:

远程调用串行改并行

对于远程调用一系列接口,可以使用异步调用的方式,减少时间消耗。

为了实现以上的效果,我也结合线程池,用到了异步的方式模拟以上两种方式的效果。

scss 复制代码
   public int getCoreSize() {
     return Runtime.getRuntime().availableProcessors();
   }
 ​
   public static void someMethod(Long minutes) {
     try {
       Thread.sleep(minutes);
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     System.out.println(Thread.currentThread().getName() + " is running.....");
   }
 ​
   public void serial() {
     Date start = new Date();
     someMethod(1000L);
     someMethod(1000L);
     someMethod(300L);
     System.out.println(DateUtil.between(start, new Date(), DateUnit.MS));
   }
 ​
   public void parallel() {
     Date start = new Date();
     CompletableFuture<Void> completableFutureOne = CompletableFuture.runAsync(() -> someMethod(1000L), poolExecutor);
     CompletableFuture<Void> completableFutureTwo = CompletableFuture.runAsync(() -> someMethod(1000L), poolExecutor);
     CompletableFuture<Void> completableFutureThree = CompletableFuture.runAsync(() -> someMethod(300L), poolExecutor);
     try {
       // get()方法会阻塞线程
       CompletableFuture.allOf(completableFutureOne, completableFutureTwo, completableFutureThree).get();
     } catch (InterruptedException | ExecutionException e) {
       e.printStackTrace();
     }
     System.out.println(DateUtil.between(start, new Date(), DateUnit.MS));
     poolExecutor.shutdown();
   }

以上就是《高性能API设计》的第一部分了,时间和篇幅原因,剩下的部分将在下一期展开。

相关推荐
东方小月21 分钟前
从零开发一个 Coding Agent(四):使用状态机校验大模型事件流
前端·人工智能·后端
都叫我大帅哥42 分钟前
“让AI修就行”?我劝你收回这句话——来自一位Java博主的硬核反常识
后端
whyfail1 小时前
前端学 Spring Boot(8):接口为什么越用越慢?
前端·spring boot·后端
Muscleheng2 小时前
Spring Boot 3.x 集成 DeepSeek 实现 Function Calling(工具调用)
人工智能·spring boot·后端·ai·spring ai·deepseek
@航空母舰2 小时前
SpringBoot通过Map实现天然的策略模式
java·spring boot·后端
IT_陈寒3 小时前
JavaScript的this又双叒叕让我怀疑人生了
前端·人工智能·后端
陈随易3 小时前
MCP协议第5次更新,从打电话到微信聊天的巨大变革
前端·后端·程序员
65岁退休Coder3 小时前
LangChain v1.3.4 笔记 - 06 RAG 检索增强生成
后端
妙码生花4 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(四十三):前后端数据验证
后端·go·ai编程