性能测试与优化 和 操作系统与文件系统

用户头像
满山李子
关注
发布于: 2020 年 07 月 21 日



1. 性能测试

性能测试是性能优化的前提和基础, 也是性能优化结果的检查和度量标准

主观视角: 用户感受到的性能

客观视角: 性能指标衡量的性能



1.1 性能测试指标

网站性能测试的主要指标有响应时间, 并发数, 吞吐量, 性能计数器等



  • 并发数: 应用系统能够同时处理的请求数码, 反映了系统的负载特性。

  • 响应时间: 应用系统从发出请求开始到收到响应数据所需的世界; 响应时间, 直观的反映了系统的 "快慢" 。

  • 吞吐量:单位时间内系统处理的请求的数量, 体现系统的处理能力;TPS(每秒事务数), HPS(每秒HTTP请求数),QPS(每秒查询次数)

  • 性能计数器: 用于描述服务器或操作系统的性能的一些指标。包括: System Load, 对象与线程,内存使用, CPU使用, 磁盘与网络I/O等指标; 这些指标也是系统监控的重要指标, 对这些指标设置报警的阈值, 当监控系统发现吸能记数器超高阈值的时候, 就向运维人员和开发人员报警, 及时发现处理系统异常。

1.2 性能测试方法

性能测试: 分为性能测试(需求要达到的), 负载测试, 压力测试, 稳定测试等。



  • 性能测试: 不断增加并发次数和请求次数,给系统施加压力, 验证系统在资源可接受范围内, 是否能到达性能预期

  • 负载测试: 对系统不增加并发请求以增加系统压力, 直到系统的某项或多项西恩感指标达到安全临界值; 如果某种资源已经处于饱和状态, 这个时候继续对系统施加压力,系统的性能指标不但不能提高, 反而会下降

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

  • 稳定性测试: 不均匀的对系统施加压力,使系统运行一段较长的时间, 以此检测系统是否稳定



下图是随着压力(并发数)增加, 吞吐量和系统资源的变化情况;

再看一下, 随着并发数增加, 响应的时间的变化情况



1.3 全链路压测

全链路压测:是指在特定业务场景下, 将相关的链路完整的串联起来同时施压,尽可能模拟出真实的用户行为,当系统整站流量都上来的时候,就会暴露出性能瓶颈, 才能探测出系统整体的真实处理能力; 以及有指导的在大促钱进行容量规划和性能优化, 这便是线上实施全链路压测的真实目的。



1.3.1. 全链路压测的挑战

  • 压测相关的业务系统众多, 并且牵涉整个链路上所有的基础设施和中间件, 如何确保压测流量能够畅通无阻, 没有死角?

  • 压测的数据怎么构造(亿万级的商品和用户)数据模型如何与真实贴近?

  • 全链路压测直接在线上的真实环境进行模拟, 怎样来保证对线上无影响?

  • 大型促销活动所带来的巨大流量要怎么制作处理?

1.3.2 数据构造



1.3.3 数据隔离

  • 逻辑隔离: 直接把测试数据与正常数据写在一起, 通过特殊标示区分;

  • 缺点: 可能污染线上数据, 破坏线上数据安全

  • 虚拟隔离:在所有写数据地方做mock, 并不真正的写进去;

  • 优点: 不会污染线上数据

  • 缺点: mock对压测结果的准确性会产生干扰

  • 说明: mock 可以理解为用于一个虚拟的对象来保存写入的数据。

  • 物理隔离: 所有写数据的地方对压测流量进行识别, 如果判断是压测流量的写, 就写到隔离的位置, 包括 存储, 缓存, 搜索引擎等等。

  • 优点:不会污染线上数据, 压测结果准确

1.3.4 流量构造

天猫双十一全链路压测流量平台, 是一个典型的主从结构, 主节点作为压测管控台, 管理着上千个从节点,从节点作为压测引擎, 负责具体的请求发送; 主节点作为压测平台的大脑, 负责的整个平台的运转控制,命令发送,数据收集, 决策等;从节点部署在全球各地的cdn节点上, 从而模拟从全球各地发送过来的用户请求。整套全链路压测的流量平台平稳输出1000w/s的用户请求, 同时保持过亿的无线用户长链接。



全链路压测平台化



