<?xml version="1.0"?>
<!--
  Copyright 2002-2004 The Apache Software Foundation

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->

<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">

<document>

  <header>
    <title>DistCp</title>
  </header>

  <body>

    <section>
      <title>概述</title>

      <p>DistCp（分布式拷贝）是用于大规模集群内部和集群之间拷贝的工具。
	  它使用Map/Reduce实现文件分发，错误处理和恢复，以及报告生成。
      它把文件和目录的列表作为map任务的输入，每个任务会完成源列表中部分文件的拷贝。
      由于使用了Map/Reduce方法，这个工具在语义和执行上都会有特殊的地方。
      这篇文档会为常用DistCp操作提供指南并阐述它的工作模型。
      </p>

    </section>

    <section>
      <title>使用方法</title>

      <section>
        <title>基本使用方法</title>
        <p>DistCp最常用在集群之间的拷贝：</p>
        <p><code>bash$ hadoop distcp hdfs://nn1:8020/foo/bar \</code><br/>
           <code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                 hdfs://nn2:8020/bar/foo</code></p>

        <p>这条命令会把nn1集群的<code>/foo/bar</code>目录下的所有文件或目录名展开并存储到一个临时文件中，这些文件内容的拷贝工作被分配给多个map任务，
        然后每个TaskTracker分别执行从nn1到nn2的拷贝操作。注意DistCp使用绝对路径进行操作。
        </p>

        <p>命令行中可以指定多个源目录：</p>
        <p><code>bash$ hadoop distcp hdfs://nn1:8020/foo/a \</code><br/>
           <code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                 hdfs://nn1:8020/foo/b \</code><br/>
           <code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                 hdfs://nn2:8020/bar/foo</code></p>

        <p>或者使用<code>-f</code>选项，从文件里获得多个源：<br/>
        <code>bash$ hadoop distcp -f hdfs://nn1:8020/srclist \</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
              &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
              &nbsp;hdfs://nn2:8020/bar/foo</code><br/></p>

        <p>其中<code>srclist</code> 的内容是<br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/a</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/b</code></p>

        <p>当从多个源拷贝时，如果两个源冲突，DistCp会停止拷贝并提示出错信息，
        如果在目的位置发生冲突，会根据<a href="#options">选项设置</a>解决。
        默认情况会跳过已经存在的目标文件（比如不用源文件做替换操作）。每次操作结束时
        都会报告跳过的文件数目，但是如果某些拷贝操作失败了，但在之后的尝试成功了，
        那么报告的信息可能不够精确（请参考<a href="#etc">附录</a>）。
	</p>

        <p>每个TaskTracker必须都能够与源端和目的端文件系统进行访问和交互。
        对于HDFS来说，源和目的端要运行相同版本的协议或者使用向下兼容的协议。
        （请参考<a href="#cpver">不同版本间的拷贝</a> ）。
        </p>

        <p>拷贝完成后，建议生成源端和目的端文件的列表，并交叉检查，来确认拷贝真正成功。
        因为DistCp使用Map/Reduce和文件系统API进行操作，所以这三者或它们之间有任何问题
        都会影响拷贝操作。一些Distcp命令的成功执行可以通过再次执行带-update参数的该命令来完成，
        但用户在如此操作之前应该对该命令的语法很熟悉。
        </p>

        <p>值得注意的是，当另一个客户端同时在向源文件写入时，拷贝很有可能会失败。
        尝试覆盖HDFS上正在被写入的文件的操作也会失败。
        如果一个源文件在拷贝之前被移动或删除了，拷贝失败同时输出异常
        FileNotFoundException。</p>

      </section> <!-- Basic -->

      <section id="options">
        <title>选项</title>

        <section>
        <title>选项索引</title>
        <table>
          <tr><th> 标识  </th><th> 描述 </th><th> 备注 </th></tr>

          <tr><td><code>-p[rbugp]</code></td>
              <td>Preserve<br/>
                  &nbsp;&nbsp;r: replication number<br/>
                  &nbsp;&nbsp;b: block size<br/>
                  &nbsp;&nbsp;u: user<br/>
                  &nbsp;&nbsp;g: group<br/>
                  &nbsp;&nbsp;p: permission<br/></td>
              <td>修改次数不会被保留。并且当指定
              <code>-update</code> 时，更新的状态<strong>不</strong>会
              被同步，除非文件大小不同（比如文件被重新创建）。
              </td></tr>
          <tr><td><code>-i</code></td>
              <td>忽略失败</td>
              <td>就像在 <a href="#etc">附录</a>中提到的，这个选项会比默认情况提供关于拷贝的更精确的统计， 同时它还将保留失败拷贝操作的日志，这些日志信息可以用于调试。最后，如果一个map失败了，但并没完成所有分块任务的尝试，这不会导致整个作业的失败。
              </td></tr>
          <tr><td><code>-log &lt;logdir&gt;</code></td>
              <td>记录日志到 &lt;logdir&gt;</td>
              <td>DistCp为每个文件的每次尝试拷贝操作都记录日志，并把日志作为map的输出。
              如果一个map失败了，当重新执行时这个日志不会被保留。
              </td></tr>
          <tr><td><code>-m &lt;num_maps&gt;</code></td>
              <td>同时拷贝的最大数目</td>
              <td>指定了拷贝数据时map的数目。请注意并不是map数越多吞吐量越大。
              </td></tr>
          <tr><td><code>-overwrite</code></td>
              <td>覆盖目标</td>
              <td>如果一个map失败并且没有使用<code>-i</code>选项，不仅仅那些拷贝失败的文件，这个分块任务中的所有文件都会被重新拷贝。
			  就像<a href="#uo">下面</a>提到的，它会改变生成目标路径的语义，所以
              用户要小心使用这个选项。
              </td></tr>
          <tr><td><code>-update</code></td>
              <td>如果源和目标的大小不一样则进行覆盖</td>
              <td>像之前提到的，这不是&quot;同步&quot;操作。
              执行覆盖的唯一标准是源文件和目标文件大小是否相同；如果不同，则源文件替换目标文件。
              像 <a href="#uo">下面</a>提到的，它也改变生成目标路径的语义，
              用户使用要小心。
              </td></tr>
          <tr><td><code>-f &lt;urilist_uri&gt;</code></td>
              <td>使用&lt;urilist_uri&gt; 作为源文件列表</td>
              <td>这等价于把所有文件名列在命令行中。
              <code>urilist_uri</code> 列表应该是完整合法的URI。
              </td></tr>

        </table>

      </section>

      <section id="uo">
        <title>更新和覆盖</title>

        <p>这里给出一些 <code>-update</code>和 <code>-overwrite</code>的例子。
        考虑一个从<code>/foo/a</code> 和
        <code>/foo/b</code> 到 <code>/bar/foo</code>的拷贝，源路径包括：
        </p>

        <p><code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/a</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/a/aa</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/a/ab</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/b</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/b/ba</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/b/ab</code></p>

        <p>如果没设置<code>-update</code>或 <code>-overwrite</code>选项，
        那么两个源都会映射到目标端的
        <code>/bar/foo/ab</code>。
        如果设置了这两个选项，每个源目录的内容都会和目标目录的
         <strong>内容</strong> 做比较。DistCp碰到这类冲突的情况会终止操作并退出。</p>

        <p>默认情况下，<code>/bar/foo/a</code> 和
        <code>/bar/foo/b</code> 目录都会被创建，所以并不会有冲突。</p>

        <p>现在考虑一个使用<code>-update</code>合法的操作:<br/>
        <code>distcp -update hdfs://nn1:8020/foo/a \</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
              &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
              hdfs://nn1:8020/foo/b \</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
              &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
              hdfs://nn2:8020/bar</code></p>

        <p>其中源路径/大小:</p>

        <p><code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/a</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/a/aa 32</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/a/ab 32</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/b</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/b/ba 64</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn1:8020/foo/b/bb 32</code></p>

        <p>和目的路径/大小:</p>

        <p><code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar/aa 32</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar/ba 32</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar/bb 64</code></p>

        <p>会产生:</p>

        <p><code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar/aa 32</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar/ab 32</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar/ba 64</code><br/>
        <code>&nbsp;&nbsp;&nbsp;&nbsp;hdfs://nn2:8020/bar/bb 32</code></p>

        <p>只有nn2的<code>aa</code>文件没有被覆盖。如果指定了
        <code>-overwrite</code>选项，所有文件都会被覆盖。
        </p>

      </section> <!-- Update and Overwrite -->

      </section> <!-- Options -->

    </section> <!-- Usage -->

    <section id="etc">
      <title>附录</title>

      <section>
        <title>Map数目</title>

          <p>DistCp会尝试着均分需要拷贝的内容，这样每个map拷贝差不多相等大小的内容。
	          但因为文件是最小的拷贝粒度，所以配置增加同时拷贝（如map）的数目不一定会增加实际同时拷贝的数目以及总吞吐量。
          </p>

          <p>如果没使用<code>-m</code>选项，DistCp会尝试在调度工作时指定map的数目
          为 <code>min (total_bytes / bytes.per.map, 20 * num_task_trackers)</code>，
		  其中<code>bytes.per.map</code>默认是256MB。</p>

          <p>建议对于长时间运行或定期运行的作业，根据源和目标集群大小、拷贝数量大小以及带宽调整map的数目。
          </p>

      </section>

      <section id="cpver">
        <title>不同HDFS版本间的拷贝</title>

        <p>对于不同Hadoop版本间的拷贝，用户应该使用HftpFileSystem。
        这是一个只读文件系统，所以DistCp必须运行在目标端集群上（更确切的说是在能够写入目标集群的TaskTracker上）。
        源的格式是
        <code>hftp://&lt;dfs.http.address&gt;/&lt;path&gt;</code>
        （默认情况<code>dfs.http.address</code>是
        &lt;namenode&gt;:50070）。</p>

      </section>

      <section>
        <title>Map/Reduce和副效应</title>

        <p>像前面提到的，map拷贝输入文件失败时，会带来一些副效应。
        </p>

        <ul>

          <li>除非使用了<code>-i</code>，任务产生的日志会被新的尝试替换掉。
          </li>

          <li>除非使用了<code>-overwrite</code>，文件被之前的map成功拷贝后当又一次执行拷贝时会被标记为
          &quot;被忽略&quot;。</li>

          <li>如果map失败了<code>mapred.map.max.attempts</code>次，剩下的map任务会被终止（除非使用了<code>-i</code>)。
          </li>

          <li>如果<code>mapred.speculative.execution</code>被设置为
          <code>final</code>和<code>true</code>，则拷贝的结果是未定义的。</li>

        </ul>

      </section>

      <!--
      <section>
        <title>Firewalls and SSL</title>

        <p>To copy over HTTP, use the HftpFileSystem as described in the
        preceding <a href="#cpver">section</a>, and ensure that the required
        port(s) are open.</p>

        <p>TODO</p>

      </section>
      -->

    </section> <!-- Appendix -->

  </body>

</document>
