Hive 是基于 Hadoop 的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的 SQL 查询功能,可以将 SQL 语句转换为 MapReduce 任务进行运行。其优点是学习成本低,可以通过类 SQL 语句快速实现简单的 MapReduce 统计,不必开发专门的 MapReduce 应用,十分适合数据仓库的统计分析,是专为批处理设计的。但随着数据越来越多,使用 Hive 进行一个简单的数据查询可能要花费几分钟到几小时,显然不能满足交互式查询的需求。


Presto 通过分布式查询,可以近实时快速高效的完成海量数据的查询。Presto 查询引擎是一个 Master-Slave 的架构,由一个 Coordinator 节点,多个 Worker 节点组成。Presto 被设计为数据仓库和数据分析产品:数据即席查询分析、大规模数据聚集和生成报表。


Hue 是一个开源的 Apache Hadoop UI 系统,由 Cloudera Desktop 演化而来,最后 Cloudera 公司将其贡献给 Apache 基金会的 Hadoop 社区,它是基于 Python Web 框架 Django 实现的。使用 Hue 我们可以在浏览器端的 Web 控制台上与 Hadoop 集群进行交互来分析处理数据,例如操作 HDFS 上的数据,运行 MapReduce Job,执行 Hive 的 SQL 语句,浏览 HBase 数据库等等。


本文将介绍如何利用这些组件搭建及管理大数据交互平台,主要包括两节内容。第一节介绍平台搭建,包括 Hive 和 Hue 双活搭建,重点介绍如何利用 Slider 搭建 Presto on Yarn 平台。第二节介绍权限管理,包括 Hue 账号体系介绍,Hive 超级用户管理,Hive 集成 Hue 账号体系,presto-jdbc 集成 Hue 账号体系。

平台搭建

利用 Cloudera Manager 可以很方便的部署 Hue 和 Hive,具体的搭建过程本文就不再详述,我们采用如图所示的结构利用 haproxy 实现双活及负载均衡。日常工作中,数据分析人员主要使用 Hue 来提交交互式查询语句,每天也会有调度工具以及应用程序完成批量作业的查询,可以是直接利用 beeline 客户端、或者使用 python 脚本封装好的存储过程、或者是通过 jdbc 连接来提交查询任务。


利用 Slider 搭建 Presto on Yarn 简介

Presto 查询引擎是一个 Master-Slave 的架构,由一个 Coordinator 节点,一个 Discovery Server 节点,多个 Worker 节点组成,Discovery Server 通常内嵌于 Coordinator 节点中。Coordinator 负责解析 SQL 语句,生成执行计划,分发执行任务给 Worker 节点执行。Worker 节点负责实际执行查询任务。Worker 节点启动后向 Discovery Server 服务注册,Coordinator 从 Discovery Server 获得可以正常工作的 Worker 节点。如果配置了 Hive Connector,需要配置一个 Hive MetaStore 服务为 Presto 提供 Hive 元信息,Worker 节点与 HDFS 交互读取数据。


在 Yarn 的架构中,一个全局 ResourceManager 以主要后台进程的形式运行,它通常在专用机器上运行,在各种竞争的应用程序之间仲裁可用的集群资源。ResourceManager 会追踪集群中有多少可用的活动节点和资源,协调用户提交的哪些应用程序应该在何时获取这些资源。ResourceManager 是唯一拥有此信息的进程,所以它可通过某种共享的、安全的、多租户的方式制定分配(或者调度)决策。Presto 的 Master-Slave 的架构可以完全将 Coordinator 和 Worker 作为单个的容器交给 Yarn 来统一管理,包括资源的分配及程序的活动管理,这样集群中每台机器的资源都可以有 Yarn 统一协调分配资源,而不用人为的单独从机器中划分一部分资源运行 Worker 或 Coordinator。


一般来说基于 Yarn 开发应用,需要用户自己编写 Application Master 来处理资源申请、容错等,难度和复杂性比较大。Apache 二级孵化项目 Slider 是 Yarn 之外的孵化项目,目的是将用户的已有服务或者应用直接部署到 Yarn 上。随着 Slider 项目的发布,用户可以在不对已存在服务进行任何修改的前提下将之部署到 Yarn 集群中。Slider 可以用来部署分布式应用(如 Hbase、Presto)到 Yarn 集群,并且 Slider 可以对应用进行监控,还可以在不停止应用的情况下动态的扩大或者缩小应用的规模。Yarn 监控 container 的状态并通知 Slider-appmaster,如果某个 container 失败,Slider-appmaster 会向 Yarn 申请一个新的 container。


