目录
Storm项目:流数据监控 <5>. 1
1 文档说明... 1
2 Zookeeper统一配置... 1
2.1 设计统一配置因素... 1
2.2 配置系统技术选型... 1
2.3 统一配置设计方案... 2
2.4 代码参考... 2
2.4.1 storm.conf.OptZookeeper.java. 3
2.4.2 storm.conf.GetTopConf.java. 6
2.4.3 storm.conf.PutConf.java. 6
2.4.4 storm.conf.GetConf.java. 8
3 Zookeeper相关API 10
4 项目拓展... 10
1 文档说明
好久没更新storm相关的文章了(快一个月了),很早以前就有朋友提出过storm的拓扑任务会出现supervisor各节点配置麻烦的问题,基于此,我很早就考虑如何解决这个问题,直到现在才把这个问题解决(工作小忙)。
使用zookeeper进行设计storm拓扑的配置系统,设计思路大家可以参考参考,代码部分就有点次了(时间太匆忙了,白天上班,大半夜写代码,写文章不容易啊),大家能看得下去就看吧,不过优化优化改改还是能用的。
好了,不多说了,喜欢的朋友,可以博客加个关注,会时常更新点东西,有兴趣一起交流的朋友,加入到191321336的技术群中,一起讨论交流~~~
2.1 设计统一配置因素
在storm的拓扑任务中,采用常规的节点配置从配置文件(conf)中直接读取,会出现以下几个问题:
(1) 节点太多,配置文件需要配置的节点太多,太繁琐;
(2) 配置需要修改时,很难保证各个节点的配置文件统一;
(3) 无法定位各个功能节点位置,每个节点需要所有配置文件,导致每个节点配置臃肿;
(4) 配置文件太分散,动态更新不现实。
以上几个主要原因导致了,采用传统的直接读取配置文件获取配置信息的方式不适合类似这种集群应用,必然需要设计一个能够进行统一配置的组件或者是系统。
2.2 配置系统技术选型
想要实现集群配置文件的统一配置,必然不能将配置文件放在各个分散的节点上,考虑的思路是放在一个节点上,然后让该节点读取,其他节点从该节点获取,这样就保证了配置文件统一性(只此一份)。可以考虑以下几种设计思路:
(1) 在storm的拓扑主类(Topology)中使用Config conf = new Config();进行参数传递,及在主类中读取配置文件中的所有配置,然后通过Config类传递给相应节点。这种方式,笔者曾经尝试过,确实能解决配置文件统一问题,只需nimbus节点读取配置即可,但是,这种方式在配置文件很多时,会导致主类Topology相当臃肿,相当麻烦,并且相应节点获取参数时也相当麻烦。
(2) 考虑使用mysql。在nimbus中,读取配置文件,然后将配置信息按一定规则存放到一个所有节点都能访问的mysql服务器中。该mysql服务器专门用来保存集群的配置文件,所有节点都上该mysql服务器中读取配置信息,这种方式能够解决配置统一的问题,并且也不少集群应用是这么干的。但这种方式有不少的缺点,首先是需要部署一个专门的mysql服务器,这是额外的代价,如果部署到其中一个节点中倒是可以,但也会影响性能,并且mysql并没有对配置信息变更有效的监控措施,并且存在单点问题,安全性,实用性都不太高。
(3) 还有就是使用zookeeper来实现了。Zookeeper的其中有个功能有点类似向zookeeper集群中提供公共文件夹(官方语言称其为“节点”)。能够访问zk服务器的服务器都能够对其公共节点进行访问。利用该机制,我们就可以使用zk设计统一配置系统了。Nimbus以客户端的身份向zk服务器申请公共节点,并在公共节点上存储配置信息,其他节点需要获取配置文件时,以客户端的身份从zk服务器中获取公共节点的相关值,这样就实现了统一配置。实用zk进行统一配置有如下好处:storm集群中已经有zk,不需要额外部署;zk无单点故障问题,配置信息安全性高;zk对节点具有watch功能,节点变更能够进行提醒并可以自定义相关操作。所以就目前来说,使用zk进行系统统一配置是比较合理的。
2.3 统一配置设计方案
图2.3 Zookeeper统一配置架构图
(1) 如图2.3所示,nimbus读取Topology.conf文件,将除nimbus节点配置参数以外的配置信息放入zookeeper服务器(zk集群)的最底层公共节点上(创建一定规则的节点树,放在最底层的子节点上);
(2) zookeeper集群维护这些配置节点,进行永久保持(除非主动删除),并执行watcher,监视配置节点的变更,随时通知supervisor配置变化;
(3) supervisor第一次从zk集群中读取配置信息,然后开始检测zk的watcher信号,当检测到wather信号(配置变更),重新获取配置信息,从而不但实现了统一配置,还能实现配置的在线动态更新(这点非常重要);
(4) 整个zk中配置节点目录层次为第一层是总配置根节点/TopologyConf;第二层则是根据拓扑提交的任务名进行创建,使用任务民进行配置节点创建不会导致有重复配置出现,因为一个storm集群中,拓扑的任务名是唯一的;底层节点为各个存储配置的名称,值为配置参数。
整个架构围绕zookeeper而存在,而zookeeper也是storm集群的依赖集群,需要额外配置,只需跟storm共享zookeeper集群即可,并且不需要担心配置系统会出现故障,zookeeper集群不存在系统故障。
这个架构不单纯可以在storm的应用集群中使用,其他类似的集群应用配置都可以进行类似设计,能够进行统一配置,无单点故障,实现动态更新。
PS:这部分代码由于时间关系,写的比较糙,只是实现了配置信息的存取,没有实现wather功能,并且相对于之前版本的工程代码,变化较大,舍弃了所有可观性不强的xml方式配置,使用标准方式配置格式,并把所有配置相关的类整合在一个包里(storm.conf),供主类及其他bolt调用。
该类主要定义了一些zookeeper的基本操作,作为一个基本类供其他conf类调用,其中主要的一些方法定义如下:
private static final int SESSION_TIMEOUT = 3000;
private ZooKeeper zookeeper0;
// 以下为各种zk操作方法
private Watcher watcher = new Watcher() {
public void process(WatchedEvent event) {
System.out.println("process: " + event.getType());
}
};
// 链接zk,参数为连接zookeeper的server地址及端口,可设置多个以“,”隔开
// 如zkServer=192.168.2.240:2181,192.168.2.241:2181
public ZooKeeper connect(String zkServer) throws IOException {
zookeeper0 = new ZooKeeper(zkServer, SESSION_TIMEOUT, watcher);
return zookeeper0;
}
// 关闭zk
public void close(ZooKeeper zookeeper) throws InterruptedException {
zookeeper.close();
}
// 创建节点
// 若节点存在则只设置值,若不存在则创建,若出错返回false
public boolean createNode(ZooKeeper zookeeper, String node, String value) {
try {
if (zookeeper.exists(node, null) != null) {
zookeeper.setData(node, value.getBytes(), -1);
} else {
zookeeper.create(node, value.getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
} catch (Exception e) {
System.out.println(e.getMessage());
Assert.fail();
return false;
}
return true;
}
// 删除节点,忽略版本,若不存在则抛错
public boolean deleteNode(ZooKeeper zookeeper, String node) {
try {
if (zookeeper.exists(node, null) != null) {
zookeeper.delete(node, -1);
return true;
} else {
System.out.println(node + " exists now!");
return false;
}
} catch (Exception e) {
System.out.println(e.getMessage());
Assert.fail();
return false;
}
}
// 获取数据,若节点不存在抛错
public String getData(ZooKeeper zookeeper, String node) {
String result = null;
try {
if (zookeeper.exists(node, null) != null) {
byte[] bytes = zookeeper.getData(node, null, null);
result = new String(bytes);
} else {
System.out.println(node + " exists now!");
}
// 类型转换
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println(e.getMessage());
Assert.fail();
}
return result;
}
// 获取数据,设置watch,只要节点改变(节点的值改变)则触发watcher,执行watcher中函数
public String getDataWatch(ZooKeeper zookeeper, String node) {
String result = null;
try {
if (zookeeper.exists(node, null) != null) {
byte[] bytes = zookeeper.getData(node, new Watcher() {
public void process(WatchedEvent event) {
System.out.println("testExistWatch2 Watch : {}"
+ event.getType());
}
}, null);
result = new String(bytes);
} else {
result = null;
}
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println(e.getMessage());
Assert.fail();
}
return result;
}
// 设置对应目录下的数据,-1表示忽略版本
// 该设置能改变原始值
public boolean setData(ZooKeeper zookeeper, String node, String value) {
try {
if (zookeeper.exists(node, null) != null) {
zookeeper.setData(node, value.getBytes(), -1);
return true;
} else {
return false;
}
} catch (Exception e) {
System.out.println(e.getMessage());
Assert.fail();
return false;
}
}
// 获取子节点,返回一个list
public List getNodeChild(ZooKeeper zookeeper, String node) {
try {
if (zookeeper.exists(node, null) != null) {
List list = zookeeper.getChildren(node, true);
return list;
} else {
return null;
}
} catch (Exception e) {
System.out.println(e.getMessage());
Assert.fail();
return null;
}
}
该类主要是提供给主类相关的配置信息,由于主类是在nimbus上执行,所以其所需配置文件则直接获取,不经过zookeeper配置系统。
public void getTopConf(String configFile) throws Exception {
Properties pros = new Properties();
File config = new File(configFile);
pros.load(new FileInputStream(config));
// 获取规则列表路径
this.TopologyList = pros.getProperty("topologyList.path",
"TopologyList.rule");
// 获取worker数值
this.WorkerNum = pros.getProperty("worker.num", "10");
if (isNum.isNumeric(WorkerNum)) {
this.MaxWorker = Integer.parseInt(WorkerNum);
}
if (TopologyList != null) {
FileInputStream fis = new FileInputStream(TopologyList);
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
BufferedReader br = new BufferedReader(isr);
String line = "";
// 计算规则列表有效节点数
while ((line = br.readLine()) != null) {
if (line.length() != 0) {
this.Topology[this.Top_num] = line;
this.Top_num++;
}
}
}
}
该类为主要的申请节点,往zookeeper中写入配置信息,该类供主类调用,在nimbus执行时,将配置信息写入zk中。
private static final int SESSION_TIMEOUT = 3000;
private ZooKeeper zookeeper;
private OptZookeeper optzk = new OptZookeeper();
public void putZkConf(String configFile, String taskName) throws Exception {
Properties pros = new Properties();
File config = new File(configFile);
pros.load(new FileInputStream(config));
// 从nimbus域中获取配置文件zk服务器地址并连接zk
String nimbusZkConect = pros.getProperty("nimbus.zkConnect",
"localhost:2181");
zookeeper = optzk.connect(nimbusZkConect);
// 创建配置根目录
String ConfRoot = "TopologyConf";
String StrTmp = "/";
String node1 = StrTmp + ConfRoot;
String node2 = StrTmp + ConfRoot + StrTmp + taskName;
// 创建一级根目录"/TopologyConf"
if (zookeeper.exists(node1, null) == null) {
optzk.createNode(zookeeper, StrTmp + ConfRoot, "TopologyConf");
}
// 创建二级根目录"/TopologyConf/taskName"
if (zookeeper.exists(node2, null) != null) {
List list = optzk.getNodeChild(zookeeper, node2);
for (String chNode : list) {
optzk.deleteNode(zookeeper, node2 + StrTmp + chNode);
}
} else {
optzk.createNode(zookeeper, node2, "taskName");
System.out.println("TaskName");
}
// 获取MetaSpout并写入zk配置服务器中
String SpoutZkConnect = pros.getProperty("spout.zkConnect",
"localhost:2181");
String SpoutZkRoot = pros.getProperty("spout.zkRoot", "/meta");
String SpoutZkTopic = pros.getProperty("spout.zkTopic", "storm_test");
String SpoutZkGroup = pros.getProperty("spout.zkGroup", "storm_test");
optzk.createNode(zookeeper, node2 + StrTmp + "spout.zkConnect",
SpoutZkConnect);
optzk.createNode(zookeeper, node2 + StrTmp + "spout.zkRoot",
SpoutZkRoot);
optzk.createNode(zookeeper, node2 + StrTmp + "spout.zkTopic",
SpoutZkTopic);
optzk.createNode(zookeeper, node2 + StrTmp + "spout.zkGroup",
SpoutZkGroup);
// 获取MetaBolt并写入zk配置服务器中
String BoltZkConnect = pros.getProperty("bolt.zkConnect",
"localhost:2181");
String BoltZkRoot = pros.getProperty("bolt.zkRoot", "/meta");
String BoltZkTopic = pros.getProperty("bolt.zkTopic", "storm_test");
optzk.createNode(zookeeper, node2 + StrTmp + "bolt.zkConnect",
BoltZkConnect);
optzk.createNode(zookeeper, node2 + StrTmp + "bolt.zkRoot", BoltZkRoot);
optzk.createNode(zookeeper, node2 + StrTmp + "bolt.zkTopic",
BoltZkTopic);
// 获取MonitorBolt参数并写入zk配置服务器中
String MatchLogic = pros.getProperty("match.logic", "AND");
String MatchType = pros.getProperty("match.type",
"regular::range::routine0");
String MatchField = pros.getProperty("match.field", "1::2::5");
String MatchFieldValue = pros.getProperty("match.fieldValue",
".*google.*::200,2001::ina");
optzk.createNode(zookeeper, node2 + StrTmp + "match.logic", MatchLogic);
optzk.createNode(zookeeper, node2 + StrTmp + "match.type", MatchType);
optzk.createNode(zookeeper, node2 + StrTmp + "match.field", MatchField);
optzk.createNode(zookeeper, node2 + StrTmp + "match.fieldValue",
MatchFieldValue);
// 获取MysqlBolt参数并写入zk配置服务器中
String MysqlHost = pros.getProperty("mysql.host_port",
"192.168.2.240:3306");
String MysqlDatabase = pros.getProperty("mysql.database", "storm");
String MysqlFrom = pros.getProperty("mysql.from", "monitor");
String MysqlUsername = pros.getProperty("mysql.username", "root");
String MysqlPassword = pros.getProperty("mysql.password", "123456");
optzk.createNode(zookeeper, node2 + StrTmp + "mysql.host_port",
MysqlHost);
optzk.createNode(zookeeper, node2 + StrTmp + "mysql.database",
MysqlDatabase);
optzk.createNode(zookeeper, node2 + StrTmp + "mysql.from", MysqlFrom);
optzk.createNode(zookeeper, node2 + StrTmp + "mysql.username",
MysqlUsername);
optzk.createNode(zookeeper, node2 + StrTmp + "mysql.password",
MysqlPassword);
optzk.close(zookeeper);
}
该类为从zookeeper中获取配置信息的类,为各个bolt调用,从节点中获取配置信息。
public boolean getZkConf(String configFile, String taskName)
throws Exception {
Properties pros = new Properties();
File config = new File(configFile);
pros.load(new FileInputStream(config));
// 从nimbus域中获取配置文件zk服务器地址并连接zk
String nimbusZkConect = pros.getProperty("nimbus.zkConnect",
"localhost:2181");
zookeeper = optzk.connect(nimbusZkConect);
// 创建配置根目录
String ConfRoot = "TopologyConf";
String StrTmp = "/";
String node2 = StrTmp + ConfRoot + StrTmp + taskName;
// 创建二级根目录"/TopologyConf/taskName"
if (zookeeper.exists(node2, null) != null) {
获取MetaSpout的配置信息
SpoutZkConnect = optzk.getData(zookeeper, node2 + StrTmp + "spout.zkConnect");
SpoutZkRoot = optzk.getData(zookeeper, node2 + StrTmp + "spout.zkRoot");
SpoutZkTopic = optzk.getData(zookeeper, node2 + StrTmp + "spout.zkTopic");
SpoutZkGroup = optzk.getData(zookeeper, node2 + StrTmp + "spout.zkGroup");
// 获取MetaBolt的配置信息
BoltZkConnect = optzk.getData(zookeeper, node2 + StrTmp + "bolt.zkConnect");
BoltZkRoot = optzk.getData(zookeeper, node2 + StrTmp + "bolt.zkRoot");
BoltZkTopic = optzk.getData(zookeeper, node2 + StrTmp + "bolt.zkTopic");
// 获取MonitorBolt的配置信息
MatchLogic = optzk.getData(zookeeper, node2 + StrTmp + "match.logic");
MatchType = optzk.getData(zookeeper, node2 + StrTmp + "match.type");
MatchField = optzk.getData(zookeeper, node2 + StrTmp + "match.field");
MatchFieldValue = optzk.getData(zookeeper, node2 + StrTmp + "match.fieldValue");
// 获取MysqlBolt的配置信息
MysqlHost = optzk.getData(zookeeper, node2 + StrTmp + "mysql.host_port");
MysqlDatabase = optzk.getData(zookeeper, node2 + StrTmp + "mysql.database");
MysqlFrom = optzk.getData(zookeeper, node2 + StrTmp + "mysql.from");
MysqlUsername = optzk.getData(zookeeper, node2 + StrTmp + "mysql.username");
MysqlPassword = optzk.getData(zookeeper, node2 + StrTmp + "mysql.password");
optzk.close(zookeeper);
return true;
} else {
System.out.println("Conf is not exist!");
return false;
}
}
方法名称 | 描述 |
String create(final String path, byte data[], List acl, CreateMode createMode) | 创建一个znode节点, 参数: 路径、 znode内容,ACL(访问控制列表)、 znode创建类型 |
void delete(final String path, int version) | 删除一个znode节点, 参数:路径、版本号;如果版本号与znode的版本号不一致,将无法删除,是一种乐观加锁机制;如果将版本号设置为-1,不会去检测版本,直接删除; |
Stat exists(final String path, Watcher watcher) | 判断某个znode节点是否存在 参数:路径、Watcher(监视器);当这个znode节点被改变时,将会触发当前Watcher |
Stat exists(String path, boolean watch) | 判断某个znode节点是否存在 参数: 路径、并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher |
Stat setData(final String path, byte data[], int version) | 设置某个znode上的数据 参数: 路径、数据、版本号;如果为-1,跳过版本检查 |
byte[] getData(final String path, Watcher watcher, Stat stat) | 获取某个znode上的数据 参数: 路径、监视器、数据版本等信息 |
List getChildren(final String path, Watcher watcher) | 获取某个节点下的所有子节点 参数: 路径、监视器;该方法有多个重载 |
以上为zookeeper客户端编程的API,通过调用API,对zookeeper节点能够进行创建,删除,变更,获取子节点等操作。
4 项目拓展
MonitorTopology目前已经完善了metaq与storm接口,实时过滤处理,数据存储方面实现了metaq重写接口,mysql存储接口,整个架构的统一配置系统。就总体实时处理架构来说,已经越来越完善。
项目扩展的目标:
(1) metaq数据获取,即尝试从现有前端动态日志中(模拟前端业务系统动态产生类似log4j日志)动态获取日志,并写入metaq中作为源数据;
(2) 丰富实时处理部分的bolt,增加其他功能bolt,如求Top N、条件统计、数据拆分等等;
(3) 丰富数据存储部分接口,特别是与hadoop接口;
(4) 使用zookeeper或者是mysql实现架构的元数据部分。
不积跬步无以至千里,我会一步一步把这个storm架构填充丰满。
PS:设计思路可以先参考参考,代码周末的时候调试调试,没问题放出来。