让代码取代你的配置文件吧

最近, 在编写一个专门压测NameNode的工具(以下简称s4nn), 它有两个难点 :

  1. s4nn需要可以模拟上万个DataNode ;
  2. s4nn 需要灵活的支持对NameNode访问行为的定义.

后者导致了本文的思考.

命令行参数和配置文件是最常用来配置系统的方法, 前者用于配置项较少, 后者则适合配置复杂情况. 这两种方式都有共同令人痛苦的地方:

  1. 编写代码去载入->解析->转换, 通常如同处理协议般无聊(要是有个什么变更, KMN!!);
  2. 对于复杂的配置文件编写而言, 总是没有顺手的编辑器支持, 写起来既累又易错.

要是用代码取代配置文件呢?

呃… 这会很麻烦吧, 像java这样的改了代码那还不要重新编译啊?

嗯, 确实, 但并没有想象中那么麻烦, 一些技巧可以让它变得简单(至少java是这样), 如Btrace.

用代码取代独立的配置文件不是新鲜的做法, 像GuiceGant 等已经以Internal DSL的代码代替了XML. 好处很明显:

  1. 良好的DSL风格, 简洁易懂 ;
  2. 免去对配置文件的解析转换 ;
  3. 最好的编辑器支持, 语法高亮, 一键格式化, 提示补全, 重构 ;
  4. 编译器帮助查错;
  5. 与代码无缝结合, 能够容易在变化中保持一致性.

简言之两个字, “高效”! 这倒是挺适合s4nn的, 不妨一试!

从需求的角度出发, 配置应该能够完成:

  1. 定义一组Client RPC调用行为, 其调用参数, 次数;
  2. 定义DataNode的运行时特征, 个数.

为满足这两点, 代码设计上可能会有:

class ClientDriver {
  void addRpc(times, name, args)
  void setNameNode(address)
}

class DataNodeSimulator {
  void setCapacity(size)
  void setHeartbeatInterval(sec)
  void setBlockReportInterval(sec)
  void setNameNode(address)
}

那么其配置文件会是:

 1 <configuration>
 2   <client>
 3     <rpc times="100000" name="getFileInfo">
 4       <arguments>
 5         <param name="src">/foo</param>
 6       </arguments>
 7     </rpc>
 8   </client>
 9   <namenode>
10     <address>host:port</address>
11   </namenode>
12   <datanode>
13     <simulator num="10000">
14       <capacity unit="GB">200</capacity>
15       <heartbeatInterval unit="SECOND">1</heartbeatInterval>
16       <blockReportInterval unit="HOUR">1</blockReportInterval>
17     </simulator>
18   </datanode>
19 </configuration>

简单吗? 简单, 那是因为这只是最基本的, 实际的配置应考虑到:

  1. 有30种RPC方法, 其中参数个数最多的有6,  参数类型并非都是基本类型, 参数值可能需要按照某种规则随即生成;
  2. DataNode模拟器也会有可能需要支持多种选项组合, 如实际集群种不是所有的机器的容量都一样的等.

这样的XML会臃肿到什么程度…

好吧, 看看用代码的效果:

 1 import com.taobao.s4nn.*;
 2
 3 public class Main extends Bootstrap {
 5
 6   protected void config() {
 7     setConcurrency(16);
 8     setMaxRandomPathDepth(10);
 9
10     /*
11      * DataNode simulator config
12      */
13     simulate(3, new DataNodeSimulatorGenerator() {
14       protected void config() {
15         setCapacity(gibibyte(1));
16         setTickPeriodSecond(1);
17         setHeartbeatInterval(1);
18         setBlockReportInterval(3);
19         setNameNode(proxyOfNameNodeAt("localhost:10001"));
20       }
21     });
22
23     /*
24      * NameNode Status pre-setting config
25      */
26     parallel(1, new StatusPresetterBuilder() {
27       protected void config() {
28         setName(client("InitalStatus"));
29         setNamenode(proxyOfNameNodeAt("localhost:10001"));
30         times(1, create(randomSrc, defaultFsPermission, overwrite(true), replication((short) 3), blocks(1)));
31       }
32     });
33
34     /*
35      * Client RPC driver config
36      */
37     parallel(10, new ClientDriverBuilder() {
38       protected void config() {
39         setName(seqNameWith("client"));
40         setNamenode(proxyOfNameNodeAt("localhost:10001"));
41
42         times(1, getBlockLocations(src("/foo"), randomOffsetIn(0, 1024), randomLengthIn(0, 2048)));
43         times(2, getListing(src("/foo")));
44         times(3, getLocatedListing(src("/foo")));
45         times(4, getStats());
46         times(5, getDatanodeReport(all));
47         times(6, getPreferriedBlockSize(src("/foo")));
48         times(7, getFileInfo(src("/foo")));
49         times(8, getContentSummary(src("/foo")));
50         times(9, setOwner(src("/foo"), username("jushi"), groupname("dwbasis")));
51         times(10, setReplication(src("/foo"), replication((short) 4)));
52         times(11, setPermission(src("/foo"), defaultFsPermission));
53         times(12, rename(src("/foo"), src("/bar")));
54         times(13, mkdirs(src("/bar"), defaultFsPermission));
55         times(14, setQuota(src("/foo"), randomNamespaceQuotaIn(1024, 2048), randomDiskspaceQuotaIn(1024, 4096)));
56         times(15, setTime(src("/foo"), currentTimeMillis, unchanged));
57       }
58     });
59   }
60 }

嗯,  既保持易读性又更为简洁,  重点是不用再写那些额外处理XML的代码了.

这里效仿了Guice中AbstractModule的DSL做法, 提供了setXxx, times等内置方法, 用Java IDE编写起来那是相当轻快啊~~

要是改用Scala或Groovy来实现, 那么还要简化, 使得代码味更少些, 而且直接运行简单到一条命令就搞定了:)

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.