本小节接下来将详细介绍如何利用 Slider 将 Presto 搭建在 Yarn 上。

Yarn 基于 label 的 CapacityScheduler 设置

因为在 Presto 的使用过程中,需要知道 Presto 的 Coordinator 连接地址,为了使用方便最好是能将 Coordinator 固定在某台机器上。本文所采取的方案是利用 Yarn 的标签调度机制,给其中某台机器打上 Coordinator 标签,剩下的打上 Worker 标签以保证每次 Presto 提交到 Yarn 上时,Coordinator 这个组件都能运行在固定的机器上。所以在介绍搭建过程之前,先介绍 Yarn 基于 label 的 CapacityScheduler 设置。


给集群中的节点打上 label 是将相似特点的节点进行分组的一种途径,借助 queue 可以达到指定某些类型的 application 运行在标有特定标签的机器上。例如,Spark Streaming 实时长时服务与 MapReduce、Spark、Hive 等批处理应用共享 Yarn 集群资源。在共享环境中,经常因一个批处理应用占用大量网络资源或者 CPU 资源导致 Spark Streaming 资源被抢占,服务不稳定。基于标签的调度策略的基本思想是:用户可以为每个 nodemanager 标注几个标签,比如 highmem,highdisk 等,以表明该 nodemanager 的特性;同时,用户可以为调度器中每个队列标注几个标签,这样提交到某个队列中的作业,只会使用标注有对应标签的节点上的资源。


举个例子:假设某 hadoop 集群共有 7 个节点,硬件资源是 16GB 内存,4TB 磁盘;随着内存计算应用 spark streaming 程序越来越多,可以再另外添加 5 个大内存节点,比如内存是 64GB,为了让 spark 程序与 mapreduce 等其他程序更加和谐地运行在一个集群中,希望 spark 程序只运行在后来的 5 个大内存节点上,而之前的 mapreduce 程序既可以运行在之前的 7 个节点上,也可以运行在后来的 5 个大内存节点上,怎么办?


基于标签的调度解决方案是:
步骤 1:为旧的 7 个节点打上 normal 标签,为新的 5 个节点打上 highmem 标签;
步骤 2:在 capacity scheduler 中,创建两个队列,分别是 hadoop 和 spark,其中 hadoop 队列可使用的标签是 nornal 和 highmem,而 spark 则是 highmem,并配置两个队列的 capacity 和 maxcapacity。


这样就可以实现 spark 作业只能跑在 spark 集群上而不会影响生产的 mapreduce 作业,当无 spark 作业运行时,spark 集群的资源也可以被 mapreduce 作业使用。


在部署 Presto on Yarn 时,因为 Coordinator 所在的 container 受 Yarn 任意分配控制,每次部署 Presto 时都可能被部署在任意一个 nodemanager 上,也就是说用户的连接 host 不是固定的。这时可以通过指定 Coordinator 的 “yarn.label.expression”=”presto_coordinator” 参数进行限制,将 “presto_coordinator” 标签只打在一个节点上,这样就间接的达到了固定 coordinator 所在 host 的目的。


添加存放标签的 hdfs 目录

可以将节点与标签的对应关系数据保存在 hdfs 中,这样 yarn 在每次重启时都不会丢失。

hadoop fs -mkdir -p /yarn/node-labels hadoop fs -chown -R cloudera-scm:cloudera-scm /yarn hadoop fs -chmod -R 700 /yarn

yarn 设置 yarn-site.xml

<property>  <name>yarn.node-labels.enabled</name>  <value>true</value></property><property>  <name>yarn.node-labels.fs-store.root-dir</name>  <value>hdfs://nameservice1:8020/yarn/node-labels</value>
</property>

添加标签

