Hive源码剖析之HiveServer2服务启动过程

一、前言

    前段时间用Hive JDBC出现了阻塞问题,客户端一直处于等待状态,为了解决该问题,花了几天研究了Hive源码,于是准备写成系列博文,与大家分享其中的心得。有不当之处,请大家更正。

二、背景

    我们生活在一个数据的时代,我们在经意与不经意间留下了数据。带着手机出门,不经意间我们留下了计步数据和活动轨迹数据,我们网购商品,不经意间留下个人偏好数据,发个朋友圈,留下了各种图片和文字数据……我们每个人都是数据贡献者。

    数据的暴增给数据的存储、分析和查询带来了很多难点,于是谷歌提出了很多解决方案,并发表了三篇意义重大的论文Google File System、Google MapReduce、Google Bigtable,基于这三篇论文,便产生了Hadoop和HBase。Hadoop是基于GFS和MapReduce理论的开源实现,解决大文件的存储与分析问题,HBase是基于Bigtable理论的开源实现,解决海量数据的实时检索。

    MapReduce对于Hadoop而言,既是算法模型,也是框架。要使用MapReduce必须先把业务转化为适合该算法模型,并基于该框架编程。对于框架,笔者这样认为,框架提高了开发效率的同时,也限定了人的思维范围和编程范围。房子是一个框架结构,住在房子里的人,活动范围只限于活动范围之间,开发框架亦是如此,Struts2是MVC框架,开发人员只能按照它所限定的模式开发,这何尝不是一种禁锢呢?作为一个软件架构者,了解框架实现的细节,才能走出禁锢。这也是笔者坚持看各种框架源码的原因之一。

    编写出好的MapReduce程序本身不是轻而易举的事,所有就有了Hive,它能把Sql指令,转化为MapReduce任务,让开发人员用Sql的方式去使用Hadoop的运算能力。我们在使用这些工具的同时,也不得不赞叹这些杰出科学家奇思妙想。

三、分享重点

    HiveServer2服务启动过程

四、源码

    1、开一段程序,入口肯定是main方法

public static void main(String[] args) {
    //设置加载配置
	HiveConf.setLoadHiveServer2Config(true);
	try {
		ServerOptionsProcessor oproc = new ServerOptionsProcessor(
				"hiveserver2");
		ServerOptionsProcessorResponse oprocResponse = oproc.parse(args);
		String initLog4jMessage = LogUtils.initHiveLog4j();
		LOG.debug(initLog4jMessage);
		HiveStringUtils
				.startupShutdownMessage(HiveServer2.class, args, LOG);
		LOG.debug(oproc.getDebugMessage().toString());
        //这里便是要启动服务了
		oprocResponse.getServerOptionsExecutor().execute();
	} catch (LogInitializationException e) {
		LOG.error("Error initializing log: " + e.getMessage(), e);
		System.exit(-1);
	}
}

    2、ServerOptionsExecutor接口有三个实现类

    DeregisterOptionExecutor:将HiveServer2实例从zookeeper中移除

    HelpOptionExecutor:打印help参数

    ServerOptionsExecutor:启动HiveServer2服务

    这里很显然,我们应该看ServerOptionsExecutor类

static class StartOptionExecutor implements ServerOptionsExecutor {
	@Override
	public void execute() {
		try {
            //启动服务
			startHiveServer2();
		} catch (Throwable t) {
			LOG.fatal("Error starting HiveServer2", t);
			System.exit(-1);
		}
	}
}

    3、startHiveServer2方法,重要的地方给出了必要的注释

private static void startHiveServer2() throws Throwable {
	long attempts = 0, maxAttempts = 1;
	while (true) {
		LOG.info("Starting HiveServer2");
		HiveConf hiveConf = new HiveConf();
		maxAttempts = hiveConf
				.getLongVar(HiveConf.ConfVars.HIVE_SERVER2_MAX_START_ATTEMPTS);
		HiveServer2 server = null;
		try {
			server = new HiveServer2();
            //初始化
			server.init(hiveConf);
            //启动
			server.start();
           //省略了一些代码
			break;
		} catch (Throwable throwable) {
			throwable.printStackTrace();
            //出现异常就停止服务
			if (server != null) {
				try {
					server.stop();
				} catch (Throwable t) {
					LOG.info(
							"Exception caught when calling stop of HiveServer2 before retrying start",
							t);
				} finally {
					server = null;
				}
			}
            //如果抛出异常,并尝试启动超过了配置的最大尝试次数,抛出错误,启动失败
			if (++attempts >= maxAttempts) {
				throw new Error("Max start attempts " + maxAttempts
						+ " exhausted", throwable);
			} else {
                //60秒后再次尝试启动
				LOG.warn("Error starting HiveServer2 on attempt "
						+ attempts + ", will retry in 60 seconds",
						throwable);
				try {
					Thread.sleep(60L * 1000L);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
		}
	}
}

    4、接下来我们看看server的初始化做了哪些事情

@Override
public synchronized void init(HiveConf hiveConf) {
    //实例化clientService实例,该实例用于把用户请求转化并传递给Driver
	cliService = new CLIService(this);
	addService(cliService);
	if (isHTTPTransportMode(hiveConf)) {
		thriftCLIService = new ThriftHttpCLIService(cliService);
	} else {//默认情况是Thrift二进制服务
		thriftCLIService = new ThriftBinaryCLIService(cliService);
	}
    //添加进服务列表
	addService(thriftCLIService);
	super.init(hiveConf);
	// 省略获取配置信息……
	
	// 启动web UI,改web UI用于查看正在运行的Hive任务,默认端口10002
	try {
		        //省略大量获取的配置的代码
                。。。。。。
				webServer = builder.build();
                //添加查询运行任务数据的servlet
				webServer.addServlet("query_page", "/query_page",
						QueryProfileServlet.class);
			}
		}
	} catch (IOException ie) {
		throw new ServiceException(ie);
	}
	// Add a shutdown hook for catching SIGTERM & SIGINT
	final HiveServer2 hiveServer2 = this;
    //添加钩子
	Runtime.getRuntime().addShutdownHook(new Thread() {
		@Override
		public void run() {
			hiveServer2.stop();
		}
	});
}

    初始化主要是获取了一些配置参数,并且告诉程序,要启动这些服务CLIService、thriftCLIService 、webServer

    5、下面是重头戏,启动服务了,紧接着第3步中server.start()看下来,start方法

