IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

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

淘宝数据平台与产品部官方博客 tbdata.org 2011-08-22 12:30:25 累计浏览 2,524 次
本机暂存

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

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

后者导致了本文的思考.

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

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

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

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

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

用代码取代独立的配置文件不是新鲜的做法, 像Guice, Gant 等已经以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来实现, 那么还要简化, 使得代码味更少些, 而且直接运行简单到一条命令就搞定了:)

同分类推荐文章

  1. 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
  2. Go 实验特性详解 (2026-06-21 10:05:27)
  3. amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)

查看更多 后端 文章 →

建议继续学习

  1. 检查nginx配置,重载配置以及重启的方法 (累计阅读 10,895)
  2. GFS, HDFS, Blob File System架构对比 (累计阅读 10,504)
  3. 读腾讯大讲堂 (累计阅读 6,146)
  4. 关于Linux的文件系统cache (累计阅读 5,915)
  5. Push Or Pull? (累计阅读 5,262)
  6. 正确用DD测试磁盘读写速度 (累计阅读 5,097)
  7. Hadoop超级安装手册 (累计阅读 4,738)
  8. 自动化运维之企业实际案例分析 (累计阅读 4,724)
  9. 使用hadoop进行大规模数据的全局排序 (累计阅读 4,603)
  10. 解决 ubuntu 的 /etc/hosts 重启就被还原 (累计阅读 4,440)