# 在集群中注册标签
yarn rmadmin -addToClusterNodeLabels "presto_coordinator"
yarn rmadmin -addToClusterNodeLabels "presto_worker"
# 给节点添加标签
yarn rmadmin -replaceLabelsOnNode "hadoop01:8041,presto_coordinator,presto_worker"
yarn rmadmin -replaceLabelsOnNode "hadoop02:8041,presto_worker"
yarn rmadmin -replaceLabelsOnNode "hadoop03:8041,presto_worker"
# 删除节点上的标签
yarn rmadmin -replaceLabelsOnNode "hadoop01:8041"
# 查看节点的标签状态
yarn node -status hadoop01:8041
# 刷新队列配置
yarn rmadmin -refreshQueues

也可以在http://hadoop02:8088/cluster/nodes上查看到集群节点所有的标签。


设置队列


调度器与 node label 相关的配置项:

  • yarn.scheduler.capacity.<queue-path>.accessible-node-labels.<label>.capacity,特定的某个标签在子队列中设置的数值和必须是100

  • yarn.scheduler.capacity.<queue-path>.default-node-label-expression,提交到队列中的作业默认使用的标签,只能设置一个值。如果不设置表示 application 会从没有 node label 的节点中获取容器。

踩过的坑

提交任务一直处于 ACCEPTED 状态

任务处于 ACCEPTED 状态说明该任务所在的队列没有可用的资源。对每个父队列及子队列都必须设置yarn.scheduler.capacity.<queue-path>.accessible-node-labels和yarn.scheduler.capacity.<queue-path>.default-node-label-expression

部署 Presto on Yarn 时,worker 所在的 container 会多次失败

因为某次部署 presto 时启动的 container 没有被正常关闭,导致后台一直存在一个 worker 在运行占用了端口。


使用 Slider 部署 Presto on Yarn

在测试环境用 Slider 搭建好的 Presto on Yarn 如图所示,显示总共向 Yarn 申请了 4 个 container,1 个用于 slider-appmaster,1 个 coordinator,2 个 worker


用Slider部署Presto

1. 调整集群yarn-site.xml参数

yarn.scheduler.minimum-allocation-mb >= 256 (ensure that YARN can allocate sufficient number of containers)

yarn.nodemanager.delete.debug-delay-sec >= 3600 (to retain for an hour allow easy debugging)

2. 从链接下载slider-0.92.0-incubating-all.tar.gz到集群的任一个节点.

ssh cloudera-scm@hadoop02 wget https://repo1.maven.org/maven2/org/apache/slider/slider-assembly/0.92.0-incubating/slider-assembly-0.92.0-incubating-all.tar.gz tar -zxvf slider-0.92.0-incubating-all.tar.gz

3. 配置 JAVA_HOME 和 HADOOP_CONF_DIR

集群的每个机器都默认设置了 JAVA_HOME 指向 jdk7,所以只需要配置 HADOOP_CONF_DIR 为/etc/hadoop/conf

vim slider-0.92.0-incubating/conf/slider-env.sh
export JAVA_HOME=${JAVA_HOME}
export HADOOP_CONF_DIR=${HADOOP_CONF_DIR}

4. 修改slider-0.92.0-incubating/conf/slider-client.xml配置 zookeeper 地址

<property>  <name>slider.zookeeper.quorum</name>  <value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
</property>
<property>  <name>fs.defaultFS</name>  <value>hdfs://nameservice1</value>
</property>

5. 编译 presto-yarn-package

从presto-yarn下载源码编译得到presto-yarn-package-1.6-SNAPSHOT-0.167.zip

git clone git@github.com:prestodb/presto-yarn.git
cd presto-yarn mvn clean package

在下载依赖包时会有两个很大的包占用很长时间,可以自己在网上下载之后放在~/.m2目录

6. 配置 appConfig.json

在 presto-yarn/presto-yarn-package/src/main/resources 目录下有 appConfig.json 和 resources-multinode.json 模板,在此基础上进行修改。

配置细则参考文档 installation-yarn-configuration-options

  • 因为 slider 命令使用 cloudera-scm 用户执行,所以在 HDFS 中要先添加用户目录用于存放临时文件。

hdfs dfs -mkdir -p /user/cloudera-scm hdfs dfs -chown cloudera-scm:cloudera-scm -R /user/cloudera-scm
  • 每个机器都要创建目录,存放部署配置文件及日志

