性能优化学习笔记
性能测试:系统性能的主要技术指标
性能优化的前提和基础,也是性能优化结果的检查和度量标准。不同视角下的网站性能有不同的标准,也有不同的优化手段
主观视角:用户感受的性能(异步操作、体验提高主观感受)
客观视角:性能指标衡量的性能(响应时间、并发数、吞吐量、性能计数器)
响应时间:系统发出请求到收到最后的响应数据所需的时间,直观的反映了系统的“快慢”
并发数:能够《同时处理》的《请求数目》(不是在线用户数、系统用户数),这个数字也反映了系统的负载特性。淘宝最高并发数 百万级别
吞吐量:单位时间内系统处理的请求的数量,体现系统的处理能力。TPS(每秒事务数)、QPS(每秒查询数)
吞吐量=(1000/响应时间ms)* 并发数。如果响应时间是1秒。吞吐量=并发数
性能计数器:描述服务器或操作系统性能的一些数据指标。System Load、对象线程数、内存、CPU、磁盘、网络...
性能测试方法
性能测试:按初期规划的性能指标,对系统不断加压,验证在系统的资源可接受范围内,是否达到性能预期
负载测试:性能测试中不断增大压力,直到系统指标出现安全临界值,测试出系统能承受的最大负载
压力测试:超过安全负载后,继续施压,直到系统奔溃,以此获取最大承受压力
稳定性测试:被测试系统在特定的硬件、软件、网络条件下,给系统加载一定业务压力,使系统运行一段较长的时间,以此检测系统是否稳定。
架构师 需要考虑 成本和收益的平衡,需要考虑承担的风险。
测试结果:
并发数,使用不同的并发数,得出下面的指标
响应时间 ms
TPS
错误率 (%)
Load
内存(G)
备注
全链路压测
前面是针对的某一接口进行针对性的测试,如果需要看系统整体性能,需要全链路压测。一般情况不需要这种测试,只有整体的大促情况需要全链路压测。
将相关链路完整的串联起来进行施压,尽可能模拟真实用户的行为,当系统整体流量上来时,才会暴露出问题。
难点:
牵连的基础设施、中间件系统多,如何确保流量压测通顺,压测的数据怎么构造
怎么保证对线上无影响
大型促销活动所带来的巨大流量要怎样制作
数据构造:线上数据 -> dump(日志、网络探针) -> 筛选、脱敏、Id隔离、订正 ->
数据隔离:读操作没问题;写操作会影响(逻辑隔离、虚拟隔离、物理隔离)
流量构造:各个CDN服务器发起测试的请求,通过流量控制平台管理
性能优化 - 系统性能优化的分层思想
两个基本原则
不能优化一个没有测试的软件(做决策需要数据、实际情况的支撑)
不能优化一个不了解的软件 (不要为了使用某技术、知识而用它,要知道是为了解决什么问题)
性能测试的主要指标
响应时间:完成一次任务花费的时间
并发数:同时处理的任务数
吞吐量:单位时间完成的任务数
性能计数器:System Load、线程数、进程数、CPU、内存、磁盘、网络使用率
性能优化的一般方法
性能测试,获得性能指标
指标分析,发现性能与资源瓶颈点。哪里与预期不符合
架构与代码分析,寻找性能与资源瓶颈关键所在。是什么原因导致的瓶颈点
架构与代码及其他优化,优化关键技术点,平衡资源利用。
性能测试,进入性能优化闭环
系统性能优化的分层思想
机房与骨干网络性能优化
异地多活(多机房架构)。解决高可用性问题
专线网络与自主 CDN 建设
服务器与硬件性能优化
更好CPU、网卡、磁盘
虚拟机的性能优化
不同的垃圾回收算法
基础组件性能优化
Apache、Nginx、中间件、数据库连接池、Jetty
软件架构性能优化
缓存、异步、集群
软件代码性能优化
遵循面向对象的设计原则与设计模式编程,很多时候性能不好是因为代码太烂
并发编程(多线程与锁)、资源复用(线程池与对象池)、异步编程(生产者与消费者)、数据结构
操作系统 - 计算机如何处理成百上千的并发请求
程序运行时架构
程序是静态的,程序需要加载到内存,交给CPU根据可执行的程序代码,一行一行运行起来以后,被称为进程。
操作系统多任务运行环境
进程在CPU分时执行;
进程的运行期状态:运行 - 就绪(只能CPU执行) - 阻塞(等待或者睡眠)
进程 VS 线程
服务器应用通常是单进程多线程,减少进程间 CPU 切换
进程从操作系统获得基本的内存空间,所有的线程共享着进程的内存地址空间。而每个线程也会拥有自己私有的内存地址范围,其他线程不能访问。
每个线程有自己的线程栈空间,线程栈中记录不同线程内部不同的局部变量的值。栈适合不断调用的规律,先进后出。
线程安全
当某些代码修改内存堆(进程共享内存)里的数据的时候,如果多个线程在同时执行,就可能会出现同时修改数据的情况。
场景:对象存储在堆中,不同线程栈中对堆中的对象操作时
临界区:多个线程访问共享资源的这段代码被称为临界区,解决线程安全问题的主要方法是使用锁,将临界区的代码加锁,只有获得锁的线程才能执行临界区的代码。
加锁导致了阻塞。
锁会引起线程阻塞,阻塞导致线程既不能继续执行,也不能释放资源。进而导致资源耗尽,最终导致系统崩溃。
如何避免阻塞引起的崩溃
限流:控制进入的请求数,进而减少创建的线程数
降级:关闭部分功能程序的执行,尽早释放线程
反应式:异步;无临界区(Actor模型)
锁 - 锁原语CAS 与 各类锁
锁原语,CAS(V, E, N)。原语的执行必须是连续的,在执行过程中不允许被中断。
V 表示要更新的变量
E 表示预期值
N 表示新值
如果 V 值等于 E 值,则将 V 值设为 N,若 V和E值不同,什么都不做。
Java 通过 CAS 原语在对象头中修改 Mark Word 实现锁
对象头中包括:Mark Word(64 bit)、Klass Word(64 bit)、数组长度(64 bit)
锁升级:获取锁 -> 失败,自旋,轻量级锁 -> 失败,添加在锁队列中,为重量级锁
偏向锁:指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价
轻量级锁:当锁是偏向锁时,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
重量级锁:指当锁是轻量级锁时,另一个线程虽然自旋,但自旋不会一直持续下去,当自旋到一定次数,还没获取到锁,就会进入阻塞,该锁膨胀为重量级锁,重量级锁会让其他申请的线程进入阻塞,性能降低。
总线锁 VS 缓存锁
总线锁:处理器发送 LOCK# 信号,内存总线上输出此信号时,其他处理器的请求将被阻塞,该处理器独占内存。
缓存锁:通过缓存一致性保证操作的原子性
公平锁:先来等来锁的线程优化获得锁
非公平锁:获取锁的顺序不是按申请锁的顺序
可重入锁:某个线程已经获取锁,可以再次获取锁而不会出现死锁
独享锁/互斥锁:该锁一次只能被一个线程所持有
共享锁:该锁可以被多个线程所持有
读写锁:多个读线程之间并不互斥,而写线程则要求与任何线程互斥
悲观锁:对于一个数据的并发操作,先加锁然后进行操作的形式。悲观锁认为不加锁的并发操作一定会出现问题。
乐观锁:在更新数据的时候,检查是否已经被修改过,如果修改过,就放弃。
分段锁:设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组的一段进行加锁操作。
JDK ConcurrentHashMap 是通过分段锁的形式来实现高效并发操作的。
自旋锁:通过 CAS 循环获取锁。
异步并发式编程框架 akka
Actor 编程模型
Akka runs on the JVM
Core concept: Actor
怎么实现事务?
版权声明: 本文为 InfoQ 作者【Yangjing】的原创文章。
原文链接:【http://xie.infoq.cn/article/9e959ac2ad12caadf6aa7694c】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论