性能测试

性能优化的第一步是性能测试。通过性能测试的数据结果才能表明性能优化是否起作用。

性能标准分为主观和客观两种视角,主观能感受到的性能,并不一定要通过性能优化实现,也可以通过提升用户体验实现。技术人员更应该关注的是客观性能。

性能测试的四个主要指标

  • 响应时间
    • 应用系统从发出请求开始到收到最后响应数据所需要的时间,直接反应系统的『快慢』。
    • 主管上的性能优化,可以在从开始到结束中间的时间点上做优化。比如在开始之后马上给用户反馈而不是等到结束后;
  • 并发数
    • 系统能够同时处理请求的数量,反应系统的负载特性
    • 对网站而言,同时提交请求的用户数(并发用户数)当前登录系统的用户数(在线用户数),可访问系统的总用户数(系统用户数)
  • 吞吐量
    • 单位时间内系统处理请求的数量,体现系统的处理能力
    • 对网站而言,可以使用『请求数/秒』或『页面数/秒』来衡量,也可以使用『访问人数/ 天』,『处理业务数/小时』来衡量
    • 指标
      • TPS(每秒事务数)
      • HPS(每秒HTTP请求数)
      • QPS(每秒查询数)

三者直接的换算

  1. 吞吐量=(1000/响应时间ms)*并发数
  2. = (1000*并发数)/响应时间ms
  3. = 并发数/响应时间s

并发数过大时,响应时间就越大,当到达极限,响应时间就会无限大,系统的吞吐量就成了零。

  • 指标:性能计数器

描述服务器或操作系统性能的一些数据指标。包括System load,对象与线程数,内存使用率,cpu使用率,磁盘与网络IO等。

System load:正在处理的任务数+等待处理的任务数
当System load大于cpu核数时,表示有任务在等待,理想情况下应该小于等于cpu核数
image.png

是使用更多机器实现每台服务器的load少,还是使用少数服务器每台load高,是需要架构师权衡的

性能测试/负载测试/压力测试的区别

性能测试:以系统设计初期规划的性能指标为预期目标,对系统不断施加压力,验证系统的资源可接受的范围内,是否能达到性能预期。

负载测试:对系统机型施加压力,直到系统的某项或多项性能达到安全临界值,当压力达到临界值,系统处理能力开始下降。

压力测试:在超过安全负载的情况下,继续施加压力,直到系统崩溃不能处理请求,以获得系统的最大承受能力。

image.png

架构师应该让系统运行在b点附近,而不是c点,因为很容易导致系统崩溃
系统运行在b点的左边还是右边,就体现在system load上,这是架构师应该权衡的。

image.png

image.png

性能优化

性能优化的两个基本原则

  • 你不能优化一个没有测试的软件
    • 没有测试就没有数据,没有数据说明系统有性能问题
    • 不能只为了应用新技术就开始性能优化
  • 你不能优化一个你不了解的软件

性能优化的一般方法

  • 性能测试,获取性能指标
  • 指标分析,获取性能与资源瓶颈点
  • 架构与代码分析,寻找性能与资源瓶颈的关键所在
  • 架构与代码优化,优化关键技术点,平衡资源利用
  • 性能测试,进入性能优化闭环

系统性能优化的分层思想

  • 机房与骨干网络性能优化
    • 异地多活的多机房架构,用户访问就近的机房
    • 专线网络与自主CDN建设
  • 服务器与硬件
  • 操作系统
  • 虚拟机
  • 基础组件
  • 软件架构
  • 软件代码性能
    • 遵循面向对象的设计原则和设计模式编程,代码质量要有保证
    • 并发编程,多线程与锁
    • 资源复用,线程池与对象池
    • 异步编程,生产者与消费者
    • 数据结构,数组,链表,hash表,树

image.png
image.png

image.png

image.png

image.png

性能优化的三板斧

缓存

从内存获取数据,减少响应时间
减少数据库访问,降低存储设备负载压力
缓存结果对象,而不是原始数据,减少CPU计算
缓存主要优化读操作,对热点数据的读操作

异步

即时响应,更好的用户体验
控制消费速度,合适的负载压力
异步主要优化写操作

集群

横向扩展,前提是系统架构要支持
要满足扩展性的要求,新增减少节点的时候不能影响系统运行