2. 性能优化

软件性能优化的两个基本原则:

  1. 你不能优化一个没有测试的软件

  2. 你不能优化一个你不了解的软件



2.1 性能优化的一般方法

  • 性能测试, 获得性能指标

  • 指标分析, 发现性能与资源瓶颈点

  • 架构与代码分析, 寻找性能与资源瓶颈关键所在

  • 架构与代码优化,优化冠军技术点,平衡资源利用率

  • 性能测试, 进入性能优化闭环



2.2 系统性能优化的分层思想

  • 机房与骨干网络性能优化

  • 服务器与硬件性能优化

  • 操作系统性能优化

  • 虚拟机性能优化

  • 基础组件性能优化

  • 软件架构性能优化

  • 软件代码性能优化



2.2.1 机房与骨干网络性能优化

  1. 异地多活多机房架构



异地多活架构的关键点就是异地、多活,其中异地就是指地理位置上不同的地方,类似于“不要把鸡蛋都放在同一篮子里”;多活就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是活动、活跃的意思。判断一个系统是否符合异地多活,需要满足两个标准:

  • 正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。

  • 某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。



  1. 专线网络与自主 CDN 建设



网络专线就是为某个机构拉一条独立的网线,也就是一个独立的局域网, 专线的优点就是安全性好,服务质量可以得到保证。



CDN(内容分发网络):内容分发网络,核心就是在各网络运营商(电信,联通, 移动)机房中, 放入自己的CDN服务器, CDN网络中的功能实体包括内容缓存设备、内容交换机、内容路由器、CDN内容管理系统等组成;主要用于缓存图片,视频等占用带宽资源比较多, 又不经常变化的内容;这样消耗带宽的内容用户可以就就近访问, 提高用户体验;另外这部分请求被拦截下来, 降低应用服务器的压力;CDN就像您网络上的“物流中转站”

2.2.2 服务器与硬件性能优化

使用更优的CPU, 磁盘, 内存, 网卡, 对软件的性能优化可能是数量级, 有时候远远超过代码和架构的性能优化。



硬件性能优化案例:

Spark 作业过程需要传输大量数据, 进行资源瓶颈分析,发现大量时间消耗在网络传输上。

优化方案: 升级网卡, 10G网卡代替1G网卡



2.2.3 操作系统优化案例

资源利用分析, 发现大量CPu操作为sys类型, 消耗大量的计算资源。

调查发现, 起因是部分Linux版本缺省情况,打开了tranparent huge page 导致的



优化方案: 关闭 transparent huge page



2.2.4 虚拟机性能优化

监控GC状态:使用各种JVM工具,查看当前日志,分析当前JVM参数设置,分析当前堆内存快照和GC日志,根据实际的内存区域划分和GC执行时间,觉得是否进行优化;



分析结果,判断是否需要优化:如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化;



调整GC类型和内存分配:如果内存分配过大或过小,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且找几台机器进行测试,比较优化过的机器和没有优化过的机器的性能对比,有针对性的做出最后选择;



不断的分析和调整:通过不断试验,分析并找到最合适的参数;



全面应用参数:找到最合适的参数后,将这些参数应用到所有服务器,并进行后续跟踪。

PS:阅读GC日志时,主要关注MinorGC和FullGC 的回收效率(回收前大小和回收比较)、回收的时间。



2.2.5 基础组件性能优化

通过更换同功能的基础组件或调整基础的组件的版本, 在不改变原有代码和架构情况下提升性能

2.2.6 软件架构性能优化

  • 缓存

  • 异步

  • 集群



1.缓存

从内存获取数据, 减少响应时间

减少数据库访问, 降低存储设备负载压力

缓存结果对象,而不是原始数据, 减少CPU计算

换成主要优化读取操作



2.异步

即时响应, 更好的用户体验

控制消费速度, 合适的负载压力

异步主要优化写操作



3.集群



互联网技术发展路径就是: 更多的用户访问需要消耗更多的计算资源,单一服务器计算资源的增加是有限的, 所以需要增加更多服务器。关键是如何利用起来这些服务器。



集群的技术目标只有一个: 如何使很多台服务器, 对于使用者就像一台服务器。



2.2.7 软件代码性能优化