sudo mkdir /var/lib/presto/{data,etc}
sudo chown -R cloudera-scm:cloudera-scm /var/lib/presto
  • 其余参数基本上与 prestoadmin 部署时所用的参数类似,要注意其中的 site.global.app_name 和 application.def 配置项,与本文档的上下文有关联。

7. 配置 resources.json

{  "schema": "http://example.org/specification/v2.0.0",  "metadata": {  },  "global": {    "yarn.vcores": "1"},  "components": {    "slider-appmaster": {      "yarn.label.expression":"presto_coordinator"},    "COORDINATOR": {      "yarn.role.priority": "1",      "yarn.component.instances": "1",      "yarn.component.placement.policy": "2",      "yarn.label.expression":"presto_coordinator",      "yarn.memory": "1500"},    "WORKER": {      "yarn.role.priority": "2",      "yarn.component.instances": "2",      "yarn.component.placement.policy": "2",      "yarn.label.expression":"presto_worker",      "yarn.memory": "1500"}
 }
}

其中 yarn.component.instances 表示对应角色的实例数,因为采用的是 mutlinode 模式部署,也就是 COORDINATOR 和 WORKER 不共用同一台机器,所以两个实例数之和不能大于机器的数量。yarn.vcores 和 yarn.memory 可用设定向 YARN 申请的 container 资源数量。


当 container 挂掉之后 slider-appmaster 会向 yarn 重新提交申请重启某个服务,需要将yarn.component.placement.policy设置为 2

8. 启动 Slider

先将编译好的 presto-yarn-package-1.6-SNAPSHOT-0.167.zip 及 appConfig.json,resources.json 上传到与 slider-0.92.0-incubating 同一台机器上。

cd slider-0.92.0-incubating
# 部署 presto-yarn-package
bin/slider package --install --name PRESTO --package ../presto-yarn/presto-yarn-package-1.6-SNAPSHOT-0.167.zip
# 启动 applicaiton,也就是部署presto服务
bin/slider create prestoapp --template /opt/bigdata/presto-yarn/appConfig.json  --resources /opt/bigdata/presto-yarn/resources.json --queue datax
# 管理 applicaiton
bin/slider status prestoapp bin/slider start prestoapp bin/slider stop prestoapp
# 查看 coordinator 部署在哪个节点,可以作为-- server 参数提供给 presto-cli 使用
bin/slider registry --name prestoapp --getexp presto
# 销毁 applicaiton
bin/slider destroy prestoapp --force
# 删除 presto-yarn-package
bin/slider package --delete --name PRESTO
# 动态调整 applicaiton,增加或删除 WORKER 数
bin/slider flex prestoapp --component WORKER 2

Presto 配置文件存放目录

presto server 安装在yarn.nodemanager.local-dirs下面,在 appConfigs.json 中指定了日志及配置文件在/var/lib/presto/下面


至此,我们简要的提到了利用 haproxy 搭建 Hive 和 Hue,并且详细介绍了如何利用 Slider 将 Presto 搭建在 Yarn 集群中,接下来将介绍这些组件中的权限管理方案。

权限管理

Hue 账号体系

Hue 是一个开源的 Apache Hadoop UI 系统,它是基于 Python Web 框架 Django 实现的,Hue 在数据库方面,默认使用的是 SQLite 数据库来管理自身的数据,包括用户认证和授权,另外,可以自定义为 MySQL 数据库、Postgresql 数据库、以及 Oracle 数据库。在本文中我们使用了 MySQL 数据库来管理 Hue 和 Hive 的元数据。


在 Hue 源代码的hue/desktop/core/src/desktop/auth/views.py中利用 dt_login 函数对用户的登陆行为进行验证,为了将 hue 用户体系集成到 hive 和 presto 中,我们新增了 check_account 接口进行用户密码校验,通过可选参数 show_permissions 也可以返回该用户的对 hive 数据库的权限。

# hue/desktop/core/src/desktop/urls.py新增check_account接口
dynamic_patterns += patterns('desktop.auth.api',  (r'^api/check_account/$', 'check_account'), )

Hive 自定义超级用户

