9.3 tmpfs
前几天闲来无事翻微薄,有人写道:“曾经偷情被游街,如今二奶喊干爹;曾经撞人忙救人,如今撞人再杀人;曾经私情偷着干,如今淫乱存U盘;曾经献血为扶伤,如今慈善越重洋;曾经相好牵肚肠,如今小三炫富忙;曾经摩托都挺酷,如今地铁都追尾;曾经县长做皮卡,如今少年开宝马;曾经精英成右派,如今牛逼全二代。”不禁感慨万千,这世道真是变了。
曾经内存比金子都贵,现在已经白菜价了。有时候我们在设计系统时,如果磁盘已经忙不过来了,完全可以让内存帮帮忙。不但不会有什么损失,整体执行效率几乎会有一个数量级的提升。tmpfs就是让你这么干的一个好帮手。
9.3.1 背景
在开始展开对tmpfs的论述之前,先要介绍一下RamDisk。
RamDisk是将一部分固定大小的内存当作分区来使用。相对于传统的硬盘文件访问来说,这可以极大的提高在其上进行的文件访问速度。这是一种非常古老的技术,最早可以追溯到上个世纪80年代初,我们耳熟能详的MS-DOS在其2.0版本就加入了对RamDisk的支持。自然,这么先进的Linux也会不甘寂寞的,这种技术是直接编译进内核的。
对于RamDisk的读写与读写普通磁盘分区,从本质上说是没有任何区别的。不过需要注意的是:RamDisk中的数据是保存在物理内存中的,即便较为先进的实现可以将不常使用的数据交换出去,但也是要占很大一部分内存空间的。而且一旦系统断电,RamDisk中的任何数据都会随之灰飞烟灭。
不过内存与硬盘在速度的上的较量是不言而喻的,稍微懂点计算机技术的人都清楚这个问题。只要合理的利用RamDisk时可以得到很大的速度提升的。比如使用RamDisk来做Web缓存可以极大的提高页面加载速度。
但是随着需求的不断增加以及人们在不知足中的不断探索中,RamDisk的缺点越来越明显。典型的就是非常浪费物理内存空间。因为只要你设定的RamDisk有多大,那么它就要占用多大的物理内存,即便你一个字节都没用。所有RamDisk都需要进行格式化,如果你选择了一个错误的文件系统,还会造成一定的内存浪费。虽然内存已经不是粒粒如金,但是怎么也得跟大白菜一个价,而硬盘还远不如大白菜值钱呢。必须得改进,应该更高效的利用内存,不能死扣书本,什么时间换空间,空间换时间,这玩意就快要跟封建迷信没什么区别了。一个好的算法,空间性能和时间性能都很好。
另外,在不断的生产实践中,人们发现,大量的临时文件其实很影响程序的性能。于是开始有人把程序产生的临时文件放入RamDisk来提高整体性能。其实还是拿Web服务器说,大量的缓存文件就可以看作是一种临时文件。因为临时文件有一个特性就是它是临时的,即便丢了,也无大碍。
鉴于上述的一些需求,终于在Linux 2.4内核中,引入了一个全新的文件系统――tmpfs,来满足大家对“时空”双重性能的渴望。
9.3.2 tmpfs文件系统
tmpfs类似于RamDisk,它既可以使用内存,也可以使用交换分区。tmpfs文件系统使用虚拟内存(我们后面简称VM)子系统的页面来存储文件,tmpfs自己不需要知道这些页面是在物理内存中还是在交换分区中,一切由VM说了算。所以,tmpfs跟普通的用户进程差不多,使用的只是某种形式的虚拟内存。
tmpfs的实现与很多人所理解的完全不同,它跟其它文件系统如:ext3、ext2、ReiserFS等是完全不一致的,它们在Linux中都被称为块设备(即读写大块数据的设备,与之相对应的是字符设备,如键盘、鼠标等)。而tmpfs是直接建立在VM之上的,你用一个简单的mount命令就可以创建tmpfs文件系统了,不需要什么格式化。事实上就是十分想要格式化你也做不到,因为地球上就不存在类似mkfs.tmpfs这样的命令。
你或许想知道你刚刚挂接的tmpfs文件系统到底有多大。这个问题的答案有点意外:不知道!tmpfs刚被挂接时只有很小的空间,但是随着文件的复制和创建,tmpfs文件系统驱动程序会分配更多的VM,并按照需求动态地增加文件系统的空间。当有文件被删除时,tmpfs文件系统驱动程序会动态的减少文件系统并释放VM资源。循环利用,按需分配。因为毕竟VM比磁盘金贵些,还是慎用为妙。
说道速度,虽然它使用的是VM,但是人家也是内存,毕竟蜗牛也是牛啊。所以用快如闪电来形容一点都不为过。典型的tmpfs文件系统会完全驻留在物理内存中,读写几乎可以说是不用眨眼睛的。即使用了交换分区,性能仍然是卓越的,只要VM比较空闲了,一部分tmpfs的文件就会被移动到物理内存中,而且不常用的文件,也会被自动交换出去,腾出更多地方给用户进程。显然tmpfs遵循着VM子系统的整体调度策略,相对于RamDisk拥有更好的整体协调性和灵活性。
不过说到底,tmpfs还是一个基于内存的文件系统,不要指望这种文件系统会提供什么持久性支持,想想都是错误。因为在这个领域看来,那是没有任何意义的功能。而且人家名字也起的好――tmpfs――就是告诉你,别把这儿当安家立业的世外桃源,这里只是一个驿站,风景虽然恬意,但是想留宿,门都没有。
9.3.3 tmpfs实战
我一直有一个习惯,就是总不甘于就事论事,一定要把大家往“坑”里带,所以接下来的内容大家要注意,“坑”已挖好,就等你来了。
1.使用tmpfs
即便tmpfs使用起来可以用轻松加愉快来形容,不过我总是要唐僧几句,讲讲如何使用tmpfs的,乃至在实际中要遇到的问题。
要使用tmpfs最基本的就是要把它挂接到文件系统的某一个节点。只需要使用下面这个简单的命令:
#mount tmpfs/tmp �Ct tmpfs
这个时候/tmp目录就开始使用tmpfs文件系统了。所有使用/tmp目录作为临时目录的程序都会得到很好的速度提升。简单吧?不过注意,问题很快就会出现。最典型的问题就是用光了VM,虽然不能直接扔给大家一句:“后果自负”。但是你还真得处理好这个后果。
首先,tmpfs是根据需要动态增大或减少内存的事实就让人有一个疑惑:如果tmpfs文件系统增大到耗尽了所有VM,结果会是怎样?问题到了这个地步,真的很麻烦。早期内核,比如2.4.4以前的内核,直接宕机,只能重启了事。虽然2.4.6以后的内核做了调整,不过问题仍然严重,只是不至于宕机罢了。那么会是什么样一个结果呢?不能再向tmpfs中写入数据这个自然不在话下,系统还会变得很慢,让你认为它跟宕机差不多了,因为其它程序分配不到内存了。这个时候你让管理员来给你收拾烂摊子,估计也就是重启了事。好了,有人马上会说,你说的都是2.4的内核问题,如今的2.6乃至3.0就不是这样了。的确,但是也好不到那里去。因为内核有一个内建的最后防线,用来在没有可用内存的时候来释放内存。那么它是根据什么来释放内存呢?答案是谁用的内存多,就干掉谁。你马上就会说,显然tmpfs占用内存多啊?不过马上你就会很失望,tmpfs不能被干掉。因为它是内核的一部分,并不是用户进程,而且也没有什么好办法可以让内核找出是哪个进程占满了tmpfs文件系统。所以,内核会错误地攻击它能找到的最大的占用内存的程序,通常就是X服务器。如果你碰巧在使用它,你的X服务器就会被终止,可是引起问题的真凶并没有得到法办。如果碰巧你还在跟利用X提供的图形功能跟刚刚追到女朋友聊天约会,此时对方刚刚发来见面地点还没来得及看,你是不是会很抓狂呢?
其实tmpfs的设计者们早就想到了这个问题,于是提供了一个参数,让你来设定tmpfs最大占用量。可以使用如下命令:
# mount tmpfs /tmp -t tmpfs �Co size=64m
这个命令告诉内核,/tmp所挂接的的tmpfs最多只能使用64MB的内存。在实际使用中,这个上限未必够用,或者仍然会导致VM被用光。比较好的方法是利用top工具,来监控一下你的系统在高峰期的内存用量。注意,交换分区要一同算在内。那么高峰期的余量就可以考虑成为tmpfs的上限值。不过最好这个上限值再稍微小那么一点,这样可以给你的系统留出一些余量,来应对一下突发事件。
除了容量限制,还可以通过使用nr_inodes=x参数限制一下索引节点数量,可以理解为限制了最大的文件数量。这个x可是是一个简单的整数,后面还可以跟一个k、m或g来制定千、百万或十亿个索引节点。
2.绑定挂接
前面说过,我喜欢把人往“坑”里带,那么现在这个“坑”就在这里了,至于你跟不跟我来,你说了不算!
为什么我说这是一个“坑”呢?因为绑定挂接跟tmpfs没有半毛钱关系,引入这个话题其实是为了能够更加灵活的运用tmpfs,而且通过这个例子,大家可以举一反三,在其它文件系统上同样可以运用绑定挂接这个技术,至少会给自己带来一些方便。有句广告语不是说过嘛:他好,我也好!
那么什么是绑定挂接呢?我无法用一句话清晰明了的概括出来,不过我可以用一个它的行为来描述一下。就是可以通过mount命令的一个参数,将一个已经挂接的文件系统全部或部分挂接到另外一个挂接点上。这里有一个特性(注意,开始挖坑了),任何挂接在绑定挂接文件系统内部的挂接点的文件系统都不会随之挂接。是不是很绕口(显然“坑”很深)?那么我举一个例子说明一下。
我自己的目录是/home/jagen,使用命令“mount --bind / /home/jagen”将系统根目录绑定挂接到了我自己的目录。现在访问我自己的目录跟访问根目录没有任何区别。注意,我自己的目录的访问权限已经跟根目录是相同的了,千万不要自作多情的拿权限开涮,这是没有任何意义的。一般对于根目录的划分是/boot采用一个分区,/usr采用一个分区,/home采用一个分区,/var采用一个分区,其它的采用一个分区。如果时这么分配的磁盘分区的,那么在进行绑定挂接后,我的目录中/home/jagen/usr这个目录就是空的,其它类推。只有分配给“/”根的这个分区内容被真正挂接到了我的目录(这回应该能从“坑”里爬出来了吧?)。
绑定挂接tmpfs的实例是这样操作的,见下面命令:
#mkdir/dev/shm/tmp
# chmod 1777/dev/shm/tmp
# mount --bind/dev/shm/tmp /tmp
这里解释一下,/dev/shm目录就是大多数发行版提供的一个默认的tmpfs文件系统,这是POSIX标准所规定,因为POSIX标准的共享内存就是利用tmpfs所实现的。不过目前大家常用的还是System V的共享内存,POSIX的共享内存不是很流行。这只是当前的情况,将来会怎么样,我是说不清楚。既然/dev/shm就是现成的tmpfs,那么就在它下面创建一个新的tmp目录。注意要修改权限使得所有用户都能访问,因为这是LBS对/tmp的强制规范。最后使用绑定挂接,将/dev/shm/tmp这个tmpfs绑定挂接到/tmp上,这样所有使用/tmp目录作为临时目录的程序都会受益于tmpfs所提供的超高性能。另外,这样操作有一个好处就是,/dev/shm是由发布版本厂商所提供的标准tmpfs,它的最大容量限制一般可以被认为是最为优秀的,直接拿来用总比自己动手分析要容易得多。
再针对绑定挂接多说几句。绑定挂接对于程序员来说,是非常实用的一个小帮手。我们假设这样一个场景。在一些特定开发场景,为了测试一些新功能,必须修改某个系统文件。但是这个系统文件又是放在只读文件系统上(只读只是相对的,只是修改这个文件非常麻烦罢了),或者这个文件虽然可写,但是对自己没什么把握,不敢直接修改。那么就可以利用mount --bind绑定挂接一个新的文件系统,你所有的修改都只是操作这个新的文件系统,老的是不会被改动的。当操作完毕,umount一下,就完全恢复了。即便弄出问题,重新启动一下,就没有任何问题了。
3.应用加速
淘宝,作为一个业内最著名的互联网公司,Web页面是我们对外提供服务的标准接口之一。我想在做的任何一个人,无不关心Web页面的显示速度。那么就多叨唠先叨唠几句用tmpfs来加速web页面。
说到web,apache略显老态龙钟,这个时代已经是nginx大行其道的时代,那就看看如何加速nginx吧。还记得前面说过的,使得/tmp目录成为tmpfs的方法吧。不过在实际使用中,我们可能不太想改变/tmp的性质,毕竟还有很多其他程序也要用。那么我们就单独创建一个/nginx_tmp目录好了。至于是不是这样,你自己决定就行,然后在它上面挂接tmpfs。可以利用/dev/shm,也可以自己设定大小,随你。然后在修改nginx.conf文件,添加如下内容:
client_body_temp_path/nginx_tmp/client_body_tmp;
prox_temp_path /nginx_tmp/proxy_temp;
fastcgi_temp_path/nginx_tmp/fastcgi_temp;
在Web领域,另外一个常用工具当推squid了。这个服务所面临的问题是,当访问量过大时,会急剧增加Linux系统的负载(Linux的系统负载值可以简单理解为是系统进程调度队列中,处于等待状态的进程数量)。如果利用tmpfs,可以有效降低系统负载。一个参考值就是从12降到了0.3,很是可观。具体的操作也非常简单,如何挂接tmpfs我不再复述,都是一样的。修改squid.conf,这个文件的路径一般是/etc/squid/squid.conf。修改内容只有一行,如下:
cache_dir ufs/squid_tmp 256 16 256
这里对一些参数进行一下解释:
lufs,缓存存储机制,常用的就是ufs机制。不过依赖于操作系统的不同,可以选择不同的存储机制,具体可以参考squid的使用手册
l/squid_tmp,指定的缓存路径,在这里就是一个tmpfs文件系统
l第一个256,制定缓存目录的大小,这是squid能使用的缓存上限,可以根据实际情况酌情设定
l16和第二个256,针对ufs机制,squid在缓存目录下创建二级目录树。它们分别制定了第一级和第二级目录的数量。默认就是16和256,你可以根据实际情况,酌情设定
前面讲的基本上都是静态页面的加速,虽然有涉及到fastcgi,但是并不能说明全部问题。我再列举一个动态页面加速的例子,利用tmpfs如何加速php。对于java,道理类似。tmpfs针对php的优化主要是提高session文件的访问效率,这个只要修改一下php.ini文件即可。然后php程序本身所产生的缓存文件或临时文件,直接在程序中修改就好了。
除了了加速Web之外,还可以加速一些软件,典型比如SQLite和BDB。这两个可以算是当今比较流行的桌面级数据库。
SQLite顾名思义,是一种SQL的数据库,拥有优化的SQL引擎和数据存储引擎,虽然被成为桌面级数据库,但是性能并不逊色于任何企业级DBM,结合tmpfs可以利用它作为临时表,存放一些临时数据很好用。虽然SQLite本身支持内存数据库,但是对于一个老且运行可靠的系统来说,不用修改任何代码不是一个更好的主意吗?
至于BDB,也就是BerkeleyDB,系出名门,现在已经被Oracle收编旗下。BDB是一个典型的NoSQL数据库,性能惊人,支持Key-Value存储。最典型的应用就是搜索引擎的网络爬虫。结合tmpfs使用,会得到更多意想不到的效果。
当然,对于这些应用要注意的是,不能乱用tmpfs造成内存紧张,而且一旦关机所有数据都要灰飞烟灭。不过有一些笨办法可以处理在系统正常关机下的数据转存问题,在后续章节中会有详细介绍。另外也可以自行定义一些持久化策略,使得tmpfs文件系统中的内容能够被保存下来。因为在服务器系统设计中,有一个很重要的原则就是I/O越少,性能越高,合理利用tmpfs可以有效降低系统I/O次数,因而提高性能。