第19章 操作系统、硬件、网络的优化
本章将介绍操作系统和硬件的性能优化,对于硬件,我们主要讲述CPU、内存、磁盘阵列及固态硬盘。
任何优化,首先都需要有足够的数据支持,对于操作系统下性能数据的收集,这里将不再赘述,请参考前面章节的相关内容。
19.1 基本概念
如下是需要了解的一些基本概念。
(1)什么是进程
进程可以简单地理解为程序加数据,程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。
若干进程都有可能与同一个程序有关系,且每个进程都可以用同步(循序)或异步(平行)的方式独立运行。
用户下达运行程序的命令之后,就会产生进程。
同一程序可以产生多个进程(一对多的关系),以允许同时有多位用户运行同一程序,却不会发生冲突。
进程在运行时,状态会发生改变,如新生、运行、等待、就绪、结束等,各状态的名称也可能会随着操作系统的不同而不同。
(2)什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一个进程可以有许多线程,每条线程并行执行不同的任务。
使用多线程技术(多线程即每一个线程都代表一个进程内的一个独立执行的控制流)的操作系统或计算机架构,
同一个程序的平行线程,可在多CPU主机或网络上真正做到同时运行(在不同的CPU上)。
多线程技术可以让一个进程充分利用多个CPU。
同一进程中的多条线程将会共享该进程中的全部系统资源,如虚拟地址空间、文件描述符和信号处理等。
但同一进程中的多个线程也都有各自的调用栈(callstack)、寄存器环境(registercontext)和线程本地存储(thread localstorage)。
在多核或多CPU上使用多线程程序设计的好处是显而易见的,即提高了程序的执行吞吐率。
(3)什么是内核调度
内核调度将把CPU的运行时间分成许多片,然后安排给各个进程轮流运行,使得所有进程仿佛在同时运行,
内核需要决定运行哪个进程/线程,哪个需要等待,选择要在哪个CPU核上运行线程。
内核运行于一个特殊的CPU态,内核态。
拥有完全的权限访问设备,内核将仲裁对设备的访问,以支持多任务,避免用户进程访问彼此的空间而破坏数据,除非显式被允许访问。
用户程序一般运行在用户态,它们通过系统调用的方式执行一些限制权限的操作,例如I/O操作。
(4)什么是虚拟内存
虚拟内存是计算机系统内存管理的一种技术。
它使得应用程序认为它拥有连续的、可用的内存(一个连续的、完整的地址空间),
而实际上,它通常是被分隔成多个物理内存碎片,还有部分被暂时存储在外部磁盘存储器上,在需要时将会进行数据交换。
与没有使用虚拟内存技术的系统相比,使用这种技术的系统将使得大型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。
对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为“连续的虚拟内存地址”,以此来“欺骗”程序,使它们以为自己正在使用一大块的“连续”的地址。
对于每个进程或内核而言,它们操作大块的虚拟内存,而实际虚拟内存到物理内存的映射,是由我们的虚拟内存管理系统来实现的,
也就是说,虚拟内存给进程或内核提供了一个它们独有的几乎无限内存的视图。
对于操作系统的优化,主要是对一些内核参数及文件系统进行优化。
由于默认的Linux内核参数已经基本够用,因此本书将只关注文件系统的优化。
19.2 文件系统的优化
文件系统是一种向用户提供底层数据访问的机制。它将设备中的空间划分为特定大小的块(扇区),一般每块有512B。
数据存储在这些块中,由文件系统软件来负责将这些块组织为文件和目录,并记录哪些块被分配给了哪个文件,以及哪些块没有被使用。
以下仅针对文件系统和MySQL相关的部分做一些说明。
常用的文件系统有ext3、ext4、XFS等,你可以检查Linux系统的/etc/fstab文件,以确定当前分区使用的是什么文件系统,
ext3即第三代扩展文件系统,是一个日志文件系统,低版本的Linux发行版将会使用到这种文件系统,它存在的一个问题是删除大文件时比较缓慢,这可能会导致严重的I/O问题。
ext4即第四代扩展文件系 统,是ext3文件系统的后继版本。2008年12月25日,Linux 2.6.29版公开发布之后,ext4成为了Linux官方建议的默认的文件系统。
ext4改进了大文件的操作效率,使删除大的数据文件不再可能导致严重的I/O性能问题,一个100多GB的文件,也只需要几秒就可以被删除掉。
文件系统使用缓存来提升读性能,使用缓冲来提升写性能。
在我们调整操作系统和数据库的时候,要注意批量写入数据的冲击,一些系统会缓冲写数据几十秒,然后合并刷新到磁盘中,这将表现为时不时的I/O冲击。
默认情况下,Linux会记录文件最近一次被读取的时间信息,我们可以在挂载文件系统的时候使用noatime来提升性能。
为了保证数据的安全,Linux默认在进行数据提交的时候强制底层设备刷新缓存,
对于能够在断电或发生其他主机故障时保护缓存中数据的设备,应该以nobarrier选项挂载XFS文件系统,
也就是说,如果我们使用带电池的RAID卡,或者使用Flash卡,那么我们可以使用nobarrier选项挂载文件系统,因为带电池的RAID卡和FLASH卡本身就有数据保护的机制。
还有其他的一些挂载参数也会对性能产生影响,你需要衡量调整参数所带来的益处是否值得,笔者一般不建议调整这些挂载参数,它们对性能的提升并不显著。
你可能需要留意文件系统的碎片化,碎片化意味着文件系统上的文件数据块存放得不那么连续,而是以碎片化的方式进行分布,那么顺序I/O将得不到好的性能,会变成多次随机I/O。
所以在某些情况下,使用大数据块和预先分配连续的空间是有道理的,但你也需要知道,文件碎片是一个常态,最开始的表没有什么碎片,
但随着你更新和删除数据,数据表会变得碎片化,这会是一个长期的过程,而且在绝大多数情况下,你会感觉不到表的碎片对于性能的影响,
因此,除非你能够证明表的碎片化已经严重影响了性能,否则不建议进行表的整理,比如运行OPTIMIZE TABLE命令。
Direct I/O允许应用程序在使用文件系统的同时绕过文件系统的缓存。
你可以用Direct I/O执行文件备份,这样做可以避免缓存那些只被读取一次的数据。
如果应用或数据库,已经实现了自己的缓存,那么使用Direct I/O可以避免双重缓存。
许多人期望使用mmap的方式来解决文件系统的性能问题,mmap的方式有助于我们减少一些系统调用,
但是,如果我们碰到的是磁盘I/O瓶颈,那么减少一些系统调用的开销,对于提升整体性能/吞吐的贡献将会很少。因为主要的瓶颈,主要花费的时间是在I/O上。
许多NoSQL的数据库使用了mmap的方式来持久化数据,在I/O写入量大的时候,其性能急剧下降就是这个道理。
一般来说,文件系统缓存,对于MySQL的帮助不大,可以考虑减小文件系统缓存,如vm.dirty_ratio=5。
我们推荐在Linux下使用XFS文件系统,它是一种高性能的日志文件系统,特别擅长处理大文件,对比ext3、ext4,MySQL在XFS上一般会有更好的性能,更高的吞吐。
Red Hat Enterprise Linux 7默认使用XFS文件系统。
Red Hat Enterprise Linux 5、6的内核完整支持XFS,但未包含创建和使用XFS的命令行工具,你需要自行安装。
19.3 内存
我们需要了解CPU、内存、固态硬盘及普通机械硬盘访问速度的差异,
比如内存为几十纳秒(ns),而固态硬盘大概是25μs(25000ns),而机械硬盘大概是6毫秒(6000000ns),
它们差得不是一两个数量级,机械硬盘对比内存差了五六个数量级,所以内存访问比磁盘访问要快得多,
所以总会有许多人想尽办法优化数据的访问,尽量在内存当中来访问数据。
内存往往是影响性能最重要的因素,你应该确保热点数据存储在内存中,较少的内存往往意味着更多的I/O压力。
许多应用一般是有热点数据的,且热点数据并不大,可以保存在内存中。
对于MySQL来说,应将innodb_buffer_pool_size设置得大于我们的热点数据,
否则可能会出现某个MySQL实例InnoDB的缓冲不够大,从而产生过多的物理读,进而导致I/O瓶颈。
数据库服务器应该只部署数据库服务,以免被其他程序影响,有时其他程序也会导致内存压力,如占据大量文件的系统缓存,就会导致可用内存不够。
19.4 CPU
现实世界中,CPU的技术发展得很快,一颗CPU上往往集成了4/6/8个核,由于多核很少会全部利用到,所以一般会在生产机器上部署多实例,以充分利用CPU资源。
还可以更进一步,使用CPU绑定技术将进程或线程绑定到一个CPU或一组CPU上,这样做可以提升CPU缓存的效率,提升CPU访问内存的性能。
对于NUMA架构的系统,也可以提高内存访问的局部性,从而也能提高性能。
CPU利用率衡量的是在某个时间段,CPU忙于执行操作的时间的百分比,但是,许多人不知道的是,CPU利用率高并不一定是在执行操作,而很可能是在等待内存I/O。
CPU执行指令,需要多个步骤,这其中,内存访问是最慢的,可能需要几十个时钟周期来读写内存。所以CPU缓存技术和内存总线技术是非常重要的。
我们对CPU时钟频率这个主要的指标可能有一些误解。如果CPU利用率高,那么更快的CPU不一定能够提升性能。
也就是说,如果CPU的大部分时间是在等待锁、等待内存访问,那么使用更快的CPU不一定能够提高吞吐。
关于容量规划。
对于访问模式比较固定的应用,比如一些传统制造业的生产系统,则比较容易对CPU进行容量规划,
可以按照未来的访问请求或访问客户端数量,确定CPU需要扩容的幅度,
你可以监控当前系统的CPU利用率,估算每个客户端/每个访问请求的CPU消耗,进而估算CPU100%利用率时的吞吐,安排扩容计划。
由于互联网业务,负荷往往变化比较大,多实例有时会导致CPU的容量模型更为复杂,
我们更多地依靠监控的手段提前进行预警,在CPU到达一定利用率,负载到达一定阈值时,进行优化或扩容。
如何选购CPU。
对于企业用户来说,CPU的性能并不是最重要的,最重要的是性价比,新上市的CPU往往价格偏贵,一般来说建议选择上市已经有一定时间的CPU。
而对于大规模采购,你需要衡量不同CPU的价格及测试验证实际业务的吞吐,进而能够得出一个预算成本比较合适的方案,
可能你还需要综合平衡各种其他硬件的成本以确定选购的CPU型号。
19.5 I/O
19.5.1 概述
I/O往往是数据库应用最需要关注的资源。作为数据库管理人员,你需要做好磁盘I/O的监控,持续优化I/O的性能,以免I/O资源成为整个系统的瓶颈。
本节将讲述一些硬件维护人员需要了解的磁盘硬件知识,并对其的规划和调整做一些介绍。
一些基础概念的介绍如下:
逻辑I/O:可以理解为是应用发送给文件系统的I/O指令。
物理I/O:可以理解为是文件系统发送给磁盘设备的I/O指令。
磁盘IOPS:每秒的输入输出量(或读写次数),是衡量磁盘性能的主要指标之一。
IOPS是指单位时间内系统能处理的I/O请求数量,一般以每秒处理的I/O请求数量为单位,I/O请求通常为读或写数据操作的请求。OLTP应用更看重IOPS。
磁盘吞吐:指单位时间内可以成功传输的数据数量。OLAP应用更看重磁盘吞吐。
实践当中,我们要关注的磁盘I/O的基本指标有磁盘利用率、平均等待时间、平均服务时间等。
如果磁盘利用率超过60%,则可能导致性能问题,磁盘利用率往往是大家容易忽视的一个指标,认为磁盘利用率没有达到100%,就可以接受,
其实,磁盘利用率在超过60%的时候,就应该考虑进行优化了。
对于磁盘利用率的监控,在生产中,往往也会犯一个错误,由于监控的粒度太大,比如10分钟、2分钟一次,因此会没有捕捉到磁盘高利用率的场景。
Linux有4种I/O调度算法:CFQ、Deadline、Anticipatory和NOOP,CFQ是默认的I/O调度算法。
在完全随机的访问环境下,CFQ与Deadline、NOOP的性能差异很小,
但是一旦有大的连续I/O,CFQ可能会造成小I/O的响应延时增加,数据库环境可以修改为Deadline算法,表现也将更稳定。
如下命令将实时修改I/O调度算法: echo deadline > /sys/block/sdb/queue/scheduler
如果你需要永久生效,则可以把命令写入/etc/rc.local文件内,或者写入grub.conf文件中。
19.5.2 传统磁盘
传统磁盘本质上是一种机械装置,影响磁盘的关键因素是磁盘服务时间,即磁盘完成一个I/O请求所花费的时间,它由寻道时间、旋转延迟和数据传输时间三部分构成。
一般读取磁盘的时候,步骤如下:
1)寻道:磁头移动到数据所在的磁道。
2)旋转延迟:盘片旋转将请求数据所在的扇区移至读写磁头下方。
3)传输数据。
一般随机读写取决于前两个步骤,而大数据顺序读写更多地取决于第3)个步骤,由于固态硬盘消除了前两个步骤,所以在随机读写上会比传统机械硬盘的IOPS高得多。
优化传统磁盘随机读写,也就是优化寻道时间和旋转延迟时间,一些可供考虑的措施有缓存、分离负载到不同的磁盘、硬件优化减少延时及减少震荡等。
比如,操作系统和数据库使用的是不同的盘,我们需要了解读写比率,如果大部分是读的负载,那么我们加缓存会更有效;
而如果大部分是写的负载,那么我们增加磁盘提高吞吐会更有意义。
对于Web访问,由于本身就可能有几百毫秒的延时,那么100毫秒的磁盘延时也许并不是问题;
而对于数据库应用,对于延时则要求很苛刻,那么我们需要优化或使用延时更小的磁盘。
对于数据库类应用,传统磁盘一般做了RAID,那么RAID卡自身也可能会成为整个系统的瓶颈,也需要考虑优化。
19.5.3 关于RAID
几种常用的RAID类型如下:
RAID0:将两个以上的磁盘串联起来,成为一个大容量的磁盘。
在存放数据时,数据被分散地存储在这些磁盘中,因为读写时都可以并行处理,所以在所有的级别中,RAID0的速度是最快的。
但是RAID0既没有冗余功能,也不具备容错能力,如果一个磁盘(物理)损坏,那么所有的数据都会丢失。
RAID1:RAID1就是镜像,其原理为在主硬盘上存放数据的同时也在镜像硬盘上写一样的数据。当主硬盘(物理)损坏时,镜像硬盘则代替主硬盘工作。
因为有镜像硬盘做数据备份,所以RAID1的数据安全性在所有的RAID级别上来说是最好的。
理论上读取速度等于硬盘数量的倍数,写入速度有微小的降低。
Raid10:指的是RAID1+0,RAID1提供了数据镜像功能,保证数据安全,RAID0把数据分布到各个磁盘,提高了性能。
RAID5:是一种性能、安全和成本兼顾的存储解决方案。
RAID5至少需要3块硬盘,RAID5不是对存储的数据进行备份,而是把数据和相对应的奇偶校验信息存储到组成RAID5的各个磁盘上。
当RAID5的一个磁盘数据被损坏后,可以利用剩下的数据和相应的奇偶校验信息去恢复被损坏的数据。
几种RAID的区别如下:
1)RAID10理论上可以提供比RAID5更好的读写性能因为它不需要进行奇偶性校验。
RAID5具有和RAID0相近似的数据读取速度,只是因为多了一个奇偶校验信息,写入数据的速度相对单独写入一块硬盘的速度略慢。
2)RAID10提供了更高的安全性。RAID5只能坏一块盘,RAID10视情况而定,最多可以坏一半数量的硬盘。
3)RAID5成本更低,也就是说空间利用率更高。RAID5可以理解为是RAID0和RAID1的折中方案。
RAID5可以为系统提供数据安全保障,但保障程度要比镜像低而磁盘空间利用率要比镜像高,存储成本相对较便宜。
以上的区别是一些理论上的说明,实际情况可能还会因为算法、缓存的设计而不同。
我们是使用多个RAID,还是使用一个大RAID,将取决于我们是否有足够多的磁盘。
如果我们有许多盘,比如超过10多块盘,那么我们使用多个阵列,是可取的;
而如果你只有几块盘,比如6块盘,那么单独使用两块盘来做一个RAID1用于存放操作系统,就不太可取了。
RAID卡有两种写入策略:Write Through和Write Back。
Write Through:将数据同步写入缓存(若有Cache的情况)和后端的物理磁盘。
Write Back:将数据写入缓存,然后再批量刷新到后端的物理磁盘。
一般情况下,对于带电池模块的RAID卡,我们将写入策略设置为Write Back。写缓存可以大大提高I/O性能,但由于掉电会丢失数据,所以需要用带电池的RAID卡。
如果电池模块异常,那么为了数据安全,会自动将写入策略切换为Write Through,由于无法利用缓存写操作,因此写入性能会大大降低。
一般的RAID卡电池模块仅仅保证在服务器掉电的情况下,Cache中的数据不会丢失,在电池模块电量耗尽前需要启动服务器让缓存中的数据写盘。
如果我们碰到I/O瓶颈,我们需要更强劲的存储。普通的PC服务器加传统磁盘RAID(一般是RAID1+0)加带电池的RAID卡,是一种常见的方案。
在RAID的设置中,我们需要关闭预读,磁盘的缓存也需要被关闭。同样的,你需要关闭或减少操作系统的预读。
19.5.4 关于SSD
SSD也称为固态硬盘,目前SSD设备主要分为两类,基于PCI-E的SSD和普通SATA接口的SSD。
PCE-E SSD卡性能高得多,可以达到几十万IOPS,容量可以达到几个TB以上,常用的品牌有Fusion-io,而普通的SSD虽然才几千IOPS,但性价比更好,常用的品牌有Intel等。
PCI-E相对来说稳定性、可靠性都更好,由于I/O资源的极大富余,可以大大节省机架。
普通SSD,基于容量和安全的考虑,许多公司仍然使用了RAID技术,随着SSD容量的增大和可靠性的提升,RAID技术不再显得重要甚至不再被使用。
由于许多公司的SSD的I/O资源往往运行不饱和,因此SSD的稳定、性能一致、安全、寿命显得更重要,而性能可能不是最需要考虑的因素。
依据笔者的使用经验,许多SSD的设备故障,其原因并不在于SSD设备本身,而在于SSD设备和传统电器组件之间的连接出现了问题,
主机搭载传统机械硬盘的技术已经非常成熟,而在主机上搭载SSD,仍然需要时间来提高其可靠性。
所以我们在选购主机的时候,SSD在其上运行的可靠性也是一个要考虑的因素。
我们对于磁盘RAID也应该加以监控,防止因为磁盘RAID异常而导致数据文件损毁。
传统的机械硬盘,瓶颈往往在于I/O,而在使用了固态硬盘之后,整个系统的瓶颈开始转向CPU,甚至在极端情况下,还会出现网络瓶颈。
由于固态硬盘的性能比较优越,DBA不再像以前那样需要经常进行I/O优化,可以把更多的时间和精力放在业务逻辑的设计上,
固态硬盘的成本降低了,也可以节省内存的使用,热点数据不一定需要常驻内存,即使有时需要从磁盘上访问,也能够满足响应的需求了。
传统的I/O隔离和虚拟化难度较高,很重要的原因是I/O资源本身就比较紧缺,本身就紧缺的资源,难以进行分割和共享,而高性能的PCI-E SSD卡使得虚拟化更可能落地。
传统的文件系统已经针对传统的机械磁盘阵列有许多优化,所以想在其上再做一些软件层的优化和算法设计,很可能会费力不讨好,
但是如果是SSD设备,则另当别论,用好了SSD设备,可能可以大大减少SSD设备的故障率,充分利用它的潜能,
随着固态硬盘大规模的使用,未来将需要在文件系统和数据库引擎上都做出相应的优化,以减少使用SSD的成本。
19.6 网络
对于数据库应用来说,网络一般不会成为瓶颈,CPU和I/O更容易成为瓶颈。
网络的瓶颈一般表现为流量超过物理极限,如果超过了单块网卡的物理极限,那么你可以考虑使用网卡绑定的技术增加网络带宽,同时也能提高可用性,
如果是超过了运营商的限制,那么你需要快速定位流量大的业务,以减少流量,而请运营商调整带宽在短时间内可能难以完成。
网络瓶颈也可能因为网络包的处理而导致CPU瓶颈。
交换机和路由器通过微处理器处理网络包,它们也可能会成为瓶颈,
对于主机来说,如果对于网络包的处理没有一个CPU负载均衡策略,那么网卡流量只能被一个CPU处理,CPU也可能会成为瓶颈。
网络端口,也可能会成为瓶颈所在,不过这种情况很少见,即使是有大量短连接的场合。
首先你需要优化连接,减少短连接,或者使用连接池,如果实在优化不下去了,可以考虑修改系统的内核参数net/ipv4/ip_local_port_range,调整随机端口范围,
或者减少net/ipv4/tcp_fin_timeout的值,或者使用多个逻辑IP扩展端口的使用范围。
在进行网络优化之前,我们需要清楚自己的网络架构,了解你应用的网络数据流路径,
比如是否经过了DNS服务器,你需要使用网络监控工具比如Cacti监控流量,在超过一定阈值或有丢包的情况下及时预警。
你需要意识到,跨IDC的网络完全不能和IDC内网的质量相比,且速度也可能会成为问题,
跨IDC复制,其实本质上是为了安全,是为了在其他机房中有一份数据,而不是为了实时同步,也不能要求必须是实时同步。
你需要确保应用程序能够处理网络异常,如果两个节点间距离3000英里 [1],光速是186000英里/秒,那么单程需要16毫秒,来回就需要32毫秒,
然后节点之间还有各种设备(路由器、交换机、中继器),它们都可能影响到网络质量。
所以,如果你不得不进行跨IDC的数据库同步,或者让应用程序远程访问数据库,
那么你需要确保你的应用程序能够处理网络异常,你需要确认由于跨IDC网络异常导致的复制延时不致影响到业务。
由于网络异常,Web服务器可能连接数暴涨、挂死、启动缓慢(由于需要初始化连接池),这些都是潜在的风险,你需要小心处理。
当有新的连接进来时,MySQL主线程需要花一些时间(尽管很少)来检查连接并启动一个新的线程,
MySQL有一个参数back_log来指定在停止响应新请求前在短时间内可以堆起多少请求,
你可以将其理解为一个新连接的请求队列,如果你需要在短时间内允许大量连接,则可以增加该数值。
Linux操作系统也有类似的参数 net.core.somaxconn、tcp_max_syn_backlog,你可能需要增大它们。
小结:
本章介绍了文件系统及硬件的一些知识。
读者平时应该关注资源的使用情况,并跟踪硬件的发展。
通过对操作系统的观察,如资源的使用情况和报错日志,在某些情况下更容易发现程序的性能问题。
本书介绍的许多知识都只是“泛泛而谈”,笔者自己也很少对操作系统或硬件进行调优,读者如果有兴趣深入研究操作系统和硬件,那么建议多阅读相关领域的专门著作。
[1] 1英里=1.609千米。