内存盘:你为什么可能会喜欢它,以及一个性能难题 内存盘:你为什么可能会喜欢它,以及一个性能难题

内存盘:你为什么可能会喜欢它,以及一个性能难题

内存盘(或简称 RAMdisk)是一种将内存的一部分当作磁盘来使用的方法。仔细想想,它的优缺点就显而易见了:

  • RAM 的速度比最快的磁盘还要快得多,因此在 ramdisk 上进行操作比使用 NVMe、SSD 以及机械硬盘要快得多。
  • 然而,RAM也是易失性的。如果服务器重启或崩溃,RAM磁盘上的所有内容都会丢失。

内存盘是存放缓存、会话文件和其他临时数据的绝佳场所。我甚至见过这样的配置:将数据库日志(例如 Postgres 预写日志、Oracle 重做日志等)保存在内存盘上,并由周围的系统将其复制到永久存储,在关机/启动时进行恢复,同时定期执行备份以防崩溃。

我们来看看如何设置和使用内存盘。为了好玩,我启动了一个容量为 24GB 的 Linode 系统:

 root@bigmem:~# free -m
               全部的        用过的        自由的      共享  增益/缓存  可用的
内存:          24049          65      23913          0          70      23732
交换:            511          0        511

你可能会认为需要重新配置系统并重启,但其实要简单得多。

 root@bigmem:~# mkdir /ramdisk
root@bigmem:~# mount -t tmpfs -o size=16G ramdisk16 /ramdisk
root@bigmem:/ramdisk# df -h /ramdisk
文件系统      尺寸  已使用 可用 使用率 安装在
ramdisk16        16G    0  16G  0% /ramdisk

就是这样。注意,这里的“ramdisk16”是我随意选择的名称。我认为这个参数是必需的,因为挂载命令的通用格式是“mount [类型] [选项] [设备] [挂载点]”。这里并没有真正意义上的“设备”,所以使用了占位符。

请注意,“ramdisk”这个术语有点误导性,因为你可能会认为你会得到某种 /dev/sdX 之类的设备来创建文件系统等等。你可以把 ramdisk 看作是预先创建的磁盘上的“ram 文件系统”。

现在我可以对 /ramdisk 做任何操作——创建目录、添加文件等等。但是当我卸载它(或系统重启)时,所有操作都会丢失。我可以在 /etc/fstab 中将其永久化,但这当然意味着重启后 ramdisk 会被重新创建为一个空挂载点:

 ramdisk16 /ramdisk tmpfs defaults,size=16G 0 0

那么,它真的更快吗?让我们拭目以待。敬请期待一个令人费解的问题!

IOping

使用 ioping 工具,SSD 磁盘的状态如下:

root@bigmem:/# ioping .
4 KiB <<< . (ext4 /dev/sda 19.2 GiB): 请求=1 时间=82.2 微秒(预热)
4 KiB <<< . (ext4 /dev/sda 19.2 GiB): 请求=2 时间=933.8 微秒
4 KiB <<< . (ext4 /dev/sda 19.2 GiB): 请求=3 时间=304.4 微秒
4 KiB <<< . (ext4 /dev/sda 19.2 GiB): 请求=4 时间=306.1 微秒
4 KiB <<< . (ext4 /dev/sda 19.2 GiB): 请求=5 时间=279.4 微秒
4 KiB <<< . (ext4 /dev/sda 19.2 GiB): 请求=6 时间=337.5 微秒
^C
--- . (ext4 /dev/sda 19.2 GiB) ioping 统计信息 ---
5 个请求已完成,耗时 2.16 毫秒,读取 20 KiB,IOPS 为 2.31 k,传输速度为 9.04 MiB/s。
生成了 6 个请求,耗时 5.63 秒,文件大小为 24 KiB,IOPS 为 1,吞吐量为 4.26 KiB/s。
最小值/平均值/最大值/标准差 = 279.4 微秒 / 432.2 微秒 / 933.8 微秒 / 251.4 微秒
同时,在内存盘上执行相同的操作:

root@bigmem:/# cd /ramdisk/
root@bigmem:/ramdisk# ioping .
4 KiB <<< . (tmpfs ramdisk16 16 GiB): 请求=1 时间=1.76 微秒(预热)
4 KiB <<< . (tmpfs ramdisk16 16 GiB): 请求=2 时间=8.43 微秒
4 KiB <<< . (tmpfs ramdisk16 16 GiB): 请求=3 时间=12.1 微秒
4 KiB <<< . (tmpfs ramdisk16 16 GiB): 请求=4 时间=11.8 微秒
4 KiB <<< . (tmpfs ramdisk16 16 GiB): 请求=5 时间=10.2 微秒
4 KiB <<< . (tmpfs ramdisk16 16 GiB): 请求=6 时间=9.30 微秒
4 KiB <<< . (tmpfs ramdisk16 16 GiB): 请求=7 时间=9.66 微秒
^C
--- . (tmpfs ramdisk16 16 GiB) ioping 统计信息 ---
6 个请求已完成,耗时 61.5 微秒,读取数据 24 KiB,IOPS 为 97.6 k,速度为 381.3 MiB/s。
生成了 7 个请求,耗时 6.48 秒,文件大小为 28 KiB,IOPS 为 1,吞吐量为 4.32 KiB/s。
最小值/平均值/最大值/标准差 = 8.43 微秒 / 10.2 微秒 / 12.1 微秒 / 1.32 微秒

