
# 使用distcp命令跨集群复制HDFS数据
#### 操作场景
distcp是一种在集群间或集群内部拷贝大量数据的工具。它利用MapReduce任务实现大量数据的分布式拷贝。
#### 约束与限制
- 在使用distcp复制文件到OBS上时，目录层级不能超过45。
- 在使用distcp从HDFS复制文件到OBS或者从OBS复制到HDFS时，需增加命令行参数-skipcrccheck，否则distcp命令会执行失败。
- 集群间复制数据时，如果一个集群配置了透明加密，另一个集群未配置透明加密，则：
  - 不支持复制加密区数据。
  
  - 复制非加密区数据需要在执行distcp命令集群的"*客户端安装路径*/Yarn/config/core-site.xml"中新增参数"dfs.client.ignore.namenode.default.kms.uri=true"。
   
 
#### 前提条件
- 已安装Yarn客户端。例如安装目录为"/opt/client"。
- 各组件业务用户由MRS集群管理员根据业务需要创建。集群已启用Kerberos认证（安全模式），"机机"用户需要下载keytab文件。"人机"用户第一次登录时需修改密码。（普通模式不涉及）
- 对于开启了Kerberos认证的集群（安全模式），集群之间需要配置系统互信。相关操作请参考[配置MRS集群间互信](https://support.huaweicloud.com/usermanual-mrs/mrs_01_0354.html)。
- 如需在集群间拷贝数据，拷贝数据的集群双方都需要启用集群间拷贝数据功能。相关操作请参考[启用MRS集群间拷贝功能](https://support.huaweicloud.com/usermanual-mrs/mrs_01_300047.html)。
 
#### 操作步骤
1. 登录安装客户端的节点。
2. 执行以下命令，切换到客户端安装目录。 
   ```
   cd /opt/client
   ```
   
   
3. 执行以下命令配置环境变量。 
   ```
   source bigdata_env
   ```
   
   
4. 如果集群为安全模式，执行distcp命令的用户所属的用户组必须为**supergroup** 组，且执行以下命令进行用户认证。集群未启用Kerberos认证（普通模式）集群无需执行用户认证。
   
   ```
   kinit 组件业务用户
   ```
   
   
5. 执行distcp命令。例如： 
   ```
   hadoop distcp hdfs://hacluster/source hdfs://hacluster/target
   ```
   
   
 
#### distcp常见用法
- 最常见的distcp用法，示例如下，相关参数请参见[表1]。
  ```
  hadoop distcp -numListstatusThreads 40 -update -delete -prbugpaxtq hdfs://cluster1/source hdfs://cluster2/target
  ```
  以上命令中：
  - -numListstatusThreads：指定了40个构建被拷贝文件的列表的线程数。
  
  
  
  - -update -delete：表示将源位置和目标位置的文件同步，删除掉目标位置多余的文件。如果需要增量拷贝文件，请将-delete删除。
  
  
  
  - -prbugpaxtq与-update配合，表示被拷贝文件的状态信息也会被更新。
  
  
  
  - hdfs://cluster1/source、hdfs://cluster2/target分别表示源位置和目标位置。
  
  
   表1distcp命令选项 
  | 选项                                    | **描述**                                                                                                                                                                                                  |
  |:---|:---|
  | -p\[rbugpcaxtq\]                      | 当同时使用-update选项时，即使被拷贝文件的内容没有被更新，它的状态信息也会被更新。 r：副本数，b：块大小，u：所属用户，g：所属用户组，p：许可，c：校验和类型，a：访问控制，t：时间戳，q：Quota信息                                        |
  | -i                                    | 拷贝过程中忽略失败。                                                                                                                                                                                              |
  | -log \<logdir\>                       | 指定日志路径。                                                                                                                                                                                                 |
  | -v                                    | 指定日志中的额外信息。                                                                                                                                                                                             |
  | -m \<num_maps\>                       | 最大的同时运行的执行拷贝的任务数。                                                                                                                                                                                       |
  | -numListstatusThreads                 | 构建被拷贝文件的文件列表时所用的线程数，该选项会提高distcp的运行速度。                                                                                                                                                                  |
  | -overwrite                            | 覆盖目标位置的文件。                                                                                                                                                                                              |
  | -update                               | 如果源位置和目标位置的文件的大小，校验和不同，则更新目标位置的文件。 -update和-overwrite选项用法介绍请参见[update和overwrite选项用法说明]。 |
  | -append                               | 当同时使用-update选项时，追加源位置的文件内容到目标位置的文件。                                                                                                                                                                     |
  | -f \<urilist_uri\>                    | 将\<urilist_uri\>文件的内容作为需要拷贝的文件列表。                                                                                                                                                                       |
  | -filters                              | 指定一个本地文件，其文件内容是多条正则表达式。当被拷贝的文件与某条正则表达式匹配时，则该文件不会被拷贝。                                                                                                                                                    |
  | -async                                | 异步运行distcp命令。                                                                                                                                                                                           |
  | -atomic {-tmp \<tmp_dir\>}            | 指定一次原子性的拷贝，可以添加一个临时目录的选项，作为拷贝过程中的暂存目录。                                                                                                                                                                  |
  | -bandwidth                            | 指定每个拷贝任务的传输带宽，单位MB/s。                                                                                                                                                                                   |
  | -delete                               | 删除掉目标位置中存在，但源位置不存在的文件。该选项通常会和-update配合使用，表示将源位置和目标位置的文件同步，删除掉目标位置多余的文件。                                                                                                                                 |
  | -diff \<oldSnapshot\> \<newSnapshot\> | 将新旧版本之间的差异内容，拷贝到目标位置的旧版本文件中。                                                                                                                                                                            |
  | -skipcrccheck                         | 是否跳过源文件和目标文件之间的CRC校验。                                                                                                                                                                                   |
  | -strategy {dynamic\|uniformsize}      | 指定拷贝任务的拷贝策略，默认策略是uniformsize，即每个拷贝任务复制相同的字节数。                                                                                                                                                           |
     
  

- 集群间的数据拷贝。 例如执行以下命令，将集群cluster1的"/foo/bar"中的数据拷贝至集群cluster2的"/bar/foo"路径中：
  ```
  hadoop distcp hdfs://cluster1/foo/bar hdfs://cluster2/bar/foo
  ```
  集群cluster1和集群cluster2之间的网络必须保持互通，且两个集群需要使用相同或兼容的HDFS版本。
  
- 多个源目录的数据拷贝。 例如执行以下命令，将集群cluster1的文件夹a、b拷贝到集群cluster2的"/bar/foo"目录下。
  ```
  hadoop distcp hdfs://cluster1/foo/a \
  hdfs://cluster1/foo/b \
  hdfs://cluster2/bar/foo
  ```
  以上命令的效果等效于如下命令：
  ```
  hadoop distcp -f hdfs://cluster1/srclist \
  hdfs://cluster2/bar/foo
  ```
  其中srclist里面的内容如下。注意运行distcp命令前，需要将srclist文件上传到HDFS上。
  ```
  hdfs://cluster1/foo/a 
  hdfs://cluster1/foo/b
  ```
  
 
 #### update和overwrite选项用法说明
- -update：用于被拷贝的文件在目标位置中不存在，或者更新目标位置中被拷贝文件的内容。 在使用update选项的情况下，如果被拷贝文件在目标位置中已经存在，但文件内容不同，则目标位置的文件内容会被更新。
  
- -overwrite：用于覆盖在目标位置中已经存在的文件。 在使用overwrite选项的情况下，如果被拷贝文件在目标位置中已经存在，目标位置的文件依然会被覆盖。
  
如果多个源位置有相同名称的文件，则distcp命令会失败。
在不使用update和overwrite选项的情况下，如果被拷贝文件在目标位置中已经存在，则该文件会跳过。
**不加选项和加两个选项中任一个选项的区别，示例如下：**
假设，源位置的文件结构如下：
```
hdfs://cluster1/source/first/1 
hdfs://cluster1/source/first/2 
hdfs://cluster1/source/second/10 
hdfs://cluster1/source/second/20
```
- 不加选项的命令：
  ```
  hadoop distcp hdfs://cluster1/source/first hdfs://cluster1/source/second  hdfs://cluster2/target
  ```
  上述命令默认会在目标位置创建文件夹first、second，所以拷贝结果如下：
  ```
  hdfs://cluster2/target/first/1 
  hdfs://cluster2/target/first/2 
  hdfs://cluster2/target/second/10 
  hdfs://cluster2/target/second/20
  ```
  
- 加两个选项中任一个选项的命令，例如加update选项：
  ```
  hadoop distcp -update hdfs://cluster1/source/first hdfs://cluster1/source/second  hdfs://cluster2/target
  ```
  上述命令只会将源位置的内容拷贝到目标位置，所以拷贝结果如下：
  ```
  hdfs://cluster2/target/1 
  hdfs://cluster2/target/2 
  hdfs://cluster2/target/10 
  hdfs://cluster2/target/20
  ```
  
 
#### 加密区数据拷贝
该功能仅适用于MRS 3.2.1及之后版本。
默认情况下，执行distcp时会比较文件系统提供的校验和，以验证数据是否已成功复制到目标。从普通目录到加密区、加密区到普通目录、加密区中不同目录之间复制时，由于数据块内容不同，文件系统校验和将不匹配。
在这种情况下，可以指定"-skipcrccheck"和"-update"标志以避免验证校验和。当使用"-skipcrccheck"时，distcp在每个文件的复制完成后立即执行文件大小比较来检查文件的完整性。
例如，执行命令如下：
```
hadoop distcp -skipcrccheck -update /encryptedpath /commonpath
```
使用Distcp进行跨集群拷贝如果一个集群配置了KMS透明加密，需要确保另个集群也配置了KMS透明加密，否则未配置透明加密集群使用distcp到配置透明加密集群将会失败。
#### 拷贝原始加密区数据
该功能仅适用于MRS 3.2.1及之后版本。
为了保留HDFS加密区数据，HDFS引入了一个新的虚拟路径前缀"/.reserved/raw/"，该前缀路径允许集群管理员直接访问文件系统中的基础数据块，允许集群管理员在不需要访问加密密钥的情况下拷贝数据，并避免了解密和重新加密数据的操作。这种情况源数据和复制之后的数据会完全相同，如果复制之后的数据使用新的EDEK重新加密将可能导致异常。
例如，执行命令如下：
```
hadoop distcp -px /.reserved/raw/encrptedpath /.reserved/raw/backpath
```
- 由于加密属性（如EDEK）保存在扩展属性"-px"中，为了可以解密文件，在distcp中使用"/.reserved/raw/"目录拷贝加密数据时，需要保留带有"-px"标志的扩展属性。
- 当前版本不支持该方式跨集群拷贝。
- 如需使用上述命令执行拷贝操作请确保两个目录使用相同的EZK加密。
 
#### 上传SFTP数据至HDFS
该功能仅适用于MRS 3.6.0-LTS.1及之后版本。
使用distcp命令将SFTP数据上传至HDFS的命令如下：
```
hadoop distcp -libjars ${BIGDATA_CLIENT_HOME}/HDFS/hadoop/share/adapter/lib/hadoop-distcp-sftp-*.jar -Dfs.sftp.user=root  -Dfs.sftp.job.password="{作业侧密码}" -Dfs.sftp.client.password="{客户端侧密码}" -Dfs.sftp.impl=com.huawei.hadoop.adapter.sftp.SFTPFileSystem  -Dfs.sftp.impl.disable.cache=true sftp://{SFTP服务器IP}:{SFTP服务器端口号}/source/first hdfs://cluster1/target
```
命令中参数说明如下：
表2参数说明 
| 参数                                           | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
|:---|:---|
| fs.sftp.user                                 | 连接SFTP服务器的用户名。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| fs.sftp.job.password                         | 连接SFTP服务器用户对应的密码，用于作业提交到Yarn后容器连接SFTP服务器。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| fs.sftp.client.password                      | SFTP服务器用户对应的密码，用于客户端创建distcp作业时连接SFTP服务器。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| sftp://{SFTP服务器IP}:{SFTP服务器端口号}/source/first | 需要远程拷贝的SFTP路径。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| 作业侧密码                                        | 执行以下命令，生成作业侧密码： ``` curl -k -i -u user name:password -X POST -HContent-type:application/json  -d '{"plainText":"password"}' 'https://x.x.x.x:28443/web/api/v2/tools/encrypt' ``` - *user name* **:**password分别为当前系统登录用户名和密码。  - "plainText":"*password*"中的password为连接SFTP服务器的密码。  - *x.x.x.x*为集群Manager的浮动IP。  - 命令中如果携带认证密码信息可能存在安全风险，在执行命令前建议关闭系统的history命令记录功能，避免信息泄露。                                                                                                                                                            |
| 客户端侧密码                                       | 使用如下命令，根据提示输入SFTP服务器密码，输出内容即为客户端侧密码。 ``` java -cp ${BIGDATA_CLIENT_HOME}/HDFS/hadoop/share/adapter/lib/*:${BIGDATA_CLIENT_HOME}/HDFS/hadoop/share/hadoop/common/*:${BIGDATA_CLIENT_HOME}/HDFS/hadoop/share/hadoop/common/lib/* com.huawei.hadoop.adapter.sftp.CryptUtil ``` 说明： 如果需要重新生成客户端密钥，可以执行如下操作： 登录到客户端节点，进入客户端安装目录，执行如下命令，输出rekey success表示密钥生成成功（此操作可能耗时90秒左右）。 ``` source bigdata_env ``` ``` java -cp ${BIGDATA_CLIENT_HOME}/HDFS/hadoop/share/adapter/lib/*:${BIGDATA_CLIENT_HOME}/HDFS/hadoop/share/hadoop/common/*:${BIGDATA_CLIENT_HOME}/HDFS/hadoop/share/hadoop/common/lib/* com.huawei.hadoop.adapter.sftp.CryptUtil rekey ``` 每次重新生成密钥后，必须使用新密钥对密码加密后再执行distcp命令，否则，由于密钥变更，使用旧密钥加密的密码用新密钥无法解密，distcp命令会执行失败。 |
   
#### 相关文档
- 如果使用distcp命令拷贝空文件夹报错，请参见[使用distcp命令拷贝空文件夹报错](https://support.huaweicloud.com/trouble-mrs/mrs_03_0766.html)。
- 如果使用distcp命令在安全集群上执行失败并发生异常，请参见[执行distcp命令报错如何处理](https://support.huaweicloud.com/cmpntguide-lts-mrs/mrs_01_1695.html)。
- 执行distcp跨集群拷贝文件时，出现部分文件拷贝失败并报错" Source and target differ in block-size. Use -pb to preserve block-sizes during copy. "，请参见[执行distcp跨集群拷贝文件报错"Source and target differ in block-size"](https://support.huaweicloud.com/trouble-mrs/mrs_03_0089.html)。
- 更多使用distcp命令时常见问题如下：
 
#### 当使用distcp命令时，如果某些被拷贝的文件内容较大时如何处理
当使用distcp命令时，如果某些被拷贝的文件内容较大时，建议修改执行拷贝任务的mapreduce的超时时间。可以通过在distcp命令中指定**mapreduce.task.timeout** 选项实现。例如，修改超时时间为30分钟，则命令如下：
```
hadoop distcp -Dmapreduce.task.timeout=1800000 hdfs://cluster1/source hdfs://cluster2/target
```
您也可以使用选项filters，不对这种大文件进行拷贝，命令示例如下：
```
hadoop distcp -filters /opt/client/filterfile hdfs://cluster1/source hdfs://cluster2/target
```
其中filterfile是本地文件，它的内容是多条用于匹配不拷贝文件路径的正则表达式，它的内容示例如下：
```
.*excludeFile1.*
.*excludeFile2.*
```
#### 当使用distcp命令时，报错"java.lang.OutOfMemoryError"
当使用distcp命令时，命令异常退出，报"java.lang.OutOfMemoryError"的错误。
这个问题的原因是拷贝任务运行时所需的内存超过了客户端设置的内存上限（默认为128MB）。可以通过修改"*\<客户端安装路径\>*/HDFS/component_env"中的"CLIENT_GC_OPTS"来修改客户端的内存上限。例如，需要设置该内存上限为1GB，则设置：
```
CLIENT_GC_OPTS="-Xmx1G"
```
在修改完后，使用如下命令刷新客户端配置，使之生效：
```
source <客户端安装路径>/bigdata_env
```
#### 使用dynamic策略执行distcp命令时，报错"Too many chunks created with splitRatio"
使用dynamic策略执行distcp命令时，命令异常退出，报"Too many chunks created with splitRatio"的错误。
这个问题的原因是"distcp.dynamic.max.chunks.tolerable"的值（默认值为20000）小于"distcp.dynamic.split.ratio"的值（默认为2）乘以Map数。即一般出现在Map数超过10000的情况。可以通过-m参数降低Map数小于10000：
```
hadoop distcp -strategy dynamic -m 9500 hdfs://cluster1/source hdfs://cluster2/target
```
或通过-D参数指定更大的"distcp.dynamic.max.chunks.tolerable"的值：
```
hadoop distcp -Ddistcp.dynamic.max.chunks.tolerable=30000 -strategy dynamic hdfs://cluster1/source hdfs://cluster2/target
```