遵循面向对象的设计原则与设计模式编程, 很多时候程序性能不好, 不是因为性能上有什么技术挑战, 仅仅是因为代码太烂



并发编程, 多线程与锁

资源复用, 线程池与对象池

异步编程, 生成者与消费者

数据结构, 数组, 链表, hash表, 树



代码优化案例

Spark 任务文件初始化优化:

  • 资源分析, 发现第一个stage时间特别长, 耗时长达14s, CPU和网络通信都有一定开销, 不符合应用代码逻辑

打开Spark作业log, 分析这段时间 Spark 运行状况; 根据log分析结果, 阅读 Spark相关源码。

发现 Spark 在任务初始化加载应用代码的时候, 每一个Executor都加载以此应用代码, 当时每台服务器最多启动48个Executor, 每个应用代码包17M, 导致加载开销巨大。



优化方案:Executor加载应用程序包启用本地文件缓存模式。【SPARK-2713】

优化效果:Stage1运行时间从14s,下降到不到1s



3. 操作系统

3.1 程序运行时架构



程序是静态的。

程序运行起来以后, 被称为进程。

3.2 操作系统多任务运行环境

计算机的CPU核心数是有限的。但是,服务器可以同时处理数以百计甚至数以千计的并发用户请求。

  • 那么,计算机如何做到的呢? 进程分时执行。

3.3 进程的运行期状态

运行: 当一个进程在CPU上运行是, 则称该进程处于运行状态。处于运行状态的进程数目,小于等于CPU的核数。



就绪: 当一个进程获得来除CPU以外的一切所需资源, 只要得到CPU即可运行, 则称此进程处理就绪状态, 就绪状态有时候也被称为等等运行状态。



阻塞:也称为等等或睡眠状态, 当一个进程正在等待某一事件发生(比如: 等待I/O完成, 等待锁,...)而暂时停止运行, 这时即使把CPU分配给该进程, 也无法运行, 故称该进程处于阻塞状态



3.4 进程 VS 线程

不同进程轮流在CPU上运行, 每次都要进行进程间CPU切换, 代价非常大, 因此服务器应用通常是单进程多线程.

进程从操作系统获得基本的内存框架, 所有线程共享折进程的内存地址空间。而每一个线程也会拥有自己私有的内存地址范围, 其他线程不能访问。





3.5 线程栈

线程栈,是线程的私有内存区域,主要拥有存储函数调用是产生的局部变量;正在执行的函数在栈顶,有时候也被称为函数调用栈。

举例

void f(){
int x = g(1);
x++; // g 函数返回, 当前堆栈顶部为f函数栈帧, 在当前栈帧继续执行f函数的代码.
}
int g(int x){
return x + 1;
}



3.6 Java web 应用多线程运行时视图

3.7 线程安全

某些代码修改堆内存(进程共享内存) 中的数据的时候, 如果有多个线程在同时执行, 就可能会出现同时修改数据的情况, 比如, 两个线程同时对一个堆中数据执行+1操作, 最终这个数据只会被加1次, 这就是线程安全问题, 实际上线程的结果应该一次加1, 最终的结果应该是+2。



3.8 临界区

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



lock.lock(); // 线程获得锁
i++; //临界区代码, 假设这里的i位于堆中
lock.unlock(); // 线程释放锁

3.8 阻塞导致高并发系统崩溃

锁(IO)会引起线程阻塞.阻塞导致线程既不能继续运行, 也不能释放资源。进而导致资源耗尽。最终导致系统崩溃。





3.9 避免阻塞引起的崩溃

限流: 控制进入计算机的请求数, 进而减少创建的线程数

降级: 关闭部分功能程序的执行, 尽早释放线程。

避免阻塞: 异步I/O; 无临界区(Akka 的 Actor模型)



4. 锁

4.1 锁源语(CAS)

CAS:Compare and Swap,即比较再交换。

CAS(V,E,N)

  • V 表示要更新的变量

  • E 表示预期值

  • N 表示新值



如果 V值等于E值, 则将V的值设置为N, 若V值和E值不同, 什么都不做。

CAS是一种系统源语,源语的执行必须是连续的, 在执行过程中不允许被中断。



Java 通过CAS源语在对象头中修改Mark Word实现加锁