我原本打算计算百分比差异,但我觉得没必要了。

dd 写入和读取

让我们传输一个 1GB 的文件:

 root@bigmem:/ramdisk# time dd if=/dev/zero of=/ramdisk/1gbfile bs=1MB count=1024
1024+0 条记录
1024+0 条记录
已复制 1024000000 字节(1.0 GB,977 MiB),耗时 0.38423 秒,速度为 2.7 GB/秒
实际 0 分 0.450 秒
用户 0m0.000s
系统 0分0.448秒

现在,这里有个有趣的现象。我原本以为 2GB 的文件需要两倍的时间才能下载完成,事实也的确如此。但是 4GB 的文件需要多长时间呢?8GB 的​​文件呢?显然,下载时间并非线性增长。以下是一些示例:

内存盘文件大小使用 dd 分配时间
1GB大约0.4秒
2GB大约0.75秒
4GB大约32秒
8GB大约145秒

我的理论是,物理主机服务器可以轻松获取 1GB 或 2GB 的内存,而且可能本身就闲置着这么多内存。但要找到 8GB 的​​可用内存,它就必须更费力地搜索。我打个比方:去停车场想停一辆车和想停 32 辆车——停车场虽然有空位,但前者需要花更多时间才能找到空位。

向社区寻求解释

回到我们的内存盘。让我们把内存盘和 NVMe 做个比较。内存盘:

 root@bigmem:/ramdisk# for run in 1 2 3 ; do time dd if=/dev/zero of=/ramdisk/2gbfile bs=1MB count=2048 ; done
2048+0 条记录
2048+0 条记录已发布
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.727246 秒,速度为 2.8 GB/秒

实际 0 分 0.853 秒
用户 0m0.000s
系统 0分0.851秒
2048+0 条记录
2048+0 条记录已发布
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.729331 秒,速度为 2.8 GB/秒

实际 0 分 0.855 秒
用户 0m0.000s
系统 0分0.853秒
2048+0 条记录
2048+0 条记录已发布
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.726534 秒,速度为 2.8 GB/秒

实际 0 分 0.853 秒
用户 0m0.000s
系统 0分0.851秒

大约0.85秒。NVMe:

 root@bigmem:/ramdisk# for run in 1 2 3 ; do time dd if=/dev/zero of=/2gbfile bs=1MB count=2048 ; done
2048+0 条记录
2048+0 条记录已发布
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 1.86907 秒,速度为 1.1 GB/秒

实际 0 分 2.059 秒
用户 0m0.000s
系统 0m1.441s
2048+0 条记录
2048+0 条记录已发布
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 1.66434 秒,速度为 1.2 GB/秒

实际 0 分 1.855 秒
用户 0m0.000s
系统 0m1.437s
2048+0 条记录
2048+0 条记录已发布
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 1.69488 秒,速度为 1.2 GB/秒

实际 0 分 1.887 秒
用户 0m0.004s
系统 0m1.424s

现在我们来尝试读取数据:

 root@bigmem:/ramdisk# for run in 1 2 3 ; do time dd if=/ramdisk/2gbfile of=/dev/null bs=1024k ; done
1953+1 条记录
1953+1 条记录
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.183827 秒,速度为 11.1 GB/秒

实际 0 分 0.185 秒
用户 0m0.000s
系统 0 分 0.185 秒
1953+1 条记录
1953+1 条记录
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.18002 秒,速度为 11.4 GB/秒

实际时间 0 分 0.181 秒
用户 0m0.000s
系统 0 分 0.181 秒
1953+1 条记录
1953+1 条记录
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.180192 秒,速度为 11.4 GB/秒

实际时间 0 分 0.181 秒
用户 0m0.000s
系统 0 分 0.181 秒
root@bigmem:/ramdisk# for run in 1 2 3 ; do time dd if=/2gbfile of=/dev/null bs=1024k ; done
1953+1 条记录
1953+1 条记录
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.172908 秒,速度为 11.8 GB/秒

实际时间 0 分 0.174 秒
用户 0m0.000s
系统 0 分 0.174 秒
1953+1 条记录
1953+1 条记录
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.235156 秒,速度 8.7 GB/秒

实际时间 0 分 0.236 秒
用户 0m0.000s
系统 0分0.236秒
1953+1 条记录
1953+1 条记录
已复制 2048000000 字节(2.0 GB,1.9 GiB),耗时 0.171044 秒,速度为 12.0 GB/秒

实际时间 0 分 0.172 秒
用户 0m0.000s
系统 0 分 0.172 秒

从这些数据来看,我猜测操作系统缓存对磁盘性能提升很大,这当然是好事。正如我们所预期的,内存盘的延迟和写入速度都远胜于实体存储设备。尤其是在写入方面,内存盘执行单个CPU操作码的速度远低于实体存储设备,后者需要与存储设备交互,请求其提交数据并等待响应。当然,实体存储设备本身的速度也很快,但肯定不如那些始终在主内存内进行操作的设备快。