优化相关的基础知识

操作系统

程序运行时架构

程序是静态的没有生命的,运行起来,程序才是『活的』,被称为『进程』。

image.png

多任务运行环境

CPU通过分时的方式执行进程,但只有被CPU调度到执行,才是真正的『活的』,我们感觉到服务器上有很多的进程在同时执行,只是因为分时的粒度足够小。

进程的生命周期:
运行:进程在CPU上正在运行的状态,处于运行状态的进程数量小于等于CPU的数量。
就绪:进程已经获取到了除CPU之外的其他资源,一旦得到CPU资源就能马上运行。
阻塞:也叫等待或睡眠状态。进程正在等待别的资源,比如等待IO完成,等待锁,而暂时停止运行,这是即使得到CPU资源也无法运行;

进程VS线程

不同进程轮流在CPU上执行,每次都需要进行CPU切换,代价非常大。

类似于进程与操作系统的关系,线程是轻量级的进程,线程从进程中获取内存地址,每个线程也有自己私有的内存地址范围。

多个线程可以真正的并发执行。
image.png
线程的生命周期与进程相同。

线程栈

先进先出

每个线程有自己的堆栈内存,执行自己的逻辑,避免不同线程之间产生影响。

每个正在执行的函数会有一个位于栈顶的栈帧,执行完之后就会被弹出。
函数内的变量以及运算,在栈帧中完成。

当stack的空间过小,或者方法调用层次过多(比如无限的递归调用),就会导致Stack Overflow异常的出现。

image.png

线程安全

当多个线程修改堆内存(进程共享内存)中的数据时,就可能出现线程安全问题。
函数内的变量是在栈中的,每个线程都有自己的栈内存,不会被其他的线程访问,所以函数中的变量不会有安全问题。

不同于基本数据类型是在栈中的,new一个对象时,对象是放在堆内存中的,每个线程的栈中存放的是对象的引用。

image.png

多个线程访问共享资源的这段代码被称为临界区,解决线程安全的方法是 将临界区的代码加锁,只有获得锁的线程才能执行临界区的代码。
image.png

锁会导致线程阻塞,阻塞导致线程既不能继续执行,也不能是否已经获取的资源,进而导致资源耗尽,最终导致系统崩溃。
image.png
如何避免阻塞引起的崩溃

  • 限流:限制进入服务器的请求数,进而减少创建的线程数。
  • 降级:关闭部分功能程序的执行,尽早释放线程
  • 反应式:异步;无临界区(Actor模型)

锁是如何实现的

CAS(compare and set)是一种系统原语,原语的执行必须是连续的,执行过程中不允许被中断。

CAS(V,E,N)
三个参数:

  • v要更新的变量
  • e预期值,无锁的
  • n新值,加锁

Java中是通CAS原语在对象头中修改Mark Word实现加锁的
image.png

Java中三种锁类型

  • 偏向锁
  • 轻量级锁
  • 重量级锁

image.png

多CPU情况下的锁

  • 总线锁:锁定主存
  • 缓存锁:不会锁定主存

image.png

锁的分类

缓存锁/可重入锁/公平锁、非公平锁/独享锁、互斥锁/共享锁/读写锁/自旋锁

  • 乐观锁:乐观锁认为对于同一个数据的并发操作,是不会发生修改的,在更新数据时,检查是否已经被修改过,如果修改过,就放弃;
  • 悲观锁:悲观锁认为对于同一个数据的并发操作,一定会发生修改。即时没有被修改也会认为被修改。悲观锁认为不加锁的并发一定会出问题。

存储

追求读写速度快,并且数据安全

硬盘raid

image.png

分布式文件系统HDFS
image.png

扩展

system load 的意义

发现问题比解决方案更重要,发现问题要靠经验和时间,不是能一蹴而就的。

知识不是知道就能用的。

线程栈。

慢网络请求导致系统崩溃的原理。

反应式编程为什么无临界区
缓存一致性协议
缓存锁/可重入锁/公平锁、非公平锁/独享锁、互斥锁/共享锁/读写锁/自旋锁
分段锁,ConcurrenHashMap的实现原理

一种无锁的实现:异步并发分布式编程,框架Akka
Actor编程模型

B+树原理及访问硬盘的逻辑