Hive 从 0.10 版本(包含 0.10 版本)以后可以通过元数据来控制权限,默认情况下,所有用户只要是指向同一个元数据,就具备相同的操作所有Hive表的权限,进而操作 HDFS,即超级管理员的权限。在 Cloudera Manager 配置hive-site.xml的 Hive 服务高级配置代码段,可以开启权限验证。

<property>     <name>hive.security.authorization.enabled</name>     <value>true</value>     <description>enable or disable the hive client authorization</description>
</property>
<property>     <name>hive.security.authorization.createtable.owner.grants</name>     <value>ALL</value>     <description>the privileges automatically granted to the owner whenever a table gets created. An example like"select,drop" will grant select and drop privilege to the owner of the table</description></property><property>     <name>hive.security.authorization.task.factory</name>     <value>org.apache.hadoop.hive.ql.parse.authorization.HiveAuthorizationTaskFactoryImpl</value>
</property>

hive.security.authorization.enabled:是开启权限验证,默认为 false。
hive.security.authorization.createtable.owner.grants:是指表的创建者对表拥有所有权限,例如创建一个表 table1,这个用户对表 table1 拥有 SELECT、DROP 等操作。还有个值是 NULL,表示表的创建者无法访问该表,这个肯定是不合理的。


可以添加超级权限用户,限定只有某些用户可以执行重要指令,例如create database, grant role, revoke role等等。实现方式是实现 Hive 的扩展函数,本节中通过实现AbstractSemanticAnalyzerHook函数限定了只有 hive 用户为超级用户,抛出的 SemanticException 会出现在日志中或者 beeline 的界面中。

package cn.caijiajia.hivex.hook;
public class AuthHook extends AbstractSemanticAnalyzerHook {    
   private static String adminUser = "hive";  
   @Override    public ASTNode preAnalyze(HiveSemanticAnalyzerHookContext context,                              ASTNode ast) throws SemanticException {        
   switch (ast.getToken().getType()) {            
       case HiveParser.TOK_CREATEDATABASE:            
       case HiveParser.TOK_DROPDATABASE:            
       case HiveParser.TOK_CREATEROLE:            
       case HiveParser.TOK_DROPROLE:            
       case HiveParser.TOK_GRANT:            
       case HiveParser.TOK_REVOKE:            
       case HiveParser.TOK_GRANT_ROLE:            
       case HiveParser.TOK_REVOKE_ROLE:            String userName = null;              
       if (SessionState.get() != null                        && SessionState.get().getAuthenticator() != null) {               userName = SessionState.get().getAuthenticator().getUserName();                }                
       if (!adminUser.equalsIgnoreCase(userName)) {                    
           throw new SemanticException("Permission denied. " + userName + " can't use ADMIN options.");                }              
           break;            
       default:                
           break;        }        
    return ast;    } }

同样在 hive-site.xml 中添加如下片段,指定自定义的 hook 扩展函数,注意修改其中的 package 为自己的名称。将 hivex 工程生成 jar 包放到/var/lib/hive中之后重启 hive 就可以生效。

<property>     <name>hive.semantic.analyzer.hook</name>     <value>cn.caijiajia.hivex.hook.AuthHook</value>
</property>

Hive 用户密码管理

一般来说访问 Hive 的方式包括 hue 服务连接、beeline 连接、jdbc 连接,但是在上文中只是校验了用户名称,并没有校验密码,因此还需要再写一个 hook 函数完成密码校验。本文提供的解决方案是集成 hue 的用户体系,扩展PasswdAuthenticationProvider函数,通过上节中扩展的 hue 接口 check_account 来校验密码。

package cn.caijiajia.hivex.hook;
public class CheckPasswordHook implements PasswdAuthenticationProvider {    @Override    public void Authenticate(String username, String password)            throws AuthenticationException {        
       try {            Content content = Request.Post(LatteDataConfig.CHECK_ACCOUNT_HUE_API)                    .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")                    .bodyForm(Form.form()                    .add("username", username)                    .add("password", password)                    .build())                    .execute().returnContent();            JSONObject jObject = JSON.parseObject(content.toString());            
           if (jObject.getInteger("status") != 0) {                
               throw new AuthenticationException("Invalid password of user: " + username);            }        } catch (Exception e) {            e.printStackTrace();            
           throw new AuthenticationException("Hue api error", e);        }    } }

