Bash网络重定向上传HDFS请求体不完整的原因及解决方法
我用Bash网络重定向通过WebHDFS API向HDFS上传文件。当文件内容接近1MB时,不加sleep操作的话,文件内容没法完整上传到HDFS;加120秒sleep后,文件就能完整上传。
代码示例
content_length=$(wc -c $local_dump_file) content_length=${content_length% *} exec 3<> $socket_file content="PUT $path HTTP/1.1\n" content="${content}Host: $url:$port\nAccept: */*\nContent-Length: $content_length\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\n\n$(cat $local_dump_file)" echo -e "$content" >&3 sleep 120 exec 3<&-
注:所有变量均正确,小文件(1字节至1000字节)上传无截断问题。不添加sleep时,HDFS上的文件内容每次上传截断长度不同(如500KB、615KB等),仿佛
echo -e "$content" >&3命令无提示终止。
疑问
- 为何出现此现象?
- 如何无需添加任意sleep即可强制发送全部数据?
1. 问题原因分析
这是因为Bash通过重定向向套接字写入数据时,采用异步I/O机制:echo命令只是把数据写入系统的套接字发送缓冲区,缓冲区满了就会返回,但此时数据可能还没完全发送到HDFS服务器。如果紧接着关闭套接字(exec 3<&-),缓冲区中未发送的数据会被直接丢弃,导致文件上传截断。
小文件体积小,能一次性填满缓冲区并快速发送完成,所以不会出现问题;而接近1MB的大文件,缓冲区无法容纳全部数据,echo返回后还有大量数据留在缓冲区等待发送,此时关闭套接字就会丢失剩余数据。添加sleep 120相当于给了系统足够时间把缓冲区数据全部发送到服务器,因此文件能完整上传。
另外,WebHDFS基于HTTP协议,要求客户端发送完请求后等待服务器响应,但你的代码没有读取服务器响应就直接关闭套接字,这会导致服务器可能还没接收完数据就中断连接,进一步加剧截断问题。
2. 无需sleep的解决方案
方案一:读取服务器响应后再关闭套接字
HTTP是请求-响应协议,客户端发送完请求后等待服务器响应的过程,会自动确保所有数据发送完成。修改代码如下:
content_length=$(wc -c "$local_dump_file") content_length=${content_length% *} exec 3<> "$socket_file" # 用printf替代echo,避免不同系统转义字符处理不一致的问题 printf "PUT $path HTTP/1.1\nHost: $url:$port\nAccept: */*\nContent-Length: $content_length\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\n\n" >&3 cat "$local_dump_file" >&3 # 读取服务器响应,确保数据全部发送完成 cat <&3 exec 3<&-
同时分开发送请求头和文件内容,避免把大文件全部加载到内存中(文件更大时会引发问题)。
方案二:用dd强制刷写缓冲区
dd的conv=fsync参数可以强制等待数据完全发送到服务器后再返回:
content_length=$(wc -c "$local_dump_file") content_length=${content_length% *} # 构造请求头到临时文件 printf "PUT $path HTTP/1.1\nHost: $url:$port\nAccept: */*\nContent-Length: $content_length\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\n\n" > /tmp/request_header.tmp # 合并请求头和文件内容,用dd发送并强制刷写 cat /tmp/request_header.tmp "$local_dump_file" | dd of="$socket_file" conv=fsync rm /tmp/request_header.tmp
方案三:改用curl工具(最推荐)
curl是专门的HTTP客户端,会自动处理HTTP协议的所有细节(数据发送、响应读取、缓冲区管理等),比手动套接字重定向稳定得多:
curl -X PUT -T "$local_dump_file" "http://$url:$port$path" \ -H "Content-Type: application/x-www-form-urlencoded; charset=utf-8"
内容的提问来源于stack exchange,提问作者Itération 122442

