性能优化 (一):性能测试、优化方法论、性能分析
性能优化需从设计、编码、测试、监控运维等整个研发全流程的各个环节全局思考,形成有反馈的闭环。
性能测试
性能优化的前提和基础是性能测试,通过性能测试才能知道当前系统的性能基准数据,通过数据分析才能知道性能的瓶颈,再针对具体问题具体优化。
其实网站性能标准可从不同视角来看:
主观视角:用户感受的性能,其实现在也可进行监控,通过客户端/前端 性能监控平台;
客观视角:性能指标衡量,服务器的基础设施监控,接口性能监控等;
1. 指标
响应时间:完成一次任务花费的时间, 反映系统快慢
并发数:同时处理的任务数,还包含:在线用户数,系统用户数等指标,反映系统的负载特性
吞吐量:单位时间内完成的任务数,包含:TPS, QPS, HPS,反映系统的处理能力
吞吐量 = (1000/ 响应时间ms)* 并发数
性能计数器:描述服务器或OS性能的一些数据指标。system load(运行线程数+等待线程数,最优是CPU核数), 线程数,进程数,CPU, 内存,磁盘,网络使用率
2. 性能测试分类
性能测试:以系统规划的性能指标为预期目标,对系统不断施加压力,验证系统在资源可接受范围内,是否能达到性能预期;
负载测试:再不断增加并发请求施加压力,直到系统某项或多项性能指标达到安全临界值,系统资源已呈现饱和状态,系统的处理能力不但不能提高,反而下降;
压力测试:超过安全负载情况下持续加压,直到系统崩溃或不能再处理任何请求,以此获得系统最大压力承受能力;
稳定性测试:在特定硬件、软件、网络环境下,加载一定业务压力,使系统运行一段较长时间,监测系统的是否稳定。加载的压力是波浪式的。
3. 性能测试分析
系统资源使用率
响应时间
并发用户数
4. 压测平台本身的性能问题
随着并发数的增加,会造成压测机器的资源的消耗,也可能引起压测机器的崩溃。资源消耗包含:
连接端口不够用
线程数不够
内存不够
网络带宽不够
单机瓶颈
可通过下面方式优化:
i) 分布式集群,通过主控机控制执行机个数
ii) 压测平台集群的监控,合理规划容量
iii) 执行完测试程序,及时清理和释放资源
iv) 对异常、超时任务做一些策略处理
v) 定期重启机器
大规模分布式系统性能优化方法论
重要:不能优化一个没有测试的系统;不能优化一个不了解的系统
1. 一般方法
也可以叫做性能优化的步骤:
性能测试:获得性能基准数据,性能指标
性能数据分析:发现性能与资源的瓶颈点
架构与代码分析:瓶颈关键所在
架构与代码优化:优化关键技术点,平衡资源利用
性能测试:形成性能优化闭环
2. 分层思想
性能优化是一个系统的工程,需在系统服务的整个链路中涉及到的硬件、软件、网络等各方面因素找到瓶颈,进行对应的优化
机房与骨干网络 性能优化:异地多活多机房架构,专线网络,CDN建设
服务器与硬件 性能优化:使用更优的CPU。磁盘、内存、网卡
操作系统 性能优化:OS参数调优
虚拟机 性能优化:虚拟机参数调优
基础组件 性能优化:基础组件更换或更新
软件架构 性能优化:见下文
软件代码 性能优化:见下文
3. 软件架构性能优化
软件架构的优化,对性能优化有时起到决定性的作用。目前常用的三板斧
缓存
从内存或最近路径读取数据,可减少RT, 数据库访问,CPU计算,优化读操作
异步
可及时响应,提升用户体验;削峰填谷,减少负载压力;优化写操作
集群
增加更多的计算资源,提升并发,吞吐量,响应时间。
技术难点:如何使多台服务器对使用者而言看起来像一台服务器。
4 . 软件代码性能优化
代码设计和编写,也是性能问题的一个重要因素。需合理利用现有的经验和技巧
并发编程:多线程,锁
资源复用:线程池、对象池
异步编程: 生产者消费者
数据结构&算法:数据、链表、hash表、树
设计模式:面向对象,函数式编程, 23中设计模式
性能分析
分析性能瓶颈,需熟悉整个计算机体系的基本运行原理,才能快速判断问题的根源
1. 操作系统
程序运行时架构:从静态程序 --->运行的动态进程
程序代码从保存在磁盘上,加载到内存中,通过调度到CPU执行,变成一个有生命周期的进程。
一个进程的内存模型:可执行代码、堆内存空间、栈内存空间、进程数据结构等
OS多任务运行
CPU的核数是有限,进程是通过分时执行来处理很多任务的,会分成很小的时间片
进程运行期状态:就绪、运行、阻塞等
进程 vs 线程
进程:从OS中获取基本的内存空间,所有线程共享的进程内存地址空间, 堆内存
线程:自己私有地址范围,其他线程不能访问 栈内存
线程安全:某些代码修改内存堆或进程共享内存中的数据时,多个线程同时执行,可能会出现同时修改同一数据情况。就可能产生线程安全的问题。
临界区:多个线程访问共享资源的这段代码,解决线程安全问题主要方法时使用锁,将临界资源加锁,只有获取锁才能执行临界区代码。
阻塞:锁会引起线程阻塞,导致线程即不能继续执行,也不能释放资源。耗尽资源,导致系统崩溃。
解决办法:
i)限流:控制请求数,减少创建的线程数
ii)降级:关闭部分功能,尽快释放线程
iii)避免阻塞:异步IO; 无临界区(Actor模型)
2. 锁
锁原语CAS
CAS(V, E, N), 如果V值等于E,则将V值设为N;若不同,什么都不做。
CAS是一种系统原语,执行必须是连续的,在执行过程中不允许被中断。
Java通过CAS原语在对象头中修改Mark Word实现加锁
偏向锁:一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低取锁的代价(消除数据在无竞争情况下的同步原语);
轻量级锁:当是偏向锁时,被另一个线程所访问,会升级为轻量级锁,其他线程通过自旋(循环)的形式尝试获取锁,不会阻塞;
重量级锁:当时轻量级锁时,另一个线程虽然自旋,但不会一直持续下去,到一定次数时,还没获取到锁,就会进入阻塞,膨胀为重量级锁,会让其他申请的线程进入阻塞,性能降低;
多CPU情况下锁
总线锁:使用处理器的LOCK信号,当一个处理器在内存总线上输出此信号是,其他处理器的请求被阻塞,该处理器独占内存。
缓存锁:只内存区域缓存在cache中,允许缓存一致性机制(会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理回写已被锁定的缓存行数据时,会使缓存行无效)保证操作的原子性。
其他几种锁的介绍
是否按申请顺序获取锁:i) 公平锁;ii) 非公平锁
可重入锁:某个线程已经获得某个锁,可再次获取锁而不会出现死锁;
是否可被多个线程持有:i) 独享锁/互斥锁:一次只能被一个线程所持有;ii) 共享锁:可被多个线程所持有;iii) 读写锁:多个读线程之间并不互斥,而写程序要求与任何线程互斥;
认为并发操作是,数据会不会发生修改:i)乐观锁:同一个数据并发操作,是不会发生修改的,在更新数据时,检查是否已经被修改过,如果修改过,就放弃 ; ii)悲观锁:认为对同一个数据并发操作时,一定是会发生修改的。哪怕没修改,也会认为修改。采用加锁的形式,不加锁的并发操作一定会出问题。
分段锁:细化锁的粒度,就仅针对数组的一段进行加锁操作
自旋锁:尝试获取锁不会立即阻塞,而是采用循环方式去尝试获取锁,好处:减少线程上下文切换的消耗,缺点:循环会消耗CPU;
3. 文件系统和硬盘I/O
数据库文件系统的数据结构
B+树
leaf page 为数据节点, 其他都为Index page;
query:按照Index page搜索数据;
insert: 分三种情况, i) leaf page 没满, Index page没满, 直接插入;ii) leaf page 满, Index page没满,拆分leaf;iii) leaf page 满, Index page满,拆分leaf, Index;
delete: 分三种情况, i) leaf fill factor 没满50%, Index fill factor 没满50%, 直接删除;ii) eaf fill factor 满50%, Index fill factor 没满50%,,合并leaf;iii) eaf fill factor 满50%, Index fill factor 满50%,,合并leaf, Index;
LSM树
基于日志+跳表的数据结构,WAL(write ahead log) +skiplist +分层有序表sstable。优化了写的性能,同时兼顾查询性能
文件控制块
文件系统将硬盘以块为单位进行划分,每个文件占若干个块,通过一个文件控制块FCB记录每个文件占据的数据库块。
Linux Inode文件控制块:
i) inode记录文件权限、所有者、修改时间,文件大小等属性信息,以及文件数据块硬盘地址索引
ii) inode是固定结构,能记录的硬盘地址索引数是固定15个索引
iii) 可存储 12 + 256 + 256 * 256 + 256 * 256 * 256个数据块, 每个块 4k, 也就是单个文件最大不超过70G
RAID磁盘阵列
通过分片在在多个磁盘上,提供存储和读取的吞吐量和访问速度;通过循环冗余校验,提高数据可靠性,安全性,容错能力。
分布式文件系统 HDFS
HDFS采用Master/Slave的架构来存储数据,这种架构主要有四部分组成。
i) Client: 文件切分,上次HDFS时,将文件分成一个一个Block, 进行存储;与NameNode交互,获取文件位置信息;与DataNode交互,读取或写入数据;提供一些命令来管理HDFS; 通过一些命令来访问HDFS;
ii) NameNode: 是master,是一个主管,管理者。管理HDFS的名称空间;管理数据块Block映射信息;配置副本策略;处理客户端读写请求;
iii) DataNode: 是Slave, NameNode下达命令,执行实际的操作。存储实际的数据块;执行数据块的读写操作;
iv) Secondary NameNode: 不是NameNode的热备,NameNode挂掉,并不能马上替换并提供服务。 辅助NameNode, 分担其工作量;定期合并fsimage和fsedits, 并推送给NameNode; 紧急情况下,可辅助恢复NameNode;
版权声明: 本文为 InfoQ 作者【dony.zhang】的原创文章。
原文链接:【http://xie.infoq.cn/article/199d1fe6a1c9d5031cd4b0d98】。文章转载请联系作者。
评论