从Cassandra 数据库 IO 暴涨说起
问题产生
某天生产集群的cassandra磁盘突然开始飙高,具体表现为磁盘读IOPS陡增至 5K+, 磁盘读带宽升高到 1.3GB/s。而与此同时业务量级没有发现有较大的变化。网络流量也没有明显变化。出现的异常非常耐人寻味。
经过排查,发现cassandra某个表的sstable大小超过到物理内存大小,linux 操作系统已经无法将全部数据缓存在内存中,每次读表都需要做磁盘的读IO。导致磁盘有大量的读请求,同时sstable也导致了cpu负载上升。
问题解决方案
cassandra 默认的压缩策略是SizeTieredCompactionStrategy。而 LeveledCompactionStrategy是更加适合读多写少的压缩策略。修改线上cassandra压缩策略之后,磁盘IO有了立竿见影的效果。
cassandra 原理剖析
LCS compaction会保证从L1开始没有重复数据。所以对于读操作来说,只要检索1个或者2个sstable就能查到数据(L0 + LN)。事实上,90%的读操作都只需要读取1个sstable。由于L0是不压缩的,如果大量读请求集中在L0,任然可能导致大量读IO消耗。
LCS compaction发生的会更频繁,会消耗更多IO。如果是写密集型的,并不适合,因为IO开销可能大于读的收益。
cassandra官方文档解释
https://cassandra.apache.org/doc/latest/operating/compaction/lcs.html#lcs
cassandra会定期将插入表中的数据压缩成sstable,默认的压缩策略会生成一个大的sstable,也就是SizeTieredCompactionStrategy。
LeveledCompactionStrategy 策略如下图所示。
经过多次的数据写入之后sstable分布。 每一级数量等级为上一级的10倍,且默认sstable大小为160MB。这样就可以将超大sstable拆分成小的sstable。降低了系统每次做cache的数据大小。
按理说到这里我们cassandra的问题就已经解决了,但是我又想到了另外一个问题。linux 到底是怎么做磁盘与内存的交换呢?
第二层思考
以下内容较长,嫌麻烦的同学直接看总结即可。
首先我们找一台linux
服务器做一次free操作,你会看到类似如下输出。相信每一个服务端工程师都能看懂这张表,但是buff/cache这一列你真的理解过么?
本次测试环境为
CentOS Linux release 8.1.1911 (Core)
4.18.0-147.8.1.el8_1.x86_64
free 输出
total used free shared buff/cache available
Mem: 3725128 355152 3135692 484 234284 3150848
Swap: 0 0 0
遇到无法解决的问题我们第一步想到的一定是祭出 man
命令了. 这时候发现 buff/cache
的描述并没有什么详细的解释,反而让人更加迷惑了。buff/cache
到底用在什么地方呢?
从字面已经上我们理解 `buff `是 `缓冲区`,`cache` 是 `缓存区`。具体是如何与磁盘做操作的仍然不得而知。
# man free
FREE(1) User Commands FREE(1)
NAME
free - Display amount of free and used memory in the system
SYNOPSIS
free [options]
DESCRIPTION
free displays the total amount of free and used physical and swap memory in the system, as well as the buffers and caches used by the kernel. The information is gathered by parsing /proc/meminfo. The displayed columns are:
total Total installed memory (MemTotal and SwapTotal in /proc/meminfo)
used Used memory (calculated as total - free - buffers - cache)
free Unused memory (MemFree and SwapFree in /proc/meminfo)
shared Memory used (mostly) by tmpfs (Shmem in /proc/meminfo)
buffers
Memory used by kernel buffers (Buffers in /proc/meminfo)
cache Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
buff/cache
Sum of buffers and cache
available
Estimation of how much memory is available for starting new applications, without swapping. Unlike the data provided by the cache or free fields, this field takes into account page cache and also that not all reclaimable memory slabs will be reclaimed due to items being in use (MemAvailable in
/proc/meminfo, available on kernels 3.14, emulated on kernels 2.6.27+, otherwise the same as free)
这个时候就需要我们理解 DirectIO
与 BufferIO
了。我们大多数在做程序开发的过程中使用的都是 DirectIO
而很少使用 DirectIO
。接下来我带大家了解一下DirectIO,以及linux是如何做磁盘与内存的缓存的。
首先我们需要安装 sysstat
工具套件,具体安装方法 sudo yum install sysstat
本次我们只需要 vmstat
这个命令即可。
# vmstat 1 # 每秒打印一次输出结果
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 3086572 0 340948 0 0 1 8 12 12 0 0 100 0 0
0 0 0 3086436 0 340948 0 0 0 0 1230 1467 0 1 100 0 0
0 0 0 3086436 0 340948 0 0 0 0 1190 1425 0 0 100 0 0
0 0 0 3086436 0 340948 0 0 0 0 1215 1443 0 0 100 0 0
0 0 0 3086464 0 340948 0 0 0 0 1278 1481 0 0 100 0 0
0 0 0 3086464 0 340948 0 0 0 0 1283 1419 0 1 100 0 0
vmstat 输出了很多组信息,我们主要关注 memory
,io
这两组即可。其中 free/buff/cache
大家都已经很熟了,这里输出的就是free
命令输出的结果。 bi/bo
则代表块设备
的读写。这里主要是指磁盘。
准备以下六组demo,每个读demo 读取 1GB数据,每个写demo 写入 1GB数据。
DirectIo | BufferIO | 直接操作磁盘 | |
---|---|---|---|
read | 直接IO读文件 | 缓存IO读文件 | 直接读磁盘 |
write | 直接IO写文件 | 缓存IO写文件 | 直接写磁盘 |
# man 2 open
O_DIRECT (since Linux 2.4.10)
Try to minimize cache effects of the I/O to and from this file. In general this will degrade performance, but it is useful in special situations, such as when applications do their own caching. File I/O is done directly to/from user-space buffers. The O_DIRECT flag on its own makes an effort
to transfer data synchronously, but does not give the guarantees of the O_SYNC flag that data and necessary metadata are transferred. To guarantee synchronous I/O, O_SYNC must be used in addition to O_DIRECT. See NOTES below for further discussion.
A semantically similar (but deprecated) interface for block devices is described in raw(8).
C语言关键代码如下。
#define BUF_SIZE 1024 // 这里需要注意一下
fd = open("./demo.data", O_RDONLY, 0755); // BuffIO
fd = open("./direct_io.data", O_RDONLY | O_DIRECT, 0755); // DirectIO
if (fd < 0){
perror("open ./demo.data failed");
exit(-1);
}
do {
ret = read(fd, buf, BUF_SIZE);
if (ret < 0) {
perror("read ./direct_io.data failed");
}
} while (ret > 0);
直接IO读
buff/cache 均没有明显变化,仅bi产生读数据。且读速度很慢,大概只能到2.6MB/s。稍后会解释原因
# echo 3 > /proc/sys/vm/drop_caches
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 3282608 0 154368 0 0 1 8 12 12 0 0 100 0 0
0 0 0 3282488 0 154368 0 0 0 0 1308 1460 0 0 100 0 0
0 0 0 3282488 0 154368 0 0 0 0 1309 1398 0 0 100 0 0
0 0 0 3282488 0 154368 0 0 0 72 1322 1417 0 0 100 0 0
0 0 0 3282488 0 154368 0 0 0 0 1349 1407 0 0 100 0 0
0 0 0 3282488 0 154368 0 0 0 0 1270 1410 0 1 100 0 0
0 0 0 3282520 0 154368 0 0 0 0 1210 1415 0 0 100 0 0
0 0 0 3281696 0 154368 0 0 0 0 1233 1449 0 0 100 0 0
0 0 0 3281696 0 154368 0 0 0 0 1355 1408 0 0 100 0 0
0 1 0 3281384 0 154388 0 0 1583 0 3277 4701 1 1 74 25 0
0 1 0 3281356 0 154388 0 0 2571 0 4391 6620 0 1 53 47 0
0 1 0 3282164 0 154388 0 0 2664 0 4549 7054 0 1 53 47 0
0 1 0 3282148 0 154388 0 0 2602 0 4322 6683 0 1 52 46 0
0 1 0 3282148 0 154388 0 0 2625 16 4355 6755 0 1 51 48 0
0 1 0 3282148 0 154388 0 0 2615 0 4337 6718 0 1 53 47 0
0 1 0 3282148 0 154388 0 0 2561 0 4336 6617 0 2 51 48 0
0 1 0 3282148 0 154388 0 0 2640 0 4391 6796 0 0 53 47 0
0 1 0 3282180 0 154388 0 0 2701 0 4523 7014 0 1 52 47 0
0 1 0 3282180 0 154388 0 0 2599 0 4341 6675 0 1 53 46 0
0 1 0 3281932 0 154612 0 0 2642 0 4418 6781 0 1 54 45 0
0 1 0 3281824 0 154736 0 0 2620 0 4370 6699 0 1 55 45 0
0 1 0 3281008 0 154780 0 0 2608 0 4327 6722 0 1 52 47 0
0 1 0 3281008 0 154784 0 0 2623 4 4350 6752 0 1 52 47 0
0 1 0 3281040 0 154784 0 0 2628 0 4314 6730 0 1 54 45 0
0 1 0 3281040 0 154784 0 0 2576 0 4244 6625 0 1 51 47 0
缓存IO读
buff 没有变化。bi优先出现读数据,cache随之变大(读cache开始生效)。且读速率明显高于直接IO读速率。
# echo 3 > /proc/sys/vm/drop_caches
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 3281616 0 154372 0 0 1 8 0 0 0 0 100 0 0
0 0 0 3281560 0 154372 0 0 0 0 1233 1466 0 0 100 0 0
0 0 0 3281560 0 154372 0 0 0 0 1236 1459 0 0 100 0 0
0 0 0 3281464 0 154468 0 0 0 0 1220 1471 0 0 99 0 0
0 0 0 3281208 0 154648 0 0 0 0 1318 1503 0 0 100 0 0
0 0 0 3281072 0 154752 0 0 0 4 1240 1481 0 0 100 0 0
0 1 0 3178540 0 257028 0 0 102260 0 2086 1669 1 4 64 31 0
0 1 0 3063524 0 372004 0 0 114688 0 1567 1552 1 4 50 46 0
0 1 0 2944492 0 490996 0 0 118784 0 1514 1556 1 3 50 46 0
0 1 0 2825380 0 610108 0 0 118784 0 1536 1548 1 4 50 46 0
0 1 0 2706820 0 729100 0 0 118784 0 2154 1574 1 4 50 46 0
0 1 0 2591980 0 844108 0 0 114688 0 1572 1556 1 4 49 46 0
0 1 0 2473420 0 963060 0 0 118784 0 1582 1550 1 4 53 43 0
0 1 0 2354348 0 1082220 0 0 118784 0 1524 1573 1 4 50 46 0
0 0 0 2248784 0 1186668 0 0 104448 0 1692 1913 1 3 54 42 0
0 0 0 2248868 0 1186712 0 0 0 0 1386 1486 0 0 100 0 0
0 0 0 2248872 0 1186712 0 0 0 0 1312 1455 0 0 99 0 0
0 0 0 2248872 0 1186712 0 0 0 0 1322 1446 0 0 100 0 0
0 0 0 2248872 0 1186712 0 0 0 0 1350 1438 0 0 100 0 0
0 0 0 2248872 0 1186712 0 0 0 0 1305 1433 0 1 100 0 0
0 0 0 2248928 0 1186776 0 0 64 0 1297 1463 0 0 100 0 0
0 0 0 2248928 0 1186776 0 0 0 68 1368 1467 0 0 100 0 0
0 0 0 2248928 0 1186776 0 0 0 0 1243 1436 0 0 100 0 0
0 0 0 2248928 0 1186776 0 0 0 0 1228 1409 0 0 100 0 0
直接IO写
buff/cache 均没有明显变化,仅bi产生读数据。且写速度很慢,大概只能到2.6MB/s。稍后会解释原因
# echo 3 > /proc/sys/vm/drop_caches
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 3275780 0 160284 0 0 2 8 0 0 0 0 100 0 0
0 0 0 3275668 0 160320 0 0 0 0 1124 1454 0 0 100 0 0
0 0 0 3275640 0 160320 0 0 0 0 1163 1431 1 0 100 0 0
0 0 0 3275640 0 160320 0 0 0 0 1230 1428 0 0 100 0 0
0 0 0 3275476 0 160384 0 0 64 0 1246 1449 0 0 99 0 0
2 1 0 3271848 0 160420 0 0 20 808 2885 24343 12 5 74 9 0
1 1 0 3271744 0 160420 0 0 0 2367 5924 67685 32 10 34 25 0
0 1 0 3266128 0 160420 0 0 0 2348 5965 69803 30 10 38 22 0
0 1 0 3265916 0 160420 0 0 0 2137 5684 64334 27 9 35 29 0
0 1 0 3264864 0 160420 0 0 0 2178 5768 65714 27 10 42 21 0
5 1 0 3264200 0 160420 0 0 0 2393 5964 71769 31 10 30 28 0
1 1 0 3264984 0 160420 0 0 0 2323 5906 69802 30 10 29 32 0
5 1 0 3256200 0 160420 0 0 0 2190 5650 65994 26 10 39 25 0
0 1 0 3256756 0 160424 0 0 0 2267 5872 67135 27 10 39 24 0
2 0 0 3257244 0 160424 0 0 0 2392 5987 71530 29 11 32 28 0
1 1 0 3256664 0 160424 0 0 0 2281 5844 68170 28 9 37 26 0
0 1 0 3258416 0 160424 0 0 0 2164 5820 64458 26 9 40 26 0
0 1 0 3257924 0 160424 0 0 0 2380 6012 71000 28 11 41 21 0
0 1 0 3257484 0 160424 0 0 0 2376 6054 71012 30 9 35 27 0
1 1 0 3257148 0 160424 0 0 0 2216 5779 66918 28 9 37 26 0
1 1 0 3256744 0 160424 0 0 0 2230 5835 66852 26 9 38 26 0
1 1 0 3256640 0 160424 0 0 0 2400 6062 71874 28 11 32 28 0
0 1 0 3256392 0 160424 0 0 0 2233 5816 67265 27 9 34 30 0
0 1 0 3255208 0 160424 0 0 0 1909 5383 57867 24 8 39 29 0
缓存IO写
buff 没有变化。bo优先出现写数据,cache随之变大(写cache开始生效)。且读速率明显高于直接IO写速率。
# echo 3 > /proc/sys/vm/drop_caches
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 3273116 0 111104 0 0 3 14 2 10 0 0 100 0 0
0 0 0 3272920 0 111244 0 0 60 0 1238 1518 0 0 100 0 0
0 0 0 3272924 0 111244 0 0 0 0 1220 1443 0 1 100 0 0
0 0 0 3272896 0 111244 0 0 0 0 1304 1493 0 1 100 0 0
0 0 0 3272704 0 111244 0 0 0 0 1346 1574 1 0 100 0 0
0 0 0 3272704 0 111244 0 0 0 0 1259 1490 0 0 100 0 0
0 0 0 3272652 0 111244 0 0 0 0 1220 1465 0 0 100 0 0
3 0 0 3295696 0 116424 0 0 5160 0 3658 34549 18 7 63 14 0
2 1 0 3281404 0 126820 0 0 10384 0 5811 70459 35 13 21 31 0
1 1 0 3270940 0 137300 0 0 10596 0 5985 69433 36 12 20 32 0
1 1 0 3259476 0 147720 0 0 10336 0 5844 72153 35 14 22 30 0
1 1 0 3247920 0 158136 0 0 10468 0 5743 69734 33 14 23 30 0
1 1 0 3236548 0 168740 0 0 10652 0 6009 74150 36 13 21 29 0
3 1 0 3220228 0 179180 0 0 10380 0 5770 67241 35 12 20 33 0
4 1 0 3210580 0 189416 0 0 10228 0 5827 73897 35 13 23 29 0
1 1 0 3200456 0 199084 0 0 9700 0 6112 82889 38 16 20 26 0
1 1 0 3191508 0 208668 0 0 9516 0 6133 84151 39 14 23 24 0
0 1 0 3181200 0 218476 0 0 9656 12 6209 86196 40 16 20 24 0
1 1 0 3171644 0 227672 0 0 9052 0 5927 82649 39 14 21 26 0
1 1 0 3161996 0 237196 0 0 9472 0 6000 81657 38 14 23 25 0
0 1 0 3151228 0 246736 0 0 9568 0 6166 88014 42 14 18 26 0
1 0 0 3141260 0 255864 0 0 9088 0 6087 82353 39 14 21 26 0
2 1 0 3134628 0 262612 0 0 6684 53316 5197 61027 29 10 18 43 0
1 1 0 3127916 0 272020 0 0 9396 4099 6018 76374 37 12 23 28 0
2 0 0 3117048 0 282376 0 0 10408 0 5785 68907 33 13 23 31 0
直接读磁盘
sudo dd if=/dev/vda1 of=/dev/null bs=1M count=1000
bi 出现快速增长,buff 也随之增长(块设备缓存生效),同时cache也减少了,稍后解释原因。
# echo 3 > /proc/sys/vm/drop_caches
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 125956 0 3258400 0 0 0 0 1201 1445 0 0 100 0 0
0 0 0 126624 0 3258400 0 0 0 0 1335 1561 1 0 100 0 0
0 0 0 126480 0 3258400 0 0 0 0 1363 1650 1 1 99 0 0
0 0 0 126480 0 3258400 0 0 0 0 1207 1413 0 0 100 0 0
0 0 0 125892 0 3258400 0 0 0 0 1290 1564 0 0 100 0 0
0 0 0 127048 0 3258400 0 0 0 0 1336 1608 1 1 98 0 0
0 0 0 127048 0 3258400 0 0 0 0 1248 1428 0 0 100 0 0
0 0 0 127080 0 3258400 0 0 0 0 1323 1544 0 0 100 0 0
0 1 0 122152 18432 3241376 0 0 25616 0 1596 1985 1 2 93 4 0
0 1 0 99564 141312 3140436 0 0 122880 2 1873 1880 1 3 49 48 0
0 1 0 102116 256000 3023204 0 0 114688 0 1634 1598 0 3 54 44 0
0 1 0 111536 374784 2894692 0 0 118784 0 2072 1651 1 3 54 43 0
0 1 0 116144 493568 2771440 0 0 118784 49 1988 1625 0 3 53 44 0
0 1 0 108432 612352 2660512 0 0 118784 0 1735 1570 0 3 55 43 0
0 1 0 125028 727040 2529572 0 0 114688 0 1743 1585 0 3 54 43 0
0 1 0 100944 845824 2434584 0 0 118784 0 1859 1563 0 3 50 48 0
0 1 0 109452 964608 2306756 0 0 118784 32 1885 1543 1 3 49 48 0
1 0 0 109516 1030144 2243800 0 0 66616 0 1737 1823 1 2 66 32 0
直接写磁盘
bi/bo 出现快速增长,buff 也随之增长(块设备缓存生效)
sudo dd if=/dev/zero of=/dev/vdb bs=1M count=1000
# echo 3 > /proc/sys/vm/drop_caches
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 3274060 0 110360 0 0 3 14 1 9 0 0 100 0 0
0 0 0 3273952 0 110336 0 0 0 0 1189 1391 0 0 100 0 0
0 0 0 3273868 0 110336 0 0 0 0 1227 1423 0 0 99 0 0
0 0 0 3273728 0 110344 0 0 44 0 1266 1451 0 0 100 0 0
0 1 0 3088760 167936 124100 0 0 0 0 1940 2075 1 4 70 26 0
0 1 0 2847324 405504 127916 0 0 0 118813 1659 1709 1 4 56 40 0
0 1 0 2612444 636928 131464 0 0 0 114688 1605 1498 0 4 56 41 0
0 2 0 2423300 823296 134252 0 0 0 61316 2602 1771 0 4 9 87 0
0 2 0 2263096 980992 136612 0 0 0 63544 2677 1709 0 4 1 95 0
0 2 0 2104056 1136640 140360 0 0 0 58200 2621 1642 4 4 10 82 0
0 2 0 1956420 1282048 142468 0 0 0 67328 2513 1442 0 3 15 82 0
0 2 0 1798448 1437696 144828 0 0 0 65472 2394 1470 0 4 0 95 0
1 2 0 1644972 1588856 147080 0 0 0 63755 2379 1481 0 3 0 97 0
0 2 0 1490776 1740800 149336 0 0 0 62081 2379 1480 0 4 3 93 0
0 2 0 1341268 1888256 151624 0 0 0 67780 2591 1520 0 4 0 96 0
0 2 0 1183180 2043904 153976 0 0 0 62784 2612 1545 0 3 5 92 0
0 2 0 1172080 2054144 154152 0 0 0 108652 3663 9241 0 3 32 65 0
0 2 0 1172560 2054144 154152 0 0 0 110148 4521 32787 0 3 14 83 0
1 1 0 1172500 2054144 154152 0 0 0 127376 3440 6847 1 2 50 48 0
1 0 0 1257668 1970924 152172 0 0 0 105788 3149 2776 0 2 52 46 0
0 0 0 2221616 1030144 131116 0 0 0 5 1589 2100 1 6 93 1 0
0 0 0 2221588 1030144 131124 0 0 0 0 1152 1391 0 0 100 0 0
0 0 0 2221620 1030144 131124 0 0 0 0 1283 1431 0 0 100 0 0
0 0 0 2221620 1030144 131124 0 0 0 0 1172 1353 1 0 100 0 0
0 0 0 2221620 1030144 131124 0 0 0 0 1188 1392 0 0 100 0 0
总结
buff 块设备缓存(读写)
cache 文件缓存(读写)
ps 比较旧的内核版本会有不同。
答疑
至于为什么我写的C代码读写磁盘只能达到 2.6MB/s呢?是由于博主穷,只能购买阿里云最便宜的云盘来做测试,这个云盘IOPS最大只能支持2600,而我每次读写内存buff的设置为1KB,1KB * 2600 = 2.6MB/s.
#define BUF_SIZE 1024
测试结果如下,tps 仅能达到2600
左右就上不去了。
# iostat 1
Linux 4.18.0-147.8.1.el8_1.x86_64 (dEdge-test) 2020年06月06日 _x86_64_ (2 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
0.10 0.01 0.15 0.05 0.00 99.69
Device tps kB_read/s kB_wrtn/s kB_read kB_wrtn
vda 3.25 13.95 34.19 23977687 58749917
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 0.50 0.50 0.00 99.00
Device tps kB_read/s kB_wrtn/s kB_read kB_wrtn
vda 176.00 194.00 0.00 194 0
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 1.01 22.61 0.00 76.38
Device tps kB_read/s kB_wrtn/s kB_read kB_wrtn
vda 2592.00 2592.00 0.00 2592 0
avg-cpu: %user %nice %system %iowait %steal %idle
0.50 0.00 1.00 23.50 0.00 75.00
Device tps kB_read/s kB_wrtn/s kB_read kB_wrtn
vda 2647.00 2647.00 0.00 2647 0
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 1.00 31.00 0.00 68.00
Device tps kB_read/s kB_wrtn/s kB_read kB_wrtn
vda 2608.00 2518.00 12256.00 2518 12256
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 1.01 37.69 0.00 61.31