| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 5秒到1秒,记一次效果“非常”显著的性能优化 -> 正文阅读 |
|
[Java知识库]5秒到1秒,记一次效果“非常”显著的性能优化 |
性能优化,有时候看起来是一个比较 这很让人伤心,但这是悲催的现实。 性能优化,通常由有技术追求的人发起,根据观测指标进行的正向优化。他们通常具有工匠精神,对每一毫秒的耗时都吹毛求疵,力求完美。当然,前提是你得有时间。 1. 优化背景和目标我们本次的性能优化,就是由于达到了无法忍受的程度,才进行的优化工作,属于事后补救,问题驱动的方式。这通常没什么问题,毕竟业务第一嘛,迭代在填坑中进行。 先说背景。本次要优化的服务,请求响应时间十分的不稳定。随着数据量的增加,大部分请求,要耗时5-6秒左右!超出了常人能忍受的范围。 当然需要优化。 为了说明要优化的目标,我大体画了一下它的拓扑结构。如图所示,这是一套微服务架构的服务。 其中,我们优化的目标,就处于一个比较靠上游的服务。它需要通过Feign接口,调用下游非常多的服务提供者,获取数据后进行聚合拼接,最终通过zuul网关和nginx,来发送到浏览器客户端。 为了观测服务之间的调用关系和监控数据,我们接入了Skywalking调用链平台和Prometheus监控平台,收集重要的数据以便能够进行优化决策。要进行优化之前,我们需要首先看一下优化需要参考的两个技术指标。
平均响应时间自然是越小越好,它越小,吞吐量越高。吞吐量的增加还可以合理利用多核,通过并行度增加单位时间内的发生次数。 我们本次优化的目标,就是减少某些接口的平均响应时间,降低到1秒以内;增加吞吐量,也就是提高QPS,让单实例系统能够承接更多的并发请求。 2. 通过压缩让耗时急剧减少我想要先介绍让系统飞起来最重要的一个优化手段:压缩。 通过在 这么大的数据,光下载就需要耗费大量时间。如下图所示,是我请求juejin主页的某一个请求,其中的 为了减少数据在网络上的传输时间,可以启用gzip压缩。gzip压缩是属于时间换空间的做法。对于大多数服务来说,最后一环是nginx,大多数人都会在nginx这一层去做压缩。它的主要配置如下:
压缩率有多惊人呢?我们可以看一下这张截图。可以看到,数据压缩后,由8.95MB缩减到了368KB!瞬间就能够被浏览器下载下来。 但是等等,nginx只是最外面的一环,还没完,我们还可以让请求更快一些。 请看下面的请求路径,由于采用了微服务,请求的流转就变得复杂起来:nginx并不是直接调用了相关得服务,它调用的是zuul网关,zuul网关才真正调用的目标服务,目标服务又另外调用了其他服务。内网带宽也是带宽,网络延迟也会影响调用速度,同样也要压缩起来。
要想Feign之间的调用全部都走压缩通道,还需要额外的配置。我们是 加入它的依赖:
开启服务端配置:
开启客户端配置:
经过这些压缩之后,我们的接口平均响应时间,直接从5-6秒降低到了2-3秒,优化效果非常显著。 当然,我们也在结果集上做了文章,在返回给前端的数据中,不被使用的对象和字段,都进行了精简。但一般情况下,这些改动都是伤筋动骨的,需要调整大量代码,所以我们在这上面用的精力有限,效果自然也有限。 3. 并行获取数据,响应飞快接下来,就要深入到代码逻辑内部进行分析了。上面我们提到,面向用户的接口,其实是一个数据聚合接口。它的每次请求,通过Feign,调用了几十个其他服务的接口,进行数据获取,然后拼接结果集合。 为什么慢?因为这些请求全部是串行的!Feign调用属于远程调用,也就是网络I/O密集型调用,多数时间都在等待,如果数据满足的话,是非常适合并行调用的。 首先,我们需要分析这几十个子接口的依赖关系,看一下它们是否具有严格的顺序性要求。如果大多数没有,那就再好不过了。 分析结果喜忧参半,这堆接口,按照调用逻辑,大体上可以分为A,B类。首先,需要请求A类接口,拼接数据后,这些数据再供B类使用。但在A,B类内部,是没有顺序性要求的。 也就是说,我们可以把这个接口,拆分成顺序执行的两部分,在某个部分都可以并行的获取数据。 那就按照这种分析结果改造试试吧,使用concurrent包里的CountDownLatch,很容易的就实现了并取功能。
结果非常让人满意,我们的接口耗时,又减少了接近一半!此时,接口耗时已经降低到2秒以下。 你可能会问,为什么不用Java的并行流呢?关于并行流的坑,可以参考这篇文章。非常不建议你使 5秒到1秒,记一次效果“非常”显著的性能优化
性能优化,有时候看起来是一个比较 这很让人伤心,但这是悲催的现实。 性能优化,通常由有技术追求的人发起,根据观测指标进行的正向优化。他们通常具有工匠精神,对每一毫秒的耗时都吹毛求疵,力求完美。当然,前提是你得有时间。 1. 优化背景和目标我们本次的性能优化,就是由于达到了无法忍受的程度,才进行的优化工作,属于事后补救,问题驱动的方式。这通常没什么问题,毕竟业务第一嘛,迭代在填坑中进行。 先说背景。本次要优化的服务,请求响应时间十分的不稳定。随着数据量的增加,大部分请求,要耗时5-6秒左右!超出了常人能忍受的范围。 当然需要优化。 为了说明要优化的目标,我大体画了一下它的拓扑结构。如图所示,这是一套微服务架构的服务。 其中,我们优化的目标,就处于一个比较靠上游的服务。它需要通过Feign接口,调用下游非常多的服务提供者,获取数据后进行聚合拼接,最终通过zuul网关和nginx,来发送到浏览器客户端。 为了观测服务之间的调用关系和监控数据,我们接入了Skywalking调用链平台和Prometheus监控平台,收集重要的数据以便能够进行优化决策。要进行优化之前,我们需要首先看一下优化需要参考的两个技术指标。
平均响应时间自然是越小越好,它越小,吞吐量越高。吞吐量的增加还可以合理利用多核,通过并行度增加单位时间内的发生次数。 我们本次优化的目标,就是减少某些接口的平均响应时间,降低到1秒以内;增加吞吐量,也就是提高QPS,让单实例系统能够承接更多的并发请求。 2. 通过压缩让耗时急剧减少我想要先介绍让系统飞起来最重要的一个优化手段:压缩。 通过在 这么大的数据,光下载就需要耗费大量时间。如下图所示,是我请求juejin主页的某一个请求,其中的 为了减少数据在网络上的传输时间,可以启用gzip压缩。gzip压缩是属于时间换空间的做法。对于大多数服务来说,最后一环是nginx,大多数人都会在nginx这一层去做压缩。它的主要配置如下:
压缩率有多惊人呢?我们可以看一下这张截图。可以看到,数据压缩后,由8.95MB缩减到了368KB!瞬间就能够被浏览器下载下来。 但是等等,nginx只是最外面的一环,还没完,我们还可以让请求更快一些。 请看下面的请求路径,由于采用了微服务,请求的流转就变得复杂起来:nginx并不是直接调用了相关得服务,它调用的是zuul网关,zuul网关才真正调用的目标服务,目标服务又另外调用了其他服务。内网带宽也是带宽,网络延迟也会影响调用速度,同样也要压缩起来。
要想Feign之间的调用全部都走压缩通道,还需要额外的配置。我们是 加入它的依赖:
开启服务端配置:
开启客户端配置:
经过这些压缩之后,我们的接口平均响应时间,直接从5-6秒降低到了2-3秒,优化效果非常显著。 当然,我们也在结果集上做了文章,在返回给前端的数据中,不被使用的对象和字段,都进行了精简。但一般情况下,这些改动都是伤筋动骨的,需要调整大量代码,所以我们在这上面用的精力有限,效果自然也有限。 3. 并行获取数据,响应飞快接下来,就要深入到代码逻辑内部进行分析了。上面我们提到,面向用户的接口,其实是一个数据聚合接口。它的每次请求,通过Feign,调用了几十个其他服务的接口,进行数据获取,然后拼接结果集合。 为什么慢?因为这些请求全部是串行的!Feign调用属于远程调用,也就是网络I/O密集型调用,多数时间都在等待,如果数据满足的话,是非常适合并行调用的。 首先,我们需要分析这几十个子接口的依赖关系,看一下它们是否具有严格的顺序性要求。如果大多数没有,那就再好不过了。 分析结果喜忧参半,这堆接口,按照调用逻辑,大体上可以分为A,B类。首先,需要请求A类接口,拼接数据后,这些数据再供B类使用。但在A,B类内部,是没有顺序性要求的。 也就是说,我们可以把这个接口,拆分成顺序执行的两部分,在某个部分都可以并行的获取数据。 那就按照这种分析结果改造试试吧,使用concurrent包里的CountDownLatch,很容易的就实现了并取功能。
结果非常让人满意,我们的接口耗时,又减少了接近一半!此时,接口耗时已经降低到2秒以下。 你可能会问,为什么不用Java的并行流呢?关于并行流的坑,可以参考这篇文章。非常不建议你使用它。 《parallelStream的坑,不踩不知道,一踩吓一跳》 并发编程一定要小心,尤其是在业务代码中的并发编程。我们构造了专用的线程池,来支撑这个并发获取的功能。
压缩和并行化,是我们本次优化中,最有效的手段。它们直接砍掉了请求大半部分的耗时,非常的有效。但我们还是不满足,因为每次请求,依然有1秒钟以上呢。 4. 缓存分类,进一步加速我们发现,有些数据的获取,是放在循环中的,有很多无效请求,这不能忍。
如果将这些常用的结果缓存起来,那么就可以大大减少网络IO请求的次数,增加程序的运行效率。 缓存在大多数应用程序的优化中,作用非常大。但由于压缩和并行效果的对比,缓存在我们这个场景中,效果不是非常的明显,但依然减少了大约三四十毫秒的请求时间。 我们是这么做的。 首先,我们将一部分代码逻辑简单,适合 但是,仅仅这么做是不够的,因为有些业务逻辑非常的复杂,更新的代码发非常的分散,不适合使用
针对于这种情况,我们设计了存在时间极短的堆内内存缓存,数据在1秒之后,就会失效,然后重新从数据库中读取。加入某个节点调用服务端接口是1秒钟1k次,我们直接给降低到了1次。 在这里,使用了Guava的LoadingCache,减少的Feign接口调用,是数量级的。
5. MySQL索引的优化我们的业务系统,使用的是MySQL数据库,由于没有专业DBA介入,而且数据表是使用JPA生成的。在优化的时候,发现了大量不合理的索引,当然是要优化掉。 由于SQL具有很强的敏感性,我这里只谈一些在优化过程中碰到的索引优化规则问题,相信你一样能够在自己的业务系统中进行类比。 索引非常有用,但是要注意,如果你对字段做了函数运算,那索引就用不上了。常见的索引失效,还有下面两种情况:
MySQL的索引优化,最基本的是遵循最左前缀原则,当有a、b、c三个字段的时候,如果查询条件用到了a,或者a、b,或者a、b、c,那么我们就可以创建(a,b,c)一个索引即可,它包含了a和ab。当然,字符串也是可以加前缀索引的,但在平常应用中较少。 有时候,MySQL的优化器,会选择了错误的索引,我们需要使用 另外一个优化是减少回表。由于InnoDB采用了 6. JVM优化我通常将JVM的优化放在最后一环。而且,除非系统发生了严重的卡顿,或者OOM问题,都不会主动对其进行过度优化。 很不幸的是,我们的应用,由于开启了大内存(8GB+),在JDK1.8默认的并行收集器下,经常发生卡顿。虽然不是很频繁,但动辄几秒钟,已经严重影响到部分请求的平滑性。 程序刚开始,是光秃秃跑在JVM下的,GC信息,还有OOM,什么都没留下。为了记录GC信息,我们做了如下的改造。 第一步,加入GC问题排查的各种参数。
这样,我们就可以拿着生成的GC文件,上传到 第二步,开启SpringBoot的GC信息,接入Promethus监控。 在pom中加入依赖。
然后配置暴露点就可以了。这样,我们就拥有了实时的分析数据,有了优化的依据。
在观测了JVM的表现之后,我们切换成了G1垃圾回收器。G1有最大停顿目标,可以让我们的GC时间更加的平滑。它主要有以下几个调优参数:
切换成G1之后,这种不间断的停顿,竟然神奇的消失了!期间,还发生过很多次内存溢出的问题,不过有MAT这种神器的加持,最终都很easy的被解决了。 7. 其他优化在工程结构和架构方面,如果有硬伤的话,那么代码优化方面,起到的作用其实是有限的,就比如我们这种情况。 但主要代码还是要整一下容得。有些处于高耗时逻辑中的关键的代码,我们对其进行了格外的关照。按照开发规范,对代码进行了一次统一的清理。其中,有几个印象比较深深刻的点。 有同学为了能够复用map集合,每次用完之后,都使用clear方法进行清理。
这些map中的数据,特别的多,而clear方法有点特殊,它的时间复杂度事O(n)的,造成了较高的耗时。
同样的线程安全的队列,有
另外,有些服务的web页面,本身响应就非常的慢,这是由于业务逻辑复杂,前端JavaScript本身就执行缓慢。这部分代码优化,就需要前端的同事去处理了,如图,使用chrome或者firefox的performance选项卡,可以很容易发现耗时的前端 代码。 8. 总结性能优化,其实也是有套路的,但一般团队都是等发生了问题才去优化,鲜有未雨绸缪的。但有了监控和APM就不一样,我们能够随时拿到数据,反向推动优化过程。 有些性能问题,能够在业务需求层面,或者架构层面去解决。凡是已经带到代码层,需要程序员介入的优化,都已经到了需求方和架构方不能再乱动,或者不想再动的境地。 性能优化首先要收集信息,找出瓶颈点,权衡CPU、内存、网络、、IO等资源,然后尽量的减少平均响应时间,提高吞吐量。 缓存、缓冲、池化、减少锁冲突、异步、并行、压缩,都是常见的优化方式。在我们的这个场景中,起到最大作用的,就是数据压缩和并行请求。当然,加上其他优化方法的协助,我们的业务接口,由5-6秒的耗时,直接降低到了1秒之内,这个优化效果还是非常可观的。估计在未来很长一段时间内,都不会再对它进行优化了。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 22:26:47- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |