Week7 性能优化学习总结
性能测试
性能测试是性能优化的前提和基础,也是性能优化结果的检查和度量标准。不同视角下的网站性能有不同的标准,也有不同的优化手段。
主观视角:用户感受到的性能
客观视角:性能指标衡量的性能
性能测试指标
不同视角下有不同的性能标准,不同的标准有不同的性能测试指标,网站性能测试的主要指标有响应时间、并发数、吞吐量、性能计数器等。
响应时间
指应用系统从发出请求开始到收到最后响应数据所需要的时间。响应时间是系统最重要的性能指标,直观的反映了系统的“快慢”。
并发数
系统能够同时处理请求的数目,这个数字也反映了系统的负载特性。对于网站而言,并发数即系统并发用户数,指同时提交请求的用户数目,于此相对应,还有在线用户数(当前登录系统的用户数)和系统用户数(可能访问系统的总用户数)。
吞吐量
指单位时间内系统处理的请求的数量,体现件系统的处理能力。对于网站,可 以用“请求数/秒”或是“页面数/秒”来衡量,也可以用“访问人数/天”或是“处理的 业务数/小时”等来衡量。
TPS(每秒事务数)也是吞吐量的一个指标,此外还有HPS(每秒HTTP请求数), QPS(每秒查询数)等。
吞吐量 = ( 1000 / 响应时间ms ) × 并发数
性能计数器
性能计数器:是描述服务器或操作系统性能的一些数据指标。包括 System Load、对象 与线程数、内存使用、CPU 使用、磁盘与网络 I/O 等指标。这些指标也是系统监控的重 要参数,对这些指标设置报警阀值,当监控系统发现性能计数器超过阀值的时候,就向 运维和开发人员报警,及时发现处理系统异常。
性能测试方法
性能测试是一个总称,具体可细分为性能测试、负载测试、压力测试、稳定性测试
性能测试
性能测试: 以系统设计初期规划的性能指标为预期目标,对系统不断施加压力,验证系 统在资源可接受范围内,是否能达到性能预期。
负载测试
负载测试: 对系统不断地增加并发请求以增加系统压力,直到系统的某项或多项性能指 标达到安全临界值,如某种资源已经呈饱和状态,这时候继续对系统施加压力,系统的 处理能力不但不能提高,反而会下降。
压力测试
压力测试: 超过安全负载的情况下,对系统继续施加压力,直到系统崩溃或不能再处理 任何请求,以此获得系统最大压力承受能力。
稳定性测试
稳定性测试: 被测试系统在特定硬件、软件、网络环境条件下,给系统加载一定业务压 力,使系统运行一段较长时间,以此检测系统是否稳定。在生产环境,请求压力是不均 匀的,呈波浪特性,因此为了更好地模拟生产环境,稳定性测试也应不均匀地对系统施 加压力。
并发数|响应时间|TPS|错误率(%)|Load|内存|压测类型
---|---|---|---|---|---|---
10|500|20|0|5|8|性能测试
全链路压测
全链路压测其实指的就是在特定的业务场景下,将相关的链路完整的串联起来同时施压,
尽可能模拟出真实的用户行为,当系统整站流量都被打上来的时候,必定会暴露出性能
瓶颈,才能够探测出系统整体的真实处理能力,以及有指导的在大促前进行容量规划和
性能优化,这便是线上实施全链路压测的真正目的。
全链路压测挑战
压测相关的业务系统上众多,并且牵涉到整条链路上所有的基础设施和中间件,如何 确保压测流量能够通畅无阻,没有死角?压测的数据怎么构造(亿万级的商品和用 户),数据模型如何与真实贴近?
全链路压测直接在线上的真实环境进行模拟,怎么样来保障对线上无影响?
大型促销活动所带来的巨大流量要怎么样制作出来?
数据构造
数据抽取不影响生产
数据脱敏
规划生产/测试隔离
数据隔离
逻辑隔离,直接把测试数据和正常数据写到一起,通过特殊的标识能够区分开
- 可能污染线上数据,破坏线上数据安全性。
虚拟隔离,在所有写数据的地方做 mock,并不真正的写进去。
- 这个方案不会对线上产生污染,但是 mock 对压测结果的准确性会产生干扰。
物理隔离,所有写数据的地方对压测流量进行识别,判断一旦是压测流量的写,就写到隔离的位置,包括存储、缓存、搜索引擎等等。
流量构造
网络供应商cdn服务器模拟
平台话
性能优化
两个基本原则
你不能优化一个没有测试的软件
你不能优化一个你不了解的软件
性能测试的主要指标
响应时间:完成一次任务花费的时间
并发数:同时处理的任务数
吞吐量:单位时间完成的任务数
性能计数器:System Load,线程数,进程数,CPU、内存、磁盘、网络使用率
性能优化的一般方法
性能测试,获得性能指标
指标分析,发现性能与资源瓶颈点
架构与代码分析,寻找性能与资源瓶颈关键所在
架构与代码优化,优化关键技术点,平衡资源利用
性能测试,进入性能优化闭环
性能优化的分层思想
机房与骨干网络性能优化
服务器与硬件性能优化
操作系统性能优化
虚拟机性能优化
基础组件性能优化
软件架构性能优化
软件代码性能优化
软件优化三板斧
缓存
异步
集群
缓存
从内存获取数据,减少响应时间
减少数据库访问,降低存储设备负载压力
缓存结果对象,而不是原始数据,减少 CPU 计算 缓存主要优化读操作
异步
即时响应,更好的用户体验
控制消费速度,合适的负载压力
异步主要优化写操作
集群
集群的技术目标只有一个:如何使很多台服务器对使用者而言看起像一台服务器。
代码性能优化
遵循面向对象的设计原则与设计模式编程,很多时候程序性能不好不是因为性能上有什么技术挑战,仅仅就是因为代码太烂了。
并发编程,多线程与锁
资源复用,线程池与对象池
异步编程,生产者消费者
数据结构,数组、链表、hash 表、树
程序系统优化
Runtime
程序是静态的。程序运行起来以后,被称作进程。
cpu|<->|内存
---|---|---
cpu||可执行代码
cpu||堆内空间
cpu||栈内空间
cpu||进程数据结构
操作系统多任务运行环境
计算机的 CPU 核心数是有限的。但是,服务器可以同时处理数以百计甚至数以千计的并 发用户请求。
那么,计算机如何做到的? 进程分时执行
进程的运行期状态
运行:当一个进程在 CPU 上运行时,则称该进程处于运行状态。处于运行状态的进程的 数目小于等于 CPU 的数目。
就绪:当一个进程获得了除 CPU 以外的一切所需资源,只要得到 CPU 即可运行,则称 此进程处于就绪状态,就绪状态有时候也被称为等待运行状态。
阻塞:也称为等待或睡眠状态,当一个进程正在等待某一事件发生(例如等待 I/O 完成, 等待锁......)而暂时停止运行,这时即使把 CPU 分配给进程也无法运行,故称该进程 处于阻塞状态。
进程vs线程
进程
进程||线程1|线程N
---|---|---|---
进程控制块||线程1控制块|线程2控制块
用户地址空间||线程1用户堆栈|线程2用户堆栈
|||线程1系统堆栈|系统2系统堆栈
线程
顶部执行线程栈
线程安全
当某些代码修改内存堆(进程共享内存)里的数据的时候,如果有多个线程在同时执行, 就可能会出现同时修改数据的情况,比如,两个线程同时对一个堆中的数据执行 +1 操 作,最终这个数据只会被加一次,这就是人们常说的线程安全问题,实际上线程的结果 应该是依次加一,即最终的结果应该是 +2。
临界区
多个线程访问共享资源的这段代码被称为临界区,解决线程安全问题的主要方法是使用锁,将临界区的代码加锁,只有获得锁的线程才能执行临界区代码。
阻塞与避免
锁(I/O)会引起线程阻塞。阻塞导致线程既不能继续执行,也不能释放资源。进而导致 资源耗尽。最终导致系统崩溃。
限流:控制进入计算机的请求数,进而减少创建的线程数。
降级:关闭部分功能程序的执行,尽早释放线程。
反应式:异步;无临界区(Actor 模型)
锁
锁原语CAS
CAS(V,E,N)
V 表示要更新的变量
E 表示预期值
N表示新值
如果 V 值等于 E 值,则将 V 的值设为 N,若 V 值和E值不同,什么都不做。
CAS 是一种系统原语,原语的执行必须是连续的,在执行过程中不允许被中断。
Java 锁实现
Java 通过 CAS 原语在对象头中修改 Mark Word 实现加锁
Mark Word(64bit)|锁状态
---|---
unused:25|identity hashcode:31|unsued:1|age:4|biased lock:0|lock:01|正常
threads:54|------epoch:2------|unsued:1|age:4|biased lock:1|*lock:01*|偏向锁
-----------------------ptrtolock_record:62-----------------------|lock:00|轻量级锁
-------------------ptrtoheavyweight_monitor:62------------------|lock:10|重量级锁
锁升级
由访问线程先后,数目决定
偏向锁(被同一个线程访问)
偏向锁:指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取
锁的代价
轻量级锁(其它线程按照自旋获取锁,非阻塞)
轻量级锁:指当锁是偏向锁时,被另一个线程所访问,偏向锁就会升级为轻量级锁,其
他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
重量级锁(自旋到一定次数,进入阻塞,变成重量级)
重量级锁:指当锁是轻量级锁时,另一个线程虽然自旋,但自旋不会一直持续下去,当
自旋到一定次数时,还没获取到锁,就会进入阻塞,该锁膨胀为重量级锁,重量级锁会
让其他申请的线程进入阻塞,性能降低
公平锁/非公平锁
公平锁就是多个线程按照申请锁的顺序来获取锁的。
非公平锁就是多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,可能会造成饥饿现象。
可重入锁
可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
独享锁/互斥锁 & 共享锁 & 读写锁
独享锁/互斥锁:该锁一次只能被一个线程所持有
共享锁:该锁可以被多个线程所持有
读写锁:多个读线程之间并不互斥,而写线程则要求与任何线程互斥
乐观锁/悲观锁
悲观锁: 认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁: 则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,检查是否已经被修改过,如果修改过,就放弃。
分段锁
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数
组的一段进行加锁操作。
JDK ConcurrentHashMap 是通过分段锁的形式来实现高效并发操作的。
自旋锁
自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样 的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU。
总线锁 & 缓存锁
总线锁:使用处理器的 LOCK# 信号,当一个处理器在内存总线上输出此信号的时候, 其他处理器的请求将被阻塞,该处理器独占内存。
缓存锁 :是指内存区域如果被缓存在处理器的缓存行中,并且在 Lock 操作期间被锁定, 那么当它执行锁操作回写到内存时,处理器不在总线上声言 LOCK# 信号,而是修改内 部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制 会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的 缓存行数据时,会使缓存行无效。
版权声明: 本文为 InfoQ 作者【evildracula】的原创文章。
原文链接:【http://xie.infoq.cn/article/88c85b85ee966575b0c5011d5】。文章转载请联系作者。
评论