如题,今天说一下关于响应速度的问题,前端就简单提一提,主要偏后端一些。
前端响应速度
抛却API的耗时不提,前端的响应速度主要是如下几部分
| 环节 | 解释 |
|---|---|
| 请求资源时间 | 通常在控制台的Network里面会看到,需要加载一些JS、IMG等的资源,这部分受网络、带宽等因素影响比较严重 |
| 页面装载时间 | 资源到客户端后,浏览器需要加载这些资源 |
| 脚本执行时间 | 某些资源中会有一些自动执行的脚本 |
| 浏览器引擎渲染时间 | 浏览器会渲染DOM节点,这个时候用户能看到OK的页面了 |
按照当前客户端的性能来讲,一般情况下请求资源时间耗费的时间比较长。
随着目前国内网络情况变好、5G、某些网站也上了CDN、HTTP2等等,现在请求资源时间的整体耗时也得到了改善。
后端响应速度
在前后端分离的大环境下,通常我们会更关注API的响应速度。下面的讲述,就以正常的业务情况下进行讨论,并不含盖特殊的事例。
| 环节 | 解释 |
|---|---|
| 代理 | 一般情况,后端的服务都会提供一个网关 |
| API路由 | 服务端接受请求,并进行处理,这个地方是有池化概念的,如果线程池满了,可能进入等待队列 |
| 外部IO耗时 | API中,通常消耗时间最多的是IO的耗时,比如调用其他服务的API、请求MYSQL查询数据等等 |
代理
代理有快有慢,很多实现方式也不同。结合这么多年的工作经历,我用到的Nginx是最多的,其次是Kong、Spring Cloud Gateway等等。
绝大多数情况,瓶颈并非在网关这一层,这里节选了一篇文章(# kong笔记——kong、nginx性能压测对比)里的Kong的性能测评,看下就明白了。
当然,在某些情况下,网关的选型十分重要。
API路由
通常来说,我们选定了某个框架,其路由的实现基本上已经定下来了,当然有些还可以切换下容器是用Tomcat还是Undertow等等,这部分对性能的影响也没那么明显,直到Reactive出现在我的生活中。
以我们最熟悉的Spring为例,在官网上可以看到这个图片,如下
于是,我们这么分类
| 分类 | 解释 |
|---|---|
| Servlet Stack | 传统的Servlet模型,一般容器包含Tomcat、Undertow、Jetty等等 |
| Reactive Stack | 响应式,一般基于Netty |
当然,如果要使用Spring WebFlux时,请注意这句话
Reactive and non-blocking generally do not make applications run faster.
但是,如果你使用的是Vert.x、Quarkus Reactive这些响应式框架,那么性能可以参考跑分网站:Techempower
简单看下[Single uery]的排名,如下
| 排名 | 框架 | 跑分 |
|---|---|---|
| 2 | vertx-postgres | 95.7% |
| 59 | quarkus resteasy reactive + hibernate reactive | 48.9% |
| 68 | quarkus resteasy reactive + hibernate | 47.1% |
| 110 | quarkus | 38.5% |
| 194 | spring-webflux-pgclient | 19.7% |
| 205 | spring-webflux-jdbc | 17.7% |
| 220 | spring | 15.9% |
| 254 | spring-mongo | 13.0% |
| 264 | spring-jpa | 12.3% |
| 316 | spring-webflux-mongo | 9.0% |
看见,Spring官网的提示还是正确的,Spring WebFlux并不必Spring Web更快。
我之前用Vert.x开发过项目,从资源利用率上,很吸引人,而且速度也真心比较快,除了对编码要求高一些以外,感觉没什么大缺点。相关文章:# 一时Vert.x一时爽
外部IO耗时
其实这部分没什么好说的,毕竟很多情况下,快慢并不受我们控制。如果是架构选型的时候,可以找些性能白皮书看下,在满足性能要求的情况下,选择团队更熟悉的就好。
如果是业务对响应速度有更高的要求,那么可以按照诉求,考虑下面的方案。
| 技术名词 | 场景 | 举例 |
|---|---|---|
| 削峰填谷 | 瞬时流量毛刺导致服务完全不可用 | 消息队列、@Async、响应式编程等等 |
| 缓存 | 缓存相对稳定高频热点数据,降低执行业务逻辑的性能开销 | Redis |
| 并行 | 缩短业务响应时间 | CompletableFuture |
| 主动超时 | HTTP请求的设置超时时间,防止请求积累和资源占用不被释放 |
其中,并行的方式对复杂业务的用处比较大,是以资源换时间的典型应用。举例如下,
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
sleep();
return "Hello1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
sleep();
return "Hello2";
});
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
sleep();
return "Hello3";
});
CompletableFuture<String> future4 = CompletableFuture.supplyAsync(() -> {
sleep();
return "Hello4";
});
long start = System.currentTimeMillis();
// 方式1
String result = Stream.of(future1, future2, future3, future4)
.map(CompletableFuture::join)
.collect(Collectors.joining(""));
long stop = System.currentTimeMillis();
System.err.println("result1:" + result + ", time:" + (stop - start));
}
private static void sleep() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
当然,在业务中使用时,最好做好封装,以及做好资源的池化。
尾巴
优化是一件十分考验技术的工作,而且会比较有成就感。工作期间,多学习,多实践,才能有所成长。
本文正在参加「金石计划 . 瓜分6万现金大奖」