使用Hive的HWI接口,可以通过在Web页面上提交HQL查询操作,并浏览查询结果数据。默认情况下,HWI只支持浏览结果数据,不能够下载查询结果文件(当然,HWI可能也是考虑通过Web下载大量的结果数据,对服务器造成压力,或者处于安全方面的考虑)。我们对HWI进行了简单的改造,改造内容主要是增加了一个内置的文件服务器,可以通过页面进行查询,然后下载结果文件。
HWI安装配置
首先,要保证Hadoop集群正常运行,Hive能够正常使用。
先要安装Ant,如下所示:
1 |
wget http://mirrors.hust.edu.cn/apache//ant/binaries/apache-ant-1.9.4-bin.tar.gz |
2 |
tar xvzf apache-ant-1.9.4-bin.tar.gz |
3 |
ln -s /usr/local/apache-ant-1.9.4-bin /usr/local/ant |
修改Hive的环境配置文件hive-env.sh,增加如下配置内容:
1 |
export ANT_LIB=/usr/local/ant |
将如下JAR文件拷贝到${HIVE_HOME}/lib目录下面:
2 |
jasper-compiler-5.5.23.jar |
3 |
jasper-runtime-5.5.23.jar |
5 |
// 替换默认的servlet-api-2.5-20081211.jar,我使用的是apache-tomcat-7.0.53/lib下面的servlet-api.jar文件 |
另外,由于Hive 0.12.0版本的HWI的问题,需要将你的${JAVA_HOME}/lib/tools.jar文件也加入到${HIVE_HOME}/lib目录下。这个算是一个Bug吧,大概要到Hive 0.13.0版本能够解决。
接下来,需要在${HIVE_HOME}/conf/hive-site.xml配置文件中,加入如下配置内容:
03 |
<name>hive.hwi.listen.host</name> |
04 |
<value>0.0.0.0</value> |
05 |
<description>This is the host address the Hive Web Interface will listen on.</description> |
08 |
<name>hive.hwi.listen.port</name> |
10 |
<description>This is the port the Hive Web Interface will listen on.</description> |
13 |
<name>hive.hwi.war.file</name> |
14 |
<value>/lib/hwi.war</value> |
15 |
<description>This is the WAR file with the jsp content for Hive Web Interface.</description> |
其中,属性hive.hwi.war.file对应的hwi.war文件,我们会在后面改造之后重新打包为该文件,并拷贝到${HIVE_HOME}/lib目录下面。
HWI改造
默认使用HWI是没有查询结果文件下载功能的,可以增加一个文件服务器,用来存放并提供下载查询结果文件。这里,我发现HWI虽然也提供一个Web容器,但是由于封装的太深,所以放弃修改内部源码,而是直接通过我熟悉的方式,使用Jetty实现了一个文件服务,需要用到下面的一些JAR文件:
02 |
jetty-continuation-8.1.0.RC5.jar |
03 |
jetty-io-8.1.0.RC5.jar |
04 |
jetty-server-8.1.0.RC5.jar |
05 |
jetty-util-8.1.0.RC5.jar |
06 |
jetty-xml-8.1.0.RC5.jar |
07 |
jetty-http-8.1.0.RC5.jar |
08 |
jetty-security-8.1.0.RC5.jar |
09 |
jetty-servlet-8.1.0.RC5.jar |
10 |
jetty-webapp-8.1.0.RC5.jar |
首先,定义一个服务器接口,用来控制文件服务器的启停操作等,如下所示:
01 |
package org.shirdrn.hadoop.hive.jetty; |
03 |
public interface JettyServer { |
07 |
/** Start this server. */ |
10 |
/** Stop this server. */ |
13 |
/** Wait for this server to exit. */ |
14 |
void join() throws InterruptedException; |
然后,实现一个文件服务器,代码如下所示:
01 |
package org.shirdrn.hadoop.hive.jetty; |
03 |
import java.util.HashMap; |
04 |
import java.util.Iterator; |
06 |
import java.util.Map.Entry; |
08 |
import org.apache.commons.logging.Log; |
09 |
import org.apache.commons.logging.LogFactory; |
10 |
import org.eclipse.jetty.server.Server; |
11 |
import org.eclipse.jetty.server.handler.DefaultHandler; |
12 |
import org.eclipse.jetty.server.handler.HandlerList; |
13 |
import org.eclipse.jetty.server.handler.ResourceHandler; |
14 |
import org.eclipse.jetty.server.nio.SelectChannelConnector; |
17 |
* Jetty file server for handling hvie query result file downloading. |
21 |
public class JettyFileServer implements JettyServer { |
23 |
private static final Log LOG = LogFactory.getLog(JettyFileServer.class); |
24 |
private final Server server; |
25 |
private final int port = 9722; |
27 |
static Map<String, String> users = new HashMap<String, String>(); |
28 |
static Map<String, String> resourceBases = new HashMap<String, String>(); |
30 |
users.put("user", "user"); |
31 |
resourceBases.put("user", "/download"); |
34 |
public JettyFileServer() { |
35 |
server = new Server(port); |
36 |
SelectChannelConnector connector = new SelectChannelConnector(); |
37 |
server.addConnector(connector); |
39 |
HandlerList handlers = new HandlerList(); |
40 |
createFileServerContexts(handlers); |
42 |
handlers.addHandler(new DefaultHandler()); |
44 |
server.setHandler(handlers); |
47 |
private void createFileServerContexts(HandlerList handlers) { |
48 |
Iterator<Entry<String, String>> iter = users.entrySet().iterator(); |
49 |
while(iter.hasNext()) { |
50 |
Entry<String, String> entry = iter.next(); |
51 |
String user = entry.getKey(); |
52 |
String fileBase = resourceBases.get(user); |
53 |
ResourceHandler resourceHandler = createResourceHandler(user, fileBase); |
54 |
handlers.addHandler(resourceHandler); |
58 |
private ResourceHandler createResourceHandler(String user, String fileBase) { |
59 |
ResourceHandler resourceHandler = new ResourceHandler(); |
60 |
resourceHandler.setDirectoriesListed(true); |
61 |
resourceHandler.setWelcomeFiles(new String[]{ "index.html" }); |
62 |
resourceHandler.setResourceBase(fileBase); |
63 |
return resourceHandler; |
67 |
public int getPort() { |
75 |
} catch (InterruptedException e) { |
76 |
} catch (Exception e) { |
77 |
throw new RuntimeException("Fail to start Jetty file server!", e); |
85 |
} catch (Exception e) { |
86 |
throw new RuntimeException("Fail to stop Jetty file server!", e); |
91 |
public void join() throws InterruptedException { |
这里,为了简化,我没有使用配置的方式,配置文件服务器的resource base目录,直接写死路径在代码里面,默认是/download目录,主要的是,这个目录需要设置一下权限,可以允许任何人写,但是不能覆盖别人已经写的文件(执行查询生成的结果文件):
- 增加一个ServletContextListener
主要用来启动文件服务器,实现类FileServerContextListener的代码如下所示:
01 |
package org.shirdrn.hadoop.hive.jetty; |
03 |
import javax.servlet.ServletContextEvent; |
05 |
import org.apache.commons.logging.Log; |
06 |
import org.apache.commons.logging.LogFactory; |
08 |
public class FileServerContextListener implements javax.servlet.ServletContextListener { |
10 |
protected static final Log l4j = LogFactory.getLog(FileServerContextListener.class |
12 |
private JettyServer fileServer; |
14 |
public void contextInitialized(ServletContextEvent sce) { |
16 |
l4j.info("Start Jetty file server..."); |
17 |
JettyServer server = new JettyFileServer(); |
19 |
l4j.info("Jetty file server started!"); |
22 |
public void contextDestroyed(ServletContextEvent sce) { |
25 |
l4j.info("Jetty file server stopped!"); |
然后,需要在web.xml文件中增加如下配置:
2 |
<listener-class>org.shirdrn.hadoop.hive.jetty.FileServerContextListener</listener-class> |
因为我们可能需要将HWI暴露给组织内部的其他项目团队使用,只允许他们查询,就应该限制Hive的DDL、DML操作,不运行他们建库建表、
这里,需要修改页面session_manage.jsp,修改后的内容,如下所示:
001 |
<%@page import="org.apache.hadoop.hive.hwi.*" %> |
002 |
<%@page import="java.util.Arrays" %> |
003 |
<%@page import="java.util.List" %> |
004 |
<%@page errorPage="error_page.jsp" %> |
005 |
<% HWISessionManager hs = (HWISessionManager) application.getAttribute("hs");; %> |
007 |
<% HWIAuth auth = (HWIAuth) session.getAttribute("auth"); %> |
008 |
<% if (auth==null) { %> |
009 |
<jsp:forward page="/authorize.jsp" /> |
011 |
<% String sessionName=request.getParameter("sessionName"); %> |
012 |
<% HWISessionItem sess = hs.findSessionItemByName(auth,sessionName); %> |
013 |
<% String message=null; %> |
015 |
String randomFile = String.valueOf(System.currentTimeMillis()) + ".txt"; // 生成随机文件名称 |
016 |
String errorFile=request.getParameter("errorFile"); |
017 |
String resultFile=request.getParameter("resultFile"); |
018 |
resultFile = "/user/download/" + resultFile; // 结果文件存储的路径 |
019 |
String query = request.getParameter("query"); |
020 |
String silent = request.getParameter("silent"); |
021 |
String start = request.getParameter("start"); |
023 |
// 简单的HQL操作过滤,限制DDL、DML操作 |
024 |
String[] stoppedKeywords = new String[] { |
025 |
"INSERT", "DELETE", "TRUNCATE", |
026 |
"CREATE", "DROP", "ALTER", |
027 |
"GRANT", "REVOKE", "LOAD" |
029 |
List<String> list = Arrays.asList(stoppedKeywords); |
031 |
String q = query.toUpperCase(); |
032 |
String[] a = q.split("\\s+"); |
034 |
if(list.contains(w)) { // 如果包含上述关键词,直接抛出异常,限制执行操作 |
035 |
String err = "Permission denied! Excludes operations: " + list; |
036 |
throw new HWIException(err); |
042 |
if (request.getParameter("start")!=null ){ |
043 |
if ( sess.getStatus()==HWISessionItem.WebSessionItemStatus.READY){ |
044 |
sess.setErrorFile(errorFile); |
045 |
sess.setResultFile(resultFile); |
048 |
for (String q : query.split(";") ){ |
051 |
if (query.length()==0){ |
052 |
message="You did not specify a query"; |
055 |
if (silent.equalsIgnoreCase("YES") ) |
056 |
sess.setSSIsSilent(true); |
058 |
sess.setSSIsSilent(false); |
060 |
message="Changes accepted."; |
061 |
if (start.equalsIgnoreCase("YES") ){ |
063 |
message="Session is set to start."; |
071 |
<title>Manage Session <%=sessionName%></title> |
072 |
<link href="css/bootstrap.min.css" rel="stylesheet"> |
074 |
<body style="padding-top: 60px;"> |
075 |
<jsp:include page="/navbar.jsp"></jsp:include> |
076 |
<div class="container"> |
079 |
<jsp:include page="/left_navigation.jsp" /> |
084 |
<%=sessionName%></h2> |
086 |
<% if (message != null) { %> |
087 |
<div class="alert alert-info"><%=message %></div> |
090 |
<% if (sess.getStatus()==HWISessionItem.WebSessionItemStatus.QUERY_RUNNING) { %> |
091 |
<div class="alert alert-warning">Session is in QUERY_RUNNING |
092 |
state. Changes are not possible!</div> |
095 |
<% if (sess.getStatus()==HWISessionItem.WebSessionItemStatus.QUERY_RUNNING){ %> |
097 |
View JobTracker: <a href="<%= sess.getJobTrackerURI() %>">View Job</a><br> |
098 |
Kill Command: <%= sess.getKillCommand() %> |
099 |
Session Kill: <a href="/hwi/session_kill.jsp?sessionName=<%=sessionName%>"><%=sessionName%></a><br> |
103 |
<div class="btn-group"> |
104 |
<a class="btn" href="/hwi/session_history.jsp?sessionName=<%=sessionName%>"><i class="icon-book"></i> History</a> |
105 |
<a class="btn" href="/hwi/session_diagnostics.jsp?sessionName=<%=sessionName%>"><i class="icon-cog"></i> Diagnostics</a> |
106 |
<a class="btn"href="/hwi/session_remove.jsp?sessionName=<%=sessionName%>"><i class="icon-remove"></i> Remove</a> |
107 |
<a class="btn"href="/hwi/session_result.jsp?sessionName=<%=sessionName%>"><i class=" icon-download-alt"></i> Result Bucket</a> |
110 |
<form action="session_manage.jsp" class="form-horizontal"> |
111 |
<input type="hidden" name="sessionName" value="<%=sessionName %>"> |
114 |
<legend>Session Details </legend> |
115 |
<div class="control-group"> |
116 |
<label class="control-label" for="fldresfile">Result File</label> |
117 |
<div class="controls"> |
118 |
<input id="fldresfile" type="text" name="resultFile" |
119 |
readonly value="<%=randomFile%>"> |
120 |
<% if (sess.getResultFile()!=null) { %> |
121 |
<a href="/hwi/view_file.jsp?sessionName=<%=sessionName%>">View File</a> |
123 |
String[] a = sess.getResultFile().split("/"); |
124 |
String file = a[a.length - 1]; |
127 |
<a target="_blank" href="http://192.168.1.105:9722/<%=file%>">Download File</a> |
132 |
<div class="control-group"> |
133 |
<label class="control-label" for="flderrfile">Error File</label> |
134 |
<div class="controls"> |
135 |
<input id="flderrfile" type="text" name="errorFile" |
137 |
if (sess.getErrorFile()==null) { out.print(""); } else { out.print(sess.getErrorFile()); } |
142 |
<div class="control-group"> |
143 |
<label class="control-label" for="fldquery">Query</label> |
144 |
<div class="controls"> |
145 |
<textarea id="fldquery" name="query" rows="8" cols="70"> |
147 |
if (sess.getQueries()==null) { |
150 |
for (String qu: sess.getQueries() ) { |
151 |
out.print(qu); out.print(" ; "); |
160 |
<div class="control-group"> |
161 |
<label class="control-label" for="fldsilent">Silent Mode</label> |
162 |
<div class="controls"> |
163 |
<select id="fldsilent" name="silent"> |
165 |
<% if (sess.getSSIsSilent()==true) { out.print("SELECTED=\"TRUE\""); } %>>YES</option> |
167 |
<% if (sess.getSSIsSilent()==false) { out.print("SELECTED=\"TRUE\""); } %>>NO</option> |
172 |
<div class="control-group"> |
173 |
<label class="control-label" for="fldstart">Start Query</label> |
174 |
<div class="controls"> |
175 |
<select id="fldstart" name="start"> |
176 |
<option value="YES" SELECTED="TRUE">YES</option> |
177 |
<option value="NO">NO</option> |
184 |
<h3>Query Return Codes</h3> |
186 |
<% for (int i=0; i< sess.getQueryRet().size();++i ){ %> |
189 |
<%=sess.getQueryRet().get(i)%><br> |
193 |
<% if (sess.getStatus()!=HWISessionItem.WebSessionItemStatus.QUERY_RUNNING) { %> |
194 |
<div class="form-actions"> |
195 |
<button type="submit" class="btn btn-primary">Submit</button> |
上面注释的地方,说明了修改的内容,可以查看。
HWI重新构建
我们只需要将我们自己实现的代码部分,打包到WAR文件里面就可以,因为默认的HWI的Java实现部分,可以在Hive的软件包中找到,可以查看${HIVE_HOME}/lib/hive-hwi-0.12.0.jar。上面我们改造过程中实现了一个Jetty文件服务器,编译完成后,需要删除默认目录${HWI_HOME}\target\WEB-INF\classes下面,不是我们改造后增加的类的类文件,然后就可以执行如下命令构建:
然后,将生成的hwi文件,拷贝到部署Hive的对应目录${HIVE_HOME}/lib/下面即可,启动HWI服务:
1 |
hive --service hwi >> /tmp/hwi/hwi.log & |
然后,就可以通过Web页面访问。
HWI使用
访问页面,例如我的是http://10.10.2.245:9999/hwi,然后,可以看到HWI首页,执行如下操作进行查询:
- 点击“Authorize”,填写“User”和“Groups”的内容,例如都是hadoop,然后点击提交按钮“Submit”;
- 点击“Create Session”,填写“Session name”,例如MYSESSION[hadoop],然后点击提交按钮“Submit”;
- 这时,进入到Manage Session MYSESSION[hadoop]页面,可以在“Query”中输入HQL查询语句,“Start Query”选择“YES”,然后击提交按钮“Submit”;
- 可以通过“View File”和“Result Bucket”查看结果内容;
- 可以通过我们改造后的“Download File”链接,进行查询结果文件的下载。
参考链接
- https://cwiki.apache.org/confluence/display/Hive/HiveWebInterface#HiveWebInterface-FeaturesofHWI
- https://issues.apache.org/jira/browse/HIVE-5132
- http://ant.apache.org/bindownload.cgi
- http://mvnrepository.com/artifact/org.eclipse.jetty/jetty-server/8.1.0.RC5
- http://mvnrepository.com/artifact/org.eclipse.jetty/jetty-servlet/8.1.0.RC5
来源URL:http://shiyanjun.cn/archives/885.html