性能优化
快来分享你的内容吧~
- Spring Boot和MySQL内存优化方案寻求帮助Bug 描述问题一:关于 Java项目内存使用一直过高问题咨询。我们的这台服务器现在是32G的内存,我们有一个Spring Boot 项目,启动方式是"nohup java -Xmx8G -Xms8G -agentlib:jdwp=transport=dt_socket,server=y,suspend=n -jar /**/iotbaima-platform.jar > /dev/null 2>...查看全文程序员鱼皮:不错,比上次一句话提问多提供了一些信息,但其实别人还是没办法通过一个内存占用数量就帮你定位问题,只能给你一些排查方法和建议了。先从监控数据来看,你已经设置了 "-Xmx8G -Xms8G",为什么实际占用 9.55GB?答案很简单,因为 JVM 的内存不只是堆内存,还有元空间、线程栈、直接内存等,这些加起来超出 1-2GB 也是 ok 的。先说 Java 内存占用,你不能说 9.55 GB 就一定

2025-01-09·Java后端- 2024-11-15·前端开发
- 2024-09-19基于ArkUI的List组件实现的滚动列表视图,在手指抛滑场景下,通过分析掉帧情况来判断List滑动是否流畅,保障用户极致流畅体验。查看全文明凡:@编程导航 图片的质量能不能调高一点啊 看不清431分享
2023-03-17怎么进行站点内的图片性能优化?查看全文编程导航:站点内的图片性能优化可以从以下几个方面入手: 图片压缩:通过压缩图片的大小和质量,可以减少图片的加载时间。可以使用图片压缩工具(如 TinyPNG、JPEGmini 等)来压缩图片。 图片格式优化:选择正确的图片格式可以减少图片的大小。常见的图片格式有 JPEG、PNG 和 GIF,可以根据图片的特点和用途选择合适的格式。 延迟加载:对于页面中的长列表或懒加载场景,可以延迟加载图片,减少页面加载时
1203分享
2022-10-29为什么性别字段不适合建立索引?查看全文编程导航:因为你访问索引需要付出额外的IO开销,你从索引中拿到的只是地址,要想真正访问到数据还是要对表进行一次IO。 假如你要从表的100万行数据中取几个数据,那么利用索引迅速定位,访问索引的这IO开销就非常值了。但如果你是从100万行数据中取50万行数据,就比如性别字段,那你相对需要访问50万次索引,再访问50万次表,加起来的开销并不会比直接对表进行一次完整扫描小。 同时,虽然索引大大提高了查询速度,同时
48128分享
鱼皮哥哥你好,我想问一下Vue鱼皮哥哥你好,我想问一下Vue+antdesign做的项目,上线后前端控制台能看到一个文件加载时间很长,时间也不固定,最长等待50s,已知未按需加载组件,服务器带宽1Mbps,请问你们大厂如何做优化?...查看全文程序员鱼皮:1. webpack-analyzer 分析一下包的大小,减少依赖和重复代码,精简项目体积2. 按需加载3. 使用 CDN 加速4. web 服务器开启 gzip 压缩,减少传输5. 合理开启服务器及本地缓存6. 服务端渲染7. 你这 2 个 m 的文件能加载 9s,说明你这服务器网络不太行,可以提高服务器的带宽或者切换网络位置
Spring Boot和MySQL内存优化方案寻求帮助
### Bug 描述 问题一:关于 Java项目内存使用一直过高问题咨询。 我们的这台服务器现在是32G的内存,我们有一个Spring Boot 项目,启动方式是`nohup java -Xmx8G -Xms8G -agentlib:jdwp=transport=dt_socket,server=y,suspend=n -jar /**/iotbaima-platform.jar > /dev/null 2>&1 &`。已经配置了JVM的具体参数,但是从监控上面来看,使用内存一直都非常大,具体如下图:  我目前的解决方法是直接通过重启的方式短暂的处理这个问题,但是我想从根本上解决这个问题,目前没有思路,请问针对这种内存使用过大的话应该如何去处理以及需要注意哪些内容。 问题二:关于Mysql 5.7 内存过高问题咨询。 我们的mysql是通过宝塔直接进行安装的,目前数据库中确实也存在大量的数据,目前的数据库大概有4G左右的数据量。针对这个运行内存在6G左右的问题应该如何进行排查,目前是一头雾水。下面是我数据库的具体状态信息:  ### 期望结果 希望Java项目的运行内存能够大幅度降低,mysql的内存能够尽量优化到最优
我救了一个网站,性能提升了1500 多倍!
这是一个加载慢慢慢慢慢到不行的网站,据说是由一位低级程序员鱼皮开发的,等了几分钟都没加载完:  你能想到多少种办法,来拯救这个网站的加载速度呢? 我能想到 **至少 12 种**,如果你能想到更多方法,先受我一拜,你真的很厉害;如果你想到的方法比我少,那么这期内容,一定会让你有收获。 下面我们就来聊聊《网站性能优化》。 ## 如何测量网站性能? 衡量网站性能的指标非常多,比如首屏加载时间、白屏时间、可交互时间等等。  但这里为了帮助大家理解,我们主要关注用户最直观能感受到的 **网站加载时长**。 怎么测量网站加载时长呢? 最简单的方法就是按 F12 打开浏览器的开发者工具,切换到 Network 网络面板,刷新页面就能看到每个资源的加载时间了。  当然,还有更专业的网站性能分析工具,在本期的最后会分享。 ## 网站性能优化的关键 虽然网站性能优化的方法非常多,但思路很简单。 问个问题,大家都收过快递吧?快递是怎么送到你家的呢? 首先,商家从仓库把商品打包,然后通过物流网络和快递员配送到你手里,你拆开包裹就能使用了。 访问网站也是一样的:**从服务器获取到网站文件,然后在浏览器中加载**。 要让网站访问更快,我们可以从三个方向来优化: - 网站传输更快 - 网站体积更小 - 网站加载更快 下面我们就按照这些方向,来优化现在这个要加载 3 分多钟的辣鸡网站,看看最后能优化到多少秒。  ## 一、网站传输优化 想要更快获取到网站文件,我们可以按照网站文件传输的路径 **服务器 => 网络传输 => 客户端** 进行优化。 ### 升级服务器配置 毫无疑问,从服务器获取网站文件是需要网络的,服务器带宽越大,网速越快,网站文件下载越快。 所以如果你不知道怎么优化网站性能,最简单粗暴的方法就是加钱!升级服务器的带宽! 比如我把 2M 带宽的小水管升级到 8M,网站加载时长就从 3 分钟优化到了 40 秒,速度优化了 4 倍多!  不过一般来说,对于个人小网站,1-5M 就够用了,毕竟带宽挺贵的。 有同学会问了,光升级带宽就够了么?升级内存、CPU、硬盘有没有用? 这就要看你网站的类别了,对于纯静态网站来说,服务器要做的就是把网站文件发送出去,这个过程主要受带宽限制。但如果你的网站有复杂的后端逻辑,那 CPU 和内存就很重要了。 ### CDN 缓存加速 如果用户离我们的网站服务器较远,传输网站文件的时间就会更长,很影响体验。 如何解决这个问题呢?我们不妨类比一下网购,平台会在全国建立区域仓库,提前把热门商品分配到各地仓库,用户下单后从最近的仓库发货,而不是都从总仓发货,就能更快收货。 这就是 CDN 内容分发网络的原理,提前从源服务器获取到网站文件并缓存到全国各地的节点,用户访问时就可以直接从最近的节点获取资源。不仅延迟更低,而且能同时支持更多用户的访问。  我们使用云服务平台配置一下 CDN,指定原始网站服务器作为源站。  然后设置缓存,可以只缓存图片等媒体资源,也可以缓存整套网站文件,这里我全都要。  试一下效果,首次访问会比较慢,因为 CDN 节点还没有缓存,需要从网站服务器拉取文件;之后速度就飚起来了,直接从 40 秒优化到了 6 秒,性能优化了 6 倍多!效果显著。  不过 CDN 可是把双刃剑,按流量计费,鉴于我被刷了上万元流量费的血泪经验,建议 CDN 能不用就不用,即使要用 CDN 也要做好访问频率限制、用量封顶配置和监控告警。  ### 浏览器缓存 除了 CDN 外,但还有一个更彻底的优化方案:**让网站文件根本不用传输**! 这就是 **浏览器缓存** 的作用,将已经请求过的网站文件存储到用户本地,下次再访问网站时,都不用去找服务器了,直接从本地加载资源。 我们可以通过 Web 服务器的 HTTP 缓存头配置或者 CDN 的浏览器缓存过期配置来更改缓存策略,更新不频繁的网站缓存时间可以设置长一些。  我这里设置为 1 小时,效果很明显,直接从 6 秒优化到了 1.69 秒,不过理论上还可以更快。  这样一来,我们就形成了一个完整的网站缓存体系:**CDN 缓存解决地理距离问题,浏览器缓存解决重复访问的问题**。实际情况下两种方法建议结合使用。 ### 升级 HTTP 协议 此外,想要升级网站传输的速度,可以升级请求协议到 HTTP/2。 相比于 HTTP/1.1,HTTP/2 最大的改进是 **多路复用**。HTTP/1.1 虽然可以建立多个连接,但每个连接内的请求必须按顺序处理,容易产生队头阻塞问题。而 HTTP/2 在单个连接上就能同时处理多个请求,真正实现了并行传输。 升级 HTTP/2 的方式很简单,只需要在 Web 服务器(比如 Nginx)添加配置: ```nginx server { listen 443 ssl http2; # 开启HTTP/2支持 server_name your-domain.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; # 其他配置... } ``` 如果你用的是 CDN,只需要在 CDN 配置页面一键开启 HTTP/2 即可:  测试一下效果,这次没有用到本地缓存,网站加载时长也从 6 秒缩短到了 1.6 秒,性能优化了 3 倍多!  仅仅点了一下按钮,速度就上来了,是不是没想到? 那你可能问了,现在不是还有 HTTP/3 吗? HTTP/3 确实更先进,它基于 QUIC 协议,有更快的连接建立速度、更好的多路复用性能和更少的队头阻塞问题,但兼容性和稳定性还需要时间验证,选用 HTTP/2 就足够了。 至此,在没有改变网站本身的情况下,我们就已经把网站加载时间优化到了秒级! ## 二、网站体积优化 如果把网站文件当做货物,体积越小,自然传送越快。 如何优化网站文件体积呢? 核心就 2 个字 —— **压缩**。 ### 网站资源压缩 首先可以压缩网站引用的媒体资源,比如图片、音视频、字体文件等等。 很多朋友都知道这个道理,但是经常忽略。不信打开浏览器控制台看看你们自己的网站,有哪些资源可以进一步优化呢? 对很多网站来说,图片是消耗流量较多的一类资源,因此我们性能优化时,要重点关注图片。 只需要把 JPG 和 PNG 的图片统一压缩为 WebP(或 AVIF)格式,这样就能减少 20% 以上的文件体积;想追求更高的压缩比例,还可以调整图片压缩的质量,一般 80% 左右对原图的影响是可以接受的。 举个例子,我让 AI 通过编写 Python 脚本批量压缩网站内的图片,总体积直接减少了 20 多倍!  再次访问网站时,相比之前从服务器加载耗时 40 秒,这次直接缩短到了 2 秒!竟然跟使用 CDN + HTTP 2 的效果旗鼓相当,优化了 20 倍!  而且不仔细看的话,你能分辨出来优化前后图片的区别么?  需要注意的是,如果你的网站允许用户自主上传图片,一定要在后端服务器对这些图片进行压缩,否则可能就会出现下图这种惨状:一个用户头像都消耗了 5M 流量。  ### 网站代码压缩 除了引用的资源外,网站代码本身也是可以压缩的。 代码压缩的原理很好理解,去掉代码中的空格、注释、换行,把变量名缩短就可以了。举个例子:  虽然网站代码压缩的工具网上一抓一大把(比如 [Minifier](https://www.minifier.org/) 和 [JSCompress](https://jscompress.com)),但一般我们不需要手动压缩代码。对于独立的 CSS 和 JS 脚本文件,可以直接使用 [官方提供的 Min 压缩版本](https://www.bootcdn.cn/)。  而对于目前主流的 Vue / React 前端工程化项目,一般会使用打包工具(比如 Vite / Webpack)自动对代码文件进行优化、压缩和打包。 比如经典的 Tree Shaking(摇树优化),就像摇树一样,通过静态分析,把代码中没用到的地方 “摇” 掉,从而减小 JS 文件体积。 来试试看,我的网站项目使用了 Vite 作为打包工具,添加这么一段配置,然后构建和部署项目。  对比一下,效果立竿见影!原本 363 KB 的代码,压缩后只有 159 KB,减少到了一半!  网站加载时长也进一步缩短到了 1.62 秒,又优化了 20%。  ### Gzip 传输压缩 除了手动压缩外,还可以在网站传输时利用 Gzip 实现自动压缩。 它的原理也很简单,浏览器和服务器之间有个约定: 1)浏览器请求网站时通过请求头告诉服务器:“我支持 gzip 压缩”。 示例 Accept-Encoding 请求头: ```markdown GET /index.html HTTP/1.1 Host: codefather.cn Accept-Encoding: gzip, deflate, br ``` 2)服务器收到这个请求,如果开启了 gzip 压缩,会把文件压缩后再发送,并且通过响应头告诉浏览器:“这是压缩过的”。 示例 Content-Encoding 响应头: ```markdown HTTP/1.1 200 OK Content-Type: text/html Content-Encoding: gzip Content-Length: 1024 ``` 3)浏览器收到文件后,会自动进行解压。 让我们测试一下,开启 gzip 的方式很简单,只需要在 Web 服务器中添加配置: ```nginx # 开启gzip压缩 gzip on; # 启用gzip压缩的HTTP版本 gzip_http_version 1.1; # 压缩级别 (1-9) # 1: 最快压缩,压缩率最低 # 9: 最慢压缩,压缩率最高 # 6: 平衡压缩速度和压缩率的推荐值 gzip_comp_level 6; # 小于1KB的文件不压缩 # 小文件压缩后可能反而变大,且消耗CPU资源 gzip_min_length 1024; ``` 然后我们利用 CURL 工具分别发送不开启 Gzip 和开启 Gzip 的两个请求: ```shell # 开启 Gzip - 详细版 curl -H "Accept-Encoding: gzip" -w "Gzip: %{size_download} bytes" -o /dev/null -s http://love.codefather.cn/assets/index-d746a13e.js # 不开启 Gzip - 详细版 curl -H "Accept-Encoding: identity" -w "原始: %{size_download} bytes" -o /dev/null -s http://love.codefather.cn/assets/index-d746a13e.js ``` 发现开启 Gzip 压缩后,文件大小能减少一半多!  但由于我们网站的代码文件体积本来就不大,所以开启 gzip 后的优化效果不会那么明显,但仍然建议开启。 ## 三、网站加载优化 前面我们优化了网站传输和网站体积,HTML 等网站文件能更快到达用户浏览器。但浏览器接下来还需要加载脚本、图片等大量资源,并且动态请求后端数据。 这个过程也是可以优化的,也是最有意思的,我们可以通过很多策略来优化网站的加载速度。 ### 加载策略 首先是几种典型的加载策略,目标是 **让用户在合适的时机获得合适的内容**。 #### 1、延迟加载 **懒加载** 是最常用的延迟加载技术,核心思想很简单:用户看不到的内容就先不加载。这种策略特别适合长页面和图片较多的网站。 比如我们通过 HTML 图片标签自带的 lazy 属性实现懒加载: ```html <img src="image.jpg" loading="lazy" alt="懒加载图片"> ``` > 如果想实现更严格的懒加载,可以采用 Intersection Observer 来观察图片是否进入视窗 测试一下效果,首屏只用了 339 毫秒就加载完成了,加载时长又缩短了 5 倍!  当用户滚动页面到对应的位置时,图片才开始加载,不仅大幅减少了首屏加载时间,而且节约了流量。  #### 2、按需加载 利用 **代码分割** 技术,可以把原本庞大的代码包拆分成多个小模块,根据用户实际需要的功能来加载对应代码。 比如之前网站所有页面的 CSS 和 JS 文件是合并在一起的,现在可以按照页面分割成多个小文件,访问哪个页面就加载哪个页面的代码: ```javascript // 传统方式:一次性加载所有页面代码 import UserProfile from './UserProfile.vue' // 代码分割:访问页面时才加载对应代码 const UserProfile = () => import('./UserProfile.vue') ``` 这样一来,访问网站首页时,只按需加载和首页有关的部分代码,减少了网站首次加载的文件体积,首屏加载速度就能大幅提升,缩短到 162 毫秒,又优化了 1 倍!  之后当访问其他页面时,才会加载对应的网站文件。 #### 3、分层加载 这个策略的核心是 **先快后好**,先让用户立即看到内容,再根据情况提升质量,避免用户等待的焦虑感。常见的实现方式有 2 种。 首先是 **缩略图**。在列表页显示低清小图,用户点击某个感兴趣的内容后再加载高清大图,这样几乎不影响用户体验,又节省了流量。实现方法很简单,为每个高清原图额外生成一个缩略图,不同的页面加载不同的图片就好。 这样一来,首屏加载的资源更小,加载时长缩短到了 118 毫秒,又优化了 25%!  还有一种更高级的策略 —— **渐进式加载**,先加载低质量内容,再自动加载高质量内容。比如图片会先显示一个模糊的预览,然后逐渐变清晰。这样用户不会看到空白区域和页面错位,浏览体验更丝滑。  #### 4、预加载 这个策略的核心是 **预判用户需求**,在用户需要访问之前就把资源准备好,让体验更流畅。 只需要一行代码,就能指定预加载的资源: ```html <link rel="prefetch" href="next-page.js"> ``` 举个典型的场景,比如用户浏览博客网站的文章列表时,可以预加载几篇文章详情页,这样用户点击时几乎秒开。  但我建议使用预加载前,最好先对网站的结构和访问情况进行分析,关键是要把握好度。预加载太少效果不明显,预加载太多又会产生流量浪费。就像你提前为客人准备了第二天的饭,结果人家当天就走了。 ### 请求优化 除了几种加载策略外,还有个比较高级的技巧 —— **请求合并**。 当你要多次调用后端接口来请求数据时,由于浏览器对同一域名有并发请求数限制(一般是 6 个左右),可能会产生请求排队和阻塞,导致数据加载耗时过长。这时,我们可以让后端提供一个聚合接口,一个接口返回某个页面需要的所有数据。不过这需要你跟后端关系不错,否则你就只能自己搭个 Node.js 中间层来做请求聚合了。 请求网站小图标也是类似的,与其一个个请求图标文件,不如利用 **CSS 雪碧图** 特性,把所有小图标合并为一张图片,然后在前端利用 CSS 的 `background-position` 来加载图片的指定位置。  这样只需要请求 1 次,就达到了同样的效果,网站的加载时长也会进一步缩短。  ## 性能分析关键 通过前面分享的这些网站优化技巧,最终我们网站的加载时间竟然 **从 180 秒优化到了 118 毫秒**,性能提升了 1500 多倍!效果还是非常炸裂的,弱网环境也能很快访问网站。 **但这就是极限了么?** 可别忘了,我们的体积优化和加载优化并没有使用 CDN 进行演示,那如果我们同时开启 CDN 和 HTTP 2,效果又如何呢? 结果可能让大家失望了,经过我的多次测试,**性能并没有明显的提升**。我们对网站本身的优化越多,传输数据量就更小,再加上各种不稳定因素,CDN 在提速方面的效果可能就没那么明显了。这就是为什么我建议大家先对网站本身进行优化,CDN 还是要谨慎使用。 虽然方法教给大家了,怎么合理运用这些方法进行优化,是需要大家持续探索的。记住,性能优化一定是 “**针对具体场景,先分析再优化**”。因此我们也一定要准备一些网站性能分析的工具,我一般会优先使用浏览器开发者工具内置的 Lighthouse,使用很方便。点击一键分析,就能帮你进行全面的网站性能评估,还给出了很多优化建议,有一些不正是我们今天分享到的方法么?  此外,还有一些免费工具也可以试试看: - [GTmetrix](https://gtmetrix.com/) - 详细的瀑布图分析 - [WebPageTest](https://www.webpagetest.org/) - 全球多节点测试  ## 最后 OK 以上就是本期内容,虽然这并不是所有的网站优化方法(还有后端优化、Web Workers、Service Worker、WebAssembly 等等),但已经能够覆盖绝大多数优化场景了。大家可以按照上面提到的方法,一步步按需优化自己的网站。 这篇文章鱼皮总共写了 5000 多字,如果对你有帮助,记得 **点赞收藏** 支持一下!大家反馈不错的话,我后续会继续分享 **后端性能优化的一条龙服务**,绝对干货满满,学编程和 AI 的朋友记得关注鱼皮,我们下期见。 ## 更多 💻 编程学习交流:编程导航:https://www.codefather.cn/ 📃 简历快速制作:老鱼简历:https://laoyujianli.com ✏️ 面试刷题神器:面试鸭:https://mianshiya.com 📖 AI 学习指南:AI知识库:https://ai.codefather.cn/
(万字图文)简单聊一聊秒杀场景下百万并发的设计方案
### 前言 **如果大家对这方面知识不够了解往往会谈虎色变,比如我们都知道数据库的单行并发量大概300-500TPS那么怎么能支持百万级的并发呢?而恰巧本人目前参与相关业务开发对这方面知识存在涉猎,所以写下这篇万字图文讲解,如果有不对的地方请多多包涵。** ## 秒杀场景特点 秒杀场景具有流量大、并发高、时间短、数据热点、业务复杂等特点,对系统的性能和可用性提出了很高的要求。如何设计一个能够应对百万级并发的秒杀系统,是一个值得深入探讨的问题。 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/nSXBCvckpCIZFAQu.png" alt="image.png" width="439px" /> ## 前端层 **前端层是秒杀系统的第一道防线,主要负责限流、降级、缓存等功能,减轻后端服务的压力。** - 静态资源缓存 - 将静态资源如JS、CSS、图片等部署到CDN,利用CDN的缓存和分发能力,减少应用服务器的压力 - 设置合理的缓存策略,如Cache-Control、Expires等,让浏览器尽可能使用缓存 - 前端限流 - 验证码过滤机器请求 - IP限制同一IP请求频率 - 前端降级&容错 - 如关闭核心功能,只保留倒计时、秒杀按钮等 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/AFND4UP7Vzzt6ewf.png" alt="image.png" width="506px" /> ## DNS+CDN层 **DNS和CDN可以将用户请求分发到离用户最近的服务节点,提高响应速度,减轻源站压力。** - DNS负载均衡 - 通过DNS解析将用户请求分发到不同的服务器或集群,实现负载均衡 - 可以使用权重、地理位置等策略,将请求分发到最优的服务节点 - 常见的DNS服务有:阿里云DNS、腾讯云DNS、Amazon Route 53等 - CDN加速 - CDN可以将静态资源缓存到离用户最近的边缘节点,提高访问速度,减轻源站压力 - 同时CDN也可以实现动态内容的加速,如利用CDN的节点缓存动态页面 - 常见的CDN服务有:阿里云CDN、腾讯云CDN、Amazon CloudFront等 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/DeNwTxxNhOLUUuJ6.webp" alt="image.png" width="589px" /> ## 负载均衡层 **将用户请求分发到后端的多个服务器或集群,实现流量的均衡分配,提高系统的并发处理能力。** - 反向代理 - 负载均衡算法 - 会话保持 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/BQNI2Ze7SB0ri9VL.webp" alt="image.png" width="100%" /> ## 应用服务层 **应用服务层是秒杀系统的核心,负责处理用户请求,实现秒杀业务逻辑。** - 集群部署 - 降级&熔断&隔离 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/tt76Q1xcdGcGpxzR.webp" alt="image.png" width="100%" /> ## 消息队列 **消息队列可以实现系统解耦、异步处理、削峰填谷等功能,是秒杀系统的重要组件。** - 异步处理 - 削峰填谷 - 消息重复消费 - 消息可靠性 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/e8qHDRE3JnyEJzjt.webp" alt="image.png" width="640px" /> 除此之外在消息队列中还有以下的相关概念: **异常处理&实际效果** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/DVBzxct1qgyp8W92.webp" alt="image.png" width="559px" /> ## 缓存层 **缓存可以有效地减轻数据库的压力,提高系统的响应速度,是秒杀系统必不可少的组件。** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/bH7tk6D4IhgTpslq.png" alt="image.png" width="345px" /> ### 多级分层架构 **应用服务器->本地缓存(Caffeine)->分布式缓存(Redis)->数据库(MySQL)** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/iFMsSgal5RrayAny.webp" alt="image.png" width="100%" /> ### 缓存数据分类 **针对秒杀场景中一般商品如下进行分类** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/QcXAJTtv2tsTzZYD.webp" alt="image.png" width="627px" /> ### 缓存预热 预热规则、时机、数据 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/cQ0EZEJbd4MN1LP5.webp" alt="image.png" width="575px" /> ### 缓存更新策略 **首先是Cache Aside Patten** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/7sPIB1zGGTQt9qI4.webp" alt="image.png" width="100%" /> **更新策略** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/ALuR8L1WbvxkVL81.png" alt="image.png" width="511px" /> **缓存失效分类和策略选择** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/Cjb5RBoMpOOYdkUz.webp" alt="image.png" width="637px" /> ### 数据一致性 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/Xago7OIX3tgCmufZ.png" alt="image.png" width="340px" /> ## 数据库层 **主要分五个方面 如下:** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/2l6qmERdwZoDv2zQ.png" alt="image.png" width="485px" /> ### 分库分表 **垂直分库和水平分表** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/j6DGT21crEl1C2t9.webp" alt="image.png" width="100%" /> ### 读写分离 **读写分离,冷热数据分离** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/GIEiN4Mw1mLIbuqr.webp" alt="image.png" width="652px" /> **注意事项:数据/事务一致性、负载均衡等** ### 索引优化 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/U4lWJFTQWSqQjifE.webp" alt="image.png" width="100%" /> ### SQL优化 <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/fi7CPwMf58lxMcG8.webp" alt="image.png" width="674px" />  **数据库层面一些索引、SQL优化就不展开详细论述了,相对来说大家可能对这块都比较熟悉。** ## 系统可用性相关保障 **分为高可用架构、降级&熔断、监控&告警** <img src="https://pic.code-nav.cn/post_picture/1624602326562050049/n4ibic03IVZK1Do6.webp" alt="image.png" width="500px" /> ## 总结 现在我们在来梳理一下整个请求调用 - 100w用户点击秒杀按钮假设发送了100w请求 - 100wQPS到达前端层经过防抖、节流、限流等措施,过滤掉5w无效请求 - 95wQPS来到DNS+CDN层,通过请求分发分发给不同Nginx - 4台Nginx 每台Nginx请求为5000qps 那么转发2W请求 93w无效请求被nginx网关过滤 - 2W请求来到应用层,一般大促场景8-12台服务器(弹性扩容)取10台 每台服务器只需要处理2000qps - 2w请求经过应用层的处理后通过生产者发送给消息队列集群 - 假设topic主题 设置10个消费者 每个消费者QPS 10个消费者处理5000QPS 其他15000 请求进入等待队列 - 5000请求经过缓存秒返回 写到数据库层面可以是异步机制 分库分表 每张表只需要处理500TPS **总的来说,秒杀系统的请求经过了前端层、DNS和CDN层、应用层、消息队列层、缓存层、数据库层的层层过滤和处理,最终每个节点的请求量都控制在一个合理的范围内。这种分层分流的架构可以有效地应对秒杀场景下的高并发和大流量。**
知识点记录-性能优化
为什么要进行性能优化:性能优化的体现对于产品的影响很大,为了保证用户的留存率和转化率,提升应用的响应速度、交互体验,以保竞争力 1. 数据懒加载:使用IntersctionObserver监听视图是否被用户可见,可见的时候再加载数据 2. 图片懒加载:自定义指令+IntersctionObserver,在自定义指令里面的生命周期里面缓存图片的路径,设置临时占位图,使用IntersctionObserver监听是否在可视区域,当在可视区域的时候,替换图片的src 3. 打包体积过大优化:排除打包externals 4. gzip压缩:与Nginx配置开启gzip压缩
【鸿蒙实战开发】基于List的滑动丢帧性能问题分析思路&案例
## **1. 场景导入** 基于ArkUI的List组件实现的滚动列表视图,在手指抛滑场景下,通过分析掉帧情况来判断List滑动是否流畅,保障用户极致流畅体验。 ## **2. 性能指标** 最大连续丢帧数:指从页面开始有响应变化到页面结束刷新的过程中,由于显示器画面刷新频率低于预设的画面帧率而未能正常呈现的最大连续帧数。一般而言,当连续值超过3时,用户可以明显感知到卡顿掉帧,数值越大卡顿时间越长。最大连续丢帧数越接近于0,用户流畅性体验越好。 **2.2 性能衡量起止点介绍** 以大于300mm/s的速度,连续3次抛滑,每次半屏。抓取滑动过程Trace,查看Frame泳道中应用进程和RenderService的最大连续丢帧数。 List组件的抛滑过程,可以通过应用进程下的H:APP_LIST_FLING泳道标识。性能衡量的起点为第一次抛滑开始点,衡量的结束点为第三次抛滑的结束点。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/AaXX0jxCZoIhwjXn.webp" alt="image.png" width="532px" /> | 泳道 | 描述 | | --- | --- | | H:APP_LIST_FLING | 从手指按下开始拖动到抬手后的惯性滚动及最后尾动效的抛滑全过程。| | H:touchEventDispatch | 拖滑阶段,从手指开始拖动到抬起。| | H:TRAILING_ANIMATION | 抛滑尾动效阶段 | Tip:尾动效阶段系统会进行降帧处理,所以如果要统计FPS情况,通常只会统计从抛滑开始到尾动效起点的这一阶段 ## **3. 问题定位流程** **3.1.1 查看操作录屏辅助定位** 处理三方应用问题时,可以优先查看操作录屏,查看操作场景,看能否发现一些有助于定位的信息,比如卡顿的页面布局情况、卡顿的现象等等。 **3.1.2 Trace 抓取** 滑动帧率Trace抓取请参考【附录1: 滑动帧率Trace抓取方法】。 ### **3.2 问题定位思路** 滑动丢帧类问题的通用定位思路为先确认抛滑的起止点,然后看抛滑过程中最大连续丢帧数,如果大于0帧,则根据Trace信息进一步确认问题点,确认责任领域并对齐处理,处理流程如下图: <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/96Qxj4W3q65yDQuE.webp" alt="" width="526px" /> **3.2.1****确认起止点** 参考【2.2 性能衡量起止点介绍】 **3.2.2 找问题点** **3.2.2.1 判断丢帧进程** 首先通过Frame泳道判断丢帧进程,其中绿色代表没有丢帧,其他颜色均为丢帧。其中“粉红色”代表该帧的期望时间,“红色”标识超时部分。 **应用进程问题** 如下图,应用进程连续丢了5帧,但可以看到只有261帧、263帧、264帧耗时较长,因此只分析这3帧即可。另外发现最后一帧序号也是264,这是因为前一帧耗时较长,导致该帧和前一帧都提交到了RS的264帧上,应用帧上的序号是被提交到的RS帧序号相对应的。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/J6aa13VGrlvm6uPy.webp" alt="" width="526px" /> **RS 进程问题** RS进程丢帧可能是应用进程导致的,如上图RS侧丢了1帧,但可以看到RS侧264帧丢帧原因是由于应用进程的264帧耗时较长,提交较晚导致。所以这种情况只分析应用侧丢帧原因即可。如果应用进程中没有丢帧且每帧耗时比较均匀,但是RS侧发生丢帧,则说明不是应用侧导致丢帧,此时只分析RS进程丢帧原因即可。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/C6ZLKGaDU9DdzURW.webp" alt="" width="528px" /> **3.2.2.2 找丢帧Trace** 选中Frame泳道,点击Statistics下面的应用进程右侧图标进入Frame List <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/NT2seRCeAt5kOoy2.webp" alt="" width="527px" /> 过滤Jank Type为AppDeadlineMissed类型的帧,点击跳转应用进程。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/l65UPLsFRyyf2NYc.webp" alt="" width="527px" /> 详细分析丢帧Trace <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/I1XEq0WPMxX34VfP.webp" alt="" width="526px" /> **3.2.3 根因分析方法** 应用侧的渲染流程如下图所示,了解ArkUI的渲染流程有助于我们定位应用侧的卡顿问题出现在哪个环节 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/skbFPwtjuUh55Iu2.webp" alt="" width="531px" /> | 阶段 | 描述 | | --- | --- | | Animation | 动画阶段,在动画过程中会对相应的组件标记脏区 | | Events | 事件处理阶段,比如手势事件处理。在手势处理过程中也会对组件标记脏区 | | UpdateUI | 组件在首次创建或状态变量变更时会标记为需要rebuild状态,在下一次Vsync过来时会通过View的方法生成相应的组件树结构和属性样式修改任务。 | | Measure | 执行组件的大小测算任务。 | | Layout | 执行组件的布局任务。 | | Render | 执行绘制任务,执行完成后会标记请求刷新RSNode绘制 | | SendMessage | 将绘制数据提交到RS侧,请求刷新界面绘制 | **应用进程丢帧分析** 跟据Trace图,初步分析耗时较长阶段。261帧由于懒加载组件预创建耗时较长导致丢帧;263帧由于组件复用耗时长丢帧;264帧由于组件结构复杂嵌套层级多导致丢帧。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/apQ62s57ISH4psst.webp" alt="" width="532px" /> | 序号 | 所属泳道 | Trace点 | 描述 | | --- | --- | --- | --- | | 1 | 应用进程 | H:LazyForEach predict | LazyForEach预处理 | | 2 | 应用进程 | H:CustomNode:BuildRecycle 自定义组件名 | 自定义组件的复用,包含执行aboutToReuse方法的耗时 | | 3 | 应用进程 | H:CreateTaskMeasure[组件名][self:组件id][parent:父组件id] && H:Measure[组件名][self:组件id][parent:父组件id] | 执行组件的布局测量任务 | 通过ArkTS CallStack泳道,可以看到应用侧具体调用栈,进一步分析定位问题原因。如下图通过调用栈可以分析出:组件复用时会组件树进行递归,这个过程耗时较长,可以看下组件数是否组件嵌套层级过深;updateDirtyElement耗时长,应用侧可以分析下是否存在冗余节点被触发更新;aboutToReuse耗时长可以看下应用侧该回调中是否存在耗时逻辑。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/U15EjQ8aWv1JZ8Ly.webp" alt="" width="527px" /> 应用UI组件树的嵌套情况,可以通过ArkUI Inspector查看。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/hvSEOqYhMbAgc6zt.webp" alt="" width="526px" /> **经验总结:** 应用进程丢帧通常是组件结构嵌套层级深、耗时应用业务逻辑阻塞UI线程等问题导致。如果是UI结构复杂问题可以让应用通过减少嵌套层级、使用组件复用等方式优化。如果是有耗时业务逻辑,则可以通过将耗时逻辑放到Taskpool或Worker中优化。 **RS 进程丢帧分析** RS进程丢帧一般是由于界面结构过于复杂或者GPU负载过大等原因导致的。如果应用侧没有丢帧且每帧耗时比较平均,则可以初步判断应用侧没有问题,同时也可以通过应用侧Trace中H:SendCommands下的H:MarshRSTransactionData cmdCount查看应用提交的绘制指令树是否过多。如下图RS侧丢帧原因是由于RS侧的H:RSUniRender:FlushFrame阶段耗时较长,此时可以找图形子系统进一步确认耗时根因。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/8yWfSFFjAg1EjH1F.webp" alt="" width="527px" /> 经验总结: RenderService侧丢帧通常是应用侧UI线程阻塞提交绘制指令较慢导致,此时应当初步定位应用侧耗时长原因。如果应用侧无丢帧情况,绘制指令正常提交,则可以找图形子系统协助进一步分析丢帧原因。 ## **4. 典型问题** ### **4.1 耗时任务阻塞UI 主线程** Stage模型下的线程主要有三类:主线程、TaskPool、Worker。主线程主要用于执行UI绘制、处理应用代码逻辑,TaskPool和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他CPU密集型任务。当主线程存在**耗时的计算任务**时,会使**主线程阻塞**,导致应用丢帧。 ### **4.1.1 问题根因分析** 应用进程中间有一段大段“空白”,UI线程未提交任何绘制指令,同时CPU10大核却处于Running状态,表示此时应用侧正在执行ArkTS的业务代码。按每帧8.3ms算,这里阻塞了75.9ms,丢了9帧左右。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/yN6Ryes38BijpjHN.webp" alt="" width="527px" /> 通过ArkTS Callstack泳道,可以看到耗时点主要在三个文件:FlowApi.ets、BaseFeedFlowListVM.ets、Mapi.ets。 与伙伴确认业务逻辑主要为:在列表滑动过程中,List将要到达底部时,会通过网络请求会获取到一个博文的列表数据,数据量较大。然后在FlowApi.ets、Mapi.ets中会对数据进行转换处理,处理后的数据通过BaseFeedFlowListVM.ets文件再转换成 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/12n9W4EXCr1rqDkj.webp" alt="" width="527px" /> **4.1.2 优化方案** 在List滑动过程中对数据进行处理耗时较长,占用大量CPU资源,导致主线程被阻塞,这部分数据处理的相关业务逻辑与UI绘制无关,但却长时间占用CPU资源,导致UI线程被阻塞丢帧。可以将该数据处理逻辑放到TaskPool中利用多核并行化处理优化。 除应用侧的耗时逻辑外,某些与UI绘制无关的耗时系统接口调用也可以放到TaskPool中优化。 **4.2 @Prop 传参深拷贝耗时长** **4.2.1 问题根因分析** 观察应用Trace发现Measure阶段的H:CustomNode:BuildItem [MediaCard]耗时较长4ms 661μs,通过观察ArkUI Component泳道,得出自定义组件MediaCard构建耗时较长。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/cJ04SwkpSUMv8n6M.webp" alt="" width="526px" /> 通过ArkTS CallStack观察应用ArkTS调用栈。其中observeComponentCreation2为@Observed传参时相关逻辑调用栈。resetLocalValue、copyObject、deepCopyObject、deepCopyObjectInternal、getDeepCopyOfObjectRecursive为@Prop接收参数时拷贝数据的相关调用栈。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/mghhRVDGgagzkfuM.webp" alt="" width="527px" /> 由此分析得出:应用侧MediaCardComponent.ets文件中的自定义组件MediaCard通过@Observed+@State方式声明了某个对象类型的数据,并将该变量传给了子组件MediaImage,但在MediaImage中并未使用@ObjectLink接收该变量,而是使用@Prop接收,导致该状态变量发生了深拷贝,深拷贝过程耗时较长3ms 339μs。 **4.2.2 优化方案** @Prop装饰器存在性能问题,@Prop装饰的变量会对父组件传入状态值进行深拷贝,如果@Prop装饰器装饰的变量为复杂Object、class或其类型数组时,会增加状态创建时间以及占用大量内存。如果需要观察嵌套类对象的深层属性变化,推荐选择@State+@Observed+@ObjectLink组合方案。 **4.3 UI 复杂导致单帧超长** **4.3.1 问题根因分析** List滑动场景中,应用侧单帧耗时较长91.8ms,导致应用丢帧。通过Trace可以看到在这一帧中创建了大量组件。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/TBimBxqiRw0HR5tt.webp" alt="" width="529px" /> **4.3.1.1 组件复用失效** 首先,观察Trace发现,在ListItem的Measure阶段,出现了大量H:CustomNode:BuildItem,说明此时发生了大量自定义组件重新创建,而没有被复用。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/anKt3dHI4kF6iXqh.webp" alt="" width="526px" /> 经与应用讨论分析,应用的单个ListItem包含4个部分(头像、文本、九宫格、底部按钮)。ListItem的结构非固定的,其子组件可能会出现多种情况,如图文、视频、纯文本等等,动态性较高。同时又因为其复用是以整个ListItem为单位,所以在进入复用池时ListItem会存在多种可能性。导致在新ListItem期望复用创建时,在复用池中可能未找到对应的可被复用组件,自定义组件被重新创建,组件复用失效。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/283SGh1eCq4bChfs.webp" alt="" width="531px" /> **4.3.1.2 嵌套层级深** 通过ArkUI Inspector观察UI组件树结构,可以发其中存在大量自定义组件的__Common__节点(如下图红框),且存在容器之间组件冗余嵌套的情况(如下图绿框)。冗余的嵌套会带来不必要的组件节点,加深组件树的层级,在创建和布局阶段会产生较大的性能开销。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/NGyAEORGbOTl9iUJ.webp" alt="" width="527px" /> **4.3.1.3 冗余的状态变量** 框选该帧并筛选Trace点:H:ViewPU.viewPropertyHasChanged,该Trace点表示状态变量发生了更新,其中后三个参数分别为自定义组件名、自定义的状态变量名、该状态变量更新后影响的组件数量。可以发现这里有大量为“0”的Trace点,表示该状态变量更新时未触发任何组件刷新,即该状态变量未绑定UI组件,因此可以将其改为普通变量。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/UMrkdaKMdOyWqS17.webp" alt="" width="531px" /> **4.3.2 优化方案** **4.3.2.1 组件复用失效优化** 细化组件复用的颗粒度,将原来对ListItem的复用改为对ListItem中各部分子组件复用,提高组件复用成功率。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/2mfmg1HMx6iGNWE9.webp" alt="" width="532px" /> 相关修改代码参考: <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/8LpI6JSeIPeKn2xB.webp" alt="" width="528px" /> **4.3.2.2 嵌套层级深优化** **方案一:** 当自定义组件设置通用属性后,UI组件树就会产生__Common__节点,可以通过属性内移解决。将自定义组件上设置的属性内移到自定义组件中的第一层系统组件上。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/Ui5glw90ipT9CBw3.webp" alt="" width="527px" /> **方案二:** 避免冗余的嵌套,对于这类冗余的容器,应该尽量优化,减少嵌套深度。建议采用相对布局RelativeContainer进行扁平化布局,有效减少容器的嵌套层级,减少组件的创建时间。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/BNriIfHHxkPzsk4C.webp" alt="" width="530px" /> **方案三:** 使用@Builder代替@Component自定义组件。通过@Component声明的组件在创建时会产生额外耗时,建议尽量使用@Builder声明组件。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/pFeIWMMocLNjU3XZ.webp" alt="" width="525px" /> **4.3.2.3 冗余的状态变量优化** 将没有跟UI组件绑定的状态变量改为普通变量。@State、@Prop等装饰器修饰的状态变量在创建、Get、Set时都会产生耗时,因此应该尽量减少冗余的状态变量,避免性能损耗。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/faKXefBjMQxkLL7F.webp" alt="" width="526px" /> **附录1:滑动帧率Trace抓取方法** **Step1:电脑连接上设备,在DevEco Studio上打开Profiler <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/2rO12wWu2FzZhe0Q.webp" alt="" width="529px" /> **Step2 :** 设备上运行需要测试的应用,在设备列表选择设备,选择要测试的应用,和主进程 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/waaejyiPNlW5n39q.webp" alt="" width="525px" /> **Step3:** 创建Frame模板,并点击录制,待所有泳道都进入到recording状态后 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/s0tQWZSTaf5WPRXq.webp" alt="" width="526px" /> **Step4:** 执行相关滑动操作 **Step5:** 操作完成,点击结束录制,待分析完成后,可以在泳道上看到trace数据 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/Ay0695cJDqfadLlz.webp" alt="" width="528px" /> **Step6: ** trace的路径点击Help -> Show Log in Explorer <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/NdgPS9Uw8gocABPx.webp" alt="" width="424px" /> 返回到上一层,找到.insight文件下 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/y8yY4WPC1p5axthR.webp" alt="" width="527px" /> **附录2:List滑动场景通用Trace点说明** **基础List滑动** 以一个最基础的List Demo为例,通过脚本抛滑并使用IDE Profiler工具抓取Trace。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/V8mMg24LneKDjzX8.webp" alt="" width="528px" /> **拖动阶段** 选取拖动阶段某一Trace如下: <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/rqkc8TThQcKyILwe.webp" alt="" width="528px" /> | 序号 | Trace | 描述 | 参数说明 | | --- | --- | --- | --- | | 1 | H:client dispatch touchId:32903 | 系统派发touch事件 | 事件id | | 2 | H:OnVsyncEvent now: [时间戳] | 收到Vsync信号,渲染流程开始 | 时间戳--纳秒级 | | 3 | H:FlushVsync | 处理用户输入、刷新视图同步事件、计算帧信息、提交绘制渲染等 | | | 4 | H:DispatchTouchEvent id:0, pointX=[x坐标] pointY=[y坐标] type=2 | 处理拖拽手势事件 | 触摸点的xy坐标信息,type=2表示手势事件类型为移动 | | 5 | H:HandleDragUpdate | 执行拖拽更新任务 | | | 6 | H:AddDirtyLayoutNode[List][self:7][parent:6] | 标记List组件为脏区 | 组件名、组件ID、父组件ID | | 7 | H:UITaskScheduler::FlushTask | 刷新UI界面,包括布局计算、渲染和提交等 | | | 8 | H:FlushLayoutTask | 执行布局任务 | | | 9 | H:CreateTaskMeasure[List][self:7][parent:6] && H:Measure[List][self:7][parent:6] | 执行List组件的布局测量任务 | 组件名、组件ID、父组件ID | | 10 | H:ListLayoutAlgorithm::MeasureListItem:27 && H:Measure[ListItem][self:10][parent:7][key:] | 计算ListItem列表项的布局尺寸 | 列表项索引、组件名、组件ID、父组件ID | | 11 | H:SkipMeasure | 组件大小布局未发生变化,跳过measure过程 | | | 12 | H:CreateTaskLayout[List][self:7][parent:6] && H:Layout[List][self:7][parent:6] | 执行List组件布局任务 | 组件名、组件ID、父组件ID | | 13 | H:Layout[ListItem][self:10][parent:7][key:] | 执行ListItem组件布局任务 | 组件名、组件ID、父组件ID | | 14 | H:SyncGeometryNode[List][self:7][parent:6][key:] | 同步几何节点 | 组件名、组件ID、父组件ID | | 15 | H:FlushRenderTask 1 && H:FrameNode[List][id:7]::RenderTask | 执行渲染绘制任务 | 当前页面需要绘制的节点数量、需绘制的组件名和ID | | 16 | H:FlushMessages && H:SendCommands | 通知图形侧进行渲染 | | | 17 | H:MarshRSTransactionData cmdCount:14 transactionFlag:[24390,75] | 向图形侧发送绘制指令 | 绘制指令数量、应用进程号、指令序列号 | | 18 | H:OnIdle, targettime:222549467458334 | Vsync中的空闲,一般会用来做预加载之类的操作,当H:OnVsyncEvent时间小于某值时触发该事件 | 时间戳 | **惯性滑动阶段** 惯性滑动阶段Trace点与拖动阶段基本一致,唯一区别点在于拖动阶段是通过手势事件标脏,而惯性滑动阶段是通过动画触发的组件标脏。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/ouh430JBw08QHNw2.webp" alt="" width="525px" /> | 序号 | Trace | 描述 | 参数说明 | | --- | --- | --- | --- | | 1 | H:RunningCustomAnimation num:[1] | 自定义动画,RSModifierManager管理的在UI线程运行的动画 | num表示动画的数量,如果大于0,则表示有动画在运行 | | 2 | H:AddDirtyLayoutNode[List][self:7][parent:6] | 标记List组件为脏区 | 组件名、组件ID、父组件ID | | 3 | H:FlushDirtyNodeUpdate | 更新被标脏的节点 | | **尾动效阶段** 尾动效阶段相较其他两阶段的区别点,在于尾动效阶段如果移动距离小于1像素则不会向RS提交H:SendCommands。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/tguJXUd2ymesmqVM.webp" alt="" width="525px" /> 选取一个应用进程未提交帧,放大后可以看到H:SendCommands下方并无H:MarshRSTransactionData的Trace点。无该Trace点时说明ArkUI中没有需要绘制的内容,因此没有提交绘制指令到RenderService侧。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/5WjWUtFZKtozSEOj.webp" alt="" width="533px" /> **懒加载场景滑动** 基于上文List Demo改造,实现懒加载效果。List懒加载场景下滑动的Trace点与上文基础List基本一致,其区别点主要在于懒加载的滑动场景下会出现组件创建和销毁过程,因此本章节主要介绍创建和销毁组件的关键Trace点。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/KCWEuPnnwJb79KCR.webp" alt="" width="528px" /> **创建组件** 当List组件的cachedCount属性设为0时,ListItem的创建会发生在H:FlushLayoutTask阶段。如果不为0,则会在H:OnIdle阶段预创建组件。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/vOamHzh61goXgrvs.webp" alt="" width="530px" /> | 序号 | Trace | 描述 | 参数说明 | | --- | --- | --- | --- | | 1 | H:Builder:BuildLazyItem [4] | 创建一个LazyItem项目 | 创建的项目索引 | | 2 | H:Create[Child][self:28] | 创建一个自定义组件 | 自定义组件名、组件ID | | 3 | H:CustomNode:OnAppear && H:aboutToAppear | 执行自定义组件的aboutToAppear 方法 | | | 4 | H:CustomNode:BuildItem [Child][self:28][parent:27] | 执行自定义组件的build方法 | 自定义组件名、组件ID、父组件ID | | 5 | H:Create[Text][self:31] | 创建一个Text组件 | 组件名、组件ID | | 6 | H:Measure[Text][self:31][parent:27][key:] | 计算Text组件布局 | 组件名、组件ID、父组件ID | | 7 | H:LazyForEach predict | LazyForEach预处理 | | | 8 | H:List predict | List组件预处理 | | **销毁组件** 组件销毁只会发生在H:OnIdle空闲阶段。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/CSvHn7mZxwadw04i.webp" alt="" width="529px" /> | 序号 | Trace | 描述 | 参数说明 | | --- | --- | --- | --- | | 1 | H:LazyForEach predict | LazyForEach预处理 | | | 2 | H:aboutToDisappear | 执行自定义组件的aboutToDisappear方法 | | | 3 | H:aboutToBeDeleted | 删除组件 | | **组件复用场景滑动** 基于懒加载场景Demo改造,实现组件复用效果。与懒加载场景相比,在滑动过程中不会发生组件的销毁和创建,而是会在组件将要销毁时,将其放入缓存池中,在需要创建时再从缓存池中取出,并重新赋值。因此本章节主要介绍Reuse阶段和Recycle阶段关键Trace点。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/VkaoAZagZLMlfxWS.webp" alt="" width="532px" /> **Reuse****阶段** <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/8s1jVlwun97OKpQ9.webp" alt="" width="530px" /> | 序号 | Trace | 描述 | 参数说明 | | --- | --- | --- | --- | | 1 | H:CustomNode:BuildRecycle Child | 自定义组件复用,执行aboutToReuse方法 | 复用的组件名 | | 2 | H:Create[Text][self:12] | 创建Text组件 | 组件名、组件ID | | 3 | H:AddDirtyLayoutNode[ListItem][self:61][parent:0] | 标记ListItem为脏区 | 组件名、组件ID、父组件ID | Recycle阶段 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/Dx6xWtXTkfEy4Z8e.webp" alt="" width="526px" /> | 序号 | Trace | 描述 | | --- | --- | --- | | 1 | H:LazyForEach predict | LazyForEach预处理 | | 2 | H:aboutToRecycleInternal | 标识组件进入复用池并调用组件的aboutToRecycle方法 | | 3 | H:ViewFunctions::ExecuteRecycle | 执行组件回收 |
【鸿蒙实战开发】冷启动响应时延问题分析思路&案例
## **1.场景导入** 应用启动可以分为冷启动和热启动: **冷启动:** 当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这种启动方式就叫做冷启动; **热启动:** 当应用程序已经在后台运行,此时用户再次打开应用程序时,应用程序仍然在内存中,可以直接从内存中加载并继续之前的状态,而不需要重新初始化和加载资源,这种称为热启动。 **冷启动响应时延:** 应用冷启动时,从点击应用离手开始到桌面应用图标发生变化(通常指图标变大)的这一段时间称为冷启动响应时延。 ## **2. 性能指标** ### **2.1 性能指标介绍** 冷启动响应时延推荐时间:85ms ### **2.2 性能衡量起止点介绍** <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/2xRl9d29hdMyloRB.webp" alt="" width="515px" /> 应用冷启动响应时延的性能衡量的起点为用户点击应用图标离手帧时间,止点为桌面应用图标开始发生变化的首帧时间。 ## **3. 问题定位流程** ### **3.1 常规定位前置流程** **3.1.1、确认效果** 处理三方应用问题前首先需要先和三方应用及测试确认当前问题场景的预期效果: 1. 三方应用:和三方应用确认问题场景是否认可该标准,如不认可,相关问题需评审关闭。 2. 测试:和测试确认是否按照预期效果执行的测试,测试步骤和性能衡量是否准确。 **3.1.2 查看操作录屏辅助定位** 处理三方应用问题时,可以优先查看操作录屏,查看操作场景,看能否发现一些有助于定位的信息,比如应用启动是否存在横竖屏切换,是否存在页面跳转,是否包含网络加载等等。 **3.1.3 Trace 抓取** 冷启动Trace抓取请参考【附录1: 冷启动Trace抓取方法】。 ### **3.2 问题定位思路** 冷启动响应时延类问题的通用定位思路为先确认时延起止点,然后看起止点时延是否超60ms,未超过则说明达标,超过则根据Trace信息进一步确认问题点,确认责任领域并对齐处理(通常冷启动时延类问题责任领域都在大桌面),处理流程如下图: <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/TfNt8JE9Qv4Rq4R0.webp" alt="" width="527px" /> **注:图中耗时基线均为参考值,仅供定界参考,实际各子系统是否超标需和对应子系统接口人确认。** **3.2.1 确认起止点** **冷启动响应时延起点确认:** <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/vceOjd1w0mItJlaY.webp" alt="" width="533px" /> 起点Trace查找顺序:H:DispatchTouchEvent type=1(大桌面ohos.sceneboard) -> CPU Running Trace(多模子系统mmi_service)-> H:service report(多模子系统mmi_service) 1. 在大桌面泳道(ohos.sceneboard)搜索H:DispatchTouchEvent并且type=1(0,1,2分别代表按下,抬起,移动)的Trace点,该Trace点代表大桌面收到点击离手事件的Trace; 2. 然后找到多模子系统泳道(mmi_service),找到H:DispatchTouchEvent前的一个CPU Running Trace,该Trace下有一个H:service report touchId:{id}, type: up [id: 0, x:{X}, y:{Y}]的Trace点,该Trace点的X,Y坐标和H:DispatchTouchEvent是对应的,且类型也是up,代表的是多模子系统收到点击离手事件的时间,H:service report这个Trace开始位置就是起点。 **冷启动响应时延止点确认:** <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/0t3Srxhb5sXYTHVc.webp" alt="" width="526px" /> **止点Trace 查找顺序:** H:DispatchTouchEvent type=1(大桌面ohos.sceneboard) -> H:FlushMessages(大桌面ohos.sceneboard)-> H:SendCommands(大桌面ohos.sceneboard)-> H:MarshRSTransactionData(大桌面ohos.sceneboard) -> H:RSMainThread::ProcessCommandUni(RS服务render_service)-> H:ReceiveVsync(RS服务render_service) -> H:FlushBuffer(RS服务render_service)-> H:RSHardwareThread(RS送显线程RSHardwareThrea) 1. 在H:DispatchTouchEvent type=1Trace的末尾找到H:FlushMessages(图中1)-> H:SendCommands(图中2)->H:MarshRSTransactionData(图中3)的系列Trace,这些Trace代表大桌面提交图形渲染请求到render_service(RS图形渲染服务)。 2. 选中H:MarshRSTransactionData(图中3)后可以在详情界面点击箭头跳转到render_service泳道对应的Trace H:RSMainThread::ProcessCommandUni(图中4),这个代表render_service收到大桌面渲染请求的点。(H:MarshRSTransactionData后面会有个参数transactionFlag:[2664,523],括号中两个数字分别代表提交请求的进程号和提交的序号,在H:RSMainThread::ProcessCommandUni也会有一个[2664,523]与其对应,两个Trace是通过这个进程号和序号关联起来的。) 3. 然后继续找H:RSMainThread::ProcessCommandUni(图中4)所在的H:ReceiveVsync(图中5)的Trace,接着找该Trace下的H:FlushBuffer(图中6),这里代表render_service渲染完成并刷新数据到缓冲区。 4. 接着找到RS送显线程泳道RSHardwareThrea,找到根据时间顺序找到H:FlushBuffer后面第一个H:RSHardwareThread::CommitAndReleaseLayers,这里提交后就上屏显示了,这个Trace结束就是终点。(大桌面泳道的H:ReceiveVsync和RSHardwareThrea泳道的H:RSHardwareThread::CommitAndReleaseLayers的now字段也是对应的,也可以通过这个字段值直接找到H:ReceiveVsync对应的H:RSHardwareThread::CommitAndReleaseLayers)。 **3.2.2 找问题点** <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/2GNOo0K7EXkOeph9.webp" alt="" width="527px" /> | 序号 | 所属泳道 | Trace点 | 描述 | | --- | --- | --- | --- | | 1 | mmi_service | H:service report(开始) | 多模子系统收到硬件传递过来的点击离手信号 | | 2 | ohos.sceneboard | H:DispatchTouchEvent(开始) | 大桌面收到点击离手事件 | | 3 | ohos.sceneboard | H:FlushMessages(结束) | 大桌面提交图形渲染请求完成 | | 4 | render_service | H:ReceiveVsync(开始) | RS图形渲染服务收到同步信息 | | 5 | RSHardwareThrea | H:RSHardwareThread::CommitAndReleaseLayers(结束) | RS送显线程提交送显完成 | *** | 阶段 | 起点 | 终点 | 耗时基线(参考) | | --- | --- | --- | --- | | 冷启动响应时延 | 上图1 | 上图5 | 60ms | | 多模子系统时延 | 上图1 | 上图2 | 8ms | | 大桌面时延 | 上图2 | 上图3 | 25ms | | 图形子系统时延 | 上图4 | 上图5 | 20ms | 冷启动响应时延类问题找问题点方式如下: 1. 通过Trace确认完冷启动响应时延起止点后,就可以圈出冷启动响应时延的耗时区间(图中1->5),如果耗时未超过60ms,则说明达标,不需要继续分析。如果超过60ms,则需要进一步分析是哪里耗时。 2. 通过H:DispatchTouchEvent,H:FlushMessages,H:ReceiveVsync这几个关键Trace点可以进一步将冷启动响应时延划分为**多模子系统时延,大桌面时延,图形子系统时延**三个部分,然后看每个部分耗时是否超过耗时基线,如果超过则找相应的子系统团队确认处理。 **经验总结:** 就目前来看绝大部分时延超标问题都发生在大桌面时延部分,多模子系统时延和图形子系统时延很少出现,这点从上图中的Trace划分也可以看出来,大桌面时延部分的Trace区间是最长的,因此确认冷却启动响应时延超标后,通常可以直接找大桌面进一步分析超标根因。 **备注:** 冷启动响应时延预预期85ms,其中包含了硬件显示耗时25ms~30ms(硬件耗时包含点击信号从硬件传输到多模子系统及RS提交渲染数据后数据显示到屏幕上这一段时间),这里按25ms算,减去硬件耗时剩下的才是软件耗时60ms。 **3.2.3 根因分析方法** 确认耗时Trace点后,圈中该Trace范围,在下方详情中可以看到方法调用栈及耗时,层层展开就可以定位到耗时的函数,之后可以和相关领域确认函数功能及耗时是否正常。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/A9jwKv28AU0cewUo.webp" alt="" width="527px" /> 由于冷启动响应时延类问题不涉及应用进程(冷启动响应时延期间,应用进程还未开始启动,这段时间主要做的事情是大桌面拉起应用图标),因此这类问题只需要根据3.2.2章节内容定位到问题点确认系统侧责任领域即可,更进一步的根因分析通常由系统侧进行。 如果想了解更详细的冷启动时延范围内的Trace流程解读,见【附录2:冷启动响应时延Trace点解读】 ## **4. 常见根因归档** ### **4.1 因横竖屏转换启动动效导致冷启动响应时延不满足预期** ### **4.1.1 问题根因分析** <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/033JMDv2qGFMV4E4.webp" alt="" width="527px" /> 圈出大桌面时延区间,发现耗时106.6ms,远超性能基线25ms,进一步查看Trace信息发现,H:JSAnimateTo耗时29.3ms,占据了整个耗时区间的三分之一,猜测应该是问题点,再对比正常应用的H:JSAnimateTo,大约在2ms左右,所以基本可以确认H:JSAnimateTo存在问题。联系大桌面分析后得出结论是因为应用添加了横竖屏切换动效导致H:JSAnimateTo变长(通过视频也可以确认存在应用启动存在横竖屏切换)。 正常应用启动H:JSAnimateTo耗时2ms <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/JzYsETPwDBERvcPd.webp" alt="" width="526px" /> **4.1.2 优化方案** 竖屏应用启动时不需要横竖屏转换动效,建议应用侧删除横横竖屏转换动效。 ### **4.2 因启动页图标分辨率过大导致冷启动响应时延不满足预期** **4.2.1 问题根因分析** <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/sqj8y7suBR0RKVmN.webp" alt="" width="528px" /> 圈出大桌面时延区间,发现耗时106.6ms,远超性能基线25ms,进一步查看Trace信息发现,H:JSAnimateToImmediately耗时44.1ms,占据了整个耗时区间近一半的时间,猜测应该是问题点,再对比正常应用的H:JSAnimateToImmediately,大约在13ms左右,所以基本可以确认H:JSAnimateToImmediately存在问题,联系大桌面分析后得出结论是因为应用启动页图标过大(超过256*256)导致H:JSAnimateToImmediately变长。通过Trace也可以看到H:JSAnimateToImmediately下面有个Trace H:ssm:GetStartupPage耗时32.8ms,这个就是加载启动页图标的Trace。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/FfOnE1K3GNu9Cbom.webp" alt="" width="528px" /> 冷启动响应时延问题分析思路&案例 正常H:JSAnimateToImmediately耗时13ms <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/T0ssM439H6q5S8s6.webp" alt="" width="526px" /> **4.2.2 优化方案** 建议使用不超过256*256分辨率的图片走位启动页面图标,以减少图片解码带来的时延。 附录1:冷启动Trace抓取方法 **Step1:** 打开DevEco Studio的Profiler(下图1) -> 选择设备进程(下图2)-> Frame模式(下图3)-> Create Session(下图4)-> 启动录制(下图5)。 **注意:第二步选择进程时不要选择要录制的进程,因为要录制的进程需要处于未启动状态,这里是通过录制其他进程间接录制目标进程的启动Trace 信息。** <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/1B9Lexfap0kX8q2i.webp" alt="" width="527px" /> **Step2:** 启动录制后,在设备上点击应用图标启动要录制的目标应用,等到应用首页加载后,点击停止结束录制。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/f9dxzZ0FyzAnL2tA.webp" alt="" width="525px" /> **Step3 :** 录制完成后会工具会分析展示Trace数据。 <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/W7Z5yNujuUngfQp6.webp" alt="" width="529px" /> **附录2:冷启动响应时延Trace点解读** 冷启动响应时延的定义是从离手帧到应用图标发生变化的首帧耗时,这其中还包含了硬件耗时25ms-30ms,除去这部分耗时外,剩下的就是软件耗时,软件耗时的大概流程区间如下图: <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/LsxtPlkkIKN5iyIo.webp" alt="" width="527px" /> 抽象流程如下: <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/9n2sMLJkhQGALZ0P.webp" alt="" width="525px" /> 冷启动响应时延的Trace区间为多模子系统H:service report type=up Trace的开始到RS送显线程H:RSHardwareThread Trace的结束。 详细流程如下: 1. 多模子系统mmi_service收到屏幕的点击离手信号Trace点H:service report type=up(type参数类型有down,up,move分别代表按下,抬起,移动),这里是冷启动响应时延的Trace起点。 2. 桌面进程收到多模子系统传递过来的点击事件,Trace点为H:DispatchTouchEvent type=1(type值0,1,2分别代表按下,抬起,移动)。 3. H:DispatchTouchEvent type=1的Trace点下包含H:JSAnimateTo和H:JSAnimateToImmediately两个关键Trace点。这两个Trace也是常见的出问题比较多的Trace点。 * H:JSAnimateTo表示显示动画,包含启动动效加载处理(常见问题是应用启动增加旋转动画导致时间变长) * H:JSAnimateToImmediately包含启动图标的加载,页面组件创建布局刷新(这里常见问题是应用启动图标像素过大导致时间变长) H:JSAnimateToImmediately这个Trace点的最后还包含H:FlushMessages->H:SendCommands->H:MarshRSTransactionData的Trace点,这里是大桌面提交渲染请求给RS渲染服务的流程。H:MarshRSTransactionData Trace点中包含transactionFlag:[2664,523]属性,括号中两个值分别表示RS服务的进程id和提交序号,从H:MarshRSTransactionData可以直接点击箭头跳转到RS服务进程中的H:RSMainThread::ProcessCommandUni[2664,523] Trace点。也可以通过搜索[2664,523]查找(这里只会找到两个关联Trace,一个是大桌面进程中H:MarshRSTransactionData,一个是RS服务进程中的H:RSMainThread::ProcessCommandUni) <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/WQr9TVXIHmLXfSFT.webp" alt="" width="531px" /> 4. 到RS服务中的H:RSMainThread::ProcessCommandUni Trace点表示RS服务收到了大桌面提交的渲染请求,会开始进行图形渲染之后会提交渲染好的图形数据到缓冲区,关键Trace包含H:RSUniRender:FlushFrame->H:FlushBuffer 5. 执行H:FlushBuffer Trace后就会由RS服务送显线程RSHardwareThread执行送显提交,在RSHardwareThrea泳道找到H:FlushBuffer后的第一个Trace H:RSHardwareThread,这里H:Commit后就将渲染好的图像送显上屏了。 | 序号 | 所属泳道 | 关键Trace点 | Trace点说明 | | --- | --- | --- | --- | | 1 | mmi_service | H:service report touchId:[id值], type: up [id: 0, x:[x坐标] , y:[y坐标]] | 手势事件,id代表touch事件id,pointX,pointY表示x,y坐标;type标识手势类型,值有down,up,move分别代表按下,抬起,移动 | | 2 | ohos.sceneboard | H:DispatchTouchEvent id:[id值], pointX=[x坐标] pointY=[y坐标] type=1 | 手势事件,id代表事件id,同一个操作可能会有多个事件,但是id是相同的;pointX,pointY表示x,y坐标;type标识手势类型,值有0,1,2分别代表按下,抬起,移动 | | 3 | ohos.sceneboard | H:JSAnimateTo | 表示显示动画,包含启动动效的加载处理 | | 4 | ohos.sceneboard | H:JSAnimateToImmediately | 启动图标加载,组件创建页面布局刷新 | | 5 | ohos.sceneboard | H:FlushDirtyNodeUpdate | 用来标识由于变量更新,触发了某一个组件的标脏,需要对它进行刷新 | | 6 | ohos.sceneboard | H:UITaskScheduler::FlushTask | 组件创建布局和测量,这一块可以确认是什么组件在创建或者复用 | | 7 & 8 & 9 | ohos.sceneboard | H:FlushMessages & H:SendCommands & H:MarshRSTransactionData cmdCount:[num] transactionFlag:[发送进程id,序列号] | 发送渲染请求到Render_Service图形渲染服务 | | 10 | render_service | H:RSMainThread::ProcessCommandUni:[发送进程id,序列号] | Render_Service图形渲染服务收到渲染消息,可以和发送进程的Trace对应起来 | | 11 & 12 | render_service | H:RSUniRender:FlushFrame & H:FlushBuffer | 刷新图形数据到缓冲区 | | 13 & 14 | RSHardwareThrea | H:RSHardwareThread & H:Commit | 提交渲染的帧数据 | 下面针对页面布局刷新Trace点做一些补充描述: <img src="https://pic.code-nav.cn/post_picture/1834516631464710146/CIgSsBtfwJgl2TQs.webp" alt="" width="528px" /> 由于页面布局刷新属于通用Trace信息,描述表格不添加泳道信息: | 序号 | 关键Trace点 | Trace点说明 | | --- | --- | --- | | 1 | H:FlushDirtyUpdate | 用来标识由于变量更新,触发了某一个组件的标脏,需要对它进行刷新。例如上图中SCBScenePanel被标脏了 | | 2 | H:CustomNodeUpdate [组件] | 表示什么组件发生了变化 | | 3 & 4 & 5 | H:UITaskScheduler::FlushTask & H:FlushLayoutTask & H:CreateTaskMeasure | 组件创建刷新,页面布局测量 | | 6 | H:CustomNode:BuildItem [组件] | 表示新创建了什么组件,如上图新创建了SCBSceneContainer组件,该Trace的下发还可以看到创建组件的生命周期 | | 7 | H:aboutToAppear | 标识对应组件的初始化的生命周期 | | 8 | H:ssm:GetStartupPage | 加载启动页图标的Trace |
查询性能优化,极限在哪里?
<html> <head></head> <body> <div class="content ql-editor"> <p>经过 2 个月的直播,鱼皮的项目 <a href="https://yuyuanweb.feishu.cn/wiki/O3akwwSFviWzd3k4XOncRghonvd" target="_blank">定制化代码生成项目</a> 所有的核心功能已经开发完成,用户可以在线制作、分享、使用代码生成器。大家已经可以学起来了。</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/09l5fyf2.jpeg" alt=""></p> <p></p> <p>目前项目已经进入了优化阶段。在第 13 期教程中,我专门用了 4 个小时的直播,给大家带来了一场极致的 <strong>性能优化</strong> “盛宴”。</p> <p></p> <p>很多同学由于没有实习或工作经历,可能没有接触过性能优化,但这却是能区分程序员水平的重要技能。</p> <p></p> <p>为什么这么说?</p> <p></p> <p>举个例子:</p> <p></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>优秀的程序员,能用 1 台机器满足 10000 个用户的使用需求,如果满足不了,首先想到的是继续去优化。</li> <li data-list="bullet"><span class="ql-ui"></span>而一般的程序员,用 1 台机器可能只能满足 1000 个用户的使用需求,如果满足不了,就要加机器、加资源。现实往往是没有资源给你加。</li> </ol> <p></p> <p>怎么区分一个程序员有没有性能优化经验呢?</p> <p></p> <p>就先问大家一个问题吧:一台部署了 Tomcat 的服务器,每秒最多能处理多少个请求?</p> <p></p> <p>下面给大家简单分享下我直播中的性能优化过程,答案也将在最后揭晓。</p> <p></p> <p>完整教程见 <a href="https://yuyuanweb.feishu.cn/wiki/O3akwwSFviWzd3k4XOncRghonvd" target="_blank">定制化代码生成项目</a> 第 13 期。</p> <p></p> <h2>性能优化实践</h2> <p></p> <p>要优化的是一个后端查询接口,功能是查询出主页要展示的分页数据列表,逻辑很简单,就是数据库分页查询而已。</p> <p></p> <p>如下图:</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/s87bn4o3.jpeg" alt=""></p> <p></p> <p>首先我们往数据库里插入 10 万条数据,然后打开浏览器控制台,观察 10 次请求的响应耗时,平均是 700 毫秒:</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/7ia6h3m0.jpeg" alt=""></p> <p></p> <p>1)首先,我尝试优化了数据查询的 SQL 语句,让它只查询需要返回给前端的数据,减少数据量。</p> <p></p> <p>优化后的接口平均耗时是 500 多毫秒,大概响应时长缩短了 1 / 4:</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/dxak8a5z.jpeg" alt=""></p> <p></p> <p>使用 JMeter 进行压力测试,每秒启动 1000 个线程,总共启动 1 万个线程发送请求,在异常率 0% 的前提下,测试结果得到的 qps 为 20,这显然不是一个很好的成绩。</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/rfeqv9f0.jpeg" alt=""></p> <p></p> <p>2)进一步优化,使用性能更高的 Redis 分布式缓存。将分页查询结果作为 JSON 字符串写入缓存,再次查询的时候直接读取就行。</p> <p></p> <p>结果响应时长直接缩短到了平均 20 毫秒!缩短了 25 倍!</p> <p></p> <p>第一个请求是加载缓存的,所以较慢。</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/udk272lz.jpeg" alt=""></p> <p></p> <p>有缓存的情况下,压力测试得到的 qps 是 114.6,提升了 5 倍多!</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/qr4jeexb.jpeg" alt=""></p> <p></p> <p>可见缓存还是猛啊,读多写少、更新频率低、访问频率高的数据,非常适合使用缓存。</p> <p></p> <p>还能进一步优化么?</p> <p></p> <p>3)当然能!使用比分布式缓存更快的本地缓存,直接从当前服务器内存读取数据,更快~</p> <p></p> <p>用浏览器控制台测试响应时长,几乎没有变化(因为我测试过程中,Redis 也是本地启动的):</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/zp1e8psg.jpeg" alt=""></p> <p></p> <p>进行压力测试,发现 qps 略有提升,大概 10% 左右吧:</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/r7xomm9c.jpeg" alt=""></p> <p></p> <p>本地缓存按道理来说已经是最快的读取方式了,难道这就已经优化到极限了么?</p> <p></p> <p>当然没有!</p> <p></p> <p>4)虽然 I / O 难以继续优化了,但我们可以优化程序的计算逻辑、节约 CPU 资源。</p> <p></p> <p>比如我修改缓存的数据类型,不再写入 JSON 格式的缓存了,直接用 JDK 原生的序列化方式去保存对象,这样读取的时候也不需要把 JSON 转为对象。</p> <p></p> <p>优化这个逻辑后,响应时长大幅度减少!平均 8 毫秒左右:</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/7iyxvuk8.jpeg" alt=""></p> <p></p> <p>没想到吧,看似不起眼的 JSON 转换操作,这么影响性能?</p> <p></p> <p>压力测试的结果更恐怖了,吞吐量接近 16000,直接提升了 100 多倍!</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/30hrgzdf.jpeg" alt=""></p> <p></p> <p>真是不测不知道,一测吓一跳。</p> <p></p> <p>问题来了,还能继续优化么?</p> <p></p> <p>当然可以!</p> <p></p> <p>5)除了业务逻辑层,我们还可以优化请求层。比如修改 Tomcat 服务器的配置,增大工作线程数和最大线程连接数。</p> <p></p> <p>稍微改 2 行配置,吞吐量就提高了 1 / 5 左右,接近 19000!相比最开始的 qps 20 提升了近千倍!</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/twz1b1dg.jpeg" alt=""></p> <p></p> <p>这。。这次到极限了么?</p> <p></p> <p>我怎么知道啊!肯定还是需要自己测试这个极限到底在哪里!</p> <p></p> <p>如何测试呢?</p> <p></p> <p>6)我们可以编写一个没有任何业务逻辑,直接返回 "ok" 字符串的空接口。</p> <p></p> <p>每秒启动 10 万个线程来测试,得到的 qps 为 36000+,又提高了整整一倍!</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/4gjbuh6n.jpeg" alt=""></p> <p></p> <p>ok,没有任何逻辑的接口 qps 是这个数,那么你再怎么优化业务逻辑,性能的极限也不会超过这个值。</p> <p></p> <p>那么,还有办法继续优化么?</p> <p></p> <p>当然可以!</p> <p></p> <p>7)因为这只是 Tomcat 服务器 + Spring MVC 框架的极限,如果我们用别的技术呢?</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/ynjv37o1.jpeg" alt=""></p> <p></p> <p>这里我就抛砖引玉,大家感兴趣的话,可以找个项目中的查询接口、用我上面提到的方法优化下。</p> <p><br></p> <hr class="article-hr" style="height: 0px"> <p><br></p> <p>回到最开始的问题 “一台部署了 Tomcat 的服务器,每秒最多能处理多少个请求?”</p> <p></p> <p>其实这个答案是 <strong>无解</strong> ,因为缺少了最重要的测试条件、测试环境和测试基准,比如是在几核几 G、带宽多少的机器上测试呢?不同的测试环境,测试结果肯定不同。性能优化效果一定要以实际测试结果为准,下次遇到这种问题的时候,别被面试官唬住了哦~</p> <p></p> <p>这个项目视频教程接近 40 小时,文字教程十几万字,我付出了大量的头发精力,也保证绝对是一个很能打的项目。大家加油学起来~</p> <p></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1601072287388278786/w45fyfn9.jpeg" alt=""></p> <p>#技术#</p> </div> </body> </html>
【前端】面试题挑战 Day27 怎么进行站点内的图片性能优化?
怎么进行站点内的图片性能优化?
为什么性别字段不适合建立索引?
为什么性别字段不适合建立索引?
鱼皮哥哥你好,我想问一下Vue
鱼皮哥哥你好,我想问一下Vue+antdesign做的项目,上线后前端控制台能看到一个文件加载时间很长,时间也不固定,最长等待50s,已知未按需加载组件,服务器带宽1Mbps,请问你们大厂如何做优化?
