Hadoop 2.2中正式启用了hdfs nfs功能,使得hdfs的通用性迈进了一大步。在公司让小朋友搭建了一下,然后我自己进行了一点简单的试验,有一点收获,记录在此。
理论
使用hdfs nfs功能的话,数据访问路径如上图:用户或程序通过Linux自带的nfs client访问hdfs nfs服务,然后再由nfs网关作为hdfs的客户端访问hdfs。
这张图中,中间的节点就是nfs代理服务器(hdfs nfs proxy)或nfs网关(hdfs nfs gateway)。蓝色代表该模块是一个进程或服务,绿色代表该模块是一个库。图中还画了两条虚线,下、上线分别表示操作系统级别和分布式操作系统(hadpp)级别的内核态与用户态分界。
部署
在nfs网关上部署hdfs nfs服务所需要的程序包,按hadoop 2.2的部署方式,应该存在这两个文件:
share/hadoop/common/hadoop-nfs-2.2.0.jar
share/hadoop/hdfs/hadoop-hdfs-nfs-2.2.0.jar
配置文件不需要改,使用默认即可;默认的几个配置分别是nfs的服务端口(标准的2049)、mount的监听端口(4242),还有一个dump目录(/tmp/.hdfs-nfs)与写逻辑有关,暂不明原理。
部署完成后,启用服务,需要依次启动portmap和nfs两个服务;
$ hadoop-daemon.<span style="color: #0000ff;">sh</span><span style="color: #000000;"> start portmap</p><p>$ hadoop</span>-daemon.<span style="color: #0000ff;">sh</span> start nfs3
注意,portmap需要用root用户启动(因为portmap标准端口111,小于1024,是超级资源),而nfs服务应该用hdfs的超级用户启动。如果出现冲突,应该将操作系统本身的nfs服务停掉。
启动完成后,检查确认是否可用,其中nfs_server_ip是nfs网关的地址:
$ rpcinfo -<span style="color: #000000;">p $nfs_server_ip</p><p>program vers proto port</p><p></span><span style="color: #800080;">100005</span> <span style="color: #800080;">1</span> tcp <span style="color: #800080;">4242</span><span style="color: #000000;"> mountd</p><p></span><span style="color: #800080;">100000</span> <span style="color: #800080;">2</span> udp <span style="color: #800080;">111</span><span style="color: #000000;"> portmapper</p><p></span><span style="color: #800080;">100005</span> <span style="color: #800080;">3</span> tcp <span style="color: #800080;">4242</span><span style="color: #000000;"> mountd</p><p></span><span style="color: #800080;">100005</span> <span style="color: #800080;">2</span> udp <span style="color: #800080;">4242</span><span style="color: #000000;"> mountd</p><p></span><span style="color: #800080;">100003</span> <span style="color: #800080;">3</span> tcp <span style="color: #800080;">2049</span><span style="color: #000000;"> nfs</p><p></span><span style="color: #800080;">100000</span> <span style="color: #800080;">2</span> tcp <span style="color: #800080;">111</span><span style="color: #000000;"> portmapper</p><p></span><span style="color: #800080;">100005</span> <span style="color: #800080;">3</span> udp <span style="color: #800080;">4242</span><span style="color: #000000;"> mountd</p><p></span><span style="color: #800080;">100005</span> <span style="color: #800080;">1</span> udp <span style="color: #800080;">4242</span><span style="color: #000000;"> mountd</p><p></span><span style="color: #800080;">100005</span> <span style="color: #800080;">2</span> tcp <span style="color: #800080;">4242</span> mountd
$ showmount -<span style="color: #000000;">e $nfs_server_ip</p><p>Export list </span><span style="color: #0000ff;">for</span> SY-<span style="color: #800080;">0245</span><span style="color: #000000;">:</p><p></span>/ *
挂载NFS服务
创建挂载的目录
$ <span style="color: #0000ff;">mkdir</span> /mnt/hdfs
安装mount.nfs
$ <span style="color: #0000ff;">sudo</span> apt-get <span style="color: #0000ff;">install</span> nfs-common
开始挂载
$ mount.nfs $nfs_server_ip:/ /mnt/hdfs
试用及分析
尝试访问/mnt/hdfs,试用了简单的ls、cp、rm等操作,也进行了md5sum,都可以正常使用,而且响应速度明显快于通过FsShell进行操作,这应该是得益于nfs的wcc缓存及hdfs nfs的实现中对连接的缓存;
但hdfs nfs是否是一个完全兼容标准文件系统接口的实现呢,为此我测试了一下最难处理的随机写和复写,代码如下,简单的说,就是做三次写,第一次写在文件头(字符1),第二次写在文件尾(字符2),第三次写在文件中间(字符3):
#include <stdio.h><span style="color: #000000;"></p><p>#include </span><stdlib.h><span style="color: #000000;"></p><p>#include </span><stdbool.h></p><p><span style="color: #0000ff;">void</span> usage(<span style="color: #0000ff;">char</span>*<span style="color: #000000;"> argv[]) {</p><p> fprintf(stdout, </span><span style="color: #800000;">"</span><span style="color: #800000;">%s <file_length>\n</span><span style="color: #800000;">"</span>, argv[<span style="color: #800080;">0</span><span style="color: #000000;">]);</p><p> fprintf(stdout, </span><span style="color: #800000;">"</span><span style="color: #800000;">NOTE:\n</span><span style="color: #800000;">"</span><span style="color: #000000;">);</p><p> fprintf(stdout, </span><span style="color: #800000;">"</span><span style="color: #800000;"> file_length >= 3\n</span><span style="color: #800000;">"</span><span style="color: #000000;">);</p><p>}</p><p></span><span style="color: #0000ff;">bool</span> open_and_check(FILE** fpp, <span style="color: #0000ff;">int</span><span style="color: #000000;"> op_seq) {</p><p> (</span>*fpp) = fopen(<span style="color: #800000;">"</span><span style="color: #800000;">testfile</span><span style="color: #800000;">"</span>, <span style="color: #800000;">"</span><span style="color: #800000;">r+</span><span style="color: #800000;">"</span><span style="color: #000000;">);</p><p> </span><span style="color: #0000ff;">if</span> ((*fpp) ==<span style="color: #000000;"> NULL) {</p><p> fprintf(stderr, </span><span style="color: #800000;">"</span><span style="color: #800000;">%d.Can not open test file.\n</span><span style="color: #800000;">"</span><span style="color: #000000;">, op_seq);</p><p> </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">false</span><span style="color: #000000;">;</p><p> }</p><p> </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">true</span><span style="color: #000000;">;</p><p>}</p><p></span><span style="color: #0000ff;">int</span> main(<span style="color: #0000ff;">int</span> args, <span style="color: #0000ff;">char</span>*<span style="color: #000000;"> argv[]) {</p><p> </span><span style="color: #0000ff;">if</span> (args != <span style="color: #800080;">2</span><span style="color: #000000;">) {</p><p> usage(argv);</p><p> </span><span style="color: #0000ff;">return</span> -<span style="color: #800080;">1</span><span style="color: #000000;">;</p><p> }</p><p> </span><span style="color: #0000ff;">int</span> length = atoi(argv[<span style="color: #800080;">1</span><span style="color: #000000;">]);</p><p> </span><span style="color: #0000ff;">if</span> (length < <span style="color: #800080;">3</span><span style="color: #000000;">) {</p><p> fprintf(stdout, </span><span style="color: #800000;">"</span><span style="color: #800000;">file_length must be at least 3\n</span><span style="color: #800000;">"</span><span style="color: #000000;">);</p><p> </span><span style="color: #0000ff;">return</span> -<span style="color: #800080;">1</span><span style="color: #000000;">;</p><p> }</p><p> fclose(fopen(</span><span style="color: #800000;">"</span><span style="color: #800000;">testfile</span><span style="color: #800000;">"</span>, <span style="color: #800000;">"</span><span style="color: #800000;">w+</span><span style="color: #800000;">"</span><span style="color: #000000;">));</p><p> FILE</span>*<span style="color: #000000;"> fp;</p><p> </span><span style="color: #0000ff;">int</span> op_seq = <span style="color: #800080;">1</span><span style="color: #000000;">;</p><p> </span><span style="color: #0000ff;">if</span> (!open_and_check(&<span style="color: #000000;">fp, op_seq))</p><p> </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> op_seq;</p><p> putc(</span><span style="color: #800000;">'</span><span style="color: #800000;">0</span><span style="color: #800000;">'</span>+op_seq, fp); <span style="color: #008000;">//</span><span style="color: #008000;"> '1'</span></p><p><span style="color: #000000;"> fclose(fp);</p><p> op_seq</span>++<span style="color: #000000;">;</p><p> </span><span style="color: #0000ff;">if</span> (!open_and_check(&<span style="color: #000000;">fp, op_seq))</p><p> </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> op_seq;</p><p> fseek(fp, length, SEEK_SET);</p><p> putc(</span><span style="color: #800000;">'</span><span style="color: #800000;">0</span><span style="color: #800000;">'</span>+op_seq, fp); <span style="color: #008000;">//</span><span style="color: #008000;"> '2'</span></p><p><span style="color: #000000;"> fclose(fp);</p><p> op_seq</span>++<span style="color: #000000;">;</p><p> </span><span style="color: #0000ff;">if</span> (!open_and_check(&<span style="color: #000000;">fp, op_seq))</p><p> </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> op_seq;</p><p> fseek(fp, length</span>/<span style="color: #800080;">2</span><span style="color: #000000;">, SEEK_SET);</p><p> putc(</span><span style="color: #800000;">'</span><span style="color: #800000;">0</span><span style="color: #800000;">'</span>+op_seq, fp); <span style="color: #008000;">//</span><span style="color: #008000;"> '3'</span></p><p><span style="color: #000000;"> fclose(fp);</p><p> </span><span style="color: #008000;">//</span><span style="color: #008000;">op_seq++;</span></p><p> <span style="color: #0000ff;">return</span> <span style="color: #800080;">0</span><span style="color: #000000;">;</p><p>}</span>
注:参数n是第二次写之前做的偏移量,因而实际文件长度会是n+1
1. 首先用一个小文件做测试,如下:
root@xxx:/mnt/hdfs/tmp# ./a.out <span style="color: #800080;">3</span><span style="color: #000000;"></p><p>root@xxx:</span>/mnt/hdfs/tmp# <span style="color: #0000ff;">ls</span> -<span style="color: #000000;">l testfile </p><p></span>-rw-r--r-- <span style="color: #800080;">1</span> root root <span style="color: #800080;">4</span> Nov <span style="color: #800080;">27</span> <span style="color: #800080;">18</span>:<span style="color: #800080;">04</span><span style="color: #000000;"> testfile</p><p>root@xxx:</span>/mnt/hdfs/tmp# <span style="color: #0000ff;">cat</span><span style="color: #000000;"> testfile </p><p></span><span style="color: #800080;">132</span>
结果都符合预期;
2. 如果再重复执行一次呢?
root@xxx:/mnt/hdfs/tmp# ./a.out <span style="color: #800080;">3</span><span style="color: #000000;"></p><p>Segmentation fault (core dumped)</span>
从hdfs nfs网关的日志中可以找到出错的原因:
2013-11-27 18:11:53,695 ERROR org.apache.hadoop.hdfs.nfs.nfs3.RpcProgramNfs3: Setting file size is not supported when setattr, fileId: 20779
不支持重置文件大小,也就是不支持truncate,至少还“正确地”返回了失败;
3. 改变文件大小测试一下
root@SY-<span style="color: #800080;">0266</span>:/mnt/hdfs/tmp# ./a.out <span style="color: #ff0000;">4096</span> && <span style="color: #0000ff;">ls</span> -lh --full-<span style="color: #0000ff;">time</span> testfile && <span style="color: #0000ff;">sleep</span> <span style="color: #800080;">5</span> && <span style="color: #0000ff;">ls</span> -lh --full-<span style="color: #0000ff;">time</span><span style="color: #000000;"> testfile</p><p></span>-rw-r--r-- <span style="color: #800080;">1</span> root root <span style="color: #ff0000;">2.1K</span> <span style="color: #800080;">2013</span>-<span style="color: #800080;">11</span>-<span style="color: #800080;">27</span> <span style="color: #800080;">22</span>:<span style="color: #800080;">22</span>:<span style="color: #800080;">40.572000000</span> +<span style="color: #800080;">0800</span><span style="color: #000000;"> testfile</p><p></span>-rw-r--r-- <span style="color: #800080;">1</span> root root <span style="color: #ff0000;">1</span> <span style="color: #800080;">2013</span>-<span style="color: #800080;">11</span>-<span style="color: #800080;">27</span> <span style="color: #800080;">22</span>:<span style="color: #800080;">22</span>:<span style="color: #800080;">40.572000000</span> +<span style="color: #800080;">0800</span><span style="color: #000000;"> testfile</p><p>root@SY</span>-<span style="color: #800080;">0266</span>:/mnt/hdfs/tmp# <span style="color: #0000ff;">rm</span><span style="color: #000000;"> testfile </p><p>root@SY</span>-<span style="color: #800080;">0266</span>:/mnt/hdfs/tmp# ./a.out <span style="color: #ff0000;">4095</span> && <span style="color: #0000ff;">ls</span> -lh --full-<span style="color: #0000ff;">time</span> testfile && <span style="color: #0000ff;">sleep</span> <span style="color: #800080;">5</span> && <span style="color: #0000ff;">ls</span> -lh --full-<span style="color: #0000ff;">time</span><span style="color: #000000;"> testfile</p><p></span>-rw-r--r-- <span style="color: #800080;">1</span> root root <span style="color: #ff0000;">4.0K</span> <span style="color: #800080;">2013</span>-<span style="color: #800080;">11</span>-<span style="color: #800080;">27</span> <span style="color: #800080;">22</span>:<span style="color: #800080;">25</span>:<span style="color: #800080;">17.606000000</span> +<span style="color: #800080;">0800</span><span style="color: #000000;"> testfile</p><p></span>-rw-r--r-- <span style="color: #800080;">1</span> root root <span style="color: #ff0000;">4.0K</span> <span style="color: #800080;">2013</span>-<span style="color: #800080;">11</span>-<span style="color: #800080;">27</span> <span style="color: #800080;">22</span>:<span style="color: #800080;">25</span>:<span style="color: #800080;">17.606000000</span> +<span style="color: #800080;">0800</span> testfile
可以发现从4K开始,向上的文件已经无法正常完成这个测试了,文件会隐性的丢失数据。这应该与hdfs nfs对随机写和复写的实现有关,我没有具体研究代码。
从这个简单测试可以得出结论,hdfs nfs可以进行简单的文件读写、使用常用的shell命令操作,但决不可以直接当本地文件系统、通过程序进行访问。