@Override
public synchronized void start() {
	super.start();//调用父类start方法
	if (webServer != null) {
		try {
			webServer.start();//启动web服务
			LOG.info("Web UI has started on port " + webServer.getPort());
		} catch (Exception e) {
			LOG.error("Error starting Web UI: ", e);
			throw new ServiceException(e);
		}
	}
}

    start方法,做了两件事情,就是调用父类start方法,启动web服务

    6、下面我跟进去看看,父类CompositeService中定义的start方法

@Override
public synchronized void start() {
	int i = 0;
	try {
		for (int n = serviceList.size(); i < n; i++) {
			Service service = serviceList.get(i);
			service.start();
		}
		super.start();
	} catch (Throwable e) {
		stop(i);
		throw new ServiceException("Failed to Start " + getName(), e);
	}
}

这里仅仅是把第4步中添加的服务列表中的服务进行了启动,下面我们我们说服务中的重点,HiveServer2怎么提供远程服务,请看ThriftBinaryCLIService

    7、ThriftBinaryCLIService中的run方法

@Override
public void run() {
	try {
		// 定义处理请求的线程池
		String threadPoolName = "HiveServer2-Handler-Pool";
		ExecutorService executorService = new ThreadPoolExecutor(
				minWorkerThreads, maxWorkerThreads, workerKeepAliveTime,
				TimeUnit.SECONDS, new SynchronousQueue(),
				new ThreadFactoryWithGarbageCleanup(threadPoolName));


		//省略定义传输协议、配置参数代码
         ........
		int maxMessageSize = hiveConf
				.getIntVar(HiveConf.ConfVars.HIVE_SERVER2_THRIFT_MAX_MESSAGE_SIZE);
		int requestTimeout = (int) hiveConf.getTimeVar(
				HiveConf.ConfVars.HIVE_SERVER2_THRIFT_LOGIN_TIMEOUT,
				TimeUnit.SECONDS);
		int beBackoffSlotLength = (int) hiveConf
				.getTimeVar(
						HiveConf.ConfVars.HIVE_SERVER2_THRIFT_LOGIN_BEBACKOFF_SLOT_LENGTH,
						TimeUnit.MILLISECONDS);
		TThreadPoolServer.Args sargs = new TThreadPoolServer.Args(
				serverSocket)
				.processorFactory(processorFactory)
				.transportFactory(transportFactory)
				.protocolFactory(new TBinaryProtocol.Factory())
				.inputProtocolFactory(
						new TBinaryProtocol.Factory(true, true,
								maxMessageSize, maxMessageSize))
				.requestTimeout(requestTimeout)
				.requestTimeoutUnit(TimeUnit.SECONDS)
				.beBackoffSlotLength(beBackoffSlotLength)
				.beBackoffSlotLengthUnit(TimeUnit.MILLISECONDS)
				.executorService(executorService);

		// TCP Server
		server = new TThreadPoolServer(sargs);
		server.setServerEventHandler(serverEventHandler);
		server.serve();
		String msg = "Started "
				+ ThriftBinaryCLIService.class.getSimpleName()
				+ " on port " + portNum + " with " + minWorkerThreads
				+ "..." + maxWorkerThreads + " worker threads";
		LOG.info(msg);
	} catch (Throwable t) {
		LOG.fatal("Error starting HiveServer2: could not start "
				+ ThriftBinaryCLIService.class.getSimpleName(), t);
		System.exit(-1);
	}
}

    说明:这里的ExecutorService线程池的定义中用了SynchronousQueue队列,该队列的可以认为是长度为1的阻塞队列,当线程池满,并且没有空闲线程,便会阻塞。TThreadPoolServer的特点是,客户端只要不从服务器上断开连接,就会一直占据服务器的一个线程,所以出现了本文中开头出现的阻塞问题,解决办法,如果服务器内存允许,可以适当加大线程池长度,或者增加hive节点,在配合负载均衡。

    8、特别说明

    Thrift是RPC界的利器,Facebook的杰作,可以轻松实现夸语言的服务调用,支持的语言有C++, Java, Go,Python, PHP, Haskell, C#,  JavaScript, Node.js等等

   (1)、模型接口
       服务的调用接口以及接口参数model、返回值model 
   (2)、Tprotocol协议定义 
         将数据(model)编码 、解码 。 
  ( 3)、Ttramsport传输层定义
        编码后的数据传输(简单socket、http) 
   (5)、Tserver服务类型
        服务的Tserver类型,实现了几种rpc调用(单线程、多线程、非阻塞IO)

五、后记

    本文中的描述了HiveServer2的启动过程中,启动的服务,那么客户端是如何与服务端交互的呢?为什么通过JDBC的方式就可以去Hive上执行任务了呢?Hive JDBC除了实现JDBC标准接口外,还做了哪些事情呢?

快乐源于分享。

   此博客乃作者原创, 转载请注明出处

 

你可能感兴趣的:(Hive源码剖析之HiveServer2服务启动过程)