架构师训练营第 1 期 - 第 7 周学习总结
本周学习了性能优化相关的知识,比如性能的主要指标、全链路压测、性能优化的分层思想、操作系统、锁
一、性能的主要指标
性能优化的前提是要做性能测试,有了性能测试的结果,才知道系统需要优化哪些地方。性能测试的主要指标有
响应时间
响应时间是指系统从发出请求开始到处理请求返回结果所需要的时间。响应时间是系统最重要的性能指标,直接反映了系统的快慢。
并发数
并发数是系统能够同时处理请求的数目,就是在同一时间向系统发出请求的用户数量。还有在线用户数,在线用户数是指当前登录到系统的用户,但是他们可能并没有同时发出请求,同时发出请求的就是并发数。还有系统用户数,系统用户数是指系统中已经存在的所有用户,但是他们可能并没有都同时在线,只有那些在线的用户才能算在在线用户数。
吞吐量
吞吐量是系统单位时间内能够处理请求的数量。吞吐量越高,表示系统单位时间内能够处理请求的数量越大。可以用 TPS(每秒事务数)、QPS(每秒查询数)等来表示吞吐量。
性能计数器
性能计数器是描述系统性能的一些数据指标,比如线程数、内存使用率、CPU 使用率、磁盘与网络 I/O 等。这些指标是系统运维的重要参数,可以对这些指标设置阈值,当监控系统发现这些指标超过阈值的时候,可以触发报警,人工及时干预。
系统的性能测试包含性能测试、负载测试、压力测试和稳定性测试。
性能测试
性能测试是指对系统设置的性能指标进行测试,验证系统在资源接受的范围内,是否可以达成预期的目标。
负载测试
负载测试是指对系统不断增加请求压力和并发数量,直到系统的某些性能指标达到安全临界值
压力测试
压力测试是指在超过系统安全负载的情况下,继续增加请求压力,直到系统崩溃,获取系统的最大承受压力
稳定性测试
稳定性测试是指在特定的硬件、软件、网络环境下,给系统增加一定的业务压力,使系统运行一段时间,观察系统是否能够稳定运行。为了更好的贴近生产环境,稳定性测试的压力也应该是不均匀的
我们可以在性能测试、负载测试和压力测试阶段,记录系统在不同并发数的情况下,系统的响应时间、吞吐量、错误率、负载、内存等指标,然后根据这些数据,在坐标轴上画出吞吐量和并发数的曲线图、响应时间和并发数的曲线图。一般而言,随着系统并发数的不断增加,系统的吞吐量在到达最大负载点之后,就慢慢下降,直到崩溃就停止响应。响应时间是随着并发数的增加而不断延长,直到崩溃就无法处理请求了。
二、全链路压测
全链路压测是指在特定的业务场景下,对整条链路上的系统同时进行施压,尽可能模拟线上的用户行为,观察系统是否还能运行正常,尽早发现系统的性能瓶颈,以便能够在大促之前进行性能优化,并对大促的容量进行规划。
全链路压测面临很多挑战,比如如何构造压测数据?如何确保压测不会对生产环境造成影响?如何制造巨大的流量?
数据构造
需要专门的数据构造平台来构造数据。导出线上数据,并进行脱敏处理,然后导入到基础数据池,并结合 BI 数据中心的数据,在数据构造平台经过模型预测算法生成数据。
数据隔离
如果进行数据的逻辑的隔离,因为是直接把测试数据和生产数据一并写入,通过标识符区分,这样做可能会污染线上数据,破话数据安全性。
如果进行虚拟隔离,在所有写数据的地方 mock,但是不真正写入,虽然不会对线上数据造成污染,但是因为没有直接写入,压测结果可能会不准确。
如果进行物理隔离,那么需要对所有写数据的地方,都判断如果是测试数据,那么写入到隔离的地方。
流量构造
需要专门的流量构造平台,在全球各地的 CDN 节点上,模拟用户请求,并保持全链路压测所需要的流量。
全链路压测一旦建立起来,可以把整个压测链路平台化,这样一旦需要对新的业务进行性能评估时,可以在平台上申请专门的资源来进行全链路压测。
三、性能优化的分层思想
软件性能优化的 2 个基本原则是:
你不能优化一个没有测试的软件
你不能优化一个你不了解的软件
性能优化的一般方法
性能测试,获取性能指标包括响应时间、吞吐量、并发数、性能计数器等
指标分析,发现性能与资源瓶颈
分析架构和代码,发现引起性能瓶颈的关键地方
优化架构和代码
再进行性能测试,继续性能优化流程
系统性能优化的分层思想
机房与骨干网络性能优化
比如搭建异地多活的机房架构,配置专线网络和 CDN 服务器
服务器与硬件性能优化
对服务器进行垂直伸缩,增加 CPU、内存、网卡、磁盘等,采用硬件配置更好的服务器
操作系统性能优化
虚拟机性能优化
基础组件性能优化
升级底层容器的版本,升级框架的版本,采用更稳定的版本代替过时的老版本
软件架构性能优化
缓存
缓存直接加快了数据访问速度,不需要每次都从数据库查询,节约了查询时间,降低了数据库服务器的负载压力,因为直接缓存了结果,减少了 CPU 计算的时间,可以更好的响应用户的请求
异步
可以使用异步处理的方式提升系统响应用户的速度,一般会有消息队列在生产者和消费者之间临时存储要处理的请求数据,同时消息队列控制了消费速度,对网站可以起到削峰填谷的作用
集群
对服务器进行水平扩展,增加更多的服务器,就意味着更多的 CPU、内存、磁盘、网卡等,同时要把整个服务器集群当做一台服务器来使用
软件代码性能优化
对代码的优化就涉及到具体的技术实现,比如可以采用多线程的方式并发处理请求,还可以创建线程池和对象池复用系统资源,还可以用效率更高的数据结构比如 hash 表、数、链表等。
四、操作系统
操作系统上面运行着进程,比如 JVM 进程,多个线程同时在一个进程上面运行,线程共享进程的内存空间。当 CPU 从一个线程切换到另一个线程时,需要做线程上下文切换,代价很大,可以创建线程池避免不必要的上下文切换。
如果多个线程同时读写同一个数据,那么可能存在线程不安全的问题,可能多个线程在同一时刻都进行写操作,那么一个线程的写操作可能会覆盖另一个线程的写操作。这个时候,可以考虑加锁,把共享的数据保护起来,被保护的共享资源就是临界区。但是因为锁会阻塞线程,在并发请求很多的时候,会有大量的线程等待锁资源而出现阻塞,如果系统无法及时处理,那么请求会越来越多,从而导致系统资源耗尽而崩溃。避免阻塞引起的崩溃可以考虑如下几点
限流
限制请求的数量,从而减少线程的数量
降级
关闭部分不重要的功能,使系统有更多资源处理核心功能
反应式
利用反应式架构,比如 actor 模型进行编程
五、锁
CAS 锁原语
CAS(Compare And Swap)是一种底层硬件支持的 CPU 指令,给定变量 V、旧值 E、新值 N,当变量 V 的旧值是 E 时,才把变量 V 更新成 N,否则什么都不做,这样做保证了操作的原子性。Java 就是通过 CAS 指令修改对象头中的 Mark Word 来对对象进行加锁的。
偏向锁 轻量级锁 重量级锁
偏向锁是指如果同步代码一直被同一个线程访问,那么该线程会自动获得锁,减少了锁获取的代价
轻量级锁是指当锁是偏向锁时,如果另一个线程也要访问该锁,那么就把锁升级为轻量级锁,其他线程会通过自旋的方式尝试获取锁,不会阻塞,提高性能
重量级锁是指当锁是轻量级锁时,如果自旋一定次数还是没有获取到锁的话,就升级为重量级锁,获取不到锁的线程会进入阻塞,性能低下
公平锁 非公平锁
公平锁是指多个线程按照申请锁的顺序一次获取锁
非公平锁是指不按照申请锁的顺序获取锁,后申请锁的线程可能会先获取到锁,这样可能会导致 有些线程迟迟获取不到锁,造成线程饥饿
可重入锁
可重入锁是指即使某个线程已经获得该锁了,还是可以再次获得该锁而不是阻塞。
互斥锁 共享锁 读写锁
互斥锁是指锁只能被一个线程使用
共享锁是指多个线程都可以使用该锁
读写锁是指当多个线程同时读时,线程都可以获取读锁,如果有线程要写,那么在线程释放写锁前,其他线程也无法读
悲观锁 乐观锁
悲观锁是指不管当前数据有没有被修改过,都先加锁,这样导致即使数据没有修改过,也会加锁,悲观锁的性能低下
乐观锁是指不管当前数据有没有被修改,都先尝试更新,检查到数据有被修改过,就撤销更新
分段锁
分段锁是指如果只是修改当前整个数据中的一小部分数据,那么可以把整个数据进行分段,每一段都专门配置一把锁,这样修改其中的分段数据时,只需要获取分段锁就可以了,提升了整体的并发修改性能。比如 Java 中的 ConcurrentHashMap 就采用了分段锁的设计。
自旋锁
自旋转是指线程获取锁时,如果获取不到,不是立马进行阻塞,而是循环一定的次数尝试再次获取该锁,这样就避免了获取不到锁就立马阻塞,当线程切换回来而导致的上下文切换,缺点是消耗了一定的 CPU
评论