|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
转——SSD 下的 MySQL IO 优化尝试 ! ?# ~8 s# L, t! @
1、背景在阅读这篇文章之前,读者需要注意的是,为了维护隐私,用 MySQL 服务器的 D 段代替完整 IP,并且略去一些私密信息。
+ E' A n3 T0 JA 项目,因 I/O 出现规律性地剧烈波动。每 15 分钟落地一次,innodbBuffPoolPagesFlushed 参数监控波峰和波谷交替出现,磁盘 I/O 同样如此,并且 until 达到 100%。经过排查,排除了触发器、事件、存储过程、前端程序定时器、系统 crontab 的可能性。最终定位为 InnoDB 日志切换,但是否完全是日志造成的影响,还有待进一步跟踪和分析。
. c" U% ]: [' I+ y( s: `找到问题的可能所在,试图在 24 主库上做了如下调整:
. G5 k5 m4 b2 T& a$ @7 P5 I! H. d- 关闭 Query Cache;
- 设置 InnoDB Log 大小为 1280M;
- 设置 innodb_max_dirty_pages_pct 为 30,innodb_io_capacity 保持 200 不变。
. V/ K; E3 [. E; L 做了如上调整以后,I/O 趋于平稳,没有再出现大的波动。
6 _' m- V; v8 _$ ?% z为了保险起见,A 项目方面决定采用配有 SSD 的机型,对主库进行迁移,同时对 24 的从库 27 进行迁移。待迁移完成后,在新的主库 39 上,针对 SSD 以及 MySQL InnoDB 参数进行优化。待程序切换完成后,再次对针对 SSD 以及 MySQL InnoDB 参数进行优化。也就是说在上线前后进行优化,观察 I/O 状态。
5 j# D+ t8 l P' x7 J2、SSD 特性众所周知,SSD 的平均性能是优于 SAS 的。SSD 能解决 I/O 瓶颈,但互联网行业总要权衡收益与成本的。目前内存数据库是这个领域的一大趋势,一方面,越来越多的应用会往 NoSQL 迁移。另一方面,重要数据总要落地,传统的机械硬盘已经不能满足目前高并发、大规模数据的要求。总的来说,一方面,为了提高性能,尽可能把数据内存化,这也是 InnoDB 存储引擎不断改进的核心原则。后续的 MySQL 版本已经对 SSD 做了优化。另一方面,尽可能上 SSD。
7 w4 [( V" ^, B4 r" o9 CSSD 这么神秘,接下来我们看看它有哪些特性:
4 l% G5 w% ~/ G% X% K; n- 随机读能力非常好,连续读性能一般,但比普通 SAS 磁盘好;
- 不存在磁盘寻道的延迟时间,随机写和连续写的响应延迟差异不大。
- erase-before-write 特性,造成写入放大,影响写入的性能;
- 写磨损特性,采用 Wear Leveling 算法延长寿命,但同时会影响读的性能;
- 读和写的 I/O 响应延迟不对等(读要大大好于写),而普通磁盘读和写的 I/O 响应延迟差异很小;
- 连续写比随机写性能好,比如 1M 顺序写比 128 个 8K 的随即写要好很多,因为随即写会带来大量的擦除。$ U( o4 t. T! R) o. v. R
总结起来,也就是随机读性能较连续读性能好,连续写性能较随机写性能好,会有写入放大的问题,同一位置插入次数过多容易导致损坏。, I% j$ x F$ k# C, w
+ [) i9 C" X* g5 f
3、基于 SSD 的数据库优化基于 SSD 的数据库优化,我们可以做如下事情:
1 W) r' n4 Y8 W" ]- 减少对同一位置的反复擦写,也就是针对 InnoDB 的 Redo Log。因为 Redo Log 保存在 ib_logfile0/1/2,这几个日志文件是复写,来回切换,必定会带来同一位置的反复擦写;
- 减少离散写入,转化为 Append 或者批量写入,也就是针对数据文件;
- 提高顺序写入的量。
: }7 s6 B& P7 s5 e8 c 具体来说,我们可以做如下调整:
6 o" W- K- d" `( o& j; t* [0 q: l& V- 修改系统 I/O 调度算法为 NOOP;
- 提高每个日志文件大小为 1280M(调整 innodb_log_file_size);
- 通过不断调整 innodb_io_capacity 和 innodb_max_dirty_pages_pct 让落地以及 I/O 水平达到均衡;
- 关闭 innodb_adaptive_flushing,查看效果;
- 修改 innodb_write_io_threads 和 innodb_read_io_threads。1 k3 E$ f; n+ H
针对系统 I/O 调度算法,做如下解释。系统 I/O 调度算法有四种,CFQ(Complete Fairness Queueing,完全公平排队 I/O 调度程序)、NOOP(No Operation,电梯式调度程序)、Deadline(截止时间调度程序)、AS(Anticipatory,预料 I/O 调度程序)。3 d+ Y6 f ^) {) d& {: [- J
下面对上述几种调度算法做简单地介绍。! Q' q; W( |! \# `
CFQ 为每个进程/线程,单独创建一个队列来管理该进程所产生的请求,也就是说每个进程一个队列,各队列之间的调度使用时间片来调度,以此来保证每个进程都能被很好的分配到 I/O 带宽,I/O 调度器每次执行一个进程的 4 次请求。) A6 s. a7 b6 P1 n4 s
NOOP 实现了一个简单的 FIFO 队列,它像电梯的工作主法一样对 I/O 请求进行组织,当有一个新的请求到来时,它将请求合并到最近的请求之后,以此来保证请求同一介质。+ j8 e/ E4 D' B. j
Deadline 确保了在一个截止时间内服务请求,这个截止时间是可调整的,而默认读期限短于写期限,这样就防止了写操作因为不能被读取而饿死的现象。( \/ s; n; s7 T! K# J8 e( b) k
AS 本质上与 Deadline 一样,但在最后一次读操作后,要等待 6ms,才能继续进行对其它 I/O 请求进行调度。可以从应用程序中预订一个新的读请求,改进读操作的执行,但以一些写操作为代价。它会在每个 6ms 中插入新的 I/O 操作,而会将一些小写入流合并成一个大写入流,用写入延时换取最大的写入吞吐量。
/ _. @+ Q8 O8 a, e/ [在 SSD 或者 Fusion IO,最简单的 NOOP 反而可能是最好的算法,因为其他三个算法的优化是基于缩短寻道时间的,而固态硬盘没有所谓的寻道时间且 I/O 响应时间非常短。
4 m9 i* B* U6 e3 e还是用数据说话吧,以下是 SSD 下针对不同 I/O 调度算法所做的 I/O 性能测试,均为 IOPS。: Y/ w' x% b7 W% |
I/O TypeNOOPAnticipatoryDeadlineCFQ
: C, C! f4 v# ESequential Read222567955224678652
% G, l2 W j5 \' ^4 @Sequential Write4090256013701996 C( U* Y. }5 ~
Sequential RW Read63557605671149$ T. c) C) e7 m
Sequential RW Write63607605651149
5 u* ?+ z/ @( e3 E8 `7 B5 }Random Read17905208472093020671
- }! _+ g( D2 t- cRandom Write7423808681138072
' p& C+ o) |! D) ]Random RW Read4994522153165275, j% Z* v' {) G7 N
Random RW Write4991522253215278可以看到,整体来说,NOOP 算法略胜于其他算法。
3 C2 |% z- C; {* D接下来讲解需要调整的 InnoDB 参数的含义:+ o: {4 ~$ d5 m) [8 A2 b
- innodb_log_file_size:InnoDB 日志文件的大小;
- innodb_io_capacity:缓冲区刷新到磁盘时,刷新脏页数量;
- innodb_max_dirty_pages_pct:控制了 Dirty Page 在 Buffer Pool 中所占的比率;
- innodb_adaptive_flushing:自适应刷新脏页;
- innodb_write_io_threads:InnoDB 使用后台线程处理数据页上写 I/O(输入)请求的数量;
- innodb_read_io_threads:InnoDB 使用后台线程处理数据页上读 I/O(输出)请求的数量。; u: ]8 K/ y) c8 q7 @& I
5 L0 T6 D8 H$ a* B/ p& _1 P+ p( U4、A 项目 MySQL 主从关系图A 项目 MySQL 主从关系如图一:, `" B- e6 E3 J# x! p) y
, c' G3 f$ V) o! r
图一 A 项目 MySQL 主从关系图
2 k0 Z! _+ l5 d5、程序切换之前调优程序切换之前,39 只是 24 的从库,所以 IO 压力不高,以下的调整也不能说明根本性的变化。需要说明一点,以下调整的平均间隔在 30 分钟左右。, V. U3 V# }1 G* N: K9 q, e# L
5.1 修改系统 IO 调度算法系统默认的 I/O 调度算法 是 CFQ,我们试图先修改之。至于为什么修改,可以查看第3节。, z2 Z1 j4 ]2 v5 w, J: q( o, q
具体的做法如下,需要注意的是,请根据实际情况做调整,比如你的系统中磁盘很可能不是 sda。8 A9 F' w% L: s9 ~5 D; h: i' }2 C
echo "noop" > /sys/block/sda/queue/scheduler如果想永久生效,需要更改 /etc/grub.conf,添加 elevator,示例如下:5 H. ^# W, ~! [- [5 U& d
kernel /vmlinuz-x.x.xx-xxx.el6.x86_64 ro root=UUID=e01d6bb4-bd74-404f-855a-0f700fad4de0 rd_NO_LUKS rd_NO_LVM LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun16 crashkernel=auto KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM elevator=noop rhgb quiet此步调整做完以后,查看 39 I/O 状态,并没有显著的变化。
. u" E# Y* B1 w5.2 修改 innodb_io_capacity = 4000在做这个参数调整之前,我们来看看当前 MySQL 的配置:) z, v. R$ N8 _; \5 Z ? ]
innodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 200innodb_max_dirty_pages_pct 30innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 4修改方法如下:5 ~5 S i% O$ o3 o
SET GLOBAL innodb_io_capacity = 4000;网络上的文章,针对 SSD 的优化,MySQL 方面需要把 innodb_io_capacity 设置为 4000,或者更高。然而实际上,此业务 UPDATE 较多,每次的修改量大概有 20K,并且基本上都是离散写。innodb_io_capacity 达到 4000,SSD 并没有给整个系统带来很大的性能提升。相反,反而使 IO 压力过大,until 甚至达到 80% 以上。( F& T6 r4 ^* }+ P Z* p
5.3 修改 innodb_max_dirty_pages_pct = 25修改方法如下:" ~ r3 u1 C0 d" f6 b8 ]' _
SET GLOBAL innodb_max_dirty_pages_pct = 25;修改之后的 MySQL 配置:. ^9 K7 X i; R: C4 {: b8 v
innodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 4000innodb_max_dirty_pages_pct 25innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 4之前已经将 innodb_max_dirty_pages_pct 设置为 30,此处将 innodb_max_dirty_pages_pct 下调为 25%,目的为了查看脏数据对 I/O 的影响。修改的结果是,I/O 出现波动,innodbBuffPoolPagesFlushed 同样出现波动。然而,由于 39 是 24 的从库,暂时还没有切换,所有压力不够大,脏数据也不够多,所以调整此参数看不出效果。
+ i S4 l) C( h: D' \( ]: a5 ~5.4 修改 innodb_io_capacity = 2000修改方法不赘述。
0 k# u8 i, J4 K; j/ s% A/ w修改之后的 MySQL 配置:1 l/ t7 {6 D' {5 T
innodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 2000innodb_max_dirty_pages_pct 25innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 4因为 innodb_io_capacity 为 4000 的情况下,I/O 压力过高,所以将 innodb_io_capacity 调整为 2000。调整后,w/s 最高不过 2000 左右,并且 I/O until 还是偏高,最高的时候有 70%。我们同时可以看到,I/O 波动幅度减小,innodbBuffPoolPagesFlushed 同样如此。
0 f% F- M7 i' I4 I5.5 修改 innodb_io_capacity = 1500修改方法不赘述。
) V( G6 ], w3 Q% g% U. Z7 k& N修改之后的 MySQL 配置:
$ k4 }& h b5 n5 Oinnodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 1500innodb_max_dirty_pages_pct 25innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 4I/O 持续出现波动,我们接着继续下调 innodb_io_capacity,调整为 1500。I/O until 降低,I/O 波动幅度继续减小,innodbBuffPoolPagesFlushed 同样如此。
: W; }: n6 `2 A, B5 o1 `9 C( S5.6 关闭 innodb_adaptive_flushing修改方法如下:
( J. R8 A& O8 ^7 n: k* E" _$ cSET GLOBAL innodb_adaptive_flushing = OFF;修改之后的 MySQL 配置:) ~* X. m' B; y2 M9 x
innodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 1500innodb_max_dirty_pages_pct 25innodb_adaptive_flushing OFFinnodb_write_io_threads 4innodb_read_io_threads 4既然落地仍然有异常,那我们可以试着关闭 innodb_adaptive_flushing,不让 MySQL 干预落地。调整的结果是,脏数据该落地还是落地,并没有受 I/O 压力的影响,调整此参数无效。3 y3 z! I' d4 a. @( W# r$ ^% v
5.7 打开 innodb_adaptive_flushing修改方法如下:8 J0 n C& g( }; d" `
SET GLOBAL innodb_adaptive_flushing = ON;修改之后的 MySQL 配置:, d# @0 x, X5 S$ I) s
innodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 1500innodb_max_dirty_pages_pct 25innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 4
' Y Z l9 s D4 t! b经过以上调整,关闭 innodb_adaptive_flushing 没有效果,还是保持默认打开,让这个功能持续起作用吧。 w+ @7 j' P+ O0 Y- X7 n: T
5.8 设置 innodb_max_dirty_pages_pct = 20修改方法不赘述。1 z& K/ w7 w' N, N+ G0 U
修改之后的 MySQL 配置:6 M0 b1 K9 E4 m- b1 c+ n# |: I
innodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 1500innodb_max_dirty_pages_pct 20innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 4接着我们将 innodb_max_dirty_pages_pct 下调为 20,观察脏数据情况。由于 InnoDB Buffer Pool 设置为 40G,20% 也就是 8G,此时的压力达不到此阀值,所以调整参数是没有效果的。但业务繁忙时,就可以看到效果,落地频率会增高。
7 V9 D) l! L7 g' L- S5.9 设置 innodb_io_capacity = 1000修改方法不赘述。
6 W2 y# c q1 }; g8 Z6 S修改之后的 MySQL 配置:
6 F( d, @) c1 i! E6 Einnodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 1000innodb_max_dirty_pages_pct 20innodb_adaptive_flushing ON
2 c7 Q B0 q, [6 a: e+ a& `innodb_write_io_threads 4innodb_read_io_threads 4经过以上调整,我们需要的是一个均衡的 IO,给其他进程一些余地。于是把 innodb_io_capacity 设置为 1000,此时可以看到 I/O until 维持在 10% 左右,整个系统的参数趋于稳定。
1 z( }% a0 @' H- M" ^5 r2 X& `$ }后续还要做进一步的监控、跟踪、分析和优化。! r0 w0 _* G0 j; b
6、 程序切换之后调优在业务低峰,凌晨 1 点左右,配合研发做了切换。切换之后的主从关系可以查看第五节。* }) V. B" E, H6 [) d: m. @
6.1 设置 innodb_max_dirty_pages_pct = 30,innodb_io_capacity = 1500修改方法不赘述。 S) `3 @$ N! y3 m* e
修改之后的 MySQL 配置:& d3 p% C- _7 f7 B T; P
innodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 1500innodb_max_dirty_pages_pct 30innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 4在 innodb_io_capacity 为 1000,innodb_max_dirty_pages_pct 为 20 的环境下,I/O until 有小幅波动,而且波峰和波谷持续交替,这种情况是不希望看到的。innodbBuffPoolPagesFlushed 比较稳定,但 innodbBuffPoolPagESDirty 持续上涨,没有下降的趋势。故做了如下调整:innodb_max_dirty_pages_pct = 30,innodb_io_capacity = 1500。调整完成后,innodbBuffPoolPagesDirty 趋于稳定,I/O until 也比较稳定。
& b2 X: A; h5 Q- D7 g6.2 设置 innodb_max_dirty_pages_pct = 40,innodb_io_capacity = 2000修改方法不赘述。- z, ~0 P8 H0 C5 n9 r& y
修改之后的 MySQL 配置:
o5 j% H4 W0 t! b4 ]+ W. G ~innodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 2000innodb_max_dirty_pages_pct 40innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 4针对目前这种 I/O 情况,做了如下调整:innodb_max_dirty_pages_pct = 40,innodb_io_capacity = 2000。" ^/ B; W% {1 k; \- e
6.3 分析针对以上两个调整,我们通过结合监控数据来分析 I/O 状态。
. H4 E4 y/ a6 p- `7 t/ p1 z以下是高速缓冲区的脏页数据情况,如图二:
9 }+ x" H! M. y! v 4 b( a, y, ^- D t" ^! k& H
图二 主库的脏数据情况8 s2 S c8 J1 |) z. c* n
以下是脏数据落地的情况,如图三+ S% S" N# V6 l% u; c: [
9 i: ?- G1 G2 H8 Y3 {图三 主库的脏数据落地情况5 b( X6 X$ u: {
28 号早 8 点到下午 7 点,当脏数据上升,也就是在内存中的数据更多,那么落地就会很少,呈现一个平稳的趋势;当脏数据维持不变,也就是脏数据达到了 innodb_max_dirty_pages_pct 的限额(innodb_buffer_pool_size 为 40G,innodb_max_dirty_pages_pct 为 40%,也就是在内存中的脏数据最多为 16G,每个 Page 16K,则 innodbBufferPoolDirtyPages 最大为 1000K),落地就会增多,呈现上升的趋势,所以才会出现上述图片中的曲线。
7 W/ V2 ?6 `8 ?3 a8 s这是最后的配置:
, E/ B! a/ l4 w' {$ o. B. zinnodb_buffer_pool_size 42949672960innodb_log_file_size 1342177280innodb_io_capacity 2000innodb_max_dirty_pages_pct 40innodb_adaptive_flushing ONinnodb_write_io_threads 4innodb_read_io_threads 47、小结此次针对 SSD 以及 MySQL InnoDB 参数优化,总结起来,也就是以下三条:8 Z, M% K+ D L* W: } {8 s+ R
- 修改系统 I/O 调度算法;
- 分析 I/O 情况,动态调整 innodb_io_capacity 和 innodb_max_dirty_pages_pct;
- 试图调整 innodb_adaptive_flushing,查看效果。' ~$ g/ p- Y3 N, b1 M% i' ]+ N
4 n: \) @5 `; f! {; F# R, J
针对 innodb_write_io_threads 和 innodb_read_io_threads 的调优我们目前没有做,我相信调整为 8 或者 16,系统 I/O 性能会更好。3 W( w Y$ R5 W1 c* g* m) S
还有,需要注意以下几点:: l# p# E7 C0 x6 [2 M' K+ M5 x; K
- 网络文章介绍的方法有局限性和场景性,不能亲信,不能盲从,做任何调整都要以业务优先。保证业务的平稳运行才是最重要的,性能都是其次;
- 任何一个调整,都要建立在数据的支撑和严谨的分析基础上,否则都是空谈;
- 这类调优是非常有意义的,是真正能带来价值的,所以需要多下功夫,并且尽可能地搞明白为什么要这么调整。8 f7 m) j" z% B5 r4 s
文末,说一点比较有意思的。之前有篇文章提到过 SSDB。SSDB 底层采用 Google 的 LevelDB,并支持 Redis 协议。LevelDB 的设计完全是贴合 SSD 的设计思想的。首先,尽可能地转化为连续写;其次,不断新增数据文件,防止同一位置不断擦写。另外,SSDB 的名字取得也很有意思,也很有水平。我猜想作者也是希望用户将 SSDB 应用在 SSD 上吧。. G0 f: E; N, y
|
|