布景
设计出一个高性能的API,需求归纳网络、事务、数据库的优化。以下是我在实践的开发过程中总结的优化思维和一些功率提高的技巧。
批量思维
很多的数据库操作都含有batch
或者bulk
的api,如我最近常使用的mybatis
、mybatis plus
以及 elastic Search
的数据操作API。从单条循环插入到批量插入,减少了session
的链接,更高的功率。
# 优化前
for (User user: UserList) {
mapper.insert(user);
}
# 优化后
mapper.batchInsert(UserList);
对应的实践sql为:
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连接池
总结下来的功能有:
- 避免线程的频繁创立和销毁
Thread t = new Thread(() -> {System.out.println("hello world")});
t.start();
瞧瞧这new的多恐怖。spring bean
都交给容器管理了,线程还要单独new
?来看看一段高雅的代码:
@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();
}
高雅在使命直接往池子里塞,详细的什么时候完结一直等着就好了。
- 预分配
- 循环使用
在这儿,我也有必要补充一下线程池的结构办法:
/**
* 用给定的初始参数创立一个新的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;
}
线程池的逻辑如下:
长途调用串行改并行
对于长途调用一系列接口,能够使用异步调用的方法,减少时刻消耗。
为了完成以上的作用,我也结合线程池,用到了异步的方法模仿以上两种方法的作用。
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设计》的第一部分了,时刻和篇幅原因,剩下的部分将在下一期打开。