同样需要在hive-site.xml中指定自定义的扩展函数。

<property>     <name>hive.server2.authentication</name>     <value>CUSTOM</value></property><property>     <name>hive.server2.custom.authentication.class</name>     <value>cn.caijiajia.hivex.hook.CheckPasswordHook</value>
</property>

presto-jdbc 用户管理

Hadoop 集群中常用 Kerberos 来进行统一安全认证,Presto 中也支持 Kerberos,但是在公司实际应用环境中,数据分析人员主要在跳板机隔离环境中通过 DBeaver 工具来访问 Presto,所以在没有引入 Kerberos 的条件下我们可以简单的在 presto-jdbc 包中进行修改,集成 Hue 用户体系完成用户密码验证以及数据库的权限验证。


在com.facebook.presto.jdbc.PrestoDriver的 connect 方法中新建了 PrestoDriverUri 实例 uri,因此在uri.setupClient(OkHttpClient.Builder builder)中在创建连接之前先从 hue 的接口中校验密码并且获取当前用户拥有权限的数据库列表 authSchemas,然后将 authSchemas 传递给 PrestoConnection 为提供给接下来的 SQL 解析校验权限使用。

 /**     * 创建连接之前先校验用户密码,获取用户拥有权限的数据库列表     * @param url     * @param info     * @return     * @throws SQLException     */    @Override    public Connection connect(String url, Properties info)            throws SQLException    {        
         if (!acceptsURL(url)) {            
             return null;            }        PrestoDriverUri uri = new PrestoDriverUri(url, info);        OkHttpClient.Builder builder = httpClient.newBuilder();        List<String> authSchemas = uri.setupClientAndGetAuthSchemas(builder);        QueryExecutor executor = new QueryExecutor(builder.build());        
       return new PrestoConnection(uri, executor, authSchemas);    }

在com.facebook.presto.jdbc.PrestoStatement的 execute 方法中执行 startQuery 方法之前可以新增一个校验方法,根据 authSchemas 来判断该用户执行的 SQL 语句是否包含了不合法的操作,例如访问了无权限的数据库。


Presto 数据查询引擎实际使用的是 Antlr 进行自定义的 SQL 解析,在com.facebook.presto.sql.parser.SqlParser中提供了 createStatement 方法,所以只需要将 SQL 传入解析器得到一个 Statement,根据 Statement 的详细情况遍历其节点及子节点并解析其内容就得到相关的数据表和列,解析过程中同样根据 AST 的路径重新生成了一棵树,通过遍历树就可以知道表别名及真实表的对应关系,以及字段和数据表的真实对应关系。


如下图所示中,在 Hue 中 Auth API 负责对外提供用户密码鉴权功能,同时也可以返回某个用户拥有权限的数据库列表。Stats 应用作为数据收集器,对外提供API负责接收并保存用户通过 Hive 或者 Presto 查询的具体 SQL 语句。



总结

本文简单介绍了大数据交互平台常用组件 Hive 和 Hue 以及 Presto 的搭建方案,重点介绍如何利用 Slider 搭建 Presto on Yarn 平台。另外介绍了如何将用户访问数据的权限体系与 Hue 保持统一,通过扩展 Hue 开放新的接口,实现 Hive 的 hook 函数来完成自定义用户管理,在公司的实际应用场景下修改 presto-jdbc 来简单的完成数据权限控制,如果要做到严格的 presto 权限控制可能需要修改 presto-server 代码。


另外,为了分析和统计哪些表被访问的更频繁一些,或者为了发现哪些表被创建之后实际上并没有人进行访问而只是浪费了资源,可以将数据分析人员每次提交的 SQL 以及所涉及到的表记录下来。在 presto-jdbc 中我们已经通过 presto 的解析器获取了语法树,可以直接记录下来。在 Hive 中可以实现 ExecuteWithHookContext 方法新加一个 hook 函数,hookContext 中可以直接获取当前用户使用的 SQL 语句 read 或者 write 了哪些表。


本文作者:Allen&张良慧   

文章来源:数禾科技技术团队公众号【泛金融技术】,欢迎关注、投稿、交流。

转载声明:未经授权不得转载,授权后转载请注明出处并附上原文链接。