【Zeus3简介】
Zeus3是一个完整的Hadoop的作业平台,是基于Zeus的一个二次开发项目,从Hadoop任务的调试运行到生产任务的周期调度,宙斯支持任务的整个生命周期从功能上来说,支持:
HadoopMapReduce任务的调试运行
Hive任务的调试运行
Shell任务的运行
Hive元数据的可视化查询与数据预览
Hadoop任务的自动调度
完整的文档管理
【作者简介】
蔡畅奇(Andy.cai),Java工程师 一直从事软件研发工作, 从2010年开始从事大数据的工作,做过ERP, 车联网, 爬虫, 电商系统的大数据体系架构. 专研过Java分布式编程以及传统、机器学习算法。287888203@qq.com,http://www.quora.com/Changqi-Cai
Andy.cai利用业余时间在阿里Zeus/Zeus2的基础上做二次开发,历时2周,并完善使用文档贡献给大家。
【文章正文】
改造Zeus的由来还要从项目组上Hive离线挖掘说起。当初项目组上Hive数据仓库需要调度各种Hive-SQL与Linux-Shell脚本,最开始的时候是通过Linux终端直接在生产环境上部署,并通过Linux的crontab调度。
生产环境上第一个脚本调度程序没有感觉,上多个后发现问题太多了:
[a]每个脚本程序运行情况不好监控,程序运行情况无法可视化查看
[b]每个服务器上跑了多少调度脚本不清楚
[c]运维同事生产发版更乱,无法简单地掌握全部调度情况
[d]复杂的workflow调度只能通过shell里面调用子shell的方式,耦合太深,不利于模块化。
[e]缺乏脚本调度的统计,多少成功,多少失败,多少延迟等
为了解决这个问题,需要采用一套调度系统,由于生产集群有安全控制需要,所以最好能是可以Web显示的系统。
我之前调研过Oozie调度系统,Oozie使用的时候有以下不便:
[a]Oozie调度的Workflow只能使用XML文件配置
[b]启动调度只能通过命令行
[c]无法通过Oozie界面调试调度脚本
[d]Oozie无法可视化调试脚本时候
[e]无法分组,权限管理等
同时调研过Kettle系统,Kettle更多关注在关系型DB之间的调度。由于Kettle不支持web页面,并且通过Hive Server调度Hive做Hive计算不太灵活。
因此视线转移到了阿里的Zeus大数据调度系统[1],但是阿里的Zeus调度系统距离现在已经2年没有维护。说到Zeus调度系统,我本人一年多前面试杭州阿里的推荐引擎大部门算法专家职位的时候,面试我的主管还特地问过我例如:Zeus是干啥的之类的问题,并给我介绍Zeus是他们团队的作品。加上Zeus是用java开发,我这几年一直研究阿里的java开源系统与java中间件开发,这样更加增加了我对Zeus调度系统改造的兴趣。
首先介绍下Github上的Zeus调度系统[1],Github上的Zeus系统刚刚上线时候很多人关注但是由于长期缺少人维护。由于时隔2年,支持的还是Hadoop1.X,我通读了里面的代码,
Zeus基于GWT开发。Google Web Toolkit的缩写,有了 GWT可以使用 Java 编程语言编写 AJAX 前端,然后 GWT 会交叉编译到优化的JavaScript 中,而 JavaScript 可以自动在所有主要浏览器上运行。GWT允许开发人员使用 Java 编程语言快速构建和维护复杂但性能高的 JavaScript 前端应用程序,从而降低了开发难度,尤其是与 Eclipse Google 插件结合使用时,优势更明显。Zeus通过MySQL存储各项调度任务,使用Dos2Unix转换脚本格式,通过读取(hive,shell以及自定义的变量)环境变量,使用Processbuilder调度。另外,使用CronTrigger定义定时调度。
Zeus能支持主要4种调度:
(1) Hive调度,将页面上的hive脚本转换为sql,然后通过hive -f x.sql执行调度。
(2)Shell调度,将页面上的shell脚本转为x.sh 通过sh x.sh调度。
(3)mapreduce调度,将页面上的jar文件通过hadoop 执行调度。
(4)调度java-jar包
Zeus的工程由以下几部分组成:
Zeus3的使用:
对一个软件系统的了解,先是了解如何使用这个系统,有哪些功能,开发的技术是啥,用了哪些套路,就像学骑车一样,先学会骑,然后再考虑如何改进与二次开发。下面再介绍Zeus3的使用场景:
Zeus3的安装与部署
[1]配置目录权限
mkdir -p /data/zeus/log
sudo -u hdfs hadoop fs -mkdir /zeus
sudo -u hdfs hadoop fs -mkdir /zeus/hdfs-upload-dir
mkdir -p /data/zeus/users/zhoufang/zeus/run_job_dir
mkdir -p /data/zeus/group/tbdataapplication/zhoufang
mkdir -p /data/zeus/users/zhoufang/zeus/logs/
[2]配置文件修改
2.1 zeus-web.properties //新增加的配置文件
2.2 persistence.xml
2.3 log4j.xml
2.4 broadcast-applicationContext.xml
[3]zeus的table创建文件
zeus3.sql
先创建zeus3数据库
[4]启动
通过tomcat起来,tomcat的内存请开大,如:12G。
另外,多次启动的时候,有可能需要kill掉之前的tomcat进程。
[5]浏览器
Zeus只支持firefox,chrome不支持IE
[6]自动发邮件设置
zeus-web.properties
mail.host=smtp.163.com
mail.smtp.auth=false
mail.smtp.timeout=25000
mail.username=xxx@163.com
mail.password=xxx
Zeus3的使用:
通过界面调试Hive脚本:
调试Hive的结果界面:
[2]调试Shell
调试页面,大体同调试Hive的界面:
[3]调度Shell
场景1:shell_task_1.sh 内部调用shell_task_yy.sh
Step1:上传shell_task_yy.sh作为资源
Step2:编辑调用
#!/bin/bash
sh ./shell_task_yy.sh
echo ‘shell_task_1’
Step3:执行
Step4:执行结果
场景2:定时调度任务,每隔1分钟调度一次,不断循环
0 0/1 * * * ?
场景3:依赖调度,先调度A再调度B
0 0/1 * * * ?
场景3:依赖调度,先调度A再调度B
具体配置细节:
(1)A任务调度配置自动一分钟一次:0 0/1 * * * ?
(2)B任务调度要配置依赖A任务,然后选择启动
(3)启动A任务
(4)查看workflow的调度结果
整个workflow由A触发
调度Hive
场景1-3:直接调用Hive-SQL,与SHELL类似
Zeus3的二次开发与改进之处:
[1] zeus-schedule工程中实现报警邮件发送,通过JavaMailSender实现
Java代码:
public class MailAlarm extends AbstractZeusAlarm{
private JavaMailSender mailSender;
@Override
public void alarm(List<String> users, String title, String content) throws Exception {
try {
MimeMessage mailMessage = mailSender.createMimeMessage();
// 设置utf-8或GBK编码,否则邮件会有乱码
MimeMessageHelper messageHelper = new MimeMessageHelper(mailMessage, true, “utf-8”);
messageHelper.setTo(Global.getConfig(“mail.sendtolist”)); // 邮件接受者
messageHelper.setFrom(Global.getConfig(“mail.username”)); // 邮件发送者
messageHelper.setSubject(title); // 主题
// 邮件内容,注意加参数true,表示启用html格式
String htmlString = content;
messageHelper.setText(htmlString, true);
mailSender.send(mailMessage);
} catch (Exception e) {
e.printStackTrace();
}
}
}
XML配置:applicationContext-mail.xml
<?xml version=“1.0” encoding=“UTF-8”?>
<beans xmlns=“http://www.springframework.org/schema/beans”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:aop=“http://www.springframework.org/schema/aop”
xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>
<bean id=“mailSender” class=“org.springframework.mail.javamail.JavaMailSenderImpl”>
<property name=“host”>
<value>smtp.163.com</value>
</property>
<property name=“javaMailProperties”>
<props>
<prop key=“mail.smtp.auth”>false</prop>
<prop key=“mail.smtp.timeout”>25000</prop>
</props>
</property>
<property name=“username”>
<value>xxx@163.com</value>
</property>
<property name=“password”>
<value>xxx#</value>
</property>
</bean>
</beans>
配置读取修改:DailyConf.java, OnlineConf.java
set(“fs.default.name”,Global.getConfig(“fs.default.name”));
set(“mapred.working.dir”,Global.getConfig(“mapred.working.dir”));
通过:Global读取配置参数
Global的读取配置实现:
Global.java
public class Global {
/**
* 保存全局属性值
*/
private static Map<String, String> map = Maps.newHashMap();
/**
* 属性文件加载对象
*/
private static PropertiesLoader propertiesLoader = new PropertiesLoader(“zeus-web.properties”);
/**
* 获取配置
*/
public static String getConfig(String key) {
String value = map.get(key);
if (value == null){
value = propertiesLoader.getProperty(key);
map.put(key, value);
}
return value;
}
}
PropertiesLoader.java,装载配置文件
public class PropertiesLoader {
private static Logger logger = LoggerFactory.getLogger(PropertiesLoader.class);
private static ResourceLoader resourceLoader = new DefaultResourceLoader();
private final Properties properties;
public PropertiesLoader(String… resourcesPaths) {
properties = loadProperties(resourcesPaths);
}
public Properties getProperties() {
return properties;
}
/**
* 取出Property,但以System的Property优先,取不到返回空字符串.
*/
private String getValue(String key) {
String systemProperty = System.getProperty(key);
if (systemProperty != null) {
return systemProperty;
}
if (properties.containsKey(key)) {
return properties.getProperty(key);
}
return “”;
}
/**
* 取出String类型的Property,但以System的Property优先,如果都为Null则抛出异常.
*/
public String getProperty(String key) {
String value = getValue(key);
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
/**
* 取出String类型的Property,但以System的Property优先.如果都为Null则返回Default值.
*/
public String getProperty(String key, String defaultValue) {
String value = getValue(key);
return value != null ? value : defaultValue;
}
/**
* 取出Integer类型的Property,但以System的Property优先.如果都为Null或内容错误则抛出异常.
*/
public Integer getInteger(String key) {
String value = getValue(key);
if (value == null) {
throw new NoSuchElementException();
}
return Integer.valueOf(value);
}
/**
* 取出Integer类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容错误则抛出异常
*/
public Integer getInteger(String key, Integer defaultValue) {
String value = getValue(key);
return value != null ? Integer.valueOf(value) : defaultValue;
}
/**
* 取出Double类型的Property,但以System的Property优先.如果都为Null或内容错误则抛出异常.
*/
public Double getDouble(String key) {
String value = getValue(key);
if (value == null) {
throw new NoSuchElementException();
}
return Double.valueOf(value);
}
/**
* 取出Double类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容错误则抛出异常
*/
public Double getDouble(String key, Integer defaultValue) {
String value = getValue(key);
return value != null ? Double.valueOf(value) : defaultValue;
}
/**
* 取出Boolean类型的Property,但以System的Property优先.如果都为Null抛出异常,如果内容不是true/false则返回false.
*/
public Boolean getBoolean(String key) {
String value = getValue(key);
if (value == null) {
throw new NoSuchElementException();
}
return Boolean.valueOf(value);
}
/**
* 取出Boolean类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容不为true/false则返回false.
*/
public Boolean getBoolean(String key, boolean defaultValue) {
String value = getValue(key);
return value != null ? Boolean.valueOf(value) : defaultValue;
}
/**
* 载入多个文件, 文件路径使用Spring Resource格式.
*/
private Properties loadProperties(String… resourcesPaths) {
Properties props = new Properties();
for (String location : resourcesPaths) {
InputStream is = null;
try {
Resource resource = resourceLoader.getResource(location);
is = resource.getInputStream();
props.load(is);
} catch (IOException ex) {
logger.info(“Could not load properties from path:” + location + “, ” + ex.getMessage());
} finally {
IOUtils.closeQuietly(is);
}
}
return props;
}
}
这样,配置就能采用key-value的形式自由定义在:zeus-web.properties中:
————-zeus-web.properties文件内容—————–
fs.default.name=hdfs://hmaster1:8020
mapred.working.dir=/group/tbdataapplication/zhoufang
mapred.job.tracker=hmaster1:8021
supers=andy.cai
email=xxx@qq.com
name=\u8521\u7545\u5947
phone=xxx
uid=andy.cai
mail.host=smtp.163.com
mail.smtp.auth=false
mail.smtp.timeout=25000
mail.sendtolist=xxx@163.com
mail.title=Zeus_Warning_Email
mail.username=xxx@163.com
mail.password=xxx#
实际上这个项目可以配合Diamond[2],dubbo[3],flash-dog[3]来实现,由于考虑到部署的复杂度,所以还是精简下,配置文件这块采用Global读取配置参数的形式,日志管理暂时去掉,服务调用采用GWT-RPC调用。
另外,ProcessJob.java, ConfUtil.java里面读取hive,hadoop等的配置文件,现在采用CDH版本的路径,后续可以改为通过配置文件读取,之前设计的是直接读取系统环境变量发现实际部署的时候有诸多不便。
以上Zeus3的工程代码下载
https://github.com/caichangqi/zeus3
文中引用:
[1]Zeus调度系统
https://github.com/alibaba/zeus
[2]diamond集中式配置管理系统
http://code.taobao.org/p/diamond/src/
[3]dubbo RPC-SOA框架
http://alibaba.github.io/dubbo-doc-static/Home-zh.htm
[4]flash-dog 日志收集系统
https://github.com/flash-dog/flash-dog