文章目录
- 9.1 什么限制了MySQL的性能
- 9.2 如何为 MySQL 选择 CPU
- 9.2.1 哪个更好:更快的 CPU 还是更多的 CPU
- 9.2.2 CPU架构
- 9.2.3 扩展到多个CPU和核心
- 9.3 平衡内存和磁盘资源
- 9.3.1 随机 I/O 和顺序 I/O
- 9.3.2 缓存,读和写
- 9.3.3 工作集是什么
- 9.3.4 找到有效的内存/磁盘比例
- 9.3.5 选择硬盘
- 9.4 固态存储
- 9.4.3 闪存的基准测试
- 9.4.4 固态硬盘驱动器(SSD)
- 用SSD做RAID
- 9.4.5 PCIe存储设备
- 9.4.6 其他类型的固态存储
- 9.4.7 什么时候应该使用闪存
- 9.4.8 使用Flashcache
- 9.4.9 优化固态存储上的 MySQL
- 9.5 为备库选择硬件
- 9.6 RAID性能优化
- 9.6.1 RAID的故障转移、恢复和镜像
- 9.6.2 平衡硬件 RAID 和软件 RAID
- 9.6.3 RAID 配置和缓存
- RAID 条带块大小
- RAID 缓存
- 9.7 SAN 和 NAS
- 9.7.1 SAN基准测试
- 9.7.2 使用基于 NFS 或 SMB 的 SAN
- 9.7.3 MySQL 在 SAN 上的性能
- 9.7.4 应该用SAN吗
- 9.8 使用多磁盘卷
- 9.9 网络配置
- 9.10 选择操作系统
- 9.11 选择文件系统
- 9.12 选择磁盘队列调度策略
- 9.13 线程
- 9.14 内存交换区
- 9.15 操作系统状态
- 9.15.1 如何阅读 `vmstat` 的输出
- 9.15.2 如何阅读 `iostat` 的输出
- 9.15.3 其他有用的工具
- 9.15.4 CPU 密集型的机器
- 9.15.5 I/O 密集型的机器
- 9.15.6 发生内存交换的机器
- 9.15.7 空闲的机器
- 9.16 总结
MySQL 服务器性能受制于整个系统最薄弱的环节,承载它的操作系统和硬件往往是限制因素。磁盘大小、可用内存和 CPU 资源、网络,以及所有连接它们的组件,都会限制系统的最终容量。因此,需要小心地选择硬件,并对硬件和操作系统进行合适的配置。例如,若工作负载是 I/O 密集型的,一种方法是设计应用程序使得最大限度地减少 MySQL 的 I/O 操作。然而,更聪明的方式通常是升级 I/O 子系统,安装更多的内存,或重新配置现有的磁盘。
硬件的更新换代非常迅速,所以本章有关特定产品或组件的内容可能将很快变得过时。像往常一样,我们的目标是帮助提升对这些概念的理解,这样对于即使没有直接覆盖到的知识也可以举一反三。这里我们将通过现有的硬件来阐明我们的观点。
9.1 什么限制了MySQL的性能
许多不同的硬件都可以影响 MySQL 的性能,但我们认为最常见的两个瓶颈是 CPU 和 I/O 资源。当数据可以放在内存中或者可以从磁盘中以足够快的速度读取时,CPU 可能出现瓶颈。把大量的数据集完全放到大容量的内存中,以现在的硬件条件完全是可行的。
另一方面,I/O 瓶颈,一般发生在工作所需的数据远远超过有效内存容量的时候。如果应用程序是分布在网络上的,或者如果有大量的查询和低延迟的要求,瓶颈可能转移到网络上,而不再是磁盘 I/O。
第3章中提及的技巧可以帮助找到系统的限制因素,但即使你认为已经找到了瓶颈,也应该透过表象去看更深层次的问题。某一方面的缺陷常常会将压力施加在另一个子系统,导致这个子系统出问题。例如,若没有足够的内存,MySQL 可能必须刷出缓存来腾出空间给需要的数据——然后,过了一小会,再读回刚刚刷新的数据(读取和写入操作都可能发生这个问题)。本来是内存不足,却导致出现了 I/O 容量不足。当找到一个限制系统性能的因素时,应该问问自己,“是这个部分本身的问题,还是系统中其他不合理的压力转移到这里所导致的?”在第3章的诊断案例中也有讨论到这个问题。
还有另外一个例子:内存总线的瓶颈也可能表现为 CPU 问题。事实上,我们说一个应用程序有“ CPU 瓶颈”或者是“ CPU 密集型”,真正的意思应该是计算的瓶颈。接下来将深入探讨这个问题。
9.2 如何为 MySQL 选择 CPU
在升级当前硬件或购买新的硬件时,应该考虑下工作负载是不是 CPU 密集型。
可以通过检查 CPU 利用率来判断是否是 CPU 密集型的工作负载,但是仅看 CPU 整体的负载是不合理的,还需要看看 CPU 使用率和大多数重要的查询的 I/O 之间的平衡,并注意 CPU 负载是否分配均匀。本章稍后讨论的工具可以用来弄清楚是什么限制了服务器的性能。
9.2.1 哪个更好:更快的 CPU 还是更多的 CPU
当遇到 CPU 密集型的工作时,MySQL 通常可以从更快的 CPU 中获益(相对更多的CPU)。
但这不是绝对的,因为还依赖于负载情况和 CPU 数量。更古老的 MySQL 版本在多 CPU 上有扩展性问题,即使新版本也不能对单个查询并发利用多个 CPU。 因此,CPU 速度限制了每个 CPU 密集型查询的响应时间。
当我们讨论 CPU 的时候,为保证本文易于阅读,对某些术语将不会做严格的定义。现在一般的服务器通常都有多个插槽( Socket), 每个插槽上都可以插一个有多个核心的 CPU( 有独立的执行单元),并且每个核心可能有多个“硬件线程”。这些复杂的架构需要有点耐心去了解,并且我们不会总是明确地区分它们。不过,在一般情况下,当谈到 CPU 速度的时候,谈论的其实是执行单元的速度,当提到的 CPU 数量时,指的通常是在操作系统上看到的数量,尽管这可能是独立的执行单元数量的多倍(超线程技术)。
这几年 CPU 在各个方面都有了很大的提升。例如,今天的 Intel CPU 速度远远超过前几代,这得益于像直接内存连接( directly attached memory) 技术以及 PCIe 卡之类的设备互联上的改善等。这些改进对于存储设备尤其有效,例如 Fusion-io 和 Virident 的 PCIe 闪存驱动器。
多和快哪个更重要?一般来说两个都想要。从广义上来说,调优服务器可能有如下两个目标:
-
低延时(快速响应)
要做到这一点,需要高速 CPU, 因为每个查询只能使用一个 CPU。
-
高吞吐
如果能同时运行很多查询语句,则可以从多个 CPU 处理查询中受益。然而,在实践中,还要取决于具体情况。因为 MySQL 还不能在多个 CPU 中完美地扩展,能用多少个 CPU 还是有极限的。在旧版本的 MySQL 中( MySQL 5.1 以后的版本已经有一些提升),这个限制非常严重。在新的版本中,则可以放心地扩展到 16 或 24 个 CPU, 或者更多,取决于使用的是哪个版本( Percona 往往在这方面略占优势)。
如果有多路 CPU, 并且没有并发执行查询语句,MySQL 依然可以利用额外的 CPU 为后台任务(例如清理 InnoDB 缓冲、网络操作,等等)服务。然而,这些任务通常比执行查询语句更加轻量化。
MySQL 复制也能在高速 CPU 下工作得非常好,而多 CPU 对复制的帮助却不大(因为 MySQL 复制是单线程的)。如果工作负载是 CPU 密集型,主库上的并发任务传递到备库以后会被简化为串行任务,这样即使备库硬件比主库好,也可能无法保持跟主库之间的同步。也就是说,备库的瓶颈通常是 I/O 子系统,而不是 CPU。
如果有一个 CPU 密集型的工作负载,考虑是需要更快的 CPU 还是更多 CPU 的另外一个因素是查询语句实际在做什么。在硬件层面,一个查询可以在执行或等待。处于等待状态常见的原因是在运行队列中等待(进程已经是可运行状态,但所有的 CPU 都忙)、等待闩锁( Latch) 或锁( Lock)、 等待磁盘或网络。那么你期望查询是等待什么呢?如果等待闩锁或锁,通常需要更快的 CPU; 如果在运行队列中等待,那么更多或者更快的 CPU 都可能有帮助。(也可能有例外,例如,查询等待 InnoDB 日志缓冲区的 Mutex, 直到 I/O 完成前都不会释放——这可能表明需要更多的 I/O 容量)。
通常可以通过堆栈跟踪来诊断这些类型的竞争问题,例如 Percona Toolkit 中的 pt-pmp 工具。如果遇到这样的问题,可能需要改变服务器的配置,禁用或改变引起问题的组件,进行数据分片(Sharding),或者通过某种方式改变做事的方法。这里无法列举所有的问题和相应的解决方案,但是一旦有一个确定的诊断,答案通常是显而易见的。大部分不幸遇到的问题都是边缘场景,最常见的问题随着时间的推移都在服务器上被修复了。
9.2.2 CPU架构
可能 99% 以上的 MySQL 实例(不含嵌入式使用)都运行在 Intel 或者AMD 芯片的 x86 架构下。本书中我们基本都是针对这种情况。64 位架构现在都是默认的了,32 位 CPU 已经很难买到了。
9.2.3 扩展到多个CPU和核心
实际上有两种类型的数据库并发问题,需要不同的方法来解决,如下所示。
-
逻辑并发问题
应用程序可以看到资源的竞争,如表或行锁争用。这些问题通常需要好的策略来解决,如改变应用程序、使用不同的存储引擎、改变服务器的配置,或使用不同的锁定提示或事务隔离级别。
-
内部并发问题
比如信号量、访问 InnoDB 缓冲池页面的资源争用,等等。可以尝试通过改变服务器的设置、改变操作系统,或使用不同的硬件解决这些问题,但通常只能缓解而无法彻底消灭。在某些情况下,使用不同的存储引擎或给存储引擎打补丁,可以帮助缓解这些问题。
MySQL 的“扩展模式”是指它可以有效利用的 CPU 数量,以及在压力不断增长的情况下如何扩展,这同时取决于工作负载和系统架构。通过“系统架构”的手段是指通过调整操作系统和硬件,而不是通过优化使用 MySQL 的应用程序。CPU 架构( RISC、CISC、 流水线深度等)、 CPU 型号和操作系统都影响 MySQL 的扩展模式。这也是为什么说基准测试是非常重要的:一些系统可以在不断增加的并发下依然运行得很好,而另一些的表现则糟糕得多。
不要盲目地为系统增加 CPU 资源,或者说应为系统增加有效的 CPU 资源。有些系统在更多的处理器下甚至可能降低整体性能,比如,在 MySQL 5.0 时代,Google 的补丁和 Percona Server 出现之前,能有效利用的 CPU 核数是 4 核,但是现在甚至可以看到操作系统报告多达 80 个 “CPU” 的服务器。如果规划一个大的升级,必须要同时考虑硬件、服务器版本和工作负载。
某些 MySQL 扩展性瓶颈在服务器层,而其他一些在存储引擎层。存储引擎是怎么设计的至关重要,有时更换到一个不同的引擎就可以从多处理器上获得更多效果。
多个内核和多个物理CPU哪个更好,这是由硬件体系结构决定的。现代CPU的另外两个复杂之处也值得提一下。首先是频率调整。这是一种电源管理技术,可以根据CPU上的压力而动态地改变CPU的时钟速度。问题是,它有时不能很好地处理间歇性突发的短查询的情况,因为操作系统可能需要一段时间来决定CPU的时钟是否应该变化。结果,查询可能会有一段时间速度较慢,并且响应时间增加了。频率调整可能使间歇性的工作负载性能低下,但可能更重要的是,它会导致性能波动。第二个复杂之处是 boost 技术。当使用单核时可能获得超频效果,而此时若想充分利用其他核心执行其他任务,会降低当前任务的性能,比如 MySQL 复制。
9.3 平衡内存和磁盘资源
配置大量内存最大的原因是避免磁盘 I/O,因为磁盘 I/O 比在内存中访问数据要慢得多。关键是要平衡内存和磁盘的大小、速度、成本和其他因素,以便为工作负载提供高性能的表现。在讨论如何做到这一点之前,暂时先回到基础知识上来。
计算机包含一个金字塔型的缓存体系,更小、更快、更昂贵的缓存在顶端,如图 9-1 所示。
在这个高速缓存层次中,最好是利用各级缓存来存放“热点”数据,以获得更快的访问速度,通常使用一些启发式的方法,例如,“最近被使用的数据可能很快再次被使用”以及“相邻的数据可能很快需要使用”,这些算法非常有效,因为它们参考了空间和时间的局部性原理。
从程序员的视角来看,CPU 寄存器和高速缓存是透明的,并且与硬件架构相关。管理它们是编译器和 CPU 的工作。然而,程序员会有意识地注意到内存和硬盘的不同,并且在程序中通常区分使用它们。
在数据库服务器上尤其明显,其行为往往非常符合我们刚才提到的预测算法所做的预测。设计良好的数据库缓存(如 InnoDB 缓冲池),其效率通常超过操作系统的缓存,因为操作系统缓存是为通用任务设计的。数据库缓存更了解数据库存取数据的需求,它包含特殊用途的逻辑(例如写入顺序)以帮助满足这些需求。此外,系统调用不需要访问数据库中的缓存数据。
这些专用的缓存需求就是为什么必须平衡缓存层次结构以适应数据库服务器特定的访问模式的原因。因为寄存器和芯片上的高速缓存不是用户可配置的,内存和存储是唯一可以改变的东西。
9.3.1 随机 I/O 和顺序 I/O
数据库服务器同时使用顺序和随机I/O,随机I/O从缓存中受益最多。想像有一个典型的混合工作负载,均衡地包含单行查找与多行范围扫描,可以说服自己相信这个说法。典型的情况是“热点”数据随机分布。因此,缓存这些数据将有助于避免昂贵的磁盘寻道。相反,顺序读取一般只需要扫描一次数据,所以缓存对它是没用的,除非能完全放在内存中缓存起来。
顺序读取不能从缓存中受益的另一个原因是它们比随机读快。这有以下两个原因:
-
顺序 I/O 比随机 I/O 快。
(单线程时)顺序操作的执行速度比随机操作快,无论是在内存还是磁盘上。(SSD 多线程随机 I/O 与顺序 I/O 速度几乎相同)假设磁盘每秒可以做 100 个随机 I/O 操作,并且可以完成每秒 50MB 的顺序读取(这大概是消费级磁盘现在能达到的水平)。如果每行 100 字节,随机读每秒可以读 100 行,相比之下顺序读可以每秒读 500000 行——是随机读的 5000 倍,或几个数量级的差异。因此,在这种情况下随机 I/O 可以从缓存中获得很多好处。
顺序访问内存行的速度也快于随机访问。现在的内存芯片通常每秒可以随机访问约 250000 次 100 字节的行,或者每秒 500 万次的顺序访问。请注意,内存随机访问速度比磁盘随机访问快了 2 500 倍,而内存中顺序访问只有磁盘 10 倍的速度。
-
存储引擎执行顺序读比随机读快。
一个随机读一般意味着存储引擎必须执行索引操作。(这个规则也有例外,但对 InnoDB 和 MyISAM 都是对的)。通常需要通过B树的数据结构查找,并且和其他值比较。相反,连续读取一般需要遍历一个简单的数据结构,例如链表。这样就少了很多工作,反复这样操作,连续读取的速度就比随机读取要快了。
最后,随机读取通常只要查找特定的行,但不仅仅只读取一行——而是要读取一整页的数据,其中大部分是不需要的。这浪费了很多工作。另一方面,顺序读取数据,通常发生在想要的页面上的所有行,所以更符合成本效益。
综上所述,通过缓存顺序读取可以节省一些工作,但缓存随机读取可以节省更多的工作。换句话说,如果能负担得起,增加内存是解决随机 I/O 读取问题最好的办法。
9.3.2 缓存,读和写
如果有足够的内存,就完全可以避免磁盘读取请求。如果所有的数据文件都可以放在内存中,一旦服务器缓存“热”起来了,所有的读操作都会在缓存命中。虽然还是会有逻辑读取,不过物理读取就没有了。但写入是不同的问题。写入可以像读一样在内存中完成,但迟早要被写入到磁盘,所以它是需要持久化的。换句话说,缓存可延缓写入,但不能像消除读取一样消除写入。
事实上,除了允许写入被延迟,缓存可以允许它们被集中操作,主要通以下两个重要途径:
-
多次写入,一次刷新
一片数据可以在内存中改变很多次,而不需要把所有的新值写到磁盘。当数据最终被刷新到磁盘后,最后一次物理写之前发生的修改都被持久化了。例如,许多语句可以更新内存中的计数器。如果计数器递增 100 次,然后写入到磁盘,100 次修改就被合并为一次写。例如,Change Buffer,仅适用于次级索引。
-
I/O 合并
许多不同部分的数据可以在内存中修改,并且这些修改可以合并在一起,通过一次磁盘操作完成物理写入。
这就是为什么许多交易系统使用预写日志( WAL) 策略。预写日志采用在内存中变更页面,而不马上刷新到磁盘上的策略,因为刷新磁盘通常需要随机 I/O, 这非常慢。相反,如果把变化的记录写到一个连续的日志文件,这就很快了。后台线程可以稍后把修改的页面刷新到磁盘;并在刷新过程中优化写操作。简单来说,WAL 就是把随机 I/O 转换为顺序 I/O 并延迟写入磁盘。
写入从缓冲中大大受益,因为它把随机 I/O 更多地转换到连续 I/O。 异步(缓冲)写通常是由操作系统批量处理,使它们能以更优化的方式刷新到磁盘。同步(无缓冲)写必须在写入到磁盘之后才能完成。这就是为什么它们受益于 RAID 控制器中电池供电的回写( Write-Back) 高速缓存(我们稍后讨论 RAID)。
9.3.3 工作集是什么
每个应用程序都有一个数据的“工作集”——就是做这个工作确实需要用到的数据。很多数据库都有大量不在工作集内的数据。
工作集大小的不同取决于应用程序。对于某些应用程序,工作集可能是总数据大小的1%,而对于其他应用,也可能接近100%。当工作集不能全放在内存中时,数据库服务器必须在磁盘和内存之间交换数据,以完成工作。这就是为什么内存不足可能看起来却像 I/O 问题。有时没有办法把整个工作集的数据放在内存中,并且有时也并不真的想这么做(例如,若应用需要大量的顺序I/O)。工作集能否完全放在内存中,对应用程序体系结构的设计会产生很大的影响。
工作集可以定义为基于时间的百分比。例如,一小时的工作集可能是一个小时内数据库使用的 95% 的页面,除了 5% 的最不常用的页面。百分比是考虑这个问题最有用的方式,因为每小时可能需要访问的数据只有 1%, 但超过 24 小时,需要访问的数据可能会增加到整个数据库中 20% 的不同页面。根据需要被缓存起来的数据量多少,来思考工作集会更加直观,缓存的数据越多,工作负载就越可能成为 CPU 密集型。如果不能缓存足够的数据,工作集就不能完全放在内存中。
应该依据最常用的页面集来考虑工作集,而不是最频繁读写的页面集。这意味着,确定工作集需要在应用程序内有测量的模块,而不能仅仅看外部资源的利用,例如 I/O 访问,因为页面的 I/O 操作跟逻辑访问页面不是同一回事。例如,MySQL 可能把一个页面读入内存,然后访问它数百万次,但如果查看 strace
, 只会看到一个 I/O 操作。缺乏确定工作集所需的检测模块,最大的原因是没有对这个主题有较多的研究。
工作集包括数据和索引,所以应该采用缓存单位来计数。一个缓存单位是存储引擎工作的数据最小单位。
不同存储引擎的缓存单位大小是不一样的,因此也使得工作集的大小不一样。例如, InnoDB 在默认情况下是 16 KB 的页。如果 InnoDB 做一个单行查找需要读取磁盘,就需要把包含该行的整个页面读入缓冲池进行缓存,这会引起一些缓存的浪费。假设要随机访问 100 字节的行。InnoDB 将用掉缓冲池中很多额外的内存来缓存这些行,因为每一行都必须读取和缓存一个完整的 16KB 页面。因为工作集也包括索引,InnoDB 也会读取并缓存查找行所需的索引树的一部分。InnoDB 的索引页大小也是 16 KB, 这意味着访问一个 100 字节的行可能一共要使用 32 KB 的缓存空间(有可能更多,这取决于索引树有多深)。因此,缓存单位也是在 InnoDB 中精心挑选聚集索引非常重要的另一个原因。聚集索引不仅可以优化磁盘访问,还可以帮助在同一页面存储相关的数据,因此在缓存中可以尽量放下整个工作集。
9.3.4 找到有效的内存/磁盘比例
找到一个良好的内存/磁盘比例最好的方式是通过试验和基准测试。如果可以把所有东西放入内存,你就大功告成了——后面没有必要再为此考虑什么。(就成了内存数据库了)但大多数的时候不可能这么做,所以需要用数据的一个子集来做基准测试,看看将会发生什么。测试的目标是一个可接受的缓存命中率。缓存未命中是当有查询请求数据时,数据不能在内存中命中,服务器需要从磁盘获取数据。
缓存命中率实际上也会决定使用了多少CPU,所以评估缓存命中率的最好方法是查看CPU使用率。例如,若CPU使用了99%的时间工作,用了1%的时间等待I/O,那缓存命中率还是不错的。
让我们考虑下工作集是如何影响高速缓存命中率的。首先重要的一点,要认识到工作集不仅是一个单一的数字而是一个统计分布,并且缓存命中率是非线性分布的。例如,有 10 GB 内存,并且缓存未命中率为 10% ,你可能会认为只需要增加 11% 以上的内存((10 / 0.9 - 10)/10 * 100%
),就可以降低缓存的未命中率到 0 。但实际上,诸如缓存单位的大小之类的问题会导致缓存效率低下,可能意味着理论上需要 50 GB 的内存,才能把未命中率降到 1% 。即使与一个完美的缓存单位相匹配,理论预测也可能是错误的:例如数据访问模式的因素也可能让事情更复杂。解决 1% 的缓存未命中率甚至可能需要 500 GB 的内存,这取决于具体的工作负载!
你应该争取一个可接受的缓存命中率,而不是将缓存未命中率降低到零。没有一个应该作为目标的数字,因为“可以接受”怎么定义,取决于应用程序和工作负载。有些应用程序有 1% 的缓存未命中都可以工作得非常好,而另一些应用实际上需要这个比例低到 0.01% 才能良好运转。(“良好的缓存未命中率”是个模糊的概念,其实有很多方法来进一步计算未命中率。)
最好的内存/磁盘的比例还取决于系统上的其他组件。假设有 16 GB 的内存、 20 GB 的数据,以及大量未使用的磁盘空间系统。该系统在 80% 的 CPU 利用率下运行得很好。如果想在这个系统上放置两倍多的数据,并保持相同的性能水平,你可能会认为只需要让 CPU 数量和内存量也增加到两倍。然而,即使系统中的每个组件都按照增加的负载扩展相同的量(一个不切实际的假设),这依然可能会使得系统无法正常工作。有 20GB 数据的系统可能使用了某些组件超过 50% 的容量——例如,它可能已经用掉了每秒 I/O 最大操作数的 80%。 并且在系统内排队也是非线性的。服务器将无法处理两倍的负载。因此,最好的内存/磁盘比例取决于系统中最薄弱的组件。
9.3.5 选择硬盘
作为一个简要回顾,从传统磁盘读取数据的过程分为三个步骤:
- 移动读取磁头到磁盘表面上的正确位置。
- 等待磁盘旋转,所有所需的数据在读取磁头下。
- 等待磁盘旋转过去,所有所需的数据都被读取磁头读出。
磁盘执行这些操作有多快,可以浓缩为两个数字:访问时间(步骤 1 和 2 合并)和传输速度。这两个数字也决定延迟和吞吐量。不管是需要快速访问时间还是快速的传输速度——或混合两者——依赖于正在运行的查询语句的种类。从完成一次磁盘读取所需要的总时间来说,小的随机查找以步骤 1 和 2 为主,而大的顺序读主要是第 3 步。
其他一些因素也可以影响磁盘的选择,哪个重要取决于应用。假设正在为一个在线应用选择磁盘,例如一个受欢迎的新闻网站,有大量小的磁盘随机读取。可能需要考虑下列因素:
-
存储容量
对在线应用来说容量很少成为问题,因为现在的磁盘通常足够大了。如果不够,用 RAID 把小磁盘组合起来是标准做法(6)。注 6:
有趣的是,有些人故意买更大容量的磁盘,然后只使用 20%~30% 的容量。这增加了数据局部性和减少寻道时间,有时可以证明值得它们高的价格。 -
传输速度
现代磁盘通常数据传输速度非常快,正如我们前面看到的。究竟多快主要取决于主轴转速和数据存储在磁盘表面上的密度,再加上主机系统的接口的限制(许多现代磁盘读取数据的速度比接口可以传输的快)。无论如何,传输速度通常不是在线应用的限制因素,因为它们一般会做很多小的随机查找。
-
访问时间
对随机查找的速度而言,这通常是个主要因素,所以应该寻找更快的访问时间的磁盘。
-
主轴转速
现在常见的转速是 7200 RPM、10000 RPM, 以及 15000 RPM。 转速不管对随机查找还是顺序扫描都有很大影响。
-
物理尺寸
所有其他条件都相同的情况下,磁盘的物理尺寸也会带来差别:越小的磁盘,移动读取磁头需要的时间就越短。服务器级的 2.5 英寸磁盘性能往往比它们的更大的盘更快。它们还可以节省电力,并且通常可以融入机箱中。
和 CPU 一样,MySQL 如何扩展到多个磁盘上取决于存储引擎和工作负载。InnoDB 能很好地扩展到多个硬盘驱动器。然而,MyISAM 的表锁限制其写的可扩展性,因此写繁重的工作加在 MyISAM 上,可能无法从多个驱动器中收益。虽然操作系统的文件系统缓冲和后台并发写入会有点帮助,但 MyISAM 相对于 InnoDB 在写可扩展性上有更多的限制。
和 CPU 一样,更多的磁盘也并不总是更好。有些应用要求低延迟需要的是更快的驱动器,而不是更多的驱动器。例如,复制通常在更快的驱动器上表现更好,因为备库的更新是单线程的(5.6 也可以按库做多线程复制)。
9.4 固态存储
固态(闪存)存储器实际上是有 30 年历史的技术,但是它作为新一代驱动器而成为热门则是最近几年的事。固态存储现在越来越便宜,并且也更成熟了,它正在被广泛使用,并且可能会在不久的将来在多种用途上代替传统磁盘。
固态存储设备采用非易失性闪存芯片而不是磁性盘片组成。它们也被称为 NVRAM, 或非易失性随机存取存储器。固态存储设备没有移动部件,这使得它们表现得跟硬盘驱动器有很大的不同。我们将详细探讨其差异。
目前 MySQL 用户感兴趣的技术可分为两大类: SSD( 固态硬盘)和 PCIe 卡。SSD 通过实现 SATA( 串行高级技术附件)接口来模拟标准硬盘,所以可以替代硬盘驱动器,直接插入服务器机箱中的现有插槽。PCIe 卡使用特殊的操作系统驱动程序,把存储设备作为一个块设备输出。PCIe 和 SSD 设备有时可以简单地都认为是 SSD。
现在 SSD 厂商提供的规格有所差异,接口分为 SATA、mSATA、M.2、PCIe、SAS、U.2 等,更多内容参考 https://zhuanlan.zhihu.com/p/431249365 。
下面是闪存性能的快速小结。高质量闪存设备具备:
- 相比硬盘有更好的随机读写性能。闪存设备通常读明显比写要快。
- 相比硬盘有更好的顺序读写性能。但是相比而言不如随机 I/O 的改善那么大,因为硬盘随机 I/O 比顺序 I/O 要慢得多。入门级固态硬盘的顺序读取实际上还可能比传统硬盘慢。
- 相比硬盘能更好地支持并发。闪存设备可以支持更多的并发操作,事实上,只有大量的并发请求才能真正实现最大吞吐量。
- 最重要的事情是提升随机 I/O 和并发性。闪存记忆体可以在高并发下提供很好的随机 I/O 性能,这正是范式化的数据库所需要的。设计非范式化的 Schema 最常见的原因之一是为了避免随机 I/O, 并且使得查询可能转化为顺序 I/O。
因此,我们相信固态存储未来将从根本上改变 RDBMS 技术。当前这一代的 RDBMS 技术几十年来都是为机械磁盘做优化的。同样成熟和深入的研究工作在固态存储上还没有真正出现。(本段内容如今已过时,使用固态存储目前已成为常态,很多震撼性的基准测试结果究其本质主要收益于硬件的进步,而不是软件设计的革新。)
9.4.3 闪存的基准测试
对闪存设备进行基准测试是复杂并且困难的。有很多情况会导致测试错误,需要了解特定设备的知识,并且需要有极大的耐心和关注,才能正确地操作。
闪存设备有一个三阶段模式,我们称为 A-B-C 性能特性。它们开始阶段运行非常快(阶段 A), 然后垃圾回收器开始工作,这将导致在一段时间内,设备处于过渡到稳定状态(阶段 B) 的阶段,最后设备进入一个稳定状态(状态 C)。 所有我们测试过的设备都有这个特点。
当然,我们感兴趣的是阶段C的性能,所以基准测试只需要测量这个部分的运行过程。这意味着基准测试要做的不仅仅是基准测试:还需要先进行一下预热,然后才能进行基准测试。但是,定义预热的终点和基准测试的起点会非常棘手。
设备、文件系统,以及操作系统通过不同方式提供 TRIM
命令的支持,这个命令标记空间准备重用。有时当删除所有文件时设备会被 TRIM
。 如果在基准测试运行的情况下发生,设备将重置到阶段 A, 然后必须重新执行 A 和B 之间的运行阶段。另一个因素是设备被填充得很满或者不满时,不同的性能表现。一个可重复的基准测试必须覆盖到所有这些因素。
通过上述分析,可知基准测试的复杂性,所以就算厂商如实地报告测试结果,但对于外行来说,厂商的基准测试和规格说明书依然可能有很多“坑”。
通常可以从供应商那得到四个数字。这里有一个设备规格的例子:
- 设备读取性能最高达 520 MB/s。
- 设备写入性能最高达 480 MB/s。
- 设备持续写入速度可以稳定在 420 MB/s。
- 设备每秒可以执行 70000 个 4 KB 的写操作。
如果再次复核这些数字,你会发现峰值 4 KB 写入达到 70000 个 IOPS( 每秒输入/输出操作),这么算每秒写入大约只有 274 MB/s, 这比第二点和第三点中说明的高峰写入带宽少了很多。这是因为达到峰值写入带宽时是用更大的块,例如 64 KB 或 128 KB。 用更小的块大小来达到峰值 IOPS。
大部分应用不会写这么大的块。InnoDB 写操作通常是 16 KB 或 512 字节的块组合到一起写回。因此,设备应该只有 274 MB/s 的写出带宽——这是阶段 A 的情况,在垃圾回收器开启和设备达到长期稳定的性能等级前!
在我们的博客中,可以找到目前的 MySQL 基准测试,以及在固态硬盘上裸设备文件 I/O 的工作负载: http://www.ssdperformanceblog.com 和 http://www.mysqlperformanceblog.com 。
9.4.4 固态硬盘驱动器(SSD)
关于 SSD 的好处是,它们有大量的品牌和型号相对是比较便宜的,同时它们比硬盘快了很多。最大的缺点是,它们并不总是像硬盘一样可靠,这取决于品牌和型号。直到最近,大多数设备都没有板载电池,但大多数设备上有一个写缓存来缓冲写入。写入缓存在没有电池备份的情况下并不能持久化,但是在快速增长的写负载下,它不能关闭,否则闪存存储无法承受。所以,如果禁用了驱动器的高速缓存以获得真正持久化的存储,将会更快地耗完设备寿命,在某些情况下,这将导致保修失效。
有些厂家完全不急于告诉购买他们固态硬盘的客户关于 SSD 的特点,并且他们对设备的内部架构等细节守口如瓶。是否有电池或电容保护写缓存的数据安全,在电源故障的情况下,通常是一个悬而未决的问题。在某些情况下,驱动器会接受禁用缓存的命令,但忽略了它。所以,除非做过崩溃试验,否则真的有可能不知道驱动器是否是持久化的,我们对一些驱动器进行了崩溃测试,发现了不同的结果。如今,一些驱动器有电容器保护缓存,使其可以持久化,但一般来说,如果驱动器不是自夸有一个电池或电容,那么它就没有。这意味着在断电的情况下不是持久化的,所以可能出现数据已经损坏却还不知情的情况。SSD 是否配置电容或电池是我们必须关注的特性。
通常,使用 SSD 都是值得的。但底层技术的挑战是不容易解决的。很多厂家做出的驱动器在高负载下很快就崩溃了,或不提供持续一致的性能。一些低端的制造商有一个习惯,每次发布新一代驱动器,就声称他们已经解决了老一代的所有问题。这往往是不真实的,当然,如果关心可靠性和持续的高性能,“企业级”的设备通常值得它的价钱。
用SSD做RAID
我们建议对 SATA SSD 盘使用 RAID(Redundant Array of Inexpensive Disks,磁盘冗余阵列)。单一驱动器的数据安全是无法让人信服的。
许多旧的 RAID 控制器并不支持 SSD 。因为它们假设管理的是机械硬盘,包括写缓冲和写排序这些特性都是为机械硬盘而设计的。这不但纯属无效工作,也会增加响应时间,因为SSD暴露的逻辑位置会被映射到底层闪存记忆体中的任意位置。现在这种情况好一点。有些 RAID 控制器的型号末尾有一个字母,表明它们是为 SSD 做了准备的。例如, Adaptec 控制器用 Z 标识。
然而,即使支持闪存的控制器,也不一定真的就对闪存支持很好。在一般情况下,RAID 控制器的性能表现,只能满足对 6~8 个驱动器的期望,而不是几十个。原因很简单,RAID 控制器达到了瓶颈。
9.4.5 PCIe存储设备
相对于 SATA SSD,PCIe 设备没有尝试模拟硬盘驱动器。这种设计是好事:服务器和硬盘驱动器之间的接口不能完全发挥闪存的性能。SAS/SATA 互联带宽比 PCIe 要低,所以 PCIe 对高性能需求是更好的选择。PCIe 设备延迟也低得多,因为它们在物理上更靠近 CPU。
SATA 接口的速度上限为 600 MB/s ,而 PCIe 3.0x2 的速度上限为 2 GB/s ,PCIe 4.0x2 甚至可达到最高 4 GB/s 。
没有什么比得上从 PCIe 设备上获得的性能。缺点就是它们太贵了。
所有我们熟悉的型号都需要一个特殊的驱动程序来创建块设备,让操作系统把它认成一个硬盘驱动器。这些驱动程序使用着混合磨损均衡和其他逻辑的策略;有些使用主机系统的 CPU 和内存,有些使用板载的逻辑控制器和 RAM( 内存)。在许多场景下,主机系统有丰富的 CPU 和 RAM 资源,所以相对于购买一个自身有这些资源的卡,利用主机上的资源实际上是更划算的策略。
我们不建议对 PCIe 设备建 RAID。 它们用 RAID 就太昂贵了,并且大部分设备无论以何种方式,都有它们自己板载的 RAID 。 我们并不真的知道控制器坏了以后会怎么样,但是厂商说他们的控制器通常跟网卡或者 RAID 控制器一样好,看起来确实是这样。换句话说,这些设备的平均无故障时间间隔( MTBF) 接近于主板,所以对这些设备使用 RAID 只是增加了大量成本而没有多少好处。
有许多家供应商在生产 PCIe 闪存卡。对 MySQL 用户来说最著名的厂商是 Fusion-io 和 Virident, 但是像 Texas Memory Systems、STEC 和 OCZ 这样的厂商也有用户。SLC 和 MLC 都有相应的 PCIe 卡产品。
现在的很多高端 NVMe SSD 主控具备断电保护功能,特别是企业级 NVMe。
9.4.6 其他类型的固态存储
除了 SSD 和 PCIe 设备,也有其他公司的产品可以选择,例如 Violin Memory、SandForce 和 Texas Memory Systems。 这些公司提供有几十 TB 存储容量,本质上是闪存 SAN 的大箱子。它们主要用于大型数据中心存储的整合。虽然价格非常昂贵,但是性能也非常高。我们知道一些使用的例子,并在某些场景下测量过性能。这些设备能够提供相当好的延迟,除了网络往返时间——例如,通过 NFS 有小于 4 毫秒的延迟。
然而这些都不适合一般的 MySQL 市场。它们的目标更针对其他数据库,如 Oracle, 可以用来做共享存储集群。一般情况下,MySQL 在如此大规模的场景下,不能有效利用如此强大的存储优势,因为在数十个 TB 的数据下 MySQL 很难良好地工作—— MySQL 对这样一个庞大的数据库的回答是,拆分、横向扩展和无共享( Shared-nothing) 架构。
虽然专业化的解决方案可能能够利用这些大型存储设备——例如 Infobright 可能成为候选人。ScaleDB 可以部署在共享存储( Shared-storage) 架构,但我们还没有看到它在生产环境应用,所以我们不知道其工作得如何。
9.4.7 什么时候应该使用闪存
固态存储最适合使用在任何有着大量随机 I/O 工作负载的场景下。随机 I/O 通常是由于数据大于服务器的内存导致的。用标准的硬盘驱动器,受限于转速和寻道延迟,无法提供很高的 IOPS。 闪存设备可以大大缓解这种问题。
当然,有时可以简单地购买更多内存,这样随机工作负载就可以转移到内存,I/O 就不存在了。但是当无法购买足够的内存时,闪存也可以提供帮助。
另一个不能总是用内存解决的问题是,高吞吐的写入负载。增加内存只能帮助减少到磁盘的写入负载,因为更多的内存能创造更多的机会来缓冲、合并写。这允许把随机写转换为更加顺序的 I/O。 然而,这并不能无限地工作下去,一些事务或插入繁忙的工作负载不能从这种方法中获益。闪存存储在这种情况下却也有帮助。
单线程工作负载是另一个闪存的潜在应用场景。当工作负载是单线程的时候,它是对延迟非常敏感的,固态存储更低的延迟可以带来很大的区别。相反,多线程工作负载通常可以简单地加大并行化程度以获得更高的吞吐量。MySQL 复制是单线程工作的典型例子,它可以从低延迟中获得很多收益。在备库跟不上主库时,使用闪存存储往往可以显著提高其性能。
闪存也可以为服务器整合提供巨大的帮助,尤其是 PCIe 方式的。我们已经看到了机会,把很多实例整合到一台物理服务器——有时高达 10 或 15 倍的整合都是可能的。更多关于这个话题的信息,请参见第 11 章。
然而闪存也可能不一定是你要的答案。一个很好的例子是,像 InnoDB 日志文件这样的顺序写的工作负载,闪存不能提供多少成本与性能优势,因为在这种情况下,闪存连续写方面不比标准硬盘快多少。这样的工作负载也是高吞吐的,会更快耗尽闪存的寿命。在标准硬盘上存放日志文件通常是一个更好的主意,用具有电池保护写缓存的 RAID 控制器。
有时答案在于内存/磁盘的比例,而不只是磁盘。如果可以买足够的内存来缓存工作负载,就会发现这更便宜,并且比购买闪存存储设备更有效。
9.4.8 使用Flashcache
虽然有很多因素需要在闪存、硬盘和 RAM 之间权衡,在存储层次结构中,这些设备没有被当作一个整体处理。有时可以使用磁盘和内存技术的结合,这就是 Flashcache 。
Flashcache 是这种技术的一个实现,可以在许多系统上发现类似的使用,例如 Oracle 数据库、 ZFS 文件系统,甚至许多现代的硬盘驱动器和 RAID 控制器。下面讨论的许多东西应用广泛,但我们将只专注于 Flashcache, 因为它和厂商、文件系统无关。
Flashcache 是一个 Linux 内核模块,使用 Linux 的设备映射器( Device Mapper)。 它在内存和磁盘之间创建了一个中间层。这是 Facebook 开源和使用的技术之一,可以帮助其优化数据库负载。
Flashcache 创建了一个块设备,并且可以被分区,也可以像其他块设备一样创建文件系统,特点是这个块设备是由闪存和磁盘共同支撑的。闪存设备用作读取和写入的智能高速缓存。
虚拟块设备远比闪存设备要大,但是没关系,因为数据最终都存储在磁盘上。闪存设备只是去缓冲写入和缓存读取,有效弥补了服务器内存容量的不足(11)。
注 11 :
意思就是内存放不下要缓存的数据时,换出到 Flashcache 上,Flashache 的闪存设备可以帮助继续缓存,而不会立刻落到磁盘。
这种性能有多好呢? Flashcache 似乎有相对较高的内核开销。(设备映射并不总是像看起来那么有效,但我们还没深入调查找出原因。)但是,尽管 Flashcache 理论上可能更高效,但最终的性能表现并不如底层的闪存存储那么好,不过它仍然比磁盘快很多,所以还是值得考虑的方案。
我们用包含数百个基准测试的一系列测试来评估 Flashcache 的性能,但是我们发现在人工模拟的工作负载下,测出有意义的数据是非常困难的。于是我们得出结论,虽然并不清楚 Flashcache 通常对写负载有多大好处,但是对读肯定是有帮助的。于是它适合这样的情况使用:有大量的读 I/O, 并且工作集比内存大得多。
除了实验室测试,我们有一些生产环境中应用 Flashcache 的经验。想到的一个例子是,有个 4TB 的数据库,这个数据库遇到了很大的复制延迟。我们给系统加了半个 TB 的 Virident PCIe 卡作为存储。然后安装了 Flashcache, 并且把 PCIe 卡作为绑定设备的闪存部分,复制速度就翻了一倍。
当闪存卡用得很满时使用 Flashcache 是最经济的,因此选择一张写得很满时其性能不会降低多少的卡非常重要。这就是为什么我们选择 Virident 卡。
Flashcache 就是一个缓存系统,所以就像任何其他缓存一样,它也有预热问题。虽然预热时间可能会非常长。例如,在我们刚才提到的情况下,Flashcache 需要一个星期的预热,才能真正对性能产生帮助。
应该使用 Flashcache 吗?根据具体情况可能会有所不同,所以我们认为在这一点上,如果你觉得不确定,最好得到专家的意见。理解 Flashcache 的机制和它们如何影响你的数据库工作集大小是很复杂的,在数据库下层(至少)有三层存储:
- 首先,是 InnoDB 缓冲池,它的大小跟工作集大小一起可以决定缓存的命中率。缓存命中是非常快的,响应时间非常均匀。
- 在缓冲池中没有命中,就会到 Flashcache 设备上去取,这就会产生分布比较复杂的响应时间。Flashcache 的缓存命中率由工作集大小和闪存设备大小决定。从闪存上命中比在磁盘上查找要快得多。
- Flashcache 设备缓存也没有命中,那就得到磁盘上找,这也会看到分布相当均匀的比较慢的响应时间。
有可能还有更多层次:例如,SAN 或 RAID 控制器的缓存。
这有一个思维实验,说明这些层是如何交互的。很显然,从 Flashcache 设备访问的响应时间不会像直接访问闪存设备那么稳定和高速。但是想象一下,假设有 1TB 的数据,其中 100 GB 在很长一段时间会承受 99% 的 I/O 操作。也就是说,大部分时候 99% 的工作集只有 100 GB。
现在,假设有以下的存储设备:一个很大的 RAID 卷,可以执行 1000 IOPS, 以及一个可以达到 100000 IOPS 的更小的闪存设备。闪存设备不足以存放所有的数据——假设只有 128 GB—— 因此单独使用闪存不是一种可能的选择。如果用闪存设备做 Flashcache, 就可以期望缓存命中远远快于磁盘检索,但 Flashcache 整体比单独使用闪存设备要慢。我们坚持用数字说话,如果 90% 的请求落到 Flashcache 设备,相当于达到 50000 IOPS。 这个思维实验的结果是什么呢?有两个要点:
- 系统使用 Flashcache 比不使用的性能要好很多,因为大多数在缓冲池未命中的页面访问都被缓存在闪存卡上,相对于磁盘可以提供快得多的访问速度。( 99% 的工作集可以完全放在闪存卡上。)
- Flashcache 设备上有 90% 的命中率意味着有 10% 没有命中。因为底层的磁盘只能提供 1000 IOPS, 因此整个 Flashcache 设备可以支持 10000 的 IOPS。 为了明白为什么是这样的,想象一下如果我们要求不止于此会发生什么: 10% 的 I/O 操作在缓存中没有命中而落到了 RAID 卷上,则肯定要求 RAID 卷提供超过 1000 IOPS, 很显然是没法处理的。因此,即使 Flashcache 比闪存卡慢,系统作为一个整体仍然受限于 RAID 卷,不止是闪存卡或 Flashcache。
上面是怎么算出的 10000 呢?
RAID 的随机 I/O 速度为 1000 IOPS,落到 RAID 上的为 Flashcache 未命中的 10% 工作负载,因而1000 / 10% = 10000
,虽然 Flashcache 可以支持 100000 IOPS,但整个存储系统受限于最差的那个存储层,即 RAID 。所以 Flashcache 最多可发挥其性能的十分之一,即刚刚算出的 10000 IOPS 。
归根到底,Flashcache 是否合适是一个复杂的决定,涉及的因素很多。一般情况下,它似乎最适合以读为主的 I/O 密集型负载,并且工作集太大,用内存优化并不经济的情况。
9.4.9 优化固态存储上的 MySQL
如果在闪存上运行 MySQL, 有一些配置参数可以提供更好的性能。InnoDB 的默认配置从实践来看是为硬盘驱动器定制的,而不是为固态硬盘定制的。不是所有版本的 InnoDB 都提供同样等级的可配置性。尤其是很多为提升闪存性能设计的参数首先出现在 Percona Server 中,尽管这些参数很多已经在 Oracle 版本的 InnoDB 中实现,或者计划在未来的版本中实现。
改进包括:
-
增加 InnoDB 的 I/O 容量
闪存比机械硬盘支持更高的并发量,所以可以增加读写 I/O 线程数到 10 或 15 来获得更好的结果。也可以在 2000 ~ 20000 范围内调整
innodb_io_capacity
选项,这要看设备实际上能支撑多大的 IOPS。 尤其是对 Oracle 官方的 InnoDB 这个很有必要,内部有更多算法依赖于这个设置。 -
让 InnoDB 日志文件更大
即使最近版本的 InnoDB 中改进了崩溃恢复算法,也不应该把磁盘上的日志文件调得太大,因为崩溃恢复时需要随机 I/O 访问,会导致恢复需要很长一段时间。闪存存储让这个过程快很多,所以可以设置更大的 InnoDB 日志文件,以帮助提升和稳定性能。对于 Oracle 官方的 InnoDB, 这个设置尤其重要,它维持一个持续的脏页刷新比例有点麻烦,除非有相当大的日志文件—— 4 GB 或者更大的日志文件,在写的时候对服务器来说是个不错的选择。Percona Server 和 MySQL 5.6 支持大于 4 GB 的日志文件。
-
把一些文件从闪存转移到 RAID
除了把 InnoDB 日志文件设置得更大,把日志文件从数据文件中拿出来,单独放在一个带有电池保护写缓存的 RAID 组上而不是固态设备上,也是个好主意。这么做有几个原因。一个原因是日志文件的 I/O 类型,在闪存设备上不比在这样一个 RAID 组上要快。InnoDB 写日志是以 512 字节为单位的顺序 I/O 写下去,并且除了崩溃恢复会顺序读取,其他时候绝不会去读。这样的 I/O 操作类型用闪存设备是很浪费的。并且把小的写入操作从闪存转移到 RAID 卷也是个好主意,因为很小的写入会增加闪存设备的写放大因子,会影响一些设备的使用寿命。大小写操作混合到一起也会引起某些设备延时的增加。
基于相同的原因,有时把二进制日志文件转移到 RAID 卷也会有好处。并且你可能会认为
ibdata1
文件也适合放在 RAID 卷上,因为ibdata1
文件包含双写缓冲( Doublewrite Buffer) 和插入缓冲( Insert Buffer), 尤其是双写缓冲会进行很多重复写入。在 Percona Server 中,可以把双写缓冲从ibdata1
文件中拿出来,单独存放到一个文件,然后把这个文件放在 RAID 卷上。(MySQL 8 也实现了该功能)还有另一个选择:可以利用 Percona Server 的特性,使用 4 KB 的块写事务日志,而不是 512 字节。因为这会匹配大部分闪存本身的块大小,所以可以获得更好的效果。(4K块大小对齐)所有的上述建议是对特定硬件而言的,实际操作的时候可能会有所不同,所以在大规模改动存储布局之前要确保已经理解相关的因素——并辅以适当的测试。
-
禁用预读
预读通过通知和预测读取模式来优化设备的访问,一旦认为某些数据在未来需要被访问到,就会从设备上读取这些数据。实际上在 InnoDB 中有两种类型的预读,我们发现在多种情况下的性能问题,其实都是预读以及它的内部工作方式造成的。在许多情况下开销比收益大,尤其是在闪存存储,但我们没有确凿的证据或指导,禁用预读究竟可以提高多少性能。
在 MySQL 5.1 的 InnoDB Plugin 中,MySQL 禁用了所谓的“随机预读”,然后在 MySQL 5.5 又重新启用了它,可以在配置文件用一个参数配置。Percona Server 能让你在旧版本里也一样可以配置为
random
( 随机)或linear read-ahead
( 线性预读)。 -
配置 InnoDB 刷新算法
这决定 InnoDB 什么时候、刷新多少、刷新哪些页面,这是个非常复杂的主题,这里我们没有足够的篇幅来讨论这些具体的细节。这也是个研究比较活跃的主题,并且实际上在不同版本的 InnoDB 和 MySQL 中有多种有效的算法。
标准 InnoDB 算法没有为闪存存储提供多少可配置性,但是如果用的是 Percona XtraDB( 包含在 Percona Server 和 MariaDB 中),我们建议设置
innodb_adaptive_checkpoint
选项为keep_average
, 不要用默认值estimate
。 这可以确保更持续的性能,并且避免服务器抖动,因为estimate
算法会在闪存存储上引起抖动。我们专门为闪存存储开发了keep_average
, 因为我们意识到对于闪存设备,把希望操作的大量 I/O 推到设备上,并不会引起瓶颈或发生抖动。另外,建议为闪存设备设置
innodb_flush_neighbor_pages=0
。 这样可以避免 InnoDB 尝试查找相邻的脏页一起刷写。这个算法可能会导致更大块的写、更高的延迟,以及内部竞争。在闪存存储上这完全没必要,也不会有什么收益,因为相邻的页面单独刷新不会冲击性能。 -
禁用双写缓冲的可能
相对于把双写缓存转移到闪存设备,可以考虑直接关闭它。有些厂商声称他们的设备支持 16 KB 的原子写入,使得双写缓冲成为多余的。如果需要确保整个存储系统被配置得可以支持 16 KB 的原子写入,通常需要
O_DIRECT
和 XFS 文件系统。没有确凿的证据表明原子操作的说法是真实的,但由于闪存存储的工作方式,我们相信写数据文件发生页面写一部分的情况是大大减少的,并且这个收益在闪存设备上比在传统磁盘上要高得多,禁用双写缓冲在闪存存储上可以提高 MySQL 整体性能差不多 50%, 尽管我们不知道这是不是 100% 安全的,但是你可以考虑下这么做。
-
限制插入缓冲大小
插入缓冲(在新版 InnoDB 中称为变更缓冲( Change Buffer)) 设计用于减少当更新行时不在内存中的非唯一索引引起的随机 I/O。 在硬盘驱动器上,减少随机 I/O 可以带来巨大的性能提升。对某些类型的工作负载,当工作集比内存大很多时,差异可能达到近两个数量级。插入缓冲在这类场景下就很有用。
然而,对闪存就没有必要了。闪存上随机 I/O 非常快,所以即使完全禁用插入缓冲,也不会带来太大影响,尽管如此,可能你也不想完全禁用插入缓存。所以最好还是启用,因为 I/O 只是修改不在内存中的索引页面的开销的一部分。对闪存设备而言,最重要的配置是控制最大允许的插入缓冲大小,可以限制为一个相对比较小的值,而不是让它无限制地增长,这可以避免消耗设备上的大量空间,并避免
ibdata1
文件变得非常大的情况。在本书写作的时候,标准 InnoDB 还不能配置插入缓存的容量上限,但是在 Percona XtraDB(Percona Server 和 MariaDB 都包含 XtraDB) 里可以。MySQL 5.6 里也会增加一个类似的变量。
除了上述的配置建议,我们还提出或讨论了其他一些闪存优化策略。然而,不是所有的策略都非常容易明白,所以我们只是提到了一部分,最好自己研究在具体情况下的好处。首先是 InnoDB 的页大小。我们发现了不同的结果,所以我们现在还没有一个明确的建议。好消息是,在 Percona Server 中不需要重编译也能配置页面大小,在 MySQL 5.6 中这个功能也可能实现。以前版本的 MySQL 需要重新编译服务器才能使用不同大小的页面,所以大部分情况都是运行在默认的 16KB 页面。当页面大小更容易让更多人进行实验时,我们期待更多非标准页面大小的测试,可能能从中得到很多重要的结论。
另一个提到的优化是 InnoDB 页面校验( Checksum) 的替代算法。当存储系统响应很快时,校验值计算可能开始成为 I/O 相关操作中显著影响时间的因素,并且对某些人来说这个计算可能替代 I/O 成为新的瓶颈。我们的基准测试还没有得出可适用于普遍场景的结论,所以每个人的情况可能有所不同。Percona XtraDB 允许修改校验算法,MySQL 5.6 也有了这个功能。
可能已经提醒过了,我们提到的很多功能和优化在标准版本的 InnoDB 中是无效的。我们希望并且相信我们引入 Percona Server 和 XtraDB 中的改进点,最终将会被广大用户接受。与此同时,如果正使用 Oracle 官方 MySQL 分发版本,依然可以对服务器采取措施为闪存进行优化。建议使用 innodb_file_per_table
, 并且把数据文件目录放到闪存设备。然后移动 ibdata1
和日志文件,以及其他所有日志文件(二进制日志、复制日志,等等),到 RAID 卷,正如我们之前讨论的。这会把随机 I/O 集中到闪存设备上,然后把大部分顺序写入的压力尽可能转移出闪存,因而可以节省闪存空间并且减少磨损。
另外,所有版本的 MySQL 服务器,都应该确认超线程开启了。当使用闪存存储时,这有很大的帮助,因为磁盘通常不再是瓶颈,任务会更多地从 I/O 密集变为 CPU 密集。
9.5 为备库选择硬件
为备库选择硬件与为主库选择硬件很相似,但是也有些不同。如果正计划着建一个备库做容灾,通常需要跟主库差不多的配置。不管备库是不是仅仅作为一个主库的备用库,都应该强大到足以承担主库上发生的所有写入,额外的不利因素是备库只能序列化串行执行。
备库硬件主要考虑的是成本:需要在备库硬件上花费跟主库一样多的成本吗?可以把备库配置得不一样以便从备库上获得更多性能吗?如果备库跟主库工作负载不一样,可以从不一样的硬件配置上获得隐含的收益吗?
这一切都取决于备库是否只是备用的,你可能希望主库和备库有相同的硬件和配置,但是,如果只是用复制作为扩展更多读容量的方法,那备库可以有多种不同的捷径。例如,可能在备库使用不一样的存储引擎,并且有些人使用更便宜的硬件或者用 RAID 0 代替 RAID 5 或 RAID 10。 也可以取消一些一致性和持久性的保证让备库做更少的工作。
这些措施在大规模部署的情况下具有很好的成本效益,但是在小规模的情况下,可能只会使事情变得更加复杂。在实践中,似乎大多数人都会选择以下两种策略为备库选择硬件:主备使用相同的硬件,或为主库购买新的硬件,然后让备库使用主库淘汰的老硬件。
在备库很难跟上主库时,使用固态硬盘有很大的意义。很好的随机 I/O 性能有助于缓解单个复制线程的影响。
9.6 RAID性能优化
存储引擎通常把数据和索引都保存在一个大文件中,这意味着用 RAID(Redundant Array of Inexpensive Disks,磁盘冗余阵列)存储大量数据通常是最可行的方法(12)。RAID 可以帮助做冗余、扩展存储容量、缓存,以及加速。但是从我们看到的一些优化案例来说,RAID 上有多种多样的配置,为需求选择一个合适的配置非常重要。
注 12 :
分区(看第7章)是另一个好办法,因为它通常把文件分成多份,你可以放在不同的磁盘上。但是,相对于分区,RAID对于很大数据是一个更简单的解决方案。这不需要你手动进行负载平衡或者在负载分布发生变化时进行干预,并且可以提供冗余,而你不能把分区文件放在不同的磁盘。
我们不想覆盖所有的 RAID 等级,或者深入细节来分析不同的 RAID 等级分别如何存储数据。关于这个主题有很多好资料,在一些书籍和在线文档可以找到。因此,我们专注于怎样配置 RAID 来满足数据库服务器的需求。
最重要的 RAID 级别如下:
-
RAID 0
如果只是简单地评估成本和性能,RAID 0 是成本最低和性能最高的 RAID 配置(但是,如果考虑数据恢复的因素,RAID 0 的代价会非常高)。因为 RAID 0 没有冗余,建议只在不担心数据丢失的时候使用,例如备库或者因某些原因只是“一次性”使用的时候。典型的案例是可以从另一台备库轻易克隆出来的备库服务器。再次说明, RAID 0 没有提供任何冗余,即使R
在RAID
中表示冗余。实际上,RAID 0 阵列的损坏概率比单块磁盘要高,而不是更低! -
RAID 1
RAID 1 在很多情况下提供很好的读性能,并且在不同的磁盘间冗余数据,所以有很好的冗余性。RAID 1 在读上比 RAID 0 快一些。它非常适合用来存放日志或者类似的工作,因为顺序写很少需要底层有很多磁盘(随机写则相反,可以从并发中受益)。这通常也是只有两块硬盘又需要冗余的低端服务器的选择。RAID 0 和 RAID 1 很简单,在软件中很好实现。大部分操作系统可以很简单地用软件创建 RAID 0 和 RAID 1。
-
RAID 5
RAID 5 有点吓人,但是对某些应用,这是不可避免的选择,因为价格或者磁盘数量(例如需要的容量用 RAID 1 无法满足)的原因。它通过分布奇偶校验块把数据分散到多个磁盘,这样,如果任何一个盘的数据失效,都可以从奇偶校验块中重建。但如果有两个磁盘失效了,则整个卷的数据无法恢复。就每个存储单元的成本而言,这是最经济的冗余配置,因为整个阵列只额外消耗了一块磁盘的存储空间。
在 RAID 5 上随机写是昂贵的,因为每次写需要在底层磁盘发生两次读和两次写,以计算和存储校验位。如果写操作是顺序的,那么执行起来会好一些,或者有很多物理磁盘也行。另外说一下,随机读和顺序读都能很好地在 RAID 5 下执行(因为读取并不需要写校验位)。RAID 5 用作存放数据或者日志是一种可接受的选择,或者是以读为主的业务,不需要消耗太多写 I/O 的场景。
RAID 5 最大的性能消耗发生在磁盘失效时,因为数据需要重分布到其他磁盘。这会严重影响性能,如果有很多磁盘会更糟糕。如果在重建数据时还保持服务器在线服务,那就别指望重建的速度或者阵列的性能会好。如果使用 RAID 5, 最好有一些机制可以做故障迁移,当有问题的时候让一台机器不再提供服务,另一台接管。不管怎样,对系统做一下故障恢复时的性能测试很有必要,这样就可以知道故障恢复时的性能表现到底如何。如果一块磁盘失效,RAID 组在重建过程中,会导致磁盘性能下降,使用这个存储的服务器整体性能可能会不成比例地被影响到慢两倍到五倍。
RAID 5 的奇偶校验块会带来额外的性能开销,这会限制它的可扩展性,超过 10 块硬盘后 RAID 5 就不能很好地扩展,RAID 缓存也会有些问题。RAID 5 的性能严重依赖于 RAID 控制器的缓存,这可能跟数据库服务器需要的缓存冲突了。我们稍后会讨论缓存。
尽管 RAID 5 有这么多问题,但有个有利因素是它非常受欢迎。因此,RAID 控制器往往针对 RAID 5 做了高度优化,虽然有理论极限,但是智能控制器充分利用高速缓存使得 RAID 5 在某些场景下有时可以达到接近 RAID 10 的性能。实际上这可能反映了 RAID 10 的控制器缺少很好的优化,但不管是什么原因,这就是我们所见到的。
-
RAID 10
RAID 10 对数据存储是个非常好的选择。它由分片的镜像组成,所以对读和写都有良好的扩展性。相对于 RAID 5, 重建起来很简单,速度也很快。另外 RAID 10 还可以在软件层很好地实现。
当失去一块磁盘时,性能下降还是比较明显的,因为条带可能成为瓶颈(15)。 性能可能下降为 50%, 具体要看工作负载。需要注意的一件事是,RAID 控制器对 RAID 10 采用了一种“串联镜像”的实现。这不是最理想的实现,由于条带化的缺点是“最经常访问的数据可能仅被放置在一对机械磁盘上,而不是分布很多份,”所以可能会遇到性能不佳的情况。
注 15 :
意思是损失一块盘,读取的时候本来可以从相互镜像的两块盘中同时读,少了一块盘就只能从另一块镜像盘上去读了。 -
RAID 50
RAID 50 由条带化的 RAID 5 组成,如果有很多盘的话,这可能是 RAID 5 的经济性和 RAID 10 的高性能之间的一个折中。它的主要用处是存放非常庞大的数据集,例如数据仓库或者非常庞大的 OLTP 系统。
表 9-2 是多种 RAID 配置的总结。
表 9-2:RAID 等级之间的比较
9.6.1 RAID的故障转移、恢复和镜像
RAID 配置(除了 RAID 0) 都提供了冗余。这很重要,但很容易让人低估磁盘同时发生故障的可能性。千万不要认为 RAID 能提供一个强有力的数据安全性保证。
RAID 不能消除甚至减少备份的需求。当出现问题的时候,恢复时间要看控制器、 RAID 等级、阵列大小、硬盘速度,以及重建阵列时是否需要保持服务器在线。
硬盘在完全相同的时间损坏是有可能的。例如,峰值功率或过热,可以很容易地废掉两个或更多的磁盘。然而,更常见的是,两个密切相关的磁盘出现故障。许多这样的隐患可能被忽视了。一个常见的情况是,很少被访问的数据,在物理媒介上损坏了。这可能几个月都检测不到,直到尝试读取这份数据,或另一个硬盘也失效了,然后 RAID 控制器尝试使用损坏的数据来重建阵列。越大的硬盘驱动器,越容易发生这种情况。
这就是为什么做 RAID 阵列的监控如此重要。大部分控制器提供了一些软件来报告阵列的状态,并且需要持续跟踪这些状态,因为不这么做可能就会忽略了驱动器失效。你可能丧失恢复数据和发现问题的时机,当第二块硬盘损坏时,已经晚了。因此应该配置一个监控系统来提醒硬盘或者 RAID 卷发生降级或失效了。
对阵列积极地进行定期一致性检查,可以减少潜在的损坏风险。某些控制器有后台巡检( Background Patrol Read) 功能,当所有驱动器都在线服务时,可以检查媒介是否有损坏并且修复,也可以帮助避免此类问题的发生。在恢复时,非常大型的阵列可能会降低检查速度,所以创建大型阵列时一定要确保制定了相应的计划。
也可以添加一个热备盘,这个盘一般是未使用状态,并且配置为备用状态,有硬盘坏了之后控制器会自动把这块盘恢复为使用状态。如果依赖于每个服务器的可用性, 这是一个好主意。对只有少数硬盘驱动器的服务器,这么做是很昂贵的,因为一个空闲磁盘的成本比例比较高,但如果有多个磁盘,而不设一个热备盘,就是愚蠢的做法。请记住,更多的磁盘驱动器会让发生故障的概率迅速增加。
除了监控硬盘失效,还应该监控 RAID 控制器的电池备份单元以及写缓存策略。如果电池失效,大部分控制器默认设置会禁用写缓存,把缓存策略从 WriteBack 改为 WriteThrough 。 这可能导致服务器性能下降。很多控制器会通过一个学习过程周期性地对电池充放电,在这个过程中缓存是被禁用的。RAID 控制器管理工具应该可以浏览和配置电池充放电计划,不会让人措手不及。
也许希望把缓存策略设为 WriteThrough 来测试系统,这样就可以知道系统性能的期望值。也许需要计划电池充放电的周期,安排在晚上或者周末,重新配置服务器修改 innodb_flush_log_at_trx_commit
和 sync_binlog
变量,或者在电池充放电时简单地切换到另一台服务器。
9.6.2 平衡硬件 RAID 和软件 RAID
操作系统、文件系统和操作系统看到的驱动器数量之间的相互作用可以是复杂的。Bug、 限制或只是错误配置,都可能会把性能降低到远远低于理论值。
如果有 10 块硬盘,理想中应该能够承受 10 个并行的请求,但有时文件系统、操作系统或 RAID 控制器会把请求序列化。面对这个问题一个可行的办法是尝试不同的 RAID 配置。例如,如果有 10 个磁盘,并且必须使用镜像冗余,性能也要好,可以考虑下面几种配置:
- 配置一个包含五个镜像对( RAID 1) 的 RAID 10 卷( 19)。 操作系统只会看到一个很大的单独的硬盘卷,RAID 控制器会隐藏底层的 10 块硬盘。
- 在 RAID 控制器中配置五个 RAID 1 镜像对,然后让操作系统使用五个卷而不是一个卷。( 20)
- 在 RAID 控制器中配置五个 RAID 1 镜像对,然后使用软件 RAID 0 把五个卷做成一个逻辑卷,通过部分硬件、部分软件的实现方式,有效地实现了 RAID 10。( 21)
注:
(19) :就是先做五个两块盘的 RAID 1,然后再把五个镜像对做成 RAID 0,形成 RAID 10。——译者注
(20) :就是做五个两块盘的 RAID 1,然后交给操作系统使用。——译者注
(21) :有些 RAID 卡不支持直接做 RAID 10,只能做成几组 RAID 1,然后由操作系统 LVM 再做 RAID 0,最终形成 RAID 10。——译者注
哪个选项是最好的?这依赖于系统中所有的组件如何相互协作。不同的配置可能获得相同的结果,也可能不同。
我们已经提醒了多种配置可能导致串行化。例如,ext3 文件系统每个 inode 有一个单一的 Mutex, 所以当 InnoDB 是配置为 innodb_flush_method=O_DIRECT
( 常见的配置)时,在文件系统会有 inode 级别的锁定。这使得它不可能对文件进行 I/O 并发操作,因而系统表现会远低于其理论上的能力。
我们见过的另一个案例,请求串行地发送到一个 10 块盘的 RAID10 卷中的每个设备,使用 ReiserFS 文件系统,InnoDB 打开了 innodb_file_per_table
选项。尝试在硬件 RAID 1 的基础上用软件 RAID 0 做成 RAID 10 的方式,获得了五倍多的吞吐,因为存储系统开始表现出五个硬盘同时工作的特性,而不再是一个了。造成这种情况的是一个已经被修复的 Bug, 但是这是一个很好的例证,说明这类事情可能发生。
串行化可能发生在任何的软件或硬件堆栈层。如果看到这个问题发生了,可能需要更改文件系统、升级内核、暴露更多的设备给操作系统,或使用不同的软件或硬件 RAID 组合方式。应该检查你的设备的并发性以确保它确实是在做并发 I/O( 本章稍后有更多关于这个话题的内容)。
最后,当准备上线一种新服务器时,不要忘了做基准测试!这会帮助你确认能获得所期望的性能。例如,若一个硬盘驱动器每秒可以做 200 个随机读,一个有8个硬盘驱动器的 RAID 10 卷应该接近每秒 1 600 个随机读。如果观察到的结果比这个少得多,比如说每秒 500 个随机读,就应该研究下哪里可能有问题了。确保基准测试对 I/O 子系统施加了跟 MySQL 一样的方式的压力——例如,使用 O_DIRECT
标记,并且如果使用没有打开 innodb_file_per_table
选项的 InnoDB, 要用一个单一的文件测试 I/O 性能。通常可以使用 sysbench
来验证新的硬件设置都是正确的。
9.6.3 RAID 配置和缓存
配置 RAID 控制器通常有几种方法,一是可以在机器启动时进入自带的设置工具,或从命令行中运行。虽然大多数控制器提供了很多选项,但其中有两个是我们特别关注的,一是条带化阵列的块大小( Chunk Size), 还有就是控制器板载缓存(也称为 RAID 缓存,我们使用术语)。
RAID 条带块大小
最佳条带块大小和具体工作负载以及硬件息息相关。从理论上讲,对随机 I/O 来说更大的块更好,因为这意味着更多的读取可以从一个单一的驱动器上满足。
为什么会是这样?在工作负载中找出一个典型的随机 I/O 操作,如果条带的块大小足够大,至少大到数据不用跨越块的边界,就只有单个硬盘需要参与读取。但是,如果块大小比要读取的数据量小,就没有办法避免多个硬盘参与读取。
这只是理论上的观点。在实践中,许多 RAID 控制器在大条带下工作得不好。例如,控制器可能用缓存中的缓存单元大小作为块大小,这可能有浪费。控制器也可能把块大小、缓存大小、读取单元的大小(在一个操作中读取的数据量)匹配起来。如果读的单位太大, RAID 缓存可能不太有效,最终可能会读取比真正需要的更多的数据,即使是微小的请求。当然,在实践中很难知道是否有数据会跨越多个驱动器。即使块大小为 16 KB, 与 InnoDB 的页大小相匹配,也不能让所有的读取对齐 16 KB 的边界。文件系统可能会把文件分成片段,每个片段的大小通常与文件系统的块大小对齐,大部分文件系统的块大小为 4KB。 一些文件系统可能更聪明,但不应该指望它。
可以配置系统以便从应用到底层存储所有的块都对齐: InnoDB 的块、文件系统的块、 LVM, 分区偏移、 RAID 条带、磁盘扇区。我们的基准测试表明,当一切都对齐时,随机读和随机写的性能可能分别提高 15% 和 23% 。 对齐一切的精密技术太特殊了,无法在这细说,但其他很多地方有很多不错的信息,包括我们的博客,http://www.mysqlperformanceblog.com 。
RAID 缓存
RAID 缓存就是物理安装在 RAID 控制器上的(相对来说)少量内存。它可以用来缓冲硬盘和主机系统之间的数据。下面是 RAID 卡使用缓存的几个原因:
-
缓存读取
控制器从磁盘读取数据并发送到主机系统后,通过缓存可以存储读取的数据,如果将来的请求需要相同的数据,就可以直接使用而无须再次去读盘。
这实际上是 RAID 缓存一个很糟糕的用法。为什么呢?由于操作系统和数据库服务器有自己更大得多的缓存。如果数据在这些上层缓存中命中了,RAID 缓存中的数据就不会被使用。相反,如果上层的缓存没有命中,就有可能在 RAID 缓存中命中,但这个概率是微乎其微的。因为 RAID 缓存要小得多,几乎肯定会被刷新掉,被其他数据填上去了。无论哪种方式,缓冲读都是浪费 RAID 缓存的事。
-
缓存预读数据
如果 RAID 控制器发现连续请求的数据,可能会决定做预读操作——就是预先取出估计很快会用到的数据。在数据被请求之前,必须有地方放这些数据。这也会使用 RAID 缓存来放。预读对性能的影响可能有很大的不同,应该检查确保预读确实有帮助。如果数据库服务器做了自己的智能预读(例如 InnoDB 的预读),RAID 控制器的预读可能就没有帮助,甚至可能会干扰所有重要的缓冲和同步写入。
-
缓冲写入
RAID 控制器可以在高速缓存里缓冲写操作,并且一段时间后再写到硬盘。这样做有双重的好处:首先,可以快得多地返回给主机系统写“成功”的信号,远远比写入到物理磁盘上要快;其次,可以通过积累写操作从而更有效地批量操作( 22)。
(22) :可以缓冲随机I/O部分合并为顺序I/O。——译者注
-
内部操作
某些 RAID 的操作是非常复杂的——尤其是 RAID 5 的写入操作,其中要计算校验位,用来在发生故障时重建数据。控制器做这类内部操作需要使用一些内存。
这也是 RAID 5 在一些 RAID 控制器上性能差的原因:为了好的性能需要读取大量数据到内存。有些控制器不能较好地平衡缓存写和 RAID 5 校验位操作所需要的内存。
一般情况下,RAID 控制器的内存是一种稀缺资源,应该尽量用在刀刃上。缓存读取通常是一种浪费,但是缓冲写入是加速 I/O 性能的一个重要途径。许多控制器可以选择如何分配内存。例如,可以选择有多少缓存用于写入和多少用于读取。对于 RAID 0、RAID 1 和 RAID 10, 应该把控制器缓存 100% 分配给写入用。对于 RAID 5, 应该保留一些内存给内部操作。通常这是正确的建议,但并不总是适用——不同的 RAID 卡需要不同的配置。
当正在用 RAID 缓存缓冲写入时,许多控制器可以配置延迟写入多久时间(例如一秒钟、五秒钟,等等)是可以接受的。较长的延迟意味着更多的写入可以组合在一起更有效地刷新到磁盘。缺点是写入会变得更加“突发的”,即会产生性能波动。但这不是一件坏事,除非应用一连串的写请求把控制器的缓存填满了才被刷新到磁盘。如果没有足够的空间存放应用程序的写入请求,写操作就会等待。保持短的延迟意味着可以有更多的写操作,并且会更低效( 23), 但能抚平性能波动,并且有助于保持更多的空闲缓存,来接收应用程序的爆发请求。(我们在这里简化了——事实上控制器往往很复杂,不同的供应商有自己的均衡算法,所以我们只是试图覆盖基本原则。)
(23):因为没有充分地合并I/O。——译者注
写入缓冲对同步写入非常有用,例如事务日志和二进制日志( sync_binlog
设置为 1) 调用的 fsync(), 但是除非控制器有电池备份单元( BBU) 或其他非易失性存储( 24), 否则不应该启用 RAID 缓存。不带 BBU 的情况下缓冲写,在断电时,有可能损坏数据库,甚至是事务性文件系统。然而,如果有 BBU, 启用写入缓存可以提升很多日志刷新的工作的性能,例如事务提交时刷新事务日志。
(24):有几种技术,包括电容器和闪存存储,但这里我们都归结到 BBU 这一类。
最后要考虑的是,许多硬盘驱动器有自己的缓存,可能有“假”的 fsync()
操作,欺骗 RAID 控制器说数据已被写入物理介质( 25)。 有时可以让硬盘直接挂载(而不是挂到 RAID 控制器上),让操作系统管理它们的缓存,但这并不总是有效。这些缓存通常在做 fsync()
操作时被刷新,另外同步 I/O 也会绕过它们直接访问磁盘,但是再次提醒,硬盘驱动器可能会骗你。应该确保这些缓存在 fsync()
时真的刷新了,否则就禁用它们,因为磁盘缓存没有电池供电(所以断电会丢失)。操作系统或 RAID 固件没有正确地管理硬盘管理已经造成了许多数据丢失的案例。
由于这个以及其他原因,当安装新硬件时,做一次真实的宕机测试(比如拔掉电源)是很有必要的。通常这是找出隐藏的错误配置或者诡异的硬盘问题的唯一办法。在 http://brad.livejournal.com/2116715.html 有个方便的脚本可以使用。
(25):就是
fsync
只是刷新到了硬盘上的缓存,这个缓存是没有电池的,所以掉电会丢失数据。——译者注
为了测试是否真的可以依赖 RAID 控制器的 BBU, 必须像真实情况一样切断电源一段时间,因为某些单元断电超过一段时间后就可能会丢失数据。这里再次重申,任何一个环节出现问题都会使整个存储组件失效。
9.7 SAN 和 NAS
SAN(Storage Area Network) 和 NAS(Network-Attached Storage) 是两个外部文件存储设备加载到服务器的方法。不同的是访问存储的方式。访问 SAN 设备时通过块接口,服务器直接看到一块硬盘并且可以像硬盘一样使用,但是 NAS 设备通过基于文件的协议来访问,例如 NFS 或 SMB。SAN 设备通常通过光纤通道协议( FCP) 或 iSCSI 连接到服务器,而 NAS 设备使用标准的网络连接。还有一些设备可以同时通过这两种方式访问,比如 NetApp Filer 存储系统。
在接下来的讨论中,我们将把这两种类型的存储统一称为 SAN。 在后面的阅读应该记住这一点。主要区别在于作为文件还是块设备访问存储。
SAN 允许服务器访问非常大量的硬盘驱动器——通常在 50 块以上——并且通常配置大容量智能高速缓存来缓冲写入。块接口在服务器上以逻辑单元号( LUN) 或者虚拟卷(除非使用 NFS) 出现。许多 SAN 也允许多节点组成集群来获得更好的性能或者增加存储容量。
目前新一代 SAN 跟几年前的不同。许多新的 SAN 混合了闪存和机械硬盘,而不仅仅是机械硬盘了。它们往往有大到 TB 级或以上的闪存作为缓存,不像旧的 SAN, 只有相对较小的缓存。此外,旧的 SAN 无法通过配置更大的缓存层来“扩展缓冲池”,而新的 SAN 有时可以。因此,相比之下新的 SAN 可以提供更好的性能。
9.7.1 SAN基准测试
SAN 可以承受大量的连续写入,因为可以缓冲并合并 I/O。SAN 提供顺序读取没有问题,因为可以做预读并从缓存中提出数据。在随机写上会慢一些,因为写入操作不能较好地合并。因为读取通常在缓存中无法命中,必须等待硬盘驱动器响应,所以 SAN 很不适合做随机读取。最重要的是,服务器和 SAN 之间有传输延迟。这是为什么通过 NFS 连接 SAN 时,提供的每秒随机读还不如一块本地磁盘的原因。
不管多么强大的 SAN, 对于小的随机操作,都无法获得良好的响应时间和吞吐量。延时的大部分都是由于服务器和 SAN 之间的链路导致的。
我们的基准测试显示的每秒操作吞吐量,并没有说出完整的故事。至少有三个重要指标:每秒吞吐量字节数、并发性和响应时间。在一般情况下,相对于直接连接存储( DAS),SAN 无论读写都可以提供更好的顺序 I/O 吞吐量。大多数 SAN 可以支持大量的并发性,但基准测试只有一个线程,这可以说明最坏的情况。但是,当工作集不能放到 SAN 的缓存时,随机读在吞吐量和延迟方面将变得很差,甚至延迟将高于直接访问本地存储。
9.7.2 使用基于 NFS 或 SMB 的 SAN
主要考虑的事情是 NFS 协议自身怎样影响性能。许多文件元信息操作,通常在本地文件系统或者 SAN 设备(非 NAS) 的内存中执行,但是在 NAS 上可能需要一次网络来回发送。例如,我们提醒过把二进制日志存在 NFS 上会损害服务器性能,即使关闭 sync_binlog 也无济于事。
也可以通过 SMB 协议访问 SAN 或者 NAS, 需要考虑的问题类似:可能有更多的网络通信,会对延迟产生影响。对传统桌面用户这没什么影响,他们通常只是在挂载的驱动器上存储电子表格或者其他文档,或者只是为了备份复制一些东西到另一台服务器。但是用作 MySQL 读写它的文件,就会有严重的性能问题。
9.7.3 MySQL 在 SAN 上的性能
I/O 基准测试只是一种观察的方式,MySQL 在 SAN 上具体性能表现如何?在许多情况下, MySQL 运行得还可以,可以避免很多 SAN 可能导致性能下降的情况。仔细地做好逻辑和物理设计,包括索引和适当的服务器硬件(尽量配置更多的内存!)可避免很多的随机 I/O 操作,或者可以转化为顺序的 I/O。 然而,应该知道的是,通过一段时间的运行,这种系统可以达到一个微妙的平衡——引入一个新的查询,Schema 的变化或频繁的操作,都很容易扰乱这种平衡。
哪些工作放在 SAN 上不合适:执行大量的随机 I/O 的单线程任务。在当前版本的 MySQL 中,复制是另一个单线程任务。因此,备库的数据存储在 SAN 上,可能更容易落后于主库。批处理作业也可能运行得更慢。在非高峰时段或周末执行一个一次性的延迟敏感的操作是可以的,但是服务器的很多部分依然需要很好的性能,例如拷贝、二进制日志,以及 InnoDB 的事务日志上总是需要很好的小随机 I/O 性能。
9.7.4 应该用SAN吗
嗯,这是个长期存在的问题——在某些情况下,数百万美元的问题。有很多因素要考虑,以下我们列出其中的几个。
-
备份
集中存储使备份更易于管理。当所有数据都存储在一个地方时,可以只备份 SAN, 只要确保已经确认过了所有的数据都在。这简化了问题,例如“你确定我们要备份所有的数据吗?”此外,某些设备有如连续数据保护( CDP) 以及强大的快照功能等功能,使得备份更容易、更灵活。
-
简化容量规划
不确定需要多大容量吗? SAN 可以提供这种能力——购买大容量存储、分享给很多应用,并且可以调整大小并按需求重新发布。
-
存储整合还是服务器整合
某些 CIO 盘点数据中心运行了哪些东西时,可能会得出结论说大量的 I/O 容量被浪费了,这是把存储空间和 I/O 容量混为一谈了。毫无疑问的是,如果集中存储可以确保更好地利用存储资源,但这样做将会如何影响使用存储的系统?典型的数据库操作在性能上可以达到数量级的差异,因此可能会发现,如果集中存储可能需要增加 10 倍的服务器(或更多)才能处理原来的工作。尽管数据中心的 I/O 容量在 SAN 上可以更好地被利用,但是会导致其他系统无法充分被利用(例如数据库服务器花费大量时间等待 I/O、 应用程序服务器花费大量时间等待数据库,依此类推)。在现实中我们已经看到过很多通过分散存储来整合服务器并削减成本的例子。
-
高可用
有时人们认为 SAN 是高可用解决方案。之所以会这样认为,可能是因为对高可用的真实含义的理解出现了分歧,我们将在第 12 章给出建议。
根据我们的经验,SAN 经常与故障和停机联系在一起,这不是因为它们不可靠——它们没什么问题,也确实很少出故障——只是因为人们都不愿意相信这样的工程奇迹其实也会坏的,因而缺乏这方面的准备。此外,SAN 有时是一个复杂的、神秘的黑盒子,当出问题的时候没有人知道该如何解决,并且价格昂贵,难以快速构建管理 SAN 所需的专业知识。大多数的 SAN 都对外缺乏可见性(就是个黑盒子),这也是为什么不应该只是简单地信任 SAN 管理员、支持人员或管理控制台的原因。我们看到过所有这三种人都错了的情况:当 SAN 出了问题,如出现硬盘驱动器故障导致性能下降( 26) 的案例。这是另一个推荐使用
sysbench
的理由:sysbench
可以快速地完成一个 I/O 基准测试以证明是否是 SAN 的问题。(26):基于网络的SAN管理控制台坚持所有硬盘驱动器是健康的——直到我们要求管理员按
Shift+F5
来禁用他的浏览器缓存并强制刷新控制台! -
服务器之间的交互
共享存储可能会导致看似独立的系统实际上是相互影响的,有时甚至会很严重。例如,我们知道一个 SAN 用户有个很粗放的认识,当开发服务器上有 I/O 密集型操作时,会引起数据库服务器几乎陷于停顿。批处理作业、
ALTER TABLE
、 备份——任何一个系统上产生大量的 I/O 操作都可能会导致其他系统的 I/O 资源不足。有时的影响远远比直觉想象的糟糕,一个看似不起眼的操作可能会导致严重的性能下降。 -
成本
成本是什么?管理和行政费用?每秒 I/O 操作数( IOPS) 中每个 I/O 操作的成本?标价?
有充分的理由使用 SAN, 但无论销售人员说什么,至少从 MySQL 需要的性能类型来看,SAN 不是最佳的选择。(选择一个 SAN 供应商并跟它们的销售谈,你可能听到他们一般也是同意的,然后告诉你他们的产品是一个例外。)如果考虑性价比,结论会更加清楚,因为闪存存储或配置有电池支持写缓存的 RAID 控制器加上老式硬盘驱动器,可以在低得多的价格下提供更好的性能。
关于这个话题,不要忘了让销售给你两台 SAN 的价格。至少需要两台,否则这台昂贵的 SAN 可能会成为故障中的单点。
有许多“血泪史”可以引以为戒,这不是试图吓唬你远离 SAN。 我们知道的 SAN 用户都非常地爱这些存储!如果正在考虑是否使用 SAN, 最重要的事情是想清楚要解决什么问题。SAN 可以做很多事情,但解决性能问题只是其中很小的一部分。相比之下,当不要求很多高性能的随机 I/O, 但是对某些功能感兴趣的话,如快照、存储整合、重复数据删除和虚拟化,SAN 可能非常适合。
因此,大多数 Web 应用不应该让数据库使用 SAN, 但 SAN 在所谓的企业级应用很受欢迎。企业通常不太受预算限制,所以能够负担得起作为“奢侈品”的 SAN。( 有时 SAN 甚至作为一种身份的象征!)
9.8 使用多磁盘卷
你可能已经听过标准建议,就是把事务日志和数据文件放在不同的卷上面,这样日志的顺序 I/O 和数据的随机 I/O 不会互相影响。但是除非有很多硬盘( 20 或更多)或者闪存存储,否则在这样做之前应该考虑清楚代价。
二进制日志和数据文件分离的真正的优势,是减少事故中同时丢失数据和日志文件的可能性。如果 RAID 控制器上没有电池支持的写缓存,把它们分开是很好的做法。
但是,如果有备用电池单元,分离卷就可能不是想象中那么必要了。性能差异很小是主要原因。这是因为即使有大量的事务日志写入,其中大部分写入都很小。因此,RAID 缓存通常会合并 I/O 请求,通常只会得到每秒的物理顺序写请求。这通常不会干预数据文件的随机 I/O, 除非 RAID 控制器真的整体上饱和了。一般的日志,其中有连续的异步写入、负荷也低,可以较好地与数据分享一个卷。
将日志放在独立的卷是否可以提升性能?通常情况下是的,但是从成本的角度来看这个问题,是否真的值得这么做,答案往往是否定的,尽管很多人不这么认为。
成本效益不是唯一考虑的因素。可能想保持 InnoDB 的数据和事务日志在同一个卷的另一个原因是,这种策略可以使用 LVM 快照做无锁的备份。某些文件系统允许一致的多卷快照,并且对这些文件系统,这是一个很轻量的操作,但对于 ext3 有很多东西需要注意。(也可以使用 Percona XtraBackup 来做无锁备份,关于此主题更多的信息,请参阅第 15 章)
如果已经启用 sync_binlog
, 二进制日志在性能方面与事务日志相似了。然而,二进制日志存储跟数据放在不同的卷,实际上是一个好主意——把它们分开存放更安全,因此即使数据丢失,二进制日志也可以保存下来。这样,可以使用二进制日志做基于时间点的恢复。这方面的考虑并不适用于 InnoDB 的事务日志,因为没有数据文件,它们就没用了,你不能将事务日志应用到昨晚的备份。(事务日志和二进制日志之间的区别在其他数据库的 DBA 看来,很难搞明白,在其他数据库这就是同一个东西。)
另外一个常见的场景是分离出临时目录的文件,MySQL 做 filesorts
( 文件排序)和使用磁盘临时表时会写到临时目录。如果这些文件不会太大的话,最好把它们放在临时内存文件系统,如 tmpfs
。 这是速度最快的选择。如果在你的系统上这不可行,就把它们放在操作系统盘上。
典型的磁盘布局是有操作系统盘、交换分区和二进制日志的盘,它们放在 RAID 1 卷上。还要有一个单独的 RAID 5 或 RAID 10 卷,放其他的一切东西。
9.9 网络配置
就像延迟和吞吐量是硬盘驱动器的限制因素一样,延迟和带宽(实际上和吞吐量是同一回事)也是网络连接的限制因素。对于大多数应用程序来说,最大的问题是延时。典型的应用程序都需要传输很多很小的网络包,并且每次传输的轻微延迟最终会被累加起来。
运行不正常的网络通常也是主要的性能瓶颈之一。丢包是一个普遍存在的问题。即使1%的丢包率也足以造成显著的性能下降,因为在协议栈的各个层次都会利用各种策略尝试修复问题,例如等待一段时间再重新发送数据包,这就增加了额外的时间。另一个常见的问题是域名解析系统(DNS)损坏或者变慢了。
DNS 足以称为“阿基里斯之踵”,因此在生产服务器上启用skip_name_resolve
是个好主意。损坏或缓慢的 DNS 解析对许多应用程序都是个问题,对 MySQL 尤为严重。当 MySQL 收到一个连接请求时,它同时需要做正向和反向 DNS 查找。有很多原因可能导致这个过程出问题。当问题出现时,会导致连接被拒绝,或者使得连接到服务器的过程变慢,这通常都会造成严重的影响,甚至相当于遭遇了拒绝服务攻击(DDOS)。如果启用 skip_name_resolve
选项,MySQL 将不会做任何 DNS 查找的工作。然而,这也意味着,用户账户必须在 host
列使用具有唯一性的 IP 地址,“localhost”
或者IP地址通配符。那些在host
列使用主机名的用户账户都将不能登录。
典型的 Web 应用中另一个常见的问题来源是 TCP 积压,可以通过 MySQL 的 back_log
选项来配置。这个选项控制 MySQL 的传入 TCP 连接队列的大小。在每秒有很多连接创建和销毁的环境中,默认值 50
是不够的。设置不够的症状是,客户端会看到零星的“连接被拒绝”的错误,配以三秒超时规则。在繁忙的系统中这个选项通常应加大。把这个选项增加到数百甚至数千,似乎没有任何副作用,事实上如果你看得远一些,可能还需要配置操作系统的 TCP 网络设置。在 GNU/Linux 系统,需要增加 somaxconn
限制,默认只有 128
, 并且需要检查 sysctl
的 tcp_max_syn_back_log
设置(在本节稍后有一个例子)。
应该设计性能良好的网络,而不是仅仅接受默认配置的性能。首先,分析节点之间有多少跳跃点,以及物理网络布局之间的映射关系。例如,假设有 10 个网页服务器,通过千兆以太网( 1 GigE) 连接到“网页”交换机,这个交换机也通过千兆网络连接到“数据库”交换机。如果不花时间去追踪连接,可能不会意识到从所有数据库服务器到所有网页服务器的总带宽是有限的!并且每次跨越交换机都会增加延时。
监控网络性能和所有网络端口的错误是正确的做法,要监控服务器、路由器和交换机的每个端口。多路由流量绘图器( Multi Router Traffic Grapher), 或者说 MRTG(http://oss.oetiker.ch/mrtg/), 对设备监控而言是个靠得住的开源解决方案。其他常见的网络性能监控工具(与设备监控不同)还有 Smokeping(http://oss.oetiker.ch/smokeping/) 和 Cacti(http://www.cacti.net)。
网络物理隔离也是很重要的因素。城际网络相比数据中心的局域网的延迟要大得多,即使从技术上来说带宽是一样的。如果节点真的相距甚远,光速也会造成影响。物理距离不仅是性能上的考虑,也包括设备之间通信的考虑。中继器、路由器和交换机,所有的性能都会有所降级。再次,越广泛地分隔开的网络节点,连接的不可预知和不可靠因素越大。
尽可能避免实时的跨数据中心的操作是明智的。如果不可能做到这一点,也应该确保应用程序能正常处理网络故障。例如,我们不希望看到由于 Web 服务器通过丢包严重的网络连接远程的数据中心时,由于 Apache 进程挂起而新建了很多进程的情况发生。
在本地,请至少用千兆网络。骨干交换机之间可能需要使用万兆以太网。如果需要更大的带宽,可以使用网络链路聚合:连接多个网卡( NIC), 以获得更多的带宽。链路聚合本质上是并行网络,作为高可用策略的一部分也很有帮助。
如果需要非常高的吞吐量,也许可以通过改变操作系统的网络配置来提高性能。如果连接不多,但是有很多的查询和很大的结果集,则可以增加 TCP 缓冲区的大小。具体的实现依赖于操作系统,对于大多数的 GNU/Linux 系统,可以改变/etc/sysctl.conf
中的值并执行 sysctl -p
, 或者使用/proc
文件系统写入一个新的值到 /proc/sys/net/
里面的文件。搜索“ TCP tuning guide”, 可以找到很多好的在线教程。
然而,调整设置以有效地处理大量连接和小查询的情况通常更重要。比较常见的调整之一,就是要改变本地端口的范围。系统的默认值如下:
[ root@server ~]# cat /proc/sys/net/ipv4/ip_local_port_range
32768 61000
有时你也许需要改变这些值,调整得更大一些。例如:
[ root@server ~]# echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range
如果要允许更多的连接进入队列,可以做如下操作:
[ root@server ~]# echo 4096 > /proc/sys/net/ipv4/tcp_max_syn_backlog
对于只在本地使用的数据库服务器,对于连接端还未断开,但是通信已经中断的事件中使用的套接字,可以缩短 TCP 保持状态的超时时间。在大多数系统上默认是一分钟,这个时间太长了:
[ root@server ~]# echo <value> > /proc/sys/net/ipv4/tcp_fin_timeout
这些设置大部分时间可以保留默认值。通常只有当发生了特殊情况,例如网络性能极差或非常大量的连接,才需要进行修改。在互联网上搜索“ TCP variables”, 可以发现很多不错的阅读资料,除了上面提到的,还能看到很多其他的变量。
9.10 选择操作系统
GNU/Linux 如今是高性能 MySQL 最常用的操作系统,但是 MySQL 本身可以运行在很多操作系统上。
谈到 GNU/Linux 发行版时,个人的喜好往往是决定性的因素。我们认为最好的策略是使用专为服务器应用程序设计的发行版,而不是桌面发行版。要考虑发行版的生命周期、发布和更新政策,并检查供应商的支持是否有效。红帽子企业版 Linux 是一个高品质、稳定的发行版; CentOS 是一个受欢迎的二进制兼容替代品(免费),但已经因为延后时间较长获得了一些批评;还有 Oracle 发行的 Oracle Enterprise Linux; 另外,Ubuntu 和 Debian 也是流行的发行版。
也可以选择 Solaris、FreeBSD、Windows 等操作系统运行 MySQL 服务器。
9.11 选择文件系统
文件系统的选择非常依赖于操作系统。在许多系统中,如 Windows 就只有一到两个选择,而且只有一个( NTFS) 真的是能用的。比较而言,GNU/Linux 则支持多种文件系统。
许多人想知道哪个文件系统在 GNU/Linux 上能提供最好的 MySQL 性能,或者更具体一些,哪个对 InnoDB 和 MyISAM 而言是最好的选择。实际的基准测试表明,大多数文件系统在很多方面都非常接近,但测试文件系统的性能确实是一件烦心事。文件系统的性能是与工作负载相关的,没有哪个文件系统是“银弹”。大部分情况下,给定的文件系统不会明显地表现得与其他文件系统不一样。除非遇到了文件系统的限制,例如,它怎么支持并发、怎么在多文件下工作、怎么对文件切片,等等。
要考虑的更重要的问题是崩溃恢复时间,以及是否会遇到特定的限制,如目录下有许多文件会导致运行缓慢(这是 ext2 和旧版本 ext3 下一个臭名昭著的问题,但当前版本的 ext3 和 ext4 中用 dir_index
选项解决了问题)。文件系统的选择对确保数据安全是非常重要的,所以我们强烈建议不要在生产系统做实验。
如果可能,最好使用日志文件系统,例如 ext3、ext4、XFS、ZFS 或者 JFS。 如果不这么做,崩溃后文件系统的检查可能耗费相当长的时间。如果系统不是很重要,非日志文件系统性能可能比支持事务的好。例如,ext2 可能比 ext3 工作得好,或者可以使用 tunefs
关闭 ext3 的日志记录功能。挂载时间对某些文件系统也是一个因素。例如,ReiserFS, 在一个大的分区上可能用很长时间来挂载和执行日志恢复。
如果使用 ext3 或者其继承者 ext4, 有三个选项来控制数据怎么记日志,这可以放在/etc/fstab
中作为挂载选项:
-
data=writeback
这个选项意味着只有元数据写入日志。元数据写入和数据写入并不同步。这是最快的配置,对 InnoDB 来说通常是安全的,因为 InnoDB 有自己的事务日志。唯一的例外是,崩溃时恰好导致
.frm
文件损坏了。这里给出一个使用这个配置可能导致问题的例子。当程序决定扩展一个文件使其更大,元数据(文件大小)会在数据实际写到(更大的)文件之前记录并写下操作情况。结果就是文件的尾部——最新扩展的区域——会包含垃圾数据。
-
data=ordered
这个选项也只会记录元数据,但提供了一些一致性保证,在写元数据之前会先写数据,使它们保持一致。这个选项只是略微比
writeback
选项慢,但是崩溃时更安全。在此配置中,如果我们再次假设程序想要扩展一个文件,该文件的元数据将不能反映文件的新大小,直到驻留在新扩展区域中的数据被写到文件中了。
-
data=journal
此选项提供了原子日志的行为,在数据写入到最终位置之前将记录到日志中。这个选项通常是不必要的,它的开销远远高于其他两个选项。然而,在某些情况下反而可以提高性能,因为日志可以让文件系统延迟把数据写入最终位置的操作。
不管哪种文件系统,都有一些特定的选项最好禁用,因为它们没有提供任何好处,反而增加了很多开销。最有名的是记录访问时间的选项,甚至读文件或目录时也要进行一次写操作。在/etc/fstab
中添加 noatime
、nodiratime
挂载选项可以禁用此选项,这样做有时可以提高 5% ~ 10% 的性能,具体取决于工作负载和文件系统(虽然在其他场景下差别可能不是太大)。下面是/etc/fstab
中的一个例子,对 ext3 选项做设置的行:
/dev/sda2 /usr/lib/mysql ext3 noatime,nodiratime,data=writeback 0 1
还可以调整文件系统的预读的行为,因为这可能也是多余的。例如,InnoDB 有自己的预读策略,所以文件系统的预读就是重复多余的。禁用或限制预读对 Solaris 的 UFS 尤其有利。使用 O_DIRECT
选项会自动禁用预读。
一些文件系统可能不支持某些需要的功能。例如,若让 InnoDB 使用 O_DIRECT
刷新方式,文件系统能支持 Direct I/O 是非常重要的。此外,一些文件系统处理大量底层驱动器比其他的文件系统更好,举例来说 XFS 在这方面通常比 ext3 好。最后,如果打算使用 LVM 快照来初始化备库或进行备份,应该确认选择的文件系统和 LVM 版本能很好地协同工作。
表 9-4 某些常见文件系统的特性总结。
表 9-4 :常见文件系统特性
文件系统 | 操作系统 | 支持日志 | 大目录 |
---|---|---|---|
ext2 | GNU/Linux | 否 | 否 |
ext3 | GNU/Linux | 可选 | 可选/部分 |
ext4 | GNU/Linux | 是 | 是 |
HFS Plus | Mac OS | 可选 | 是 |
JFS | GNU/Linux | 是 | 否 |
NTFS | Windows | 是 | 是 |
ReiserFS | GNU/Linux | 是 | 是 |
UFS (Solaris) | Solaris | 是 | 可调的 |
UFS (FreeBSD) | FreeBSD | 否 | 可选/部分 |
UFS2 | FreeBSD | 否 | 可选/部分 |
XFS | GNU/Linux | 是 | 是 |
ZFS | Solaris, FreeBSD | 是 | 是 |
我们通常建议客户使用 XFS 文件系统。ext3 文件系统有太多严重的限制,例如 inode 只有一个互斥变量,并且 fsync() 时会刷新所有脏块,而不只是单个文件。很多人感觉 ext4 文件系统用在生产环境有点太新了,不过现在似乎正日益普及。
几乎所有情况下,XFS 都优于 ext4 。
9.12 选择磁盘队列调度策略
在 GNU/Linux 上,队列调度决定了到块设备的请求实际上发送到底层设备的顺序。默认情况下使用 cfq
(Completely Fair Queueing, 完全公平排队)策略。随意使用的笔记本和台式机使用这个调度策略没有问题,并且有助于防止 I/O 饥饿,但是用于服务器则是有问题的。在 MySQL 的工作负载类型下,cfq
会导致很差的响应时间,因为会在队列中延迟一些不必要的请求。
可以用下面的命令来查看系统所有支持的以及当前在用的调度策略:
$ cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
这里 sda
需要替换成想查看的磁盘的盘符。在我们的例子中,方括号表示正在使用的调度策略。cfq
之外的两个选项都适合服务器级的硬件,并且在大多数情况下,它们工作同样出色。noop
调度适合没有自己的调度算法的设备,如硬件 RAID 控制器和 SAN。deadline
则对 RAID 控制器和直接使用的磁盘都工作良好。我们的基准测试显示,这两者之间的差异非常小。重要的是别用 cfq
, 这可能会导致严重的性能问题。
不过这个建议也需要有所保留的,因为磁盘调度策略实际上在不同的内核有很多不一样的地方,千万不能望文生义。
9.13 线程
MySQL 每个连接使用一个线程,另外还有内部处理线程、特殊用途的线程,以及所有存储引擎创建的线程。在 MySQL 5.5 中,Oracle 提供了一个线程池插件,但目前尚不清楚在实际应用中能获得什么好处。
无论哪种方式,MySQL 都需要大量的线程才能有效地工作。MySQL 确实需要内核级线程的支持,而不只是用户级线程,这样才能更有效地使用多个 CPU。 另外也需要有效的同步原子,例如互斥变量。操作系统的线程库必须提供所有的这些功能。
GNU/Linux 提供两个线程库: LinuxThreads 和新的原生 POSIX 线程库( NPTL)。LinuxThreads 在某些情况下仍然使用,但现在的发行版已经切换到 NPTL, 并且大部分应用已经不再加载 LinuxThreads。NPTL 更轻量,更高效,也不会有那些 LinuxThreads 遇到的问题。
FreeBSD 会加载许多线程库。从历史上看,它对线程的支持很弱,但现在已经变得好多了,在一些测试中,甚至优于 SMP 系统上的 GNU/Linux。 在 FreeBSD 6 和更新版本,推荐的线程库是 libthr, 早期版本使用的 linuxthreads, 是 FreeBSD 从 GNU/Linux 上移植的 LinuxThreads 库。
通常,线程问题都是过去的事了,现在 GNU/Linux 和 FreeBSD 都提供了很好的线程库。
Solaris 和 Windows 一直对线程有很好的支持,尽管直到 5.5 发布之前,MyISAM 都不能在 Windows 下很好地使用线程,但 5.5 里有显著的提升。
9.14 内存交换区
当操作系统因为没有足够的内存而将一些虚拟内存写到磁盘就会发生内存交换(28)。内存交换对操作系统中运行的进程是透明的。只有操作系统知道特定的虚拟内存地址是在物理内存还是硬盘。
(28):内存交换有时称为页面交换。从技术上来说,它们是不同的东西,但是人们通常把它们作为同义词。
内存交换对 MySQL 性能影响是很糟糕的。它破坏了缓存在内存的目的,并且相对于使用很小的内存做缓存,使用交换区的性能更差。MySQL 和存储引擎有很多算法来区别对待内存中的数据和硬盘上的数据,因为一般都假设内存数据访问代价更低。
因为内存交换对用户进程不可见,MySQL( 或存储引擎)并不知道数据实际上已经移动到磁盘,还会以为在内存中。
结果会导致很差的性能。例如,若存储引擎认为数据依然在内存,可能觉得为“短暂”的内存操作锁定一个全局互斥变量(例如 InnoDB 缓冲池 Mutex) 是 OK 的。如果这个操作实际上引起了硬盘 I/O, 直到 I/O 操作完成前任何操作都会被挂起。这意味着内存交换比直接做硬盘 I/O 操作还要糟糕。
在 GNU/Linux 上,可以用 vmstat
( 在下一部分展示了一些例子)来监控内存交换。最好查看 si
和 so
列报告的内存交换 I/O 活动,这比看 swpd
列报告的交换区利用率更重要。swpd
列可以展示那些被载入了但是没有被使用的进程,它们并不是真的会成为问题。我们喜欢 si
和 so
列的值为 0, 并且一定要保证它们低于每秒 10 个块。
si
即swap-in
,so
即swap-out
。
极端的场景下,太多的内存交换可能导致操作系统交换空间溢出。如果发生了这种情况,缺乏虚拟内存可能让 MySQL 崩溃。但是即使交换空间没有溢出,非常活跃的内存交换也会导致整个操作系统变得无法响应,到这种时候甚至不能登录系统去杀掉 MySQL 进程。有时当交换空间溢出时,甚至 Linux 内核都会完全 hang 住。
绝不要让系统的虚拟内存溢出!对交换空间利用率做好监控和报警。如果不知道需要多少交换空间,就在硬盘上尽可能多地分配空间,这不会对性能造成冲击,只是消耗了硬盘空间。有些大的组织清楚地知道内存消耗将有多大,并且内存交换被非常严格地控制,但是对于只有少量多用途的 MySQL 实例,并且工作负载也多种多样的环境,通常不切实际。如果后者的描述更符合实际情况,确认给服务器一些“呼吸”的空间,分配足够的交换空间。
在特别大的内存压力下经常发生的另一件事是内存不足( OOM), 这会导致踢掉和杀掉一些进程。在 MySQL 进程这很常见。在另外的进程上也挺常见,比如 SSH, 甚至会让系统不能从网络访问。可以通过设置 SSH 进程的 oom_adj
或 oom_score_adj
值来避免这种情况。
可以通过正确地配置 MySQL 缓冲来解决大部分内存交换问题,但是有时操作系统的虚拟内存系统还是会决定交换 MySQL 的内存。这通常发生在操作系统看到 MySQL 发出了大量 I/O, 因此尝试增加文件缓存来保存更多数据时。如果没有足够的内存,有些东西就必须被交换出去,有些可能就是 MySQL 本身。有些老的 Linux 内核版本也有一些适得其反的优先级,导致本不应该被交换的被交换出去,但是在最近的内核都被缓解了。
有些人主张完全禁用交换文件。尽管这样做有时在某些内核拒绝工作的极端场景下是可行的,但这降低了操作系统的性能(在理论上不会,但是实际上会的)。同时这样做也是很危险的,因为禁用内存交换就相当于给虚拟内存设置了一个不可动摇的限制。如果 MySQL 需要临时使用很大一块内存,或者有很耗内存的进程运行在同一台机器(如夜间批量任务),MySQL 可能会内存溢出、崩溃,或者被操作系统杀死。
操作系统通常允许对虚拟内存和 I/O 进行一些控制。我们提供过一些 GNU/Linux 上控制它们的办法。最基本的方法是修改/proc/sys/vm/swappiness
为一个很小的值,例如 0 或 1。 这告诉内核除非虚拟内存完全满了,否则不要使用交换区。下面是如何检查这个值的例子:
$ cat /proc/sys/vm/swappiness
60
这个值显示为 60, 这是默认的设置(范围是 0~ 100 )。 对服务器而言这是个很糟糕的默认值。这个值只对笔记本适用。服务器应该设置为 0:
$ echo 0 > /proc/sys/vm/swappiness
另一个选项是修改存储引擎怎么读取和写入数据。例如,使用 innodb_flush_method=O_DIRECT
, 减轻 I/O 压力。Direct I/O 并不缓存,因此操作系统并不能把 MySQL 视为增加文件缓存的原因。这个参数只对 InnoDB 有效。你也可以使用大页,不参与换入换出。这对 MyISAM 和 InnoDB 都有效。
另一个选择是使用 MySQL 的 memlock
配置项,可以把 MySQL 锁定在内存。这可以避免交换,但是也可能带来危险:如果没有足够的可锁定内存,MySQL 在尝试分配更多内存时会崩溃。这也可能导致锁定的内存太多而没有足够的内存留给操作系统。
很多技巧都是对于特定内核版本的,因此要小心,尤其是当升级的时候。在某些工作负载下,很难让操作系统的行为合情合理,并且仅有的资源可能让缓冲大小达不到最满意的值。
9.15 操作系统状态
操作系统会提供一些帮助发现操作系统和硬件正在做什么的工具。在这一节,我们会展示一些例子,包括关于怎样使用两个最常用的工具—— iostat
和 vmstat
。 如果系统不提供它们中的任何一个,有可能提供了相似的替代品。因此,我们的目的不是让大家熟练使用 iostat
和 vmstat
, 而是告诉你用类似的工具诊断问题时应该看什么指标。
除了这些,操作系统也许还提供了其他的工具,如 mpstat
或者 sar
。 如果对系统的其他部分感兴趣,例如网络,你可能希望使用 ifconfig
( 除了其他信息,它能显示发生了多少次网络错误)或者 netstat
。
默认情况下,vmstat
和 iostat
只是生成一个报告,展示自系统启动以来很多计数器的平均值,这其实没什么用。然而,两个工具都可以给出一个间隔参数,让它们生成增量值的报告,展示服务器正在做什么,这更有用。(第一行显示的是系统启动以来的统计,通常可以忽略这一行。)
9.15.1 如何阅读 vmstat
的输出
我们先看一个 vmstat
的例子。用下面的命令让它每5秒钟打印出一个报告:
可以用 Ctrl+C
停止 vmstat
。可以看到输出依赖于所用的操作系统,因此可能需要阅读一下手册来解读报告。
刚启动不久,即使采用增量报告,第一行的值还是显示自系统启动以来的平均值,第二行开始展示现在正在发生的情况,接下来的行会展示每5秒的间隔内发生了什么。每一列的含义在头部,如下所示:
procs
r这一列显示了多少进程正在等待 CPU,b 列显示多少进程正在不可中断地休眠(通常意味着它们在等待 I/O, 例如磁盘、网络、用户输入,等等)。
memory
swpd 列显示多少块被换出到了磁盘(页面交换)。剩下的三个列显示了多少块是空闲的(未被使用)、多少块正在被用作缓冲,以及多少正在被用作操作系统的缓存。
-
swap
这些列显示页面交换活动:每秒有多少块正在被换入(从磁盘)和换出(到磁盘)。它们比监控
swpd
列重要多了。大部分时间我们喜欢看到
si
和so
列是 0, 并且我们很明确不希望看到每秒超过 10 个块。突发性的高峰一样很糟糕。 -
io
这些列显示有多少块从块设备读取( bi) 和写出( bo)。 这通常反映了硬盘 I/O。
bi
即block-in
,bo
即block-out
。 -
system
这些列显示了每秒中断( in) 和上下文切换( cs) 的数量。
-
cpu
这些列显示所有的 CPU 时间花费在各类操作的百分比,包括执行用户代码(非内核)、执行系统代码(内核)、空闲,以及等待 I/O。 如果正在使用虚拟化,则第五个列可能是 st, 显示了从虚拟机中“偷走”的百分比。这关系到那些虚拟机想运行但是系统管理程序转而运行其他的对象的时间。如果虚拟机不希望运行任何对象,但是系统管理程序运行了其他对象,这不算被偷走的 CPU 时间。
vmstat
的输出跟系统有关,所以如果看到跟我们展示的例子不同的输出,应该阅读系统的 vmstat(8) 手册。一个重要的提示是:内存、交换区,以及 I/O 统计是块数而不是字节。在 GNU/Linux, 块大小通常是 1024 字节。
9.15.2 如何阅读 iostat
的输出
现在让我们转移到 iostat
(29)。 默认情况下,它显示了与 vmstat
相同的 CPU 使用信息。我们通常只是对 I/O 统计感兴趣,所以使用下面的命令只展示扩展的设备统计:
$ iostat -dx 5
Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
sda 1.6 2.8 2.5 1.8 138.8 36.9 40.7 0.1 23.2 6.0 2.6
与 vmstat
一样,第一行报告显示的是自系统启动以来的平均值(通常删掉它节省空间),然后接下来的报告显示了增量的平均值,每个设备一行。
有多种选项显示和隐藏列。官方文档有点难以理解,因此我们必须从源码中挖掘真正显示的内容是什么。说明的列信息如下:
-
rrqm/s
和wrqm/s
每秒合并的读和写请求。“合并的”意味着操作系统从队列中拿出多个逻辑请求合并为一个请求到实际磁盘。
-
r/s
和w/s
每秒发送到设备的读和写请求。
-
rsec/s
和wsec/s
每秒读和写的扇区数。有些系统也输出为
rkB/s
和wkB/s
, 意为每秒读写的千字节数。为了简洁,我们省略了那些指标说明。 -
avgrq-sz
请求的扇区数。
-
avgqu-sz
在设备队列中等待的请求数。
-
await
磁盘排队上花费的毫秒数。很不幸,
iostat
没有独立统计读和写的请求,它们实际上不应该被一起平均。当你诊断性能案例时这通常很重要。 -
svctm
服务请求花费的毫秒数,不包括排队时间。
-
%util
至少有一个活跃请求所占时间的百分比。如果熟悉队列理论中利用率的标准定义,那么这个命名很莫名其妙。它其实不是设备的利用率。超过一块硬盘的设备(例如 RAID 控制器)比一块硬盘的设备可以支持更高的并发,但是
%util
从来不会超过 100%, 除非在计算时有四舍五入的错误。因此,这个指标无法真实反映设备的利用率,实际上跟文档说的相反,除非只有一块物理磁盘的特殊例子。
可以用 iostat
的输出推断某些关于机器 I/O 子系统的实际情况。一个重要的度量标准是请求服务的并发数。因为读写的单位是每秒而服务时间的单位是千分之一秒,所以可以利用**利特尔法则(Little’s Law)**得到下面的公式,计算出设备服务的并发请求数(30):
concurrency = (r/s + w/s) * (svctm / 1000)
(30):另一种计算并发的方式是通过平均队列大小、服务时间,以及平均等待时间:
(avgqu-sz * svctm) / await
这是一个 iostat
的输出示例:
把数字带入并发公式,可以得到差不多 9.6 的并发性( 31)。 这意味着在一个采样周期内,这个设备平均要服务 9.6 次的请求。例子来自于一个 10 块盘的 RAID 10 卷,所以操作系统对这个设备的并行请求运行得相当好。另一方面,这是一个出现串行请求的设备:
(31):如果你做这个计算,会得到大约 10,因为为了格式化我们已经取整了
iostat
的输出。相信我们,确实是 9.6 。
并发公式展示了这个设备每秒只处理一个请求。两个设备都接近于满负荷利用,但是它们的性能表现完全不一样。如果设备一直都像这些例子展示的一样忙,那么应该检查一下并发性,不管是不是接近于设备中的物理盘数,都需要注意。更低的值则说明有如文件系统串行的问题,就像我们前面讨论的。
9.15.3 其他有用的工具
我们展示 vmstat
和 iostat
是因为它们部署最广泛,并且 vmstat
通常默认安装在许多类 UNIX 操作系统上。然而,每种工具都有自身的限制,例如莫名奇妙的度量单位、当操作系统更新统计时取样间隔不一致,以及不能一次性看到所有重要的点。如果这些工具不能符合需求,你可能会对 dstat
(http://dag.wieers.com/home-made/dstat/) 或 collectl
(http://collectl.sourceforge.net) 感兴趣。
我们也喜欢用 mpstat
来观察 CPU 统计;它提供了更好的办法来观察 CPU 每个工作是如何进行的,而不是把它们搅在一块。有时在诊断问题时这非常重要。当分析硬盘 I/O 利用的时候,blktrace
可能也非常有用。
我们自己开发了 iostat
的替代品,叫做 pt-diskstats
。 这是 Percona Toolkit 的一部分。它解决了一些对 iostat
的抱怨,例如显示读写统计的方式,以及缺乏对并发量的可见性。它也是交互式的,并且是按键驱动的,所以可以放大缩小、改变聚集、过滤设备,以及显示和隐藏列。即使没有安装这个工具,也可以通过简单的 Shell 脚本来收集一些硬盘统计状态,这个工具也支持分析这样采集出来的文本。可以抓取一些硬盘活动样本,然后发邮件或者保存起来,稍后分析。实际上,我们第 3 章中介绍的 pt-stalk
、pt-collect
、 和 pt-sift
三件套,都被设计得可以跟 pt-diskstats
很好地配合。
9.15.4 CPU 密集型的机器
CPU 密集型服务器的 vmstat
输出通常在 us
列会有一个很高的值,报告了花费在非内核代码上的 CPU 时钟;也可能在 sy
列有很高的值,表示系统 CPU 利用率,超过 20% 就足以令人不安了。在大部分情况下,也会有进程队列排队时间(在 r
列报告的)。
下面是一个例子:
注意,这里也有合理数量的上下文切换(在 cs
列),除非每秒超过 100000 次或更多,一般都不用担心上下文切换。当操作系统停止一个进程转而运行另一个进程时,就会产生上下文切换。
例如,一查询语句在 MyISAM 上执行了一个非覆盖索引扫描,就会先从索引中读取元素,然后根据索引再从磁盘上读取页面。如果页面不在操作系统缓存中,就需要从磁盘进行物理读取,这就会导致上下文切换中断进程处理,直到 I/O 完成。这样一个查询可以导致大量的上下文切换。
如果我们在同一台机器观察 iostat
的输出(再次剔除显示启动以来平均值的第一行),可以发现磁盘利用率低于 50% :
这台机器不是 I/O 密集型的,但是依然有相当数量的 I/O 发生,在数据库服务器中这种情况很少见。另一方面,传统的 Web 服务器会消耗大量 CPU 资源,但是很少发生 I/O, 所以 Web 服务器的输出不会像这个例子。
9.15.5 I/O 密集型的机器
在 I/O 密集型工作负载下,CPU 花费大量时间在等待 I/O 请求完成。这意味着 vmstat
会显示很多处理器在非中断休眠(b
列)状态,并且在 wa
这一列的值很高。
下面是个例子:
这台机器的 iostat
输出显示硬盘一直很忙:
%util
的值可能因为四舍五入的错误超过 100%。 什么迹象意味着机器是 I/O 密集的呢?只要有足够的缓冲来服务写请求,即使机器正在做大量的写操作,也可能可以满足,但是却通常意味着硬盘可能会无法满足读请求。这听起来好象违反直觉,但是如果思考读和写的本质,就不会这么认为了:
- 写请求能够缓冲或同步操作。它们可以被我们本书讨论过的任意一层缓冲:操作系统层、 RAID 控制器层,等等。
- 读请求就其本质而言都是同步的。当然程序可以猜测到可能需要某些数据,并异步地提前读取(预读)。无论如何,通常程序在继续工作前必须得到它们需要的数据。这就强制读请求为同步操作:程序必须被阻塞直到请求完成。
想想这种方式:你可以发出一个写请求到缓冲区的某个地方,然后过一会完成。甚至可以每秒发出很多这样的请求。如果缓冲区正确工作,并且有足够的空间,每个请求都可以很快完成,并且实际上写到物理硬盘是被重新排序后更有效地批量操作的。然而,没有办法对读操作这么做——不管多小或多少的请求,都不可能让硬盘响应说“这是你的数据,我等一会读它”。这就是为什么读需要 I/O 等待是可以理解的原因。
9.15.6 发生内存交换的机器
一台正在发生内存交换的机器可能在 swpd
列有一个很高的值,也可能不高。但是可以看到 si
和 so
列有很高的值,这是我们不希望看到的。下面是一台内存交换严重的机器的 vmstat
输出:
9.15.7 空闲的机器
为完整起见,下面也给出一台空闲机器上的 vmstat
输出。注意,没有在运行或被阻塞的进程,idle
列显示 CPU 是 100% 的空闲。这个例子来源于一台运行红帽子企业版 Linux 5(RHEL 5) 的机器,并且 st
列展示了从“虚拟机”偷来的时间。
9.16 总结
为 MySQL 选择和配置硬件,以及根据硬件配置 MySQL, 并不是什么神秘的艺术。通常,对于大部分目标需要的都是相同的技巧和知识。当然,也需要知道一些 MySQL 特有的特点。
我们通常建议大部分人在性能和成本之间找到一个好的平衡点。首先,出于多种原因,我们喜欢使用廉价服务器。举个例子,如果在使用服务器的过程中遇到了麻烦,并且在诊断时需要停止服务,或者希望只是简单地把出问题的服务器用另一台替换,如果使用的是一台$ 5000 的廉价服务器,肯定比使用一台超过$ 50000 或者更贵的服务器要简单得多。MySQL 通常也更适应廉价服务器,不管是从软件自身而言还是从典型的工作负载而言。
MySQL 需要的四种基本资源是: CPU、 内存、硬盘以及网络资源。网络一般不会作为很严重的瓶颈出现,而 CPU、 内存和磁盘通常是主要的瓶颈所在。对 MySQL 而言,通常希望有很多快速 CPU 可以用,但如果必须在快和多之间做选择,则一般会选择更快而不是更多(其他条件相同的情况下)。
CPU、 内存以及磁盘之间的关系错综复杂,一个地方的问题可能会在其他地方显现出来。在对一个资源抛出问题时,问问自己是不是可能是由另外的问题导致的。如果遇到硬盘密集的操作,需要更多的 I/O 容量吗?或者是更多的内存?答案取决于工作集大小,也就是给定的时间内最常用的数据集。
在本书写作的过程中,我们觉得以下做法是合理的。首先,通常不要超过两个插槽。现在即使双路系统也可以提供很多 CPU 核心和硬件线程了,而且四路服务器的 CPU 要贵得多。另外,四路 CPU 的使用不够广泛(也就意味着缺少测试和可靠性),并且使用的是更低的时钟频率。最终,四路插槽的系统跨插槽的同步开销也显著增加。在内存方面,我们喜欢用价格经济的服务器内存。许多廉价服务器目前有 18 个 DIMM 槽,单条 8GB 的 DIMM 是最好的选择——每 GB 的价格与更低容量的 DIMM 相比差不多,但是比 16GB 的 DIMM 便宜多了。这是为什么我们今天看到很多服务器是 144GB 的内存的原因。这个等式会随着时间的变化而变化——可能有一天具有最佳性价比的是 16GB 的 DIMM, 并且服务器出厂的内存槽数量也可能不一样——但是一般的原则还是一样的。
持久化存储的选择本质上归结为三个选项,以提高性能的次序排序: SAN、 传统硬盘,以及固态存储设备。
-
当需要功能和纯粹的容量时,SAN 是不错的。它们对许多工作负载都运行得不错,但缺点是很昂贵,并且对小的随机 I/O 操作有很大的延时,尤其是使用更慢的互联方式(如 NFS) 或工作集太大不足以匹配 SAN 内存的缓存时,延时会更大。要注意 SAN 的性能突变的情况,并且要非常小心避免灾难的场景。
-
传统硬盘很大,便宜,但是对随机读很慢。对大部分场景,最好的选择是服务器硬盘组成 RAID 10 卷。通常应该使用带有电池保护单元的 RAID 控制器,并且设置写缓存为
WriteBack
策略。这样一个配置对大部分工作负载都可以运行良好。 -
固态盘相对比较小并且昂贵,但是随机 I/O 非常快。一般分为两类: SSD 和 PCIe 设备,准确来讲应该是按接口类型分为 SATA 和 PCIe 两大类,早期固态硬盘采用 SATA 接口与旧主板总线交互。。广泛地来说,SSD 更便宜,更慢,但缺少可靠性验证。需要对 SSD 做 RAID 以提升可靠性,但是大多数硬件 RAID 控制器不擅长这个任务(有些 RAID 控制器对 SSD 支持很差,做了 RAID 性能反而下降)。PCIe 设备很昂贵并且有容量限制,但是非常快并且可靠,而且不需要 RAID 。
固态存储设备可以很大地提升服务器整体性能。有时候一个不算昂贵的 SSD, 可以帮助解决经常在传统硬盘上遇到的特定工作负载的问题,如复制。如果真的需要很强的性能,应该使用 PCIe 设备。增加高速 I/O 设备会把服务器的性能瓶颈转移到 CPU, 有时也会转移到网络。
MySQL 和 InnoDB 并不能完全发挥高端固态存储设备的性能,并且在某些场景下操作系统也不能发挥。但是提升依然很明显。Percona Server 对固态存储做了很多改进,并且很多改进在 5.6 发布时已经进入了 MySQL 主干代码。
对操作系统而言,只有很少的一些重要配置需要关注,大部分是关于存储、网络和虚拟内存管理的。如果像大部分 MySQL 用户一样使用 GNU/Linux, 建议采用 XFS 文件系统,并且为服务器的页面交换倾向率( swapiness
) 和硬盘队列调度器设置恰当的值。有一些网络参数需要改变,可能还有一些其他的地方(例如禁用 SELinux) 需要调优,但是前面说的那些改动的优先级应该更高一些。