4.2 偏向锁, 轻量级锁, 重量级锁

偏向锁: 指一段同步代码一直被一个线程所访问, 那么该线程会自动获取锁, 降低获取锁的代价



轻量锁: 指当锁是偏向锁时, 被另外一个线程访问, 偏向锁就会升级为轻量级锁, 其他线程会通过自旋的形式尝试获取锁, 不会阻塞, 提高性能



重量级锁: 指当锁是轻量级锁时, 另一个线程虽然自旋, 但是自旋不会一直持续下去, 当自旋到一定次数时, 还没有获取到锁, 就会进入阻塞, 该锁膨胀为重量级锁, 重量级锁会让其他申请的线程进入阻塞, 性能降低



4.3 多CPU情况下的锁



4.3.1 总线锁 和 缓存锁

总线锁: 使用处理器的lock# 信号, 当一个处理器在内存总线上输出此信号的时候, 其他处理器的请求将被阻塞, 该处理器独占内存.

缓存锁: 是指内存区域如果被缓存在处理器的缓存行中, 并且在Lock操作期间被锁定, 那么当它执行锁操作回写到内存时, 处理器不在总线上声言LOCK#信号, 而是修改内部的内存地址, 并允许它的缓存一致性机制来保证操作的原子性, 因为缓存一致性机制会阻止同时修改两个以上处理器缓存的内存区域数据, 当其他处理器回写已被锁定的缓存数据时, 会使缓存无效



4.3.2 公平锁 与 非公平锁

公平锁: 多个线程按照申请锁的顺序来获取锁

非公平锁: 多个线程获取锁的顺序并不是按申请锁的顺序, 有可能后申请的线程比先申请的线程优先获取锁; 可能会出现饥饿现象。



4.3.3 可重入锁

可重入锁:某个线程已经获得某个锁, 可以再次获取锁而不会出现死锁。



4.3.4 独享锁/互斥锁 共享锁 读写锁

独享锁/互斥锁: 该锁一次只能被一个线程所持有

共享锁: 该锁可以被多个对象锁持有

读写锁: 多个读线程之间并不互斥, 而写线程则要求与任何线程互斥。

4.3.5 乐观锁 与 悲观锁

悲观锁: 悲观锁认为对统一个数据的并发操作, 一定会发生修改的, 哪怕没有修改, 也会认为修改。因此对于统一个数据的并发操作, 悲观锁采取加锁的形式。 悲观锁的认为, 不加锁的并发操作一定会出问题



乐观锁: 乐观锁认为同个数据的并发操作, 是不会发丝轻修改的, 在更新数据的时候, 检查是否已经被修改, 如果修改过就放弃。



4.3.6 分段锁

分段锁的设计目的是细化锁的粒度, 当操作不需要更新整个数组的时候, 就仅仅真堵数组的一段进行加锁操作。

JDK ConcurrentHashMap是通过分段锁的形式来实现高效并发操作的。



4.3.7 自旋锁

自旋锁是指当尝试获取锁的线程不会立即阻塞, 而是采用循环的方式去尝试获取锁, 这样的好处是减少线程上下文切换的消耗, 缺点是循环会消耗CPU。



5. 文件系统



5.1 文件与磁盘IO

机械硬盘

固态硬盘



5.2 B+树

5.3 LSM树

5.4 文件控制块

文件系统将硬盘空间以块为单位进行划分, 每一个文件占用若干块, 然后再通过一个文件控制块FCB记录每个文件占据的磁盘数据块。



5.5 Linux Inode 文件控制块

  • inode中记录着文件权限, 所有者, 修改时间和文件大小等文件属性信息, 以及文件数据块磁盘地址索引

  • inode是固定结构的, 能够记录的硬盘地址索引数也是固定的, 只有15个索引。

  • 每个inode最多可以存储12+256+256*256+256*256*256个数据块, 如果数据块的大小为4K, 也就是单个文件最大不超过70G





5.4 RAID独立硬盘冗余阵列



5.6 分布式文件系统 HDFS



用户头像

满山李子

关注

还未添加个人签名 2018.09.18 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
请添加“极客大学架构师训练营”标签,方便分类
2020 年 07 月 21 日 10:50
回复
没有更多了
性能测试与优化 和 操作系统与文件系统