关于优化响应速度的那些事情

1,335 阅读4分钟

如题,今天说一下关于响应速度的问题,前端就简单提一提,主要偏后端一些。

前端响应速度

抛却API的耗时不提,前端的响应速度主要是如下几部分

环节解释
请求资源时间通常在控制台的Network里面会看到,需要加载一些JS、IMG等的资源,这部分受网络、带宽等因素影响比较严重
页面装载时间资源到客户端后,浏览器需要加载这些资源
脚本执行时间某些资源中会有一些自动执行的脚本
浏览器引擎渲染时间浏览器会渲染DOM节点,这个时候用户能看到OK的页面了

按照当前客户端的性能来讲,一般情况下请求资源时间耗费的时间比较长。

image.png

随着目前国内网络情况变好、5G、某些网站也上了CDN、HTTP2等等,现在请求资源时间的整体耗时也得到了改善。

后端响应速度

在前后端分离的大环境下,通常我们会更关注API的响应速度。下面的讲述,就以正常的业务情况下进行讨论,并不含盖特殊的事例。

环节解释
代理一般情况,后端的服务都会提供一个网关
API路由服务端接受请求,并进行处理,这个地方是有池化概念的,如果线程池满了,可能进入等待队列
外部IO耗时API中,通常消耗时间最多的是IO的耗时,比如调用其他服务的API、请求MYSQL查询数据等等

代理

代理有快有慢,很多实现方式也不同。结合这么多年的工作经历,我用到的Nginx是最多的,其次是KongSpring Cloud Gateway等等。

绝大多数情况,瓶颈并非在网关这一层,这里节选了一篇文章(# kong笔记——kong、nginx性能压测对比)里的Kong的性能测评,看下就明白了。

image.png

当然,在某些情况下,网关的选型十分重要。

API路由

通常来说,我们选定了某个框架,其路由的实现基本上已经定下来了,当然有些还可以切换下容器是用Tomcat还是Undertow等等,这部分对性能的影响也没那么明显,直到Reactive出现在我的生活中。

以我们最熟悉的Spring为例,在官网上可以看到这个图片,如下

image.png

于是,我们这么分类

分类解释
Servlet Stack传统的Servlet模型,一般容器包含Tomcat、Undertow、Jetty等等
Reactive Stack响应式,一般基于Netty

当然,如果要使用Spring WebFlux时,请注意这句话

Reactive and non-blocking generally do not make applications run faster.

但是,如果你使用的是Vert.xQuarkus Reactive这些响应式框架,那么性能可以参考跑分网站:Techempower

image.png

简单看下[Single uery]的排名,如下

排名框架跑分
2vertx-postgres95.7%
59quarkus resteasy reactive + hibernate reactive48.9%
68quarkus resteasy reactive + hibernate47.1%
110quarkus38.5%
194spring-webflux-pgclient19.7%
205spring-webflux-jdbc17.7%
220spring15.9%
254spring-mongo13.0%
264spring-jpa12.3%
316spring-webflux-mongo9.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万现金大奖」