読者です 読者をやめる 読者になる 読者になる

hadoopアドベントカレンダー2011 19日目 Hadoopのラック認識設定部分のソースを追いかけてみた

Hadoop

hadoopアドベントカレンダー2011の19日目を担当する@aoetk / id:aoe-tkです。

業務でHadoopのラック認識設定を行う必要に迫られ、そのときに調べたメモを公開したいと思います。

Hadoopはネットワークトポロジーを考慮して動くことはよく知られていることでしょう。("rack awareness" である言われることが多いです)
次のような挙動を行ったりします。

  • MapReduceのタスクをノードに配置する際、ラック間の転送よりもラック内の転送を優先させる
  • HDFSは特定のラックに偏らないような複製を行う

ただし、どのノードがどのラックに所属しているか、といった情報は外部から教えてあげる必要があります。
Hadoopはインターフェース DNSToSwitchMapping の resolve メソッドを用いてノードのネットワークロケーション情報を取得します。
この DNSToSwitchMapping のデフォルト実装が ScriptBasedMapping で、このクラスは core-site.xml の topology.script.file.name プロパティで指定されたスクリプトを、ラックのアドレスを引数として起動し、スクリプトが標準出力に出力した文字列をラックIDとして受け取ります。
ということで、ノードが所属しているラックをHadoopに教えてあげるには、スクリプトを作り、その場所を topology.script.file.name プロパティで指定しておけばよいわけです。*1

前置きが長くなりました。スクリプトを作ればよいのですが、Hadoopのドキュメントを探しても、スクリプトに求められる仕様についてはっきり説明された箇所を見つけられませんでした。特に次のような点が疑問でした。

  • スクリプトはどのタイミングで呼び出されるの?
  • スクリプトの引数は1つだけ?複数渡されるの?
  • 引数にはIPとホスト名のどっちが渡されるの?

というわけでソースを追っかけてみることにしました。参照したソースは 0.20.203 のソースです。

まず、DNSToSwitchMapping を呼び出している箇所から探してみます。次の3カ所から呼び出されていました。

  1. org.apache.hadoop.hdfs.server.namenode.FSNamesystem#initialize
  2. org.apache.hadoop.hdfs.server.namenode.FSNamesystem#resolveNetworkLocation
  3. org.apache.hadoop.mapred.JobTracker#resolveAndAddToTopology

1番はNameNodeの初期化処理から呼び出されています (呼び出し元をたどると NameNode の main 関数にたどり着きました)。NameNodeを最初に起動する際に呼び出されるのはまあ分かりますね。次のように呼び出されています。

    if (dnsToSwitchMapping instanceof CachedDNSToSwitchMapping) {
      dnsToSwitchMapping.resolve(new ArrayList<String>(hostsReader.getHosts()));
    }

dfs.hosts で設定されたホスト名をまとめて渡しているようです。(HostsFileReader#getHosts をコールしており、このメソッドは dfs.hosts で設定された情報を保持している)
CachedDNSToSwitchMapping#resolve の中がどうなっているかは後で追いかけることにしましょう。

2番はNameNodeに対して、新たにDataNodeを登録する処理の過程で呼び出されています。
そして3番ですが、JobTrackerに対して新たなTaskTrackerを登録する過程と、MapReduceジョブ実行開始時にも呼び出されていました。

以上から、最初の疑問のうち、「スクリプトはどのタイミングで呼び出されるの?」について答えが分かりました。

  • NameNodeの初期化処理
  • NameNodeに対して新たなDataNodeを登録するとき
  • JobTrackerに対して新たなTaskTrackerを登録するとき
  • MapReduce実行開始時

続いて CachedDNSToSwitchMapping#resolve について見てみます。なお、CachedDNSToSwitchMapping は ScriptBasedMapping のスーパークラスに当たります。

  public List<String> resolve(List<String> names) {
    // normalize all input names to be in the form of IP addresses
    names = NetUtils.normalizeHostNames(names);

まず、頭で NetUtils#normalizeHostNames を呼んでいます。この中でさらに normalizeHostName を呼んでいますが、その実装は次のようになっています。

  public static String normalizeHostName(String name) {
    if (Character.digit(name.charAt(0), 10) != -1) { //FIXME 
      return name;
    } else {
      try {
        InetAddress ipAddress = InetAddress.getByName(name);
        return ipAddress.getHostAddress();
      } catch (UnknownHostException e) {
        return name;
      }
    }
  }

何か "FIXME" の文字が入ってますがw 、やろうとしていることは渡された値がIPであろうがホスト名であろうが、最終的にはIPに変換して返すということです。

これで、「引数にはIPとホスト名のどっちが渡されるの?」の答えが分かりました。

  • 引数には必ずIPが渡される

さらに CachedDNSToSwitchMapping#resolve の実装を読み進めていくと次のような実装が出てきます。

    // Resolve those names
    List<String> rNames = rawMapping.resolve(unCachedHosts);

ここで名前解決をしています。ScriptBasedMapping の場合、rawMapping 変数には ScriptBasedMapping のネストクラスである RawScriptBasedMapping のインスタンスが渡されています。これの resolve メソッドを見てみましょう。

  private String runResolveCommand(List<String> args) {
    // (中略)

    while (numProcessed != args.size()) {
      int start = maxArgs * loopCount;
      List <String> cmdList = new ArrayList<String>();
      cmdList.add(scriptName);
      for (numProcessed = start; numProcessed < (start + maxArgs) && 
           numProcessed < args.size(); numProcessed++) {
        cmdList.add(args.get(numProcessed)); 
      }
      File dir = null;
      String userDir;
      if ((userDir = System.getProperty("user.dir")) != null) {
        dir = new File(userDir);
      }
      ShellCommandExecutor s = new ShellCommandExecutor(
                                   cmdList.toArray(new String[0]), dir);
      try {
        s.execute();
        allOutput.append(s.getOutput() + " ");
      // (以下略)
    }
    return allOutput.toString();
  }

ちょっと引用が長くなってしまいましたが、要はスクリプトに渡す引数が多くなりすぎないようにしつつ、一定数の引数をまとめてスクリプトに渡していることが分かります。
というわけで「スクリプトの引数は1つだけ?複数渡されるの?」の答えが分かりました。

いかがでしたでしょうか。Hadoopはこれだけ広く使われているにもかかわらず、どうも公式のドキュメントは情報不足のような気がします。なので、分からないことがあったらソースを見てみるのがいいのではないでしょうか。思いのほか読みやすいですよ。

明日の12/20は@fcicqさんの予定です。

*1:DNSToSwitchMapping を実装したクラスを作るという手もあります。その場合は topology.node.switch.mapping.impl プロパティで実装クラスを指定します。