【Docker x Hadoop x Java API】xxx could only be written to 0 of the 1 minReplication nodes,There are 3

416 阅读6分钟

脑阔疼

本人使用 docker 搭建的 hadoop 集群:【Docker x Hadoop】使用 Docker 搭建 Hadoop 集群(从零开始保姆级)
今天在使用 Java API 对 Docker 搭建的 Hadoop 集群进行操作时
创建和删除文件夹可以,但一到了上传和下载文件,就疯狂报错
上传的文件在集群中可以看到,但大小均为 0 ,主要的异常报错主要如下:


File /idea/warn.log could only be written to 0 of the 1 minReplication nodes. There are 3 datanode(s) running and 3 node(s) are excluded in this operation.


Excluding datanode DatanodeInfoWithStorage[xxx:9866,DS-2520925f-7afd-4f12-89e6-09bee8d8297b,DISK]


Exception in createBlockOutputStream blk_1073741861_1038


解决方法

在分析之后(详细可以看下边分析),我选择了一种折中的方法,只让一个结点能够写入数据,然后由于集群的同步机制,其他的容器若是没有刚上传的这个文件,而又被 namenode 指定为存放该文件,那它将会从有数据的这个容器中同步我们上传的这个文件,最后达成的效果和一次性上传到每个容器时一样的

① 第一步:配置以容器主机名访问 datanode

Configuration conf = new Configuration();
conf.set("dfs.client.use.datanode.hostname", "true");

② 第二步:将所有容器的主机名映射到服务器主机域名(统一由主机映射到各个容器)

修改位置;C:\Windows\System32\drivers\etc\hosts
添加内容:IP地址 域名(主机名),比如说我的:

(xxxx 全都配置成一样的,即各个容器主机名都指向服务器主机)
xxxx hadoop001
xxxx hadoop002
xxxx hadoop003

② 第三步:设置能写入数据的 datanode 的端口映射

这里使用的是动态为 Docker 容器添加端口映射,为补救措施,若是还没有启动容器,则可以使用 -p 进行端口映射
有兴趣进一步了解动态端口映射的,可以走这里:【Docker之轨迹】为正在运行中的容器动态添加端口映射

iptables -t nat -A DOCKER -p tcp --dport <主机端口> -j DNAT --to-destination <容器IP地址>:9866

这样,所有 namenode 都将返回 datanode 的主机名,然后在本地映射成服务器主机 IP 地址,再由服务器主机通过端口映射访问到各个容器,顺利实现 java 反问内网 docker 容器(虽然说只有设置了 9866 端口映射的这个容器能够成功写入数据)

③ 第四步:打开安全组和防火墙

如果服务器是阿里云的,记得打开安全组的 9866 端口,然后服务器主机的防火墙也要打开该端口

④ 第五步:测试

上面都做完的话,再使用原先的方法,就可以成功将数据上传和下载了!!(www困扰了我这么久)

@Test
public void test() throws IOException, InterruptedException, URISyntaxException{
	URI uri = new URI("hdfs://xxx:8020");
	String user = "root";
	Configuration conf = new Configuration();
	conf.set("dfs.client.use.datanode.hostname", "true");
	FileSystem fs = FileSystem.get(uri, conf, user);
	upload(false, false, "d:/xxx", "/xxx");
	fs.close();
}

到 web 端查看:
在这里插入图片描述
发现数据上传成功了,然后只在 hadoop001 和 hadoop003 有存放(默认是要存放在 3 台服务器的)
hadoop001 有可以理解,为什么 hadoop003 也有呢?
这是因为 hadoop003 是 2nn,会备份 namenode 的一些数据以便恢复,所以它也有

然后再过一会刷新,我们会发现 hadoop002 也有数据了,这就是它们的数据同步,以此我们实现了效果上的一致,算是比较好地解决这个问题了(要不是因为没钱QAQ,不过也收获了很多)

在这里插入图片描述


详细分析

报错为 datanode,然后结合实际情况,可以 创建和删除文件夹,而不能上传和下载文件,已经可以推测是 datanode 出现了问题
因为 namenode 负责的是管理文件的信息,创建和删除文件(夹)是它的工作,所以说明 namenode 是没问题的
而具体保存文件数据的工作是由 datanode 负责的,这一块不行就是它有问题

仔细想想确实,由于是使用 Docker 搭建的,所以外部与容器集群的通讯都是基于端口映射
但端口映射我只映射了 namenode 的 8020 端口,datanode 的却没有映射,因此就找不到了

但是该映射后的端口该怎么告诉程序?namenode 进行端口映射后,可以通过 new URI("hdfs://xxx:11111");11111 是主机端口,映射到 namenode 的 8020 端口),但 datanode 的 9866 端口映射后,该怎么告知?

通过一番了解,我发现访问的原理是,外网先访问 namenode,然后由 namenode 作出决策决定要由哪些 datanode 处理这个任务,再返回这些 datanode 的地址,由外网再去访问这些 datanode 进行数据的写入(端口是 9866)

那现在问题就转化成:如何使外网通过返回的 IP 地址和 9866 端口访问到对应的 datanode?
首先外网无法直接访问这些 datanode,因为这些 datanode 是在内网中的,导致数据无法写入。那该怎么办呢?拿到的是内网的地址。这时候我们取巧一下,修改配置使 namenode 返回的是 datanode 的主机名(而不是 IP 地址)
这种情况 java 将直接访问该域名下的 9866 端口,那我们只需要将该域名解析为我们服务器主机的域名,然后在主机中设置端口映射(9866 -> 9866),这样 java 就可以通过容器域名访问到主机,再由主机的端口映射顺利访问到 datanode 的这一个容器啦 (第一二三步)

最后由于主机的 9866 端口只能映射一次,这就注定了只有一个容器能担起写入数据的责任
一开始的我还在担心,其他容器怎么办,废了吗?

好在,集群中每个 datanode 结点有同步机制,一旦被 namenode 指定要存放该文件,但实际并没有存储时,回向其他有该文件的 datanode 同步数据,所以只要一个 datanode 结点有数据,就迟早可以同步到所有需要的结点(一般默认是 3 个结点,多个的话,同步速度到后面也会越来越快的吧,毕竟越来越多的结点成为了可同步的对象)


小声吧唧:
难受了很久,找遍网络没能有答案,处境相同的很多却没有一个一针见血的答案
不过在看了那么多答案之后,能独立分析出来这个方法,还是很开心的
虽说时间过去了很多,但理解却更为透彻,算是一点点欣慰吧
如果能够帮到更多的人,不要像我难受这么久,就最好不过了(如果有什么疏忽之处,还请多多指教~)


无助时的一根稻草,是它吗(IceClean)