论性能优化
论性能优化
转载出自知乎 牧马南山
一、前言
各位发现没有,无论是去大厂面试还是去小厂面试Java岗位,"性能优化"都是一个绕不过去的词,常见的问题如:
你们项目中如何做的性能优化,方案是什么?
你用过哪些性能优化方案?
平时你是如何对代码进行性能优化的?
用户反馈网页加载速度很慢,影响体验,你该如何优化?
有没有遇到过接口查询超时的情况,如何进行性能分析和优化?
对于上述问题来说,关键点在于要回答1、都有哪些影响性能的因素 2、针对这些因素如何有针对性的优化。古人云,知己知彼,百战不殆,要回答好如何进行性能优化,前提是先要了解影响性能的地方,然后针对每个点,由点及面,先整体后细节,只要时间允许就可以对面试官娓娓道来,如数家珍,让面试官感受到你深厚的知识储备。
**那么,先来聊聊有哪些影响性能的因素。**在系统开发中,影响性能的因素非常多,可以从多个角度进行分析。以下是一些常见的性能影响因素:
网络延迟
网络连接的速度和稳定性直接影响数据传输的效率。
WAF(Web应用防火墙)可能会增加处理时间。
服务器资源
CPU:处理器的性能,包括核心数和处理速度。
内存:RAM的大小和速度。
存储:硬盘的类型(如SSD或HDD)和读写速度。
Web服务器:(Nginx, Apache等)的性能配置。
网络资源
带宽限制:网络带宽的大小。
服务器资源限制:如CPU使用率、内存使用量等。
前端性能
静态资源:如图片、CSS、JavaScript文件的加载速率。
CDN:未使用[内容分发网络]来减少数据传输时间。
浏览器渲染:页面元素和脚本的加载顺序影响首次渲染时间。
代码问题
算法复杂度:算法的效率直接影响处理时间。
代码质量:冗余代码、循环等可能导致性能下降。
函数的调用:函数调用会引入额外的开销。
内存管理:可能存在的内存泄露,以及不合理的内存设置。
编译器优化:未充分利用编译器的优化选项,如开启高级优化等级。
缓存策略
应用端缓存:减少数据库访问次数,降低数据库 IO。
浏览器缓存:利用HTTP缓存减少重复请求,利用[客户端]的存储能力,减少网络传输。
CDN缓存:通过在网络边缘节点存储静态资源,使用户可以就近获取数据。
并发处理能力
多线程/多进程:系统处理并发请求的能力。
异步处理:未使用异步I/O,非阻塞操作、提高资源利用率可以提高性能。
数据库性能
索引:合理的索引设计可以显著提高查询速度,减少数据检索时间。
条件查询:避免在查询中使用子查询,避免使用 select *,选择合适的JOIN操作。
数据库配置:根据系统内存和数据库使用情况,合理设置缓冲区大小,以及根据服务器性能和应用需求,调整数据库的最大连接数。
数据规模:大表的存在,或者千万级以上数据量会显著影响查询性能。
主从复制:读写分离、负载均衡的设计,直接影响在分布式场景下的查询性能。
系统架构
微服务架构:微服务的拆分和通信方式,跨多微服务链路调用。
负载均衡:单点故障引起的性能问题,或不合理的负载均衡策略。
第三方服务和API
依赖的第三方服务响应时间。
API调用的频率和效率。
二、性能优化十大方案
上面介绍了影响性能的因素,不难发现,覆盖的范围包括:系统软硬件、网络、前端、数据库、程序、架构。这一章介绍了如何应对系统性能问题,本章给出了十大性能优化方案,这十个方案同样也是现在大厂常用的优化手段,里面涉及到的知识点有很多,每个点又引伸出很多个其他的知识点,这无疑对面试者提出了更高的要求,不过话又说回来,路虽远行则将至,事虽难做则必成,只有到达了这样的要求,才能 offer拿到手软。好了,废话不多说,开始介绍。
开始之前,编者先放一张图。
这是某系统的系统架构图,这也是常用的一种系统架构,如展示层、API接口层、业务服务层、基础服务层、数据层。了解了影响性能的因素,我们知道每一层如果设计不当都可能涉及到性能问题,如下是针对每一层的优化方案。
展示层:前端优化、缓存优化等。
接口层:缓存优化、多线程优化、过载保护优化等。
业务服务层:代码优化、缓存优化、异步优化、多线程优化、微服务架构优化等。
数据层:数据库优化等。
此外还少不了性能监控服务对整个系统保驾护航,以及硬件的升级。
整体可总结为如下十大方案:
2.1、前端优化
2.1.1、浏览器访问优化
浏览器访问优化是提升用户体验的关键步骤。通过减少HTTP请求次数,合并CSS和JavaScript文件,以及利用浏览器缓存等策略,另外现在流行的服务端Gzip等压缩算法,都可以减少数据传输量,显著减少页面加载时间。
2.1.2、CDN加速
在京东618或者天猫双11大促期间,商品秒杀、商品详情浏览并发量巨大,秒杀的详情页面绝大多数内容是固定的,如商品名称、描述、图片等,如果每次所有流量都直接访问服务器,那无疑会造成服务器压力巨大,甚至有宕机的风险,而且服务器的资源是宝贵的,没有必要将重复的数据频繁获取。为了减少不必要的服务端请求,大型网站都会对固定详情页面等做"静态化"处理,用户浏览商品详情操作不会请求到服务端。
以下是秒杀场景常用的性能优化手段:
**静态缓存:**一个html文件包括 css、js和图片等,内容包括秒杀商品详情的介绍等。静态资源文件提前缓存到CDN上,让用户能够就近访问秒杀页面。
**JS异步获取动态秒杀的exposed-key:**exposed-key是一个由系统生成的键,用于标识某个秒杀活动是否对用户可见。这个键可能包含活动的有效时间、用户参与资格等信息。获取exposed-key常用手段是通过系统定时任务去完成,由定时任务生成一个具备实效性的exposed-key。
此外,中国地大物博,如果服务器部署在北京,如何让新疆用户以最快的时间访问秒杀页面呢?这就需要使用CDN,它的全称是Content Delivery Network(内容分发网络)。
CDN是一种分布式网络服务,它的主要作用是提高互联网内容的分发效率,加快用户访问速度,降低原始服务器的负载。CDN服务器就是把资源放在了全国各地的子服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,CDN服务器国内如阿里云、[腾讯云]、华为云等云厂商均有提供。
2.1.3、反向代理
反向代理是一种网络服务,它位于一个或多个Web服务器的前面,并且充当客户端到服务器的中介。在Web开发中,反向代理通常用于增强安全性、提高性能、负载均衡、缓存静态内容等。常见的前端反向代理软件有Nginx、Apache HTTP Server的mod_proxy模块、HAProxy等。
以下是前端反向代理的一些主要功能和用途:
安全性增强:反向代理可以隐藏后端服务器的真实IP地址和配置信息,减少直接暴露到公网的风险。
负载均衡:它可以将客户端的请求分发到多个后端服务器上,从而提高系统的可用性和扩展性。
SSL终端:反向代理可以处理SSL加密和解密的工作,减轻后端服务器的负担。
缓存静态内容:通过缓存静态资源,如图片、CSS和JS文件,可以减少对后端服务器的请求,提高响应速度。
压缩和优化:它可以对传输的数据进行压缩,减少网络传输的数据量,加快加载速度。
身份验证和授权:反向代理可以在将请求转发到后端服务器之前,对用户进行身份验证和授权。
连接复用:它可以复用与后端服务器的连接,减少连接建立和关闭的开销。
访问控制:反向代理可以控制对后端资源的访问,例如基于IP地址、地理位置等条件限制访问。
日志记录和监控:它可以记录所有经过的请求和响应,方便进行日志分析和监控。
2.1.4、Web组件分离
通过将Web应用的各个组件分离到不同的域名下,可以提高浏览器的并发下载能力,加快页面渲染速度。
组件并行加载:利用浏览器对不同域名的并行下载优势,提升资源加载效率。
减少Cookie传输:静态资源使用独立域名,减少Cookie在HTTP请求中的传输,降低请求大小。
**提高缓存利用率:**不同域名下的资源可以被浏览器分别缓存,避免了共享缓存空间的问题,提高了缓存效率。
**优雅更新:**分离组件使得维护和更新更加灵活,可以独立更新某个组件而不影响其他部分。
**增强安全性:**将静态资源部署在不同的域名下,可以利用CORS(跨源资源共享)策略来限制资源的访问。
**便于A/B测试:**对不同域名下的资源进行A/B测试,可以更容易地比较不同版本的效果。
减少不必要的重绘和回流:将影响布局的CSS和JavaScript分离到不同的域名下,可以减少页面的重绘和回流。
2.1.5、微前端
微前端是一种将大型前端应用拆分成多个较小、独立、可维护的前端服务的架构模式。这种模式借鉴了微服务架构的思想,通过将前端应用分解为一组更小的、松散耦合的前端服务。
按业务拆分服务:根据业务逻辑将应用拆分成多个微服务,实现独立部署和扩展。
数据和服务分离:将数据存储和业务逻辑分离,提高数据处理效率和应用性能。
2.2、代码优化
在代码优化之前,先得了解程序运行有哪些性能的瓶颈。以下是生产环境中常用的性能监控分析工具:
VisualVM:这是一个基于NetBeans平台派生的工具,模块化设计,支持扩展。VisualVM可以提供Java程序的详细信息,包括本地和远程运行程序的CPU性能分析、内存性能分析,并且可以保存JVM软件的数据快照7。
JProfiler:JProfiler是一个集成了CPU、内存和线程性能分析的商业工具,支持本地和远程性能分析,适合Java EE和Java SE应用9。
async-profiler:这是一个开源的Java性能分析工具,基于HotSpot的API,可以进行CPU性能分析和Heap内存分配分析,支持生成火焰图,具有很低的性能开销。
Eclipse Memory Analyzer (MAT):Eclipse MAT是一个Java堆分析器,可以帮助找到内存泄漏和减少内存损耗,适合分析Java堆栈和计算大小。
ko-time:这是一个轻量级的Spring Boot项目性能分析工具,可以追踪方法调用链路和执行时间,快速定位性能瓶颈,支持Web展示和邮件通知。
Java Interactive Profiler (JIP):JIP是一个基于Java开发的高性能、低损耗性能分析器,可以在VM运行时开启和关闭性能分析。
2.2.1、算法优化
排序算法优化:
快速排序 :通常比简单排序(如[冒泡排序]或选择排序)更高效,因为它的平均时间复杂度为O(n log n),而简单排序为O(n^2)。
归并排序:也是一种O(n log n)的时间复杂度的排序算法,特别适合于大数据集的排序,因为它能够保证稳定的性能。
搜索算法优化:
- 二分搜索:适用于有序数组,时间复杂度为O(log n),远快于线性搜索。
哈希表优化:
- 使用HashMap进行快速查找、插入和删除操作。合理设计哈希函数以减少哈希碰撞,可以进一步提高性能。
动态规划优化:
- 减少状态转移的复杂度,通过空间优化(例如,滚动数组技术)减少内存使用。
分治算法优化:
- 优化递归算法,减少不必要的重复计算,例如使用记忆化递归(自顶向下的DP)。
位操作优化:
- 使用位运算符进行一些简单的操作,如位翻转、位与、位或等,这些操作通常比算术操作更快。
使用合适的数据结构:
- 根据用例选择正确的数据结构,例如使用LinkedList 进行频繁的插入和删除操作,使用ArrayList进行随机访问。
并行算法和多线程:
- 对于可以并行处理的任务,使用Java的并发API,如
ExecutorService
或Java 8中的parallelStream
,可以显著提高性能。
优化递归算法:
- 将递归算法转换为循环,以减少函数调用的开销。
2.2.2、循环优化
循环是程序中常见的性能瓶颈之一。优化循环包括减少循环次数、优化循环内部的计算以及使用更高效的数据结构。
例如,不要在循环内部进行数据库操作:
// 优化前代码
User user = new User();
for (int i = 0; i < n; i++) {
insertData(user);
}
//优化后代码
List<User> allData = new ArrayList<>();
for (int i = 0; i < n; i++) {
allData.add(generateData(i)); // 收集所有数据
}
batchInsert(allData); // 一次性写入所有数据
数据库访问:
//优化前代码
for (int i = 0; i < n; i++) {
User user = getUserFromDatabase(i); // 每次迭代都查询数据库
}
//优化后代码
List<User> users = getAllUsersFromDatabase(); // 一次性获取所有用户
for (User user : users) {
// 处理用户
}
深度嵌套循环:
//优化前代码
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < b.length; j++) {
// 执行操作
}
}
//优化后
// 如果可能,使用多维数组或列表代替嵌套循环
循环变量作为条件:
//优化前代码
int i = 0;
while (true) {
if (i >= n) break;
// 使用i进行操作
i++;
}
//优化后
for (int i = 0; i < n; i++) {
// 使用i进行操作
}
在循环中使用递归:
//优化前代码
for (int i = 0; i < n; i++) {
recursiveFunction(i); // 可能引起栈溢出
}
//优化后
// 避免在循环中使用递归,或者改写递归为循环
字符串连接操作:
//优化前代码
for (int i = 0; i < n; i++) {
string += someData[i]; // 低效的字符串连接
}
//优化后
StringBuilder builder = new StringBuilder();
for (int i = 0; i < n; i++) {
builder.append(someData[i]);
}
String string = builder.toString();
使用循环进行扁平映射操作:
//优化前代码
Map<Integer, List<String>> map = ...;
for (Map.Entry<Integer, List<String>> entry : map.entrySet()) {
for (String str : entry.getValue()) {
// 处理字符串
}
}
//优化后
map.values().forEach(list -> list.forEach(this::processString));
在循环中使用同步块:
//优化前代码
synchronized (this) {
for (int i = 0; i < n; i++) {
// 执行同步操作
}
}
//优化后
// 尽可能减少同步块的大小,或者使用并发集合和原子变量
2.2.3、内存管理优化
有效的内存管理可以减少程序的内存占用和提高缓存利用率。避免内存泄漏、合理使用缓存以及减少内存分配和释放操作都是重要的优化手段。
以下是一些常见的内存管理优化案例和实践:
避免内存泄漏:
不当使用:长时间持有不再需要的对象引用。
优化:确保不再使用的对象可以被垃圾回收器回收,例如关闭并置为null的数据库连接或文件句柄。
对象池:
不当使用:频繁创建和销毁对象,如数据库连接或线程。
优化:使用对象池来重用对象实例,减少内存分配和垃圾回收的开销。
减少内存分配:
不当使用:在循环中创建临时对象。
优化:重用对象或使用基本数据类型数组代替可变参数或集合。
弱引用和软引用:
不当使用:没有利用Java的引用类型来管理缓存或临时对象。
优化:使用弱引用和软引用来允许内存不足时由垃圾回收器回收对象。
减少集合的容量:
不当使用:使用默认初始容量的集合,导致频繁扩容。
优化:初始化集合时指定一个合适的容量,减少扩容次数。
减少线程局部变量:
不当使用:为每个线程分配大量局部变量。
优化:使用线程池和线程局部变量(ThreadLocal)的合理使用。
避免使用大量静态字段:
不当使用:静态字段过多占用类加载器的内存。
优化:仅对必要的数据使用静态字段,考虑使用单例模式或依赖注入。
减少序列化/反序列化开销:
不当使用:频繁地对大对象进行序列化和反序列化。
优化:使用更高效的序列化机制,或仅序列化必要的对象。
2.2.4、编译器优化
充分利用编译器的优化选项,如开启高级优化等级,可以让编译器帮助我们进行一些代码优化。以下是一些常见的编译器优化例子:
开启高级优化等级:
- 编译器通常提供不同的优化等级,例如,在Java中,
-O
(大写字母O)代表开启优化。在GCC或Clang中,-O2
或-O3
代表更高级别的优化。
javac -Xbatch HelloWorld.java # 开启批量优化模式
gcc -O2 -flto main.c -o main # 开启-O2优化和链接时优化
clang -O3 -march=native main.c -o main # 开启-O3优化和针对当前CPU架构的优化
指令重排:
- 编译器可以重新排序指令,以提高流水线的效率和指令的并行度。
逃逸分析:
编译器分析对象的作用域,如果对象只在方法内部使用,编译器可以减少内存分配或同步开销。
2.2.5、并发和多线程优化
在多核处理器上,利用并发和多线程可以显著提高程序的执行效率。合理分配任务、减少线程间的竞争以及同步开销是并发编程中的关键。
线程池:
不当使用:为每个任务创建和销毁线程。
优化:使用线程池来重用线程,减少线程创建和销毁的开销。
减少锁的使用:
不当使用:过度同步,导致线程频繁阻塞和唤醒。
优化:减少锁的范围,使用更细粒度的锁或无锁编程技术。
锁分离:
不当使用:使用单个锁保护多个资源。
优化:为每个资源使用单独的锁,减少线程间的竞争。
使用读写锁:
不当使用:在读操作远多于写操作的情况下使用普通锁。
优化:使用读写锁,允许多个读操作同时进行,只对写操作进行互斥。
使用原子变量:
不当使用:使用非原子操作进行计数或状态变更。
优化:使用原子变量来保证操作的原子性,减少锁的使用。
线程局部变量:
不当使用:多个线程共享同一资源或数据。
优化:使用线程局部变量(ThreadLocal)为每个线程提供独立的数据副本。
任务并行化:
不当使用:顺序处理集合中的元素。
优化:使用并行流(Java 8)或其他并行框架来并行处理集合元素。
使用Future和Callable:
不当使用:在没有返回结果的情况下使用Runnable接口。
优化:使用Callable接口获取线程执行的结果,使用Future来查询结果状态。
使用非阻塞算法:
不当使用:使用阻塞算法,如条件变量或阻塞队列。
优化:使用非阻塞算法和数据结构,如ConcurrentLinkedQueue。
优化线程调度:
不当使用:依赖操作系统进行线程调度。
优化:在应用层进行线程调度,如使用工作窃取算法。
2.2.6、代码重构
代码重构是改善现有代码设计而不改变其外部行为的过程。它可以帮助开发者去除重复代码、提高代码的模块化和可读性,从而间接提升性能。
代码如何重构,推荐阅读《重构:改善既有代码的设计》
2.3、缓存优化 {#h_712178640_4}
缓存是提升系统性能的关键技术之一,通过减少数据IO访问,和降低数据存储压力,加快数据检索速度。在企业级系统架构中,缓存不仅用于提升用户体验,也是实现高并发处理的核心技术。常见的缓存实现方式有:内存缓存、分布式缓存、浏览器缓存、CDN缓存。
2.3.1、缓存介绍
**内存缓存:**内存缓存利用服务器的RAM来存储热点数据,提供快速的数据访问能力。常见的内存缓存实现包括Ehcache、Memcached等。
**分布式缓存:**分布式缓存通过多台服务器共享缓存数据,解决了单机内存限制的问题,并提供了更好的扩展性和可用性。Redis和Hazelcast是流行的分布式缓存解决方案。
**浏览器缓存:**浏览器缓存利用客户端的存储能力,减少网络传输的数据量,加快页面加载速度。通过设置合适的HTTP头信息,如Cache-Control和Expires,可以控制浏览器缓存的生命周期。
**CDN缓存:**CDN通过在网络边缘节点存储静态资源,使用户可以就近获取数据,从而减少延迟和带宽消耗。
2.3.2、缓存策略的优化技巧
缓存粒度控制:
对象级别缓存:缓存整个对象。当对象较大或包含方法和状态时,这种粒度很常见。
字段级别缓存:缓存对象的特定字段或属性。适用于对象中只有部分数据需要频繁访问的情况。
数据集缓存:缓存查询结果集,例如数据库查询结果。这种粒度适用于读取操作远多于写入操作的场景。
行级别缓存:在处理数据库或类似存储时,只缓存表中的单行记录。
列级别缓存:缓存表中的特定列,适用于只有某些列经常被访问的情况。
项级别缓存:缓存集合中的单个项,例如列表、数组或集合中的元素。
键值对缓存:使用键值存储模式,缓存键值对,适用于需要根据唯一标识符检索数据的情况。
页面级别缓存:在处理文件或大块数据时,缓存整个页面或区块。
会话级别缓存:缓存用户会话信息,每个用户有自己的缓存空间。
指令级别缓存:在编译或解释执行期间缓存指令或代码片段。
请求/响应缓存:缓存Web请求和响应,减少服务器处理相同请求的次数。
缓存失效策略( 决定了哪些数据应该被移除缓存,以适应新的数据更新和访问模式*):*
过期时间(Time-to-Live, TTL):每个缓存项都有一个设定的存活时间,在时间到达后缓存项自动失效。
最少使用(Least Recently Used, LRU):移除最长时间未被访问的缓存项,以释放空间给新数据。
最少使用频率(Least Frequently Used, LFU):移除访问次数最少的缓存项。
先进先出(First In First Out, FIFO):按照数据进入缓存的顺序,先进入的先失效。
随机替换(Random Replacement):随机选择缓存中的项进行替换。
基于容量的替换:当缓存达到最大容量时,根据某种策略(如LRU、FIFO等)替换掉一部分缓存项。
写分配(Write-allocate):当数据被写入时,如果缓存中没有相应的项,则替换掉一个现有项。
读分配(Read-allocate):当数据被读取时,如果缓存中没有相应的项,则可能替换掉一个现有项。
时间戳失效:使用时间戳记录缓存项的最后更新时间,如果当前时间与时间戳的差异超过某个阈值,则认为缓存项失效。
版本号/标签:为缓存项设置版本号或标签,当数据更新时,版本号或标签也随之更新,缓存项根据版本号或标签判断是否失效。
依赖性失效:缓存项依赖于其他数据源,当数据源变化时,依赖的缓存项失效。
引用计数:跟踪缓存项被引用的次数,当引用计数降到零时,缓存项可以被移除。
惰性失效:只有在访问缓存项时才检查其有效性,如果发现已失效,则从缓存中移除。
主动失效:应用程序在数据更新时主动通知缓存系统使相关缓存项失效。
基于事件的失效:根据特定事件(如数据库更新、配置更改等)触发缓存失效。
缓存预热:
- 缓存预热是在系统启动或低负载时段预先加载热点数据到缓存中,避免在高负载时发生缓存缺失。
缓存一致性:
Cache-Aside 模式:这是最常用的缓存一致性策略。在读请求过程中,首先查询缓存,如果缓存未命中,则从数据库加载数据并更新缓存。在写请求过程中,先更新数据库,然后删除缓存。删除缓存而不是更新缓存的原因包括性能和安全性的考虑。删除操作是幂等的,并且可以在异常情况下重试,而更新缓存可能会因为并发写操作导致数据不一致。
Read-Through 模式:与 Cache-Aside 类似,但增加了一个访问控制层。这使得业务层的实现更加简洁,并且缓存层及持久化层的交互被更高程度地封装,更易于移植。
Write-Through 模式:在写请求时,更新数据库后同时更新缓存。这种方法的优势是读请求过程简单,不需要查询数据库来更新缓存。但缺点是更新效率较低,且如果更新操作失败可能导致数据不一致。
Write-Behind 模式:在写多的极端场景下使用,先写入缓存,然后异步写入数据库。这种方案可能会导致数据丢失,因此需要谨慎使用。
结合数据库日志:在读多写少的场景下,可以采用 Cache-Aside 结合消费数据库日志做补偿的方案。这样,即使在写请求更新数据库后删除缓存失败,也可以通过日志来补偿缓存的更新。
分布式锁:在写多的场景下,可以使用 Write-Through 结合分布式锁的方案,以确保更新操作的原子性和一致性。
延时双删策略:为了避免读写并发时可能带来的缓存脏数据问题,可以在更新数据库之后,延迟一段时间再次删除缓存,以确保第二次删除缓存的时间点在读请求更新缓存之后。
TTL 策略:给缓存增加 TTL(Time to Live),即使缓存没有及时更新或删除,数据也会在 TTL 过期后自然失效。
2.3.3、缓存的三大经典问题
缓存穿透、缓存击穿、缓存雪崩。
2.4、异步优化
2.4.1、异步优化概念
异步处理是一种编程模式,允许程序在等待特定操作完成时继续执行其他任务。这种技术可以显著提高应用程序的响应性和性能。
2.4.2、异步优化特点
非阻塞操作:异步操作不会阻塞主线程,用户界面(UI)可以保持流畅,提升用户体验。
提高资源利用率:通过异步执行,系统资源得到更高效的利用,尤其是在I/O密集型操作中。
2.4.3、异步优化应用场景
网络请求:在等待网络响应时,异步处理允许应用程序继续处理用户交互。
文件读写:处理大文件或慢速存储设备时,异步读写可以避免界面冻结。
数据处理:对大量数据进行处理时,异步执行可以避免长时间占用CPU资源。
2.4.4、异步技术实现
回调函数:一种传统的异步处理方式,通过将函数作为参数传递给另一个函数来处理完成时的逻辑。
事件循环:Node.js等环境使用事件循环来处理异步事件,提高性能和可伸缩性。
Promise :现代JavaScript中用于异步编程的对象,提供了一种更易读和更易管理的方式来处理异步操作。
Async/Await :基于Promise的语法糖,允许以同步的方式编写异步代码,提高代码的可读性。
2.4.5、异步优化的性能考量
减少等待时间:通过异步处理,可以减少程序的等待时间,提高整体的执行效率。
优化并发:合理控制并发数量,避免过多的并发导致资源竞争和性能下降。
监控和调优:使用性能监控工具来分析异步操作的性能,根据实际情况进行调优。
2.5、多线程优化
多线程技术允许程序中的不同部分同时执行,从而提高程序的执行效率和响应性。
2.5.1、多线程技术特点
线程管理 :合理创建和管理线程,避免线程过多导致的上下文切换开销。
同步机制:使用互斥锁、信号量等同步机制来避免竞态条件和死锁。
线程池:使用线程池来重复利用线程资源,减少线程创建和销毁的开销。
任务分解:将大任务分解为可以并行处理的小任务,提高资源利用率。
代码示例1:
Java 中可以使用 CompletableFuture
用于处理异步任务。它提供了一种灵活的方式来处理异步计算和多线程优化。
public class MultiThreadedDownloader {
// 要下载的文件列表
private static final List<String> URLs = Arrays.asList(
"http://example.com/file1",
"http://example.com/file2",
"http://example.com/file3",
"http://example.com/file4",
"http://example.com/file5"
);
public static void main(String[] args) {
// 创建一个包含下载任务的 CompletableFuture 列表
List<CompletableFuture<Void>> futures = new ArrayList<>();
// 遍历每个 URL,并为每个 URL 创建一个 CompletableFuture 任务
for (String url : URLs) {
futures.add(CompletableFuture.runAsync(() -> downloadFile(url)));
}
// 使用 allOf 方法等待所有任务完成
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
// 当所有任务完成时,输出完成信息
allOf.thenRun(() -> System.out.println("All files downloaded."));
// 阻塞主线程,直到所有任务完成
allOf.join();
}
private static void downloadFile(String url) {
try {
URL website = new URL(url);
try (InputStream in = website.openStream()) {
Files.copy(in, Paths.get(url.substring(url.lastIndexOf('/') + 1)), StandardCopyOption.REPLACE_EXISTING);
System.out.println(url.substring(url.lastIndexOf('/') + 1) + " downloaded.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
CompletableFuture是否使用默认线程池的依据,和机器的CPU核心数有关。
当 (CPU核心数-1)> 1 时,才会使用默认的线程池,forkjoin 线程池 ForkJoinPool.commonPool()。当 (CPU核心数-1)<= 1 时,也就是单核或者双核,将会为每个CompletableFuture的任务创建一个新线程去执行。forkjoin 线程池作为CompletableFuture的默认线程池,只有在双核以上的机器内才会使用。
在双核及以下的机器中,会为每个任务创建一个新线程,这种场景等于没有使用线程池,且有资源耗尽的风险 。另外,forkjoin线程池池内的核心线程数也为机器核心数-1。也就意味着假设你是4核机器,那最多也只有3个核心线程,对于CPU密集型的任务来说倒还好,但是我们平常写业务代码,更多的是IO密集型任务/混合型任务,这就问题很大。对于IO密集型、混合型的任务来说,机器核心数-1 的线程数量其实远远不够用的,会导致大量的任务在等待,导致吞吐率大幅度下降,即默认线程池比较适用于CPU密集型任务。
默认 ForkJoinPool.commonPool() 线程池缺点
1、commonPool是当前 JVM(进程) 上的所有 CompletableFuture、并行 Stream 共享的,commonPool 的目标场景是非阻塞的CPU密集型任务,其线程数默认为CPU数量减1,所以对于我们用java常做的IO密集型任务,默认线程池是远远不够使用的。
2、
CompletableFuture
是否使用默认线程池的依据,和机器的CPU核心数有关。当CPU核心数-1>1时,才会使用默认的线程池,否则将会为每个CompletableFuture的任务创建一个新线程去执行。也就是说默认线程池,只有在双核以上的机器内才会使用。在双核及以下的机器中,会为每个任务创建一个新线程,等于没有使用线程池,且有资源耗尽的风险。因此建议,在使用CompletableFuture时,务必要自定义线程池。
代码示例2:
CompletableFuture+ 自定义线程池
@Configuration
public class ThreadPoolConfig {
//参数初始化
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//核心线程数量大小
private static final int corePoolSize = Math.max(2, Math.min(CPU_COUNT-1,4));
//线程池最大容纳线程数
private static final int maxPoolSize = CPU_COUNT * 2 + 1;
//阻塞队列
private static final int workQueue = 20;
//线程空闲后的存活时长
private static final int keepAliveTime = 30;
@Bean("asyncTaskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
//最大线程数
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
//等待队列
threadPoolTaskExecutor.setQueueCapacity(workQueue);
//线程前缀
threadPoolTaskExecutor.setThreadNamePrefix("asyncTaskExecutor-");
//线程池维护线程所允许的空闲时间,单位为秒
threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveTime);
// 线程池对拒绝任务(无线程可用)的处理策略
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
@RestController
@RequestMapping("/task")
public class CompletableTaskController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
@Qualifier("asyncTaskExecutor")
private Executor asyncTaskExecutor;
@RequestMapping("testOrderTask")
public String testOrderTask(){
List<CompletableFuture<List<Integer>>> futureList = Lists.newArrayList();
// 任务1,计算3秒
CompletableFuture<List<Integer>> task1 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(3L);
return Lists.newArrayList(1,2,3);
}, asyncTaskExecutor);
futureList.add(task1);
// 任务2,计算2秒,得答案5
CompletableFuture<List<Integer>> task2 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(1L);
return Lists.newArrayList(4,5,6);
}, asyncTaskExecutor);
futureList.add(task2);
// 任务3,计算3秒,得答案5
CompletableFuture<List<Integer>> task3 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(2L);
return Lists.newArrayList(7,8,9);
}, asyncTaskExecutor);
futureList.add(task3);
// 写法1
List<Integer> newList = futureList.stream().map(CompletableFuture::join).flatMap(List::stream).collect(Collectors.toList());
return JSON.toJSONString(newList);
}
}
2.6、微服务架构优化 {#h_712178640_7}
上图是常见的微服务架构设计,微服务架构是一种将应用程序作为一套小服务的设计方法,每个服务运行在其独立的进程中,并通过轻量级的通信机制(通常是HTTP RESTful API)进行交互。微服务架构设计包含以下几个关键方面:
服务拆分:将应用程序拆分成一系列小的、松散耦合的服务,每个服务实现特定的业务功能。
服务独立性:每个服务独立开发、部署、扩展和维护,拥有自己的代码库和数据库。
服务发现:在分布式系统中动态发现服务实例,常用的工具有Eureka、Consul、Zookeeper等。
配置管理:集中管理微服务的配置,支持配置的动态更新,工具如Spring Cloud Config。
API 网关:作为微服务的统一入口,处理请求路由、负载均衡、认证授权等。
负载均衡:分散请求到多个服务实例,提高系统的可用性和扩展性。
服务容错:实现断路器模式,防止服务间的级联故障,工具如Hystrix。
服务监控:实时监控服务的健康状态和性能指标,工具如Prometheus和Grafana。
日志管理:集中收集和管理服务日志,支持日志的搜索、分析和可视化。
分布式追踪:追踪请求在微服务间的流动,帮助定位问题,工具如Skywalking。
数据库事务管理:处理跨服务的事务一致性问题,可能采用最终一致性模型或Saga模式。
服务安全:实现服务间的安全通信,如使用OAuth2、JWT等认证授权机制。
服务部署:支持自动化部署,使用容器化技术(如Docker)和编排工具(如Kubernetes)。
服务网格:使用Istio、Linkerd等服务网格技术,管理服务间的通信、安全和监控。
持续集成和持续部署(CI/CD):自动化构建、测试和部署流程,快速迭代和发布服务。
版本控制和兼容性:管理服务API的版本,确保向后兼容性。
限流和降级:控制服务的访问频率,实现服务降级以保护系统稳定性。
备份和灾难恢复:确保数据的安全性,快速恢复服务的备份。
2.6.1、微服务架构的优点
降低系统复杂度:每个服务都比较简单,只关注于一个业务功能。
松耦合:微服务架构方式是松耦合的,每个微服务可由不同团队独立开发,互不影响。
跨语言:只要符合服务 API 契约,开发人员可以自由选择开发技术。这就意味着开发人员可以采用新技术编写或重构服务,由于服务相对较小,所以这并不会对整体应用造成太大影响。
独立部署:微服务架构可以使每个微服务独立部署。开发人员无需协调对服务升级或更改的部署。这些更改可以在测试通过后立即部署。所以微服务架构也使得 CI/CD 成为可能。
Docker 容器:和 Docker 容器结合的更好。
DDD 领域驱动设计:和 DDD 的概念契合,结合开发会更好。
2.6.2、服务网格与无入侵的微服务治理
服务网格提供了一种将服务间通信控制和安全性从应用程序代码中抽象出来的方式。它支持服务发现、负载均衡、故障恢复、度量和监控等微服务治理功能,有助于提升系统的性能和稳定性。2017 年底,非侵入式的 Service Mesh 技术从萌芽到走向了成熟。Service Mesh 又译作"服务网格",作为服务间通信的基础设施层 。如果用一句话来解释什么是 Service Mesh,可以将它比作是应用程序或者说微服务间的 TCP/IP,负责服务之间的RPC调用、限流、熔断和监控。
对于编写应用程序来说一般无须关心TCP/IP这一层(比如通过HTTP协议的 RESTful 应用),同样使用 Service Mesh 也就无须关系服务之间的那些原来是通过应用程序或者其他框架实现的事情,比如 Spring Cloud、OSS现在只要交给 Service Mesh 就可以了。
Service Mesh 有如下几个特点:
应用程序间通讯的中间层
轻量级网络代理
应用程序无感知
解耦应用程序的重试/超时、监控、追踪和服务发现
Service Mesh 架构图:
关于微服务和服务网格的区别
微服务架构,关注于服务治理等方面,需要处理服务发现、负载均衡、配置管理、API管理等,有一定的入侵。
服务网格,更专注于服务间的通信,以及与DevOps更好的结合。服务网格通过Sidecar模式,将服务间通信的管理逻辑下沉到基础设施层,实现业务逻辑和非业务逻辑的分离。是无入侵的。
2.6.3、容器化
容器化技术(如Docker)可以将应用及其依赖打包在一起,确保应用在不同环境中的一致性。容器编排工具(如Kubernetes)则提供了自动化部署、扩展和管理容器化应用的能力,有助于提升资源利用率和系统弹性。
2.6.4、领域驱动架构设计
DDD建模流程、设计流程是中台化架构的绝配,通过分析业务得到核心领域、通用领域和支撑领域,再结合DDD战略设计从而引导战术设计,完成对复杂业务模型的系统架构映射。
2.7、硬件升级
硬件升级是提升系统性能的重要手段之一,特别是在性能瓶颈由硬件限制导致的情况下,硬件升级可以带来显著的性能提升。硬件升级可以直接影响应用的性能表现,特别是在处理大量数据和复杂计算时。
升级高性能硬件:升级CPU、内存和存储设备,提高应用的运行速度和响应能力。
固态硬盘(SSD):相比传统硬盘,SSD提供更快的数据读写速度,提升前端应用的加载性能。
存储设备:
存储设备的性能直接影响数据的读写速度,升级到更快的存储设备可以显著提高系统的整体性能。
传统机械硬盘(HDD)升级到固态硬盘(SSD),SSD的读写速度远超HDD,可以大幅度减少数据访问时间。
使用NVMe接口的SSD,相比SATA接口的SSD,NVMe SSD提供更高的传输速率和更低的延迟。
内存升级:
系统内存扩容可以提供更多的数据缓存空间,减少因内存不足导致的页面交换(swap),从而提高系统响应速度。
- 对于大型分布式系统和大数据处理(内存密集型)服务器场景下,RAM扩容是常见的性能提升手段,不过要注意的是,内存利用率始终居高不下可能是程序问题,需要进一步确认。
CPU升级:
在CPU密集型任务中,CPU的利用率非常高,而程序的执行速度受到CPU处理速度的限制。这类任务通常涉及大量的计算,如数据分析、加密解密、图像处理、科学计算等。要利用CPU的升级来提升系统性能,可以从以下几个方面考虑:
提高CPU时钟频率,CPU的时钟频率越高,单位时间内处理的指令数就越多,从而提升处理速度。
增加核心数量,多核CPU可以同时处理多个任务。通过增加核心数,可以并行处理更多的任务,提高系统的整体性能。
使用更高性能的CPU架构,不同的CPU架构在指令集、流水线设计、缓存结构等方面有所不同,选择更高效的CPU架构可以提升性能。
使用专用硬件加速器,对于特定类型的计算,如GPU加速的图形处理或深度学习,可以使用专用硬件加速器来提升性能。
网络设备升级:
在分布式系统中,高速网络设备可以减少数据传输时间。
升级带宽到10Gbps或更高速率,可以提高数据传输速度,减少网络延迟。
高速网卡能够更快地处理接收和发送的数据包,减少处理时间。
高速网卡通常支持全双工模式,允许同时发送和接收数据,而不会发生冲突。
在高速网络中,可以增大TCP窗口大小,减少因窗口缩放导致的性能损失。
高速网络减少了因错误和丢包导致的重传,提高了传输效率。
支持更高效的网络协议,例如,使用RDMA(Remote Direct Memory Access)可以直接在内存之间传输数据,无需CPU介入,降低延迟和CPU负载。
支持网络虚拟化,高速网卡支持网络虚拟化技术,如VLAN和VXLAN,可以更高效地管理虚拟网络。
显卡升级:
对于图形密集型或需要GPU加速的应用,显卡的性能至关重要。
2.8、数据库优化 {#h_712178640_9}
2.8.1、索引优化
数据库索引是优化查询性能的重要工具,它们可以显著加快数据检索速度。以下是一些常用的数据库索引优化手段:
选择正确的列进行索引:确定哪些列经常用于查询条件、排序或联接,并为这些列创建索引。
使用合适的索引类型:根据查询需求和数据库的具体实现,选择B-tree、Hash、Full-text、R-tree或其他类型的索引。
复合索引:如果查询经常涉及多个列,可以创建一个包含这些列的复合索引。
索引选择性:选择具有高选择性的列进行索引,即索引列的值分布广泛,避免为具有大量重复值的列创建索引。
索引列的顺序:在复合索引中,根据列在查询条件中出现的频率和顺序来安排索引列的顺序。
使用索引扫描代替表扫描:尽可能使用索引来代替全表扫描,减少数据访问量。
覆盖索引:创建覆盖索引,即索引中包含查询所需的所有列,这样查询可以直接从索引中获取数据,而不需要访问表。
索引过滤条件:确保WHERE子句中的过滤条件列上有索引,以加快查询速度。
索引前缀:对于文本或字符串类型的列,考虑基于查询模式创建索引的前缀,例如使用前缀索引。
索引压缩:一些数据库支持索引压缩技术,可以减少索引的大小,提高索引性能。
异步索引:对于大型表,使用异步索引创建和维护,以减少对写操作的影响。
查询重写:有时通过重写查询语句,可以更有效地利用现有索引。
内存索引:对于某些数据库系统,可以考虑使用内存中的索引结构,如内存索引或缓存索引,以提高访问速度。
**索引重建:**重建索引可以提高查询性能,特别是在数据频繁变动导致索引碎片化的情况下。
-- sql复制代码 重建指定表上的所有索引
ALTER TABLE table_name ENGINE=InnoDB;
-- 重建指定索引
ALTER TABLE table_name DROP INDEX index_name, ADD INDEX index_name (column_name);
2.8.2、查询优化
数据库查询优化是确保数据库应用性能的关键环节。以下是一些常用的数据库查询优化手段:
优化查询语句:重写查询语句,避免使用SELECT *,明确指定需要的列。
减少数据集:使用WHERE子句减少返回的数据量,只查询必要的数据。
使用JOIN代替子查询:在可能的情况下,用JOIN操作替换子查询,以减少数据库的嵌套查询开销。
优化JOIN操作:确保JOIN操作的ON条件列上有索引,并且JOIN顺序合理。
使用合适的聚合函数:选择适合的聚合函数,避免在大量数据上使用计算成本高的函数。
使用物化视图:对于复杂的查询,可以考虑使用物化视图预先计算并存储结果。
优化排序:使用ORDER BY时,确保排序的列上有索引,避免全表排序。
使用缓存:对于重复查询的数据,使用缓存减少数据库访问。
查询分析:使用数据库的查询分析工具,如EXPLAIN,了解查询的执行计划。
避免全表扫描:优化查询条件,避免不必要的全表扫描。
使用数据库参数调优:调整数据库配置参数,如缓冲池大小、连接数等。
优化数据表结构 :规范化数据表以减少数据冗余,或反规范化以减少JOIN操作。
分区表:对于大型表,使用分区技术可以提高查询和管理的效率。
使用索引条件推送:让数据库在访问数据时就应用索引条件,减少需要扫描的数据量。
异步写入:对于写入密集型应用,可以考虑使用异步写入提高性能。
批量操作:使用批量插入、更新或删除操作,减少数据库交互次数。
避免锁竞争:优化事务大小和持续时间,减少锁的竞争。
使用读写分离:在多用户环境中,使用读写分离来平衡负载。
2.8.3、缓存优化
数据库缓存优化是提升数据库性能的重要手段之一,尤其是在读操作远多于写操作的场景下。以下是一些常用的数据库缓存优化手段:
配置合适的缓存大小 :根据可用内存和数据库的访问模式,调整缓存的大小以最大化缓存命中率。
使用LRU(最近最少使用)算法:许多数据库管理系统使用LRU算法来淘汰最少使用的缓存项。
分区缓存:将缓存分成多个区域,每个区域可以独立管理,减少锁的竞争。
多级缓存:使用多级缓存结构,如将数据同时缓存在内存和分布式缓存系统中。
分布式缓存:使用分布式缓存系统,如Redis、Memcached,以提高缓存的扩展性和可用性。
2.8.4、配置优化
合理的数据库配置可以提升系统性能:
调整缓冲区大小:根据系统内存和数据库使用情况,合理设置缓冲区大小。
并发连接数:根据服务器性能和应用需求,调整数据库的最大连接数。
2.8.5、分库分表
数据库分库分表是一种常见的性能和可伸缩性优化手段,主要目的是为了应对大规模数据集和高并发访问带来的挑战。
当单表数据量变得非常大时,查询和更新的性能会受到影响,每次查询时会产生大量的 IO,分库分表可以减少单个数据库的负载,像这种情况属于数据库IO瓶颈,推荐使用分库和垂直分表。
当SQL中包含 join、group by、order by、非索引字段条件查询等,会增加CPU运算的操作,或者单表数据量太大,查询时扫描的行太多,会导致SQL 执行效率低,像这种情况属于CPU瓶颈,推荐使用水平分表。
如何分库分表:
垂直分库:根据业务功能将表分组,每个组部署在不同的数据库中,如:用户数据、订单数据等分别存储。
水平分库:根据某种规则(如用户ID、时间范围等)将数据分配到不同的数据库中。
垂直分表:将表中的列分成多个部分,每个部分存储在不同的表或数据库中。
水平分表:将表中的数据按行切分,每部分存储在不同的表或数据库中。
分区表:使用数据库的分区功能,根据规则将数据分布到不同的分区。
一致性哈希:使用一致性哈希算法将数据分布到不同的数据库或表中。
范围分片:根据数据的范围(如日期、ID等)进行分片。
列表分片:使用配置列表来映射数据到特定的数据库或表。
散列分片:使用散列函数将数据均匀分布到不同的数据库或表。
使用中间件:使用分库分表中间件或代理来管理数据路由和事务,如shardingsphere。
2.8.6、读写分离
读写分离旨在通过将读操作、和写操作分开,是一种常见的优化方案。通过读写分离,可以将查询操作和更新操作分散到不同的数据库服务器。进而通过主从复制来保证读(从)库和写(主)库的数据一致性,并以负载均衡技术,合理分配读操作到各个读(从)数据库。
读写分离的原理:
主库(Master):负责处理所有的写操作(比如:插入、更新、删除......)和写操作相关的事务。
从库(Slave):负责处理读操作(查询),通过主从复制机制从主库同步数据。
复制机制:主库将数据更改记录到二进制日志(binlog),从库读取并执行这些日志中的操作,以保持与主库的数据一致性。
2.8.7、其他优化
适当反范式化:
适度的反范式化可以减少复杂的连接操作,从而提高查询效率。
示例:
-- 用户表
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(100)
);
-- 订单表
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
order_total DECIMAL(10, 2),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- 反范式优化,减少连表查询 反范式化后的订单表
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
user_name VARCHAR(100),
order_total DECIMAL(10, 2)
);
批量执行:
-- 批量插入示例
INSERT INTO users (user_id, user_name, sex) VALUES
(1, 'Ali', '男'),
(2, 'Baba', '男'),
(3, 'Cheely', '女');
此外,应定期清理无用的数据,及时释放存储空间。定期重建索引,避免索引碎片化。还应该有监控报警的机制,使用监控工具实时监控数据库的运行状态,CPU使用率、内存使用率、磁盘使用、I/O情况等。通过分析慢查询日志,定期进行sql优化。
2.9、过载保护优化
过载保护是另一种确保系统稳定性和可靠性的机制,特别是在面对高流量或异常流量时。过载保护通常包括:
- 限流(Rate Limiting):对进入系统的请求数量进行限制,以防止系统过载。
- 熔断(Circuit Breaker):当系统检测到一定数量的连续错误或异常时,自动断开服务的某些部分,以防止问题扩散。
- 快速失败( Fast Fail):在检测到严重问题时,系统快速失败并通知用户,而不是尝试继续执行可能导致更多问题的复杂操作。
- 服务降级(Service Degradation):在系统负载过高时,有选择地降低某些非关键服务的优先级或质量,以保证关键服务的正常运行。
- 资源隔离:将关键资源与非关键资源隔离,确保关键操作即使在高负载下也能获得所需资源。
- 动态扩缩容:根据系统负载动态调整资源分配,如自动增加服务器数量或资源分配。
2.10、性能监控
常见的性能监控指标:
CPU 使用率:监控CPU的忙碌程度,包括用户空间、系统空间和空闲时间的比例。
内存使用率:监控已使用和空闲的内存量,包括RAM和虚拟内存。
磁盘I/O:监控磁盘的读写速度和操作延迟。
网络I/O:监控网络流量、带宽使用情况和网络延迟。
响应时间:应用或服务处理请求所需的时间。
吞吐量:在一定时间内系统能处理的事务或请求的数量。
系统负载:系统当前运行的进程数或CPU等待处理的任务数。
垃圾回收(GC):对于Java等语言,监控垃圾回收的频率、持续时间和类型。
数据库连接数:同时连接到数据库的客户端数量。
服务可用性:服务正常运行时间与总时间的比例。
活动线程数:系统中活动线程的数量。
队列长度:等待处理的任务队列的长度。
每秒事务数(TPS):每秒钟系统能处理的事务数量。
每秒查询率(QPS):每秒钟系统能处理的查询数量。
数据吞吐量:系统中数据的输入和输出速率。
页面加载时间:Web页面从请求到完全加载所需的时间。
用户会话数:系统中活跃用户的数量。
虚拟化资源使用:在虚拟化环境中,虚拟机或容器的资源使用情况。
容器健康状态:在容器化环境中,容器的运行状态和健康检查结果。
监控工具:
选择合适的监控工具对于数据的准确收集至关重要。常用的监控工具包括但不限于Prometheus(普罗米修斯)、Grafana、Zabbix、Nagios等。
实时监控与告警:
实现实时监控,当性能指标超出预设阈值时,系统应能自动发出告警,以便及时采取措施。一般的监控组件都可以集成webhook,如钉钉机器人等,及时通知开发人员。
三、写在最后
虽然在面试时,面试官提到的"性能优化"问题只有短短的一句话,但是由"性能优化"引申的知识点和深层次的问题却如马里亚纳海沟一样深不可测,"性能优化"涉及到硬件(CPU/硬盘/内存/物理网络)、软件(操作系统/数据库/应用服务器/云原生)、通讯协议(TCP/IP/HTTP/gRpc)、分布式系统、系统架构等等理论知识,唯有不断的面对挑战和及时的总结,才能拥有一套属于自己的宏观架构体系,并能够在竞争激烈的当下,至少立于不败之地。
不论世界如何变化,致所有的程序员们,保持谦卑、保持乐观、保持热爱。
世界之大,我们能看到的也只是冰山一角