开发指南

Size: px
Start display at page:

Download "开发指南"

Transcription

1 MapReduce 服务 开发指南 文档版本 01 发布日期

2 目录 目录 1 简介 MapReduce 服务样例工程构建方式 HBase 应用开发 概述 应用开发简介 常用概念 开发流程 环境准备 开发和运行环境简介 准备开发用户 配置并导入样例工程 开发程序 典型场景说明 开发思路 样例代码说明 创建 Configuration 创建 Connection 创建表 删除表 修改表 插入数据 删除数据 使用 Get 读取数据 使用 Scan 读取数据 使用过滤器 Filter 使用二级索引 添加二级索引 启用 / 禁用二级索引 查询二级索引列表 使用二级索引读取数据 删除二级索引 写 MOB 表 文档版本 01 ( ) ii

3 目录 读 MOB 数据 Region 的多点分割 ACL 安全配置 调测程序 安装客户端时编译并运行程序 未安装客户端时编译并运行程序 查看调测结果 更多信息 SQL 查询 配置 HBase 文件存储 HFS 的 JAVA API HBase 接口 Shell Java API Sqlline REST FAQ 运行 HBase 应用开发程序产生异常 bulkload 和 put 应用场景 开发规范 规则 建议 示例 附录 Hive 应用开发 概述 应用开发简介 常用概念 开发流程 环境准备 开发环境简介 准备环境 准备开发用户 准备 JDBC 客户端开发环境 准备 HCatalog 开发环境 开发程序 典型场景说明 样例代码说明 创建表 数据加载 数据查询 用户自定义函数 文档版本 01 ( ) iii

4 目录 样例程序指导 调测程序 JDBC 客户端运行及结果查看 HCatalog 运行及结果查看 Hive 接口 JDBC HiveQL WebHCat 开发规范 规则 建议 示例 MapReduce 应用开发 概述 MapReduce 简介 常用概念 开发流程 环境准备 开发环境简介 准备开发用户 准备 Eclipse 与 JDK 准备 Linux 客户端运行环境 获取并导入样例工程 准备 kerberos 认证 开发程序 MapReduce 统计样例程序 典型场景说明 样例代码说明 MapReduce 访问多组件样例程序 典型场景说明 样例代码说明 调测程序 编译并运行程序 查看调测结果 MapReduce 接口 FAQ 提交 MapReduce 任务时客户端长时间无响应 开发规范 规则 建议 示例 HDFS 应用开发 文档版本 01 ( ) iv

5 目录 6.1 概述 HDFS 简介 常用概念 开发流程 环境准备 开发环境简介 准备开发用户 准备 Eclipse 与 JDK 准备 Linux 客户端运行环境 获取并导入样例工程 开发程序 场景说明 开发思路 样例代码说明 HDFS 初始化 写文件 追加文件内容 读文件 删除文件 Colocation 设置存储策略 设置标签表达式 访问 OBS 配置 HDFS NodeLabel 调测程序 在 Linux 中调测程序 安装客户端时编译并运行程序 查看调测结果 HDFS 接口 Java API C API HTTP REST API Shell 命令介绍 开发规范 规则 建议 Spark 应用开发 概述 应用开发简介 常用概念 开发流程 环境准备 文档版本 01 ( ) v

6 目录 环境简介 准备开发用户 准备开发环境 准备 Java 开发环境 准备 Scala 开发环境 准备 Python 开发环境 准备 HiveODBC 开发环境 Windows 环境 Linux 环境 附录 准备运行环境 下载并导入样例工程 新建工程 ( 可选 ) 准备认证机制代码 开发程序 Spark Core 程序 场景说明 Java 样例代码 Scala 样例代码 Python 样例代码 Spark SQL 程序 场景说明 Java 样例代码 Scala 样例代码 Spark Streaming 程序 场景说明 Java 样例代码 Scala 样例代码 通过 JDBC 访问 Spark SQL 的程序 场景说明 Java 样例代码 Scala 样例代码 Spark on HBase 程序 场景说明 Java 样例代码 Scala 样例代码 从 HBase 读取数据再写入 HBase 场景说明 Java 样例代码 Scala 样例代码 从 Hive 读取数据再写入 HBase 场景说明 文档版本 01 ( ) vi

7 目录 Java 样例代码 Scala 样例代码 Streaming 从 Kafka 读取数据再写入 HBase 场景说明 Java 样例代码 Scala 样例代码 Spark Streaming 对接 kafka0-10 程序 场景说明 Java 样例代码 Scala 样例代码 Structured Streaming 程序 场景说明 Java 样例代码 Scala 样例代码 Spark 访问 OBS HiveODBC 程序 C 样例代码 调测程序 编包并运行程序 查看调测结果 HiveODBC 样例运行及结果查看 Windows 环境 Linux 环境 使用 HiveODBC 对接 Tableau 调优程序 Spark Core 调优 数据序列化 配置内存 设置并行度 使用广播变量 使用 External Shuffle Service 提升性能 Yarn 模式下动态资源调度 配置进程参数 设计 DAG 经验总结 SQL 和 DataFrame 调优 Spark SQL join 优化 优化数据倾斜场景下的 SparkSQL 性能 优化小文件场景下的 SparkSQL 性能 INSERT...SELECT 操作调优 Spark Streaming 调优 Spark CBO 调优 文档版本 01 ( ) vii

8 目录 7.6 Spark 接口 Java Scala Python REST API Thrift Server 接口介绍 HiveODBC 接口介绍 常用命令介绍 FAQ 如何添加自定义代码的依赖包 如何处理自动加载的依赖包 运行 SparkStreamingKafka 样例工程时报 类不存在 问题 执行 Spark Core 应用, 尝试收集大量数据到 Driver 端, 当 Driver 端内存不足时, 应用挂起不退出 Spark 应用名在使用 yarn-cluster 模式提交时不生效 如何采用 Java 命令提交 Spark 应用 SparkSQL UDF 功能的权限控制机制 由于 kafka 配置的限制, 导致 Spark Streaming 应用运行失败 如何使用 IDEA 远程调试 使用 IBM JDK 产生异常, 提示 Problem performing GSS wrap 信息 Structured Streaming 的 cluster 模式, 在数据处理过程中终止 ApplicationManager, 应用失败 开发规范 规则 建议 Storm 应用开发 概述 应用开发简介 常用概念 开发流程 环境准备 Windows 开发环境准备 开发环境简介 准备 Eclipse 与 JDK 配置并导入工程 Linux 客户端环境准备 开发程序 典型场景说明 开发思路 代码样例说明 创建 Spout 创建 Bolt 创建 Topology 运行应用 文档版本 01 ( ) viii

9 目录 生成示例 Jar 包 提交拓扑 Linux 中安装客户端时提交拓扑 查看结果 更多信息 Storm-Kafka 开发指引 Storm-JDBC 开发指引 Storm-HDFS 开发指引 Storm-OBS 开发指引 Storm-HBase 开发指引 Flux 开发指引 对外接口 开发规范 规则 建议 Kafka 应用开发 概述 应用开发简介 常用概念 开发流程 环境准备 开发环境简介 准备 Maven 和 JDK 导入样例工程 准备安全认证 开发程序 典型场景说明 样例代码说明 Old Producer API 使用样例 Old Consumer API 使用样例 Producer API 使用样例 Consumer API 使用样例 多线程 Producer API 使用样例 多线程 Consumer API 使用样例 SimpleConsumer API 使用样例 样例工程配置文件说明 调测程序 在 Windows 中调测程序 在 Linux 中调测程序 Kafka 接口 Shell 命令 Java API 文档版本 01 ( ) ix

10 目录 安全接口说明 FAQ 已经拥有 Topic 访问权限, 但是运行 Producer.java 样例运行获取元数据失败 ERROR fetching topic metadata for topics... 的解决办法 开发规范 规则 建议 A 修订记录 文档版本 01 ( ) x

11 1 简介 1 简介 概述 本文档提供 Hadoop Spark HBase Hive MapReduce Kafka Storm 大数据组件的应用开发的流程和操作指导, 以及各个大数据组件的样例工程, 同时提供了常见的问题解答和开发规范 开发准备 您已经开通了华为云 MapReduce 服务 您已经对 Hadoop Spark HBase Hive MapReduce Kafka Storm 大数据组件具备一定的认识 您已经对 Java 语法具备一定的认识 您已经对华为云弹性云服务器的使用方式和 MapReduce 服务开发组件有一定的了解 您已经对 Maven 构建方式具备一定的认识和使用方法 文档版本 01 ( ) 1

12 2 MapReduce 服务样例工程构建方式 2 MapReduce 服务样例工程构建方式 构建流程 样例工程获取地址 华为云开源镜像配置方式 MapReduce 服务样例工程构建流程包括三个主要步骤 : 1. 下载样例工程的 Maven 工程源码和配置文件, 请参见样例工程获取地址 2. 配置华为云镜像站中的 SDK 的 Maven 镜像仓库, 请参见华为云开源镜像配置方式 3. 根据用户自身需求, 根据各个组件 环境准备 章节构建完整的 Maven 工程 华为云 MRS 服务的样例工程下载地址为 : 华为云提供开源镜像站 ( 网址为 服务样例工程依赖的 jar 包都需要在华为开源镜像站下载, 剩余所依赖的开源 jar 包请直接从 Maven 中央库下载 华为云开源镜像配置方式如下所示 : 步骤 1 使用前请确保您已安装 JDK 1.8 及以上版本和 Maven 3.0 及以上版本 步骤 2 打开网址 步骤 3 找到名称为 HuaweiCloud 版块, 单击 立即使用, 按照页面设置方法进行 Maven 配置 步骤 4 使用如下任意一种配置方法配置镜像仓地址 ( 本文提供了如下两种配置方法 ) 配置方法一 : 在二次开发工程样例工程中的 pom.xml 文件添加如下镜像仓地址 : <repositories> <repository> <id>huaweicloudsdk</id> <url> <releases><enabled>true</enabled></releases> <snapshots><enabled>true</enabled></snapshots> 文档版本 01 ( ) 2

13 2 MapReduce 服务样例工程构建方式 </repository> <repository> <id>central</id> <name>mavn Centreal</name> <url> </repository> </repositories> 配置方法二 : 在 setting.xml 配置文件的 mirrors 节点中添加开源镜像仓地址 : <mirror> <id>repo2</id> <mirrorof>central</mirrorof> <url> </mirror> 在 setting.xml 配置文件的 profiles 节点中添加如下镜像仓地址 : <profile> <id>huaweicloudsdk</id> <repositories> <repository> <id>huaweicloudsdk</id> <url> <releases><enabled>true</enabled></releases> <snapshots><enabled>true</enabled></snapshots> </repository> </repositories> </profile> 在 setting.xml 配置文件的 activeprofiles 节点中添加如下镜像仓地址 : <activeprofile>huaweicloudsdk</activeprofile> 说明 华为云开源镜像站不提供第三方开源 jar 包下载, 请配置华为云开源镜像配置后, 额外配置第三方 Maven 仓库地址镜像 ---- 结束 文档版本 01 ( ) 3

14 3 HBase 应用开发 3 HBase 应用开发 3.1 概述 应用开发简介 HBase 简介 接口类型简介 HBase 是一个高可靠性 高性能 面向列 可伸缩的分布式存储系统 HBase 设计目标是用来解决关系型数据库在处理海量数据时的局限性 HBase 使用场景有如下几个特点 : 处理海量数据 (TB 或 PB 级别以上 ) 具有高吞吐量 在海量数据中实现高效的随机读取 具有很好的伸缩能力 能够同时处理结构化和非结构化的数据 不需要完全拥有传统关系型数据库所具备的 ACID 特性 ACID 特性指原子性 (Atomicity) 一致性 (Consistency) 隔离性 (Isolation, 又称独立性 ) 持久性 (Durability) HBase 中的表具有如下特点 : 大 : 一个表可以有上亿行, 上百万列 面向列 : 面向列 ( 族 ) 的存储和权限控制, 列 ( 族 ) 独立检索 稀疏 : 对于为空 (null) 的列, 并不占用存储空间, 因此, 表可以设计的非常稀疏 由于 HBase 本身是由 java 语言开发出来的, 且 java 语言具有简洁通用易懂的特性, 推荐用户使用 java 语言进行 HBase 应用程序开发 HBase 采用的接口与 Apache HBase 保持一致, 请参见 : index.html 文档版本 01 ( ) 4

15 3 HBase 应用开发 HBase 通过接口调用, 可提供的功能如表 3-1 所示 表 3-1 HBase 接口提供的功能 功能 CRUD 数据读写功能高级特性管理功能 说明增查改删过滤器 协处理器表管理 集群管理 常用概念 开发流程 过滤器 过滤器提供了非常强大的特性来帮助用户提高 HBase 处理表中数据的效率 用户不仅可以使用 HBase 中预定义好的过滤器, 而且可以实现自定义的过滤器 协处理器 允许用户执行 region 级的操作, 并且可以使用与 RDBMS 中触发器类似的功能 Client 客户端直接面向用户, 可通过 Java API 或 HBase Shell 访问服务端, 对 HBase 的表进行读写操作 本文中的 HBase 客户端特指从装有 HBase 服务的 MRS Manager 上下载的 HBase client 安装包, 里面包含通过 Java API 访问 HBase 的样例代码 本文档主要基于 JAVA API 对 HBase 进行应用开发 开发流程中各阶段的说明如图 3-1 和表 3-2 所示 文档版本 01 ( ) 5

16 3 HBase 应用开发 图 3-1 HBase 应用程序开发流程 表 3-2 HBase 应用开发的流程说明 阶段说明参考文档 了解基本概念 准备开发环境和运行环境 在开始开发应用前, 需要了解 HBase 的基本概念, 了解场景需求, 设计表等 HBase 的应用程序当前推荐使用 Java 语言进行开发 可使用 Eclipse 工具 HBase 的运行环境即 HBase 客户端, 请根据指导完成客户端的安装和配置 常用概念 开发和运行环境简介 文档版本 01 ( ) 6

17 3 HBase 应用开发 阶段说明参考文档 准备工程根据场景开发工程编译并运行程序查看程序运行结果 HBase 提供了不同场景下的样例程序, 您可以导入样例工程进行程序学习 或者您可以根据指导, 新建一个 HBase 工程 提供了 Java 语言的样例工程, 包含从建表 写入到删除表全流程的样例工程 指导用户将开发好的程序编译并提交运行 程序运行结果会写在用户指定的路径下 用户还可以通过 UI 查看应用运行情况 配置并导入样例工程开发程序调测程序查看调测结果 3.2 环境准备 开发和运行环境简介 在进行二次开发时, 要准备的开发环境如表 3-3 所示 同时需要准备运行调测的 Linux 环境, 用于验证应用程序运行正常 表 3-3 开发环境 准备项操作系统安装 JDK 安装和配置 Eclipse 网络 说明 Windows 系统, 推荐 Windows 7 及以上版本 开发环境的基本配置 版本要求 :1.8 及以上 用于开发 HBase 应用程序的工具 确保客户端与 HBase 服务主机在网络上互通 选择 Window 开发环境下, 安装 Eclipse, 安装 JDK 请安装 JDK1.8 及以上版本 Eclipse 使用支持 JDK1.8 及以上的版本, 并安装 JUnit 插件 说明 若使用 IBM JDK, 请确保 Eclipse 中的 JDK 配置为 IBM JDK 若使用 Oracle JDK, 请确保 Eclipse 中的 JDK 配置为 Oracle JDK 不同的 Eclipse 不要使用相同的 workspace 和相同路径下的示例工程 准备一个应用程序运行测试的 Linux 环境 文档版本 01 ( ) 7

18 3 HBase 应用开发 准备运行调测环境 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 在弹性云服务器管理控制台, 申请一个新的弹性云服务器, 用于应用开发运行调测 弹性云服务器的安全组需要和 MRS 集群 Master 节点的安全组相同 弹性云服务器的 VPC 需要与 MRS 集群在同一个 VPC 中 弹性云服务器的网卡需要与 MRS 集群在同一个网段中 申请弹性 IP, 绑定新申请的 ECS 的 IP, 并配置安全组出入规则 下载客户端程序 1. 登录 Manager 系统 2. 选择 服务管理 > 下载客户端 > 完整客户端, 下载客户端程序到本地机器 登录存放下载的客户端的节点, 再安装客户端 1. 执行以下命令解压客户端包 : tar -xvf /opt/mrs_services_client.tar 2. 执行以下命令校验安装文件包 : sha256sum -c /opt/mrs_services_clientconfig.tar.sha256 MRS_Services_ClientConfig.tar:OK 3. 执行以下命令解压安装文件包 : tar -xvf MRS_Services_ClientConfig.tar 4. 执行如下命令安装客户端到指定目录 ( 绝对路径 ), 例如 /opt/client 目录会自动创建 cd /opt/mrs_services_clientconfig sh install.sh /opt/client Components client installation is complete. 在主管理节点准备客户端, 并在 Master1 节点, 将集群客户端下 HBase/hbase/conf 中的配置文件从主管理节点复制到运行调测环境 步骤 6 修改 /etc/hosts 在文件中新添加弹性云服务器的主机名与弹性云服务器私有 IP 地址对应关系 在文件中新添加 OBS 域名与 IP 地址的对应关系 用户在中国华北区申请 MRS 集群, 需添加以下记录 : obs.huawei.com 步骤 7 执行以下命令, 更新客户端配置 : sh /opt/client/refreshconfig.sh 客户端安装目录客户端配置文件压缩包完整路径 例如, 执行命令 sh /opt/client/refreshconfig.sh /opt/client /opt/mrs_services_client.tar ---- 结束 准备开发用户 开发用户用于运行样例工程 用户需要有 HBase 权限, 才能运行 HBase 样例工程 文档版本 01 ( ) 8

19 3 HBase 应用开发 前提条件 MRS 服务集群开启了 Kerberos 认证, 没有开启 Kerberos 认证的集群忽略该步骤 操作步骤 步骤 1 登录 MRS Manager, 在 MRS Manager 界面选择 系统设置 > 角色管理 > 添加角色, 如图 1 添加角色所示 图 3-2 添加角色 1. 填写角色的名称, 例如 hbaserole 2. 编辑角色, 在 权限 的表格中选择 HBase> HBase Scope > global, 勾选 Admin Create Read Write 和 Execute, 单击 确定 保存, 如图 3-3 所示 图 3-3 编辑角色 步骤 2 步骤 3 步骤 4 单击 系统设置 > 用户组管理 > 添加用户组, 为样例工程创建一个用户组, 例如 hbasegroup 单击 系统设置 > 用户管理 > 添加用户, 为样例工程创建一个用户 填写用户名, 例如 hbaseuser, 用户类型为 机机 用户, 加入用户组 hbasegroup 和 supergroup, 设置其 主组 为 supergroup, 并绑定角色 hbaserole 取得权限, 单击 确定, 如图 3-4 所示 文档版本 01 ( ) 9

20 3 HBase 应用开发 图 3-4 添加用户 步骤 5 在 MRS Manager 界面选择 系统设置 > 用户管理, 在用户名中选择 hbaseuser, 单击操作中下载认证凭据文件, 保存后解压得到用户的 user.keytab 文件与 krb5.conf 文件 用于在样例工程中进行安全认证 ---- 结束 参考信息 如果修改了组件的配置参数, 需重新下载客户端配置文件并更新运行调测环境上的客户端 配置并导入样例工程 前提条件 确保本地 PC 的时间与 MRS 集群的时间差要小于 5 分钟 MRS 集群的时间可通过 MRSManager 页面右上角查看 图 3-5 MRS 集群的时间 文档版本 01 ( ) 10

21 3 HBase 应用开发 操作步骤 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 在样例工程获取地址获取 HBase 示例工程 在 HBase 示例工程根目录, 执行 mvn install 进行编译 在 HBase 示例工程根目录, 执行 mvn eclipse:eclipse 创建 Eclipse 工程 在应用开发环境中, 导入样例工程到 Eclipse 开发环境 1. 选择 File > Import > General > Existing Projects into Workspace > Next >Browse 显示 浏览文件夹 对话框 2. 选择样例工程文件夹, 单击 Finish 设置 Eclipse 的文本文件编码格式, 解决乱码显示问题 1. 在 Eclipse 的菜单栏中, 选择 Window > Preferences 弹出 Preferences 窗口 2. 在左边导航上选择 General > Workspace, 在 Text file encoding 区域, 选中 Other, 并设置参数值为 UTF-8, 单击 Apply 后, 单击 OK, 如图 2 设置 Eclipse 的编码格式所示 设置 Eclipse 的编码格式 图 3-6 设置 Eclipse 的编码格式 ---- 结束 3.3 开发程序 文档版本 01 ( ) 11

22 3 HBase 应用开发 典型场景说明 场景说明 通过典型场景, 我们可以快速学习和掌握 HBase 的开发过程, 并且对关键的接口函数有所了解 假定用户开发一个应用程序, 用于管理企业中的使用 A 业务的用户信息, 如表 3-4 所示,A 业务操作流程如下 : 创建用户信息表 在用户信息中新增用户的学历 职称等信息 根据用户编号查询用户姓名和地址 根据用户姓名进行查询 查询年龄段在 [20 29] 之间的用户信息 数据统计, 统计用户信息表的人员数 年龄最大值 年龄最小值 平均年龄 用户销户, 删除用户信息表中该用户的数据 A 业务结束后, 删除用户信息表 表 3-4 用户信息 编号姓名性别年龄地址 Zhang San Li Wantin g Wang Ming Li Gang Zhao Enru Chen Long Zhou Wei Yang Yiwen Xu Bing Xiao Kai Male 19 Shenzhen City, Guangdong Province Female 23 Hangzhou City, Zhejiang Province Male 26 Ningbo City, Zhejiang Province Male 18 Xiangyang City, Hubei Province Female 21 Shangrao City, Jiangxi Province Male 32 Zhuzhou City, Hunan Province Female 29 Nanyang City, Henan Province Female 30 Wenzhou City, Zhejiang Province Male 26 Weinan City, Shaanxi Province Male 25 Dalian City, Liaoning Province 文档版本 01 ( ) 12

23 3 HBase 应用开发 数据规划 开发思路 功能分解 合理地设计表结构 行键 列名能充分利用 HBase 的优势 本样例工程以唯一编号作为 RowKey, 列都存储在 info 列族中 根据上述的业务场景进行功能分解, 需要开发的功能点如表 3-5 所示 表 3-5 在 HBase 中开发的功能 序号步骤代码实现 1 根据表 3-4 中的信息创建表 请参见创建表 2 导入用户数据 请参见插入数据 3 增加 教育信息 列族, 在用户信息中新增用户的学历 职称等信息 请参见修改表 4 根据用户编号查询用户姓名和地址 请参见使用 Get 读取数据 5 根据用户姓名进行查询 请参见使用过滤器 Filter 6 用户销户, 删除用户信息表中该用户的数据 请参见删除数据 7 A 业务结束后, 删除用户信息表 请参见删除表 关键设计原则 样例代码说明 HBase 是以 RowKey 为字典排序的分布式数据库系统,RowKey 的设计对性能影响很大, 具体的 RowKey 设计请考虑与业务结合 创建 Configuration 功能介绍 代码样例 HBase 通过加载配置文件来获取配置项, 包括用户登录信息配置项 下面代码片段在 com.huawei.bigdata.hbase.examples 包中 调用类 TestMain 下的 init() 方法会初始化 Configuration 对象 : private static void init() throws IOException { // load hbase client info 文档版本 01 ( ) 13

24 3 HBase 应用开发 if(clientinfo == null) { clientinfo = new ClientInfo(CONF_DIR + HBASE_CLIENT_PROPERTIES); restserverinfo = clientinfo.getrestserverinfo(); // Default load from conf directory conf = HBaseConfiguration.create(); conf.addresource(conf_dir + "core-site.xml"); conf.addresource(conf_dir + "hdfs-site.xml"); conf.addresource(conf_dir + "hbase-site.xml"); 创建 Connection 功能介绍 代码样例 HBase 通过 ConnectionFactory.createConnection(configuration) 方法创建 Connection 对象 传递的参数为上一步创建的 Configuration Connection 封装了底层与各实际服务器的连接以及与 ZooKeeper 的连接 Connection 通过 ConnectionFactory 类实例化 创建 Connection 是重量级操作,Connection 是线程安全的, 因此, 多个客户端线程可以共享一个 Connection 典型的用法, 一个客户端程序共享一个单独的 Connection, 每一个线程获取自己的 Admin 或 Table 实例, 然后调用 Admin 对象或 Table 对象提供的操作接口 不建议缓存或者池化 Table Admin Connection 的生命周期由调用者维护, 调用者通过调用 close(), 释放资源 以下代码片段是创建 Connection 的示例 : private TableName tablename = null; private Configuration conf = null; private Connection conn = null; public HBaseSample(Configuration conf) throws IOException { this.conf = conf; this.tablename = TableName.valueOf("hbase_sample_table"); this.conn = ConnectionFactory.createConnection(conf); 创建表 功能简介 说明 1. 样例代码中有很多的操作, 如建表 查询 删表等等, 这里只列举了建表 testcreatetable 和删除表 droptable 这 2 种操作 可参考对应章节样例 2. 创建表操作所需的 Admin 对象是从 Connection 对象获取 3. 登录代码要避免重复调用 HBase 通过 org.apache.hadoop.hbase.client.admin 对象的 createtable 方法来创建表, 并指定表名 列族名 创建表有两种方式, 建议采用预分 Region 建表方式 : 快速建表, 即创建表后整张表只有一个 Region, 随着数据量的增加会自动分裂成多个 Region 文档版本 01 ( ) 14

25 3 HBase 应用开发 预分 Region 建表, 即创建表时预先分配多个 Region, 此种方法建表可以提高写入大量数据初期的数据写入速度 说明 表的列名以及列族名不能包含特殊字符, 可以由字母 数字以及下划线组成 代码样例 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testcreatetable 方法中 public static void testcreatetable() { LOG.info("Entering testcreatetable."); // Specify the table descriptor. HTableDescriptor htd = new HTableDescriptor(tableName); // Set the column family name to info. HColumnDescriptor hcd = new HColumnDescriptor("info"); // Set data encoding methods.hbase provides DIFF,FAST_DIFF,PREFIX // and PREFIX_TREE hcd.setdatablockencoding(datablockencoding.fast_diff); // Set compression methods,hbase provides two default compression // methods:gz and SNAPPY // GZ has the highest compression rate,but low compression and // decompression effeciency,fit for cold data // SNAPPY has low compression rate, but high compression and // decompression effeciency,fit for hot data. // it is advised to use SANPPY hcd.setcompressiontype(compression.algorithm.snappy); htd.addfamily(hcd); Admin admin = null; try { // Instantiate an Admin object. admin = conn.getadmin(); if (!admin.tableexists(tablename)) { LOG.info("Creating table..."); // create table admin.createtable(htd); LOG.info(admin.getClusterStatus()); LOG.info(admin.listNamespaceDescriptors()); LOG.info("Table created successfully."); else { LOG.warn("table already exists"); catch (IOException e) { LOG.error("Create table failed.", e); finally { if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Failed to close admin ", e); LOG.info("Exiting testcreatetable."); 代码解释 (1) 创建表描述符 文档版本 01 ( ) 15

26 3 HBase 应用开发 (2) 创建列族描述符 (3) 添加列族描述符到表描述符中 删除表 功能简介 HBase 通过 org.apache.hadoop.hbase.client.admin 的 deletetable 方法来删除表 代码样例 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 droptable 方法中 public void droptable() { LOG.info("Entering droptable."); Admin admin = null; try { admin = conn.getadmin(); if (admin.tableexists(tablename)) { // Disable the table before deleting it. admin.disabletable(tablename);// 注 [1] // Delete table. admin.deletetable(tablename); LOG.info("Drop table successfully."); catch (IOException e) { LOG.error("Drop table failed ", e); finally { if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Close admin failed ", e); LOG.info("Exiting droptable."); 注意事项 注 [1] 只有表被 disable 时, 才能被删除掉, 所以 deletetable 常与 disabletable, enabletable,tableexists,istableenabled,istabledisabled 结合在一起使用 修改表 功能简介 HBase 通过 org.apache.hadoop.hbase.client.admin 的 modifytable 方法修改表信息 代码样例 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testmodifytable 方法中 文档版本 01 ( ) 16

27 3 HBase 应用开发 public void testmodifytable() { LOG.info("Entering testmodifytable."); // Specify the column family name. byte[] familyname = Bytes.toBytes("education"); Admin admin = null; try { // Instantiate an Admin object. admin = conn.getadmin(); // Obtain the table descriptor. HTableDescriptor htd = admin.gettabledescriptor(tablename); // Check whether the column family is specified before modification. if (!htd.hasfamily(familyname)) { // Create the column descriptor. HColumnDescriptor hcd = new HColumnDescriptor(familyName); htd.addfamily(hcd); // Disable the table to get the table offline before modifying // the table. admin.disabletable(tablename);// 注 [1] // Submit a modifytable request. admin.modifytable(tablename, htd); // Enable the table to get the table online after modifying the // table. admin.enabletable(tablename); LOG.info("Modify table successfully."); catch (IOException e) { LOG.error("Modify table failed ", e); finally { if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Close admin failed ", e); LOG.info("Exiting testmodifytable."); 说明 注 [1]modifyTable 只有表被 disable 时, 才能生效 插入数据 功能简介 代码样例 HBase 是一个面向列的数据库, 一行数据, 可能对应多个列族, 而一个列族又可以对应多个列 通常, 写入数据的时候, 我们需要指定要写入的列 ( 含列族名称和列名称 ) HBase 通过 HTable 的 put 方法来 Put 数据, 可以是一行数据也可以是数据集 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testput 方法中 public void testput() { LOG.info("Entering testput."); 文档版本 01 ( ) 17

28 3 HBase 应用开发 // Specify the column family name. byte[] familyname = Bytes.toBytes("info"); // Specify the column name. byte[][] qualifiers = {Bytes.toBytes("name"), Bytes.toBytes("gender"), Bytes.toBytes("age"), Bytes.toBytes("address"); Table table = null; try { // Instantiate an HTable object. table = conn.gettable(tablename); List<Put> puts = new ArrayList<Put>(); // Instantiate a Put object. Put put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Zhang San")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Male")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("19")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Shenzhen, Guangdong")); puts.add(put); put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Li Wanting")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Female")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("23")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Shijiazhuang, Hebei")); puts.add(put); put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Wang Ming")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Male")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("26")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Ningbo, Zhejiang")); puts.add(put); put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Li Gang")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Male")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("18")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Xiangyang, Hubei")); puts.add(put); put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Zhao Enru")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Female")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("21")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Shangrao, Jiangxi")); puts.add(put); put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Chen Long")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Male")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("32")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Zhuzhou, Hunan")); puts.add(put); put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Zhou Wei")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Female")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("29")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Nanyang, Henan")); puts.add(put); put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Yang Yiwen")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Female")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("30")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Kaixian, Chongqing")); puts.add(put); put = new Put(Bytes.toBytes(" ")); 文档版本 01 ( ) 18

29 3 HBase 应用开发 put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Xu Bing")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Male")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("26")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Weinan, Shaanxi")); puts.add(put); put = new Put(Bytes.toBytes(" ")); put.addcolumn(familyname, qualifiers[0], Bytes.toBytes("Xiao Kai")); put.addcolumn(familyname, qualifiers[1], Bytes.toBytes("Male")); put.addcolumn(familyname, qualifiers[2], Bytes.toBytes("25")); put.addcolumn(familyname, qualifiers[3], Bytes.toBytes("Dalian, Liaoning")); puts.add(put); // Submit a put request. table.put(puts); LOG.info("Put successfully."); catch (IOException e) { LOG.error("Put failed ", e); finally { if (table!= null) { try { // Close the HTable object. table.close();// 注 [1] catch (IOException e) { LOG.error("Close table failed ", e); LOG.info("Exiting testput."); 注意事项 删除数据 功能简介 代码样例 不允许多个线程在同一时间共用同一个 HTable 实例 HTable 是一个非线程安全类, 因此, 同一个 HTable 实例, 不应该被多个线程同时使用, 否则可能会带来并发问题 HBase 通过 Table 实例的 delete 方法来 Delete 数据, 可以是一行数据也可以是数据集 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testdelete 方法中 public void testdelete() { LOG.info("Entering testdelete."); byte[] rowkey = Bytes.toBytes(" "); Table table = null; try { // Instantiate an HTable object. table = conn.gettable(tablename); // Instantiate an Delete object. Delete delete = new Delete(rowKey); // Submit a delete request. table.delete(delete); 文档版本 01 ( ) 19

30 3 HBase 应用开发 LOG.info("Delete table successfully."); catch (IOException e) { LOG.error("Delete table failed ", e); finally { if (table!= null) { try { // Close the HTable object. table.close(); catch (IOException e) { LOG.error("Close table failed ", e); LOG.info("Exiting testdelete."); 使用 Get 读取数据 功能简介 代码样例 要从表中读取一条数据, 首先需要实例化该表对应的 Table 实例, 然后创建一个 Get 对象 也可以为 Get 对象设定参数值, 如列族的名称和列的名称 查询到的行数据存储在 Result 对象中,Result 中可以存储多个 Cell 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testget 方法中 public void testget() { LOG.info("Entering testget."); // Specify the column family name. byte[] familyname = Bytes.toBytes("info"); // Specify the column name. byte[][] qualifier = {Bytes.toBytes("name"), Bytes.toBytes("address"); // Specify RowKey. byte[] rowkey = Bytes.toBytes(" "); Table table = null; try { // Create the Configuration instance. table = conn.gettable(tablename); // Instantiate a Get object. Get get = new Get(rowKey); // Set the column family name and column name. get.addcolumn(familyname, qualifier[0]); get.addcolumn(familyname, qualifier[1]); // Submit a get request. Result result = table.get(get); // Print query results. for (Cell cell : result.rawcells()) { LOG.info(Bytes.toString(CellUtil.cloneRow(cell)) + ":" + Bytes.toString(CellUtil.cloneFamily(cell)) + "," + Bytes.toString(CellUtil.cloneQualifier(cell)) + "," + Bytes.toString(CellUtil.cloneValue(cell))); LOG.info("Get data successfully."); catch (IOException e) { LOG.error("Get data failed ", e); finally { 文档版本 01 ( ) 20

31 3 HBase 应用开发 if (table!= null) { try { // Close the HTable object. table.close(); catch (IOException e) { LOG.error("Close table failed ", e); LOG.info("Exiting testget."); 使用 Scan 读取数据 功能简介 代码样例 要从表中读取数据, 首先需要实例化该表对应的 Table 实例, 然后创建一个 Scan 对象, 并针对查询条件设置 Scan 对象的参数值, 为了提高查询效率, 最好指定 StartRow 和 StopRow 查询结果的多行数据保存在 ResultScanner 对象中, 每行数据以 Result 对象形式存储,Result 中存储了多个 Cell 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testscandata 方法中 public void testscandata() { LOG.info("Entering testscandata."); Table table = null; // Instantiate a ResultScanner object. ResultScanner rscanner = null; try { // Create the Configuration instance. table = conn.gettable(tablename); // Instantiate a Get object. Scan scan = new Scan(); scan.addcolumn(bytes.tobytes("info"), Bytes.toBytes("name")); // Set the StartRow scan.setstartrow(bytes.tobytes(" "));// 注 [1] // Set the StopRow scan.setstoprow(bytes.tobytes(" "));// 注 [1] // Set the cache size. scan.setcaching(1000);// 注 [2] // Set the Batch size. scan.setbatch(100);// 注 [2] // Submit a scan request. rscanner = table.getscanner(scan); // Print query results. for (Result r = rscanner.next(); r!= null; r = rscanner.next()) { for (Cell cell : r.rawcells()) { LOG.info(Bytes.toString(CellUtil.cloneRow(cell)) + ":" + Bytes.toString(CellUtil.cloneFamily(cell)) + "," + Bytes.toString(CellUtil.cloneQualifier(cell)) + "," + Bytes.toString(CellUtil.cloneValue(cell))); LOG.info("Scan data successfully."); 文档版本 01 ( ) 21

32 3 HBase 应用开发 catch (IOException e) { LOG.error("Scan data failed ", e); finally { if (rscanner!= null) { // Close the scanner object. rscanner.close(); if (table!= null) { try { // Close the HTable object. table.close(); catch (IOException e) { LOG.error("Close table failed ", e); LOG.info("Exiting testscandata."); 注意事项 1. 建议 Scan 时指定 StartRow 和 StopRow, 一个有确切范围的 Scan, 性能会更好些 2. 可以设置 Batch 和 Caching 关键参数 Batch 使用 Scan 调用 next 接口每次最大返回的记录数, 与一次读取的列数有关 Caching RPC 请求返回 next 记录的最大数量, 该参数与一次 RPC 获取的行数有关 使用过滤器 Filter 功能简介 代码样例 HBase Filter 主要在 Scan 和 Get 过程中进行数据过滤, 通过设置一些过滤条件来实现, 如设置 RowKey 列名或者列值的过滤条件 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testfilterlist 方法中 public void testfilterlist() { LOG.info("Entering testfilterlist."); Table table = null; // Instantiate a ResultScanner object. ResultScanner rscanner = null; try { // Create the Configuration instance. table = conn.gettable(tablename); // Instantiate a Get object. Scan scan = new Scan(); scan.addcolumn(bytes.tobytes("info"), Bytes.toBytes("name")); // Instantiate a FilterList object in which filters have "and" // relationship with each other. FilterList list = new FilterList(Operator.MUST_PASS_ALL); // Obtain data with age of greater than or equal to 20. list.addfilter(new SingleColumnValueFilter(Bytes.toBytes("info"), Bytes.toBytes("age"), 文档版本 01 ( ) 22

33 3 HBase 应用开发 CompareOp.GREATER_OR_EQUAL, Bytes.toBytes(new Long(20)))); // Obtain data with age of less than or equal to 29. list.addfilter(new SingleColumnValueFilter(Bytes.toBytes("info"), Bytes.toBytes("age"), CompareOp.LESS_OR_EQUAL, Bytes.toBytes(new Long(29)))); scan.setfilter(list); // Submit a scan request. rscanner = table.getscanner(scan); // Print query results. for (Result r = rscanner.next(); r!= null; r = rscanner.next()) { for (Cell cell : r.rawcells()) { LOG.info(Bytes.toString(CellUtil.cloneRow(cell)) + ":" + Bytes.toString(CellUtil.cloneFamily(cell)) + "," + Bytes.toString(CellUtil.cloneQualifier(cell)) + "," + Bytes.toString(CellUtil.cloneValue(cell))); LOG.info("Filter list successfully."); catch (IOException e) { LOG.error("Filter list failed ", e); finally { if (rscanner!= null) { // Close the scanner object. rscanner.close(); if (table!= null) { try { // Close the HTable object. table.close(); catch (IOException e) { LOG.error("Close table failed ", e); LOG.info("Exiting testfilterlist."); 使用二级索引 添加二级索引 前提条件 功能介绍 代码样例 此功能需要 MRS1.7 及更高版本才开放 您可以使用 org.apache.hadoop.hbase.hindex.client.hindexadmin 中提供的方法来管理 HIndexes 该类提供了将索引添加到现有表的方法 : 根据用户是否希望在添加索引操作期间构建索引数据, 有两种不同的方法可将索引添加到表中 : addindiceswithdata() addindices() 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HIndexExample 类的 addindicesexample 方法中 : 文档版本 01 ( ) 23

34 3 HBase 应用开发 addindices(): 将索引添加到没有数据的表中 public void addindicesexample() { LOG.info("Entering Adding a Hindex."); // Create index instance TableIndices tableindices = new TableIndices(); HIndexSpecification spec = new HIndexSpecification(indexNameToAdd); spec.addindexcolumn(new HColumnDescriptor("info"), "name", ValueType.STRING); tableindices.addindex(spec); Admin admin = null; HIndexAdmin iadmin = null; try { admin = conn.getadmin(); iadmin = HIndexClient.newHIndexAdmin(admin); // add index to the table iadmin.addindices(tablename, tableindices); // Alternately, add the specified indices with data // iadmin.addindiceswithdata(tablename, tableindices); LOG.info("Successfully added indices to the table " + tablename); catch (IOException e) { LOG.error("Add Indices failed for table " + tablename + "." + e); finally { if (iadmin!= null) { try { // Close the HIndexAdmin object. iadmin.close(); catch (IOException e) { LOG.error("Failed to close HIndexAdmin ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Failed to close admin ", e); LOG.info("Exiting Adding a Hindex."); 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HIndexExample 类的 addindicesexamplewithdata 方法中 : addindiceswithdata(): 将索引添加到具有大量预先存在数据的表中 public void addindicesexamplewithdata() { LOG.info("Entering Adding a Hindex With Data."); // Create index instance TableIndices tableindices = new TableIndices(); HIndexSpecification spec = new HIndexSpecification(indexNameToAdd); spec.addindexcolumn(new HColumnDescriptor("info"), "age", ValueType.STRING); tableindices.addindex(spec); Admin admin = null; HIndexAdmin iadmin = null; try { admin = conn.getadmin(); iadmin = HIndexClient.newHIndexAdmin(admin); // add index to the table iadmin.addindiceswithdata(tablename, tableindices); // Alternately, add the specified indices with data // iadmin.addindiceswithdata(tablename, tableindices); LOG.info("Successfully added indices to the table " + tablename); catch (IOException e) { LOG.error("Add Indices failed for table " + tablename + "." + e); finally { if (iadmin!= null) { try { 文档版本 01 ( ) 24

35 3 HBase 应用开发 启用 / 禁用二级索引 前提条件 功能介绍 代码样例 // Close the HIndexAdmin object. iadmin.close(); catch (IOException e) { LOG.error("Failed to close HIndexAdmin ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Failed to close admin ", e); LOG.info("Exiting Adding a Hindex With Data."); 此功能需要 MRS1.7 及更高版本才开放 您可以使用 org.apache.hadoop.hbase.hindex.client.hindexadmin 中提供的方法来管理 HIndexes 这个类提供了启用 / 禁用现有索引的方法 根据用户是否想要启用 / 禁用表,HIndexAdmin 提供以下 API: disableindices () enableindices () 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HIndexExample 类的 enableindicesexample 方法中 enableindices (): 启用指定的索引 ( 索引状态将从 INACTIVE 变为 ACTIVE 状态 ), 因此可用于扫描索引 public void enableindicesexample() { LOG.info("Entering Enabling a Hindex."); List<String> indexnamelist = new ArrayList<String>(); indexnamelist.add(indexnametoadd); Admin admin = null; HIndexAdmin iadmin = null; try { admin = conn.getadmin(); iadmin = HIndexClient.newHIndexAdmin(admin); // Disable the specified indices iadmin.enableindices(tablename, indexnamelist); // Alternately, disable the specified indices // iadmin.disableindices(tablename, indexnamelist) LOG.info("Successfully enable indices " + indexnamelist + " of the table " + tablename); catch (IOException e) { LOG.error("Failed to enable indices " + indexnamelist + " of the table " + tablename); finally { if (iadmin!= null) { try { // Close the HIndexAdmin object. iadmin.close(); 文档版本 01 ( ) 25

36 3 HBase 应用开发 catch (IOException e) { LOG.error("Failed to close HIndexAdmin ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Failed to close admin ", e); LOG.info("Exiting Enabling a Hindex."); 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HIndexExample 类的 disableindicesexample 方法中 disableindices (): 禁用指定的索引 ( 索引状态将从 ACTIVE 更改为 INACTIVE 状态 ), 因此对于索引扫描将变得无法使用 public void disableindicesexample() { LOG.info("Entering Disabling a Hindex."); List<String> indexnamelist = new ArrayList<String>(); indexnamelist.add(indexnametoadd); Admin admin = null; HIndexAdmin iadmin = null; try { admin = conn.getadmin(); iadmin = HIndexClient.newHIndexAdmin(admin); // Disable the specified indices iadmin.disableindices(tablename, indexnamelist); // Alternately, enable the specified indices // iadmin.enableindices(tablename, indexnamelist); LOG.info("Successfully disabled indices " + indexnamelist + " of the table " + tablename); catch (IOException e) { LOG.error("Failed to disable indices " + indexnamelist + " of the table " + tablename); finally { if (iadmin!= null) { try { // Close the HIndexAdmin object. iadmin.close(); catch (IOException e) { LOG.error("Failed to close HIndexAdmin ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Failed to close admin ", e); LOG.info("Exiting Disabling a Hindex."); 查询二级索引列表 前提条件 此功能需要 MRS1.7 及更高版本才开放 文档版本 01 ( ) 26

37 3 HBase 应用开发 功能介绍 您可以使用 org.apache.hadoop.hbase.hindex.client.hindexadmin 中提供的方法来管理 HIndexes 该类提供了列出表的现有索引的方法 HIndexAdmin 为给定表格列出索引提供以下 API: listindices(): 该 API 可用于列出给定表的所有索引 代码样例 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HIndexExample 类的 listindicesintable 方法中 使用二级索引读取数据 前提条件 功能介绍 代码样例 public void listindicesintable() { LOG.info("Entering Listing Hindex."); Admin admin = null; HIndexAdmin iadmin = null; try { admin = conn.getadmin(); iadmin = HIndexClient.newHIndexAdmin(admin); // Retreive the list of indices and print it List<Pair<HIndexSpecification, IndexState>> indiceslist = iadmin.listindices(tablename); LOG.info("indicesList:" + indiceslist); LOG.info("Successfully listed indices for table " + tablename + "."); catch (IOException e) { LOG.error("Failed to list indices for table " + tablename + "." + e); finally { if (iadmin!= null) { try { // Close the HIndexAdmin object. iadmin.close(); catch (IOException e) { LOG.error("Failed to close HIndexAdmin ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Failed to close admin ", e); LOG.info("Exiting Listing Hindex."); 此功能需要 MRS1.7 及更高版本才开放 在具有 HIndexes 的用户表中,HBase 使用 Filter 来查询数据 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HIndexExample 类的 scandatabyhindex 方法中 文档版本 01 ( ) 27

38 3 HBase 应用开发 删除二级索引 前提条件 功能介绍 public void scandatabyhindex() { LOG.info("Entering HIndex-based Query."); Table table = null; ResultScanner rscanner = null; try { table = conn.gettable(tablename); // Create a filter for indexed column. SingleColumnValueFilter filter = new SingleColumnValueFilter(Bytes.toBytes("info"), Bytes.toBytes("age"), CompareOp.GREATER_OR_EQUAL, Bytes.toBytes("26")); filter.setfilterifmissing(true); Scan scan = new Scan(); scan.setfilter(filter); rscanner = table.getscanner(scan); // Scan the data LOG.info("Scan data using indices.."); for (Result result : rscanner) { LOG.info("Scanned row is:"); for (Cell cell : result.rawcells()) { LOG.info(Bytes.toString(CellUtil.cloneRow(cell)) + ":" + Bytes.toString(CellUtil.cloneFamily(cell)) + "," + Bytes.toString(CellUtil.cloneQualifier(cell)) + "," + Bytes.toString(CellUtil.cloneValue(cell))); LOG.info("Successfully scanned data using indices for table " + tablename + "."); catch (IOException e) { LOG.error("Failed to scan data using indices for table " + tablename + "." + e); finally { if (rscanner!= null) { rscanner.close(); if (table!= null) { try { table.close(); catch (IOException e) { LOG.error("failed to close table, ", e); LOG.info("Entering HIndex-based Query."); 此功能需要 MRS1.7 及更高版本才开放 您可以使用 org.apache.hadoop.hbase.hindex.client.hindexadmin 中提供的方法来管理 HIndexes 该类提供了从表中删除现有索引的方法 根据用户是否希望删除索引数据以及索引删除操作, 有两种不同的 API 可将索引删除到表中 : dropindices() dropindiceswithdata() 文档版本 01 ( ) 28

39 3 HBase 应用开发 代码样例 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HIndexExample 类的 dropindicesexample 方法中 dropindices(): 从指定的表中删除指定的索引, 但索引数据不会被删除 public void dropindicesexample() { LOG.info("Entering Deleting a Hindex."); List<String> indexnamelist = new ArrayList<String>(); indexnamelist.add(indexnametoadd); Admin admin = null; HIndexAdmin iadmin = null; try { admin = conn.getadmin(); iadmin = HIndexClient.newHIndexAdmin(admin); // Drop the specified indices without dropping index data iadmin.dropindices(tablename, indexnamelist); // Alternately, drop the specified indices with data // iadmin.dropindiceswithdata(tablename, indexnamelist); LOG.info("Successfully dropped indices " + indexnamelist + " from the table " + tablename); catch (IOException e) { LOG.error("Failed to drop indices " + indexnamelist + " from the table " + tablename); finally { if (iadmin!= null) { try { // Close the HIndexAdmin object. iadmin.close(); catch (IOException e) { LOG.error("Failed to close HIndexAdmin ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Failed to close admin ", e); LOG.info("Exiting Deleting a Hindex."); 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HIndexExample 类的 dropindiceswithdata 方法中 dropindiceswithdata(): 从指定的表中删除指定的索引, 并从用户表中删除与这些索引对应的所有索引数据 public void dropindicesexamplewithdata() { LOG.info("Entering Deleting a Hindex With Data."); List<String> indexnamelist = new ArrayList<String>(); indexnamelist.add(indexnametoadd); Admin admin = null; HIndexAdmin iadmin = null; try { admin = conn.getadmin(); iadmin = HIndexClient.newHIndexAdmin(admin); // Drop the specified indices without dropping index data iadmin.dropindiceswithdata(tablename, indexnamelist); // Alternately, drop the specified indices with data // iadmin.dropindiceswithdata(tablename, indexnamelist); LOG.info("Successfully dropped indices " + indexnamelist + " from the table " + tablename); catch (IOException e) { LOG.error("Failed to drop indices " + indexnamelist + " from the table " + tablename); 文档版本 01 ( ) 29

40 3 HBase 应用开发 写 MOB 表 finally { if (iadmin!= null) { try { // Close the HIndexAdmin object. iadmin.close(); catch (IOException e) { LOG.error("Failed to close HIndexAdmin ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Failed to close admin ", e); LOG.info("Exiting Deleting a Hindex With Data."); 功能简介 代码样例 HBase MOB 数据的写入与普通 HBase 数据的写入没有什么区别, 对客户来说是透明的 为了使用 HBase MOB 功能需要在 hbase-site.xml 中添加 HBase MOB 相关的配置项, 具体请参见 除此之外还需要在指定 column family 上开启 MOB 功能, 样例代码如下 : 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testcreatemobtable 方法中 public void testcreatemobtable() { LOG.info("Entering testcreatemobtable."); Admin admin = null; try { // Create Admin instance admin = conn.getadmin(); HTableDescriptor tabdescriptor = new HTableDescriptor(tableName); HColumnDescriptor mob = new HColumnDescriptor("mobcf"); // Open mob function mob.setmobenabled(true); // Set mob threshold mob.setmobthreshold(10l); tabdescriptor.addfamily(mob); admin.createtable(tabdescriptor); LOG.info("MOB Table is created successfully."); catch (Exception e) { LOG.error("MOB Table is created failed ", e); finally { if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Close admin failed ", e); LOG.info("Exiting testcreatemobtable."); 文档版本 01 ( ) 30

41 3 HBase 应用开发 样例 : 用 Put 接口写入 MOB 数据 以下代码片段在 com.bigdata.hbase.examples 包的 HBaseSample 类的 testmobdatainsertion 方法中 public void testmobdatainsertion() { LOG.info("Entering testmobdatainsertion."); Table table = null; try { // set row name to "row" Put p = new Put(Bytes.toBytes("row")); byte[] value = new byte[1000]; // set the column value of column family mobcf with the value of "cf1" p.addcolumn(bytes.tobytes("mobcf"), Bytes.toBytes("cf1"), value); // get the table object represent table tablename table = conn.gettable(tablename); // put data table.put(p); LOG.info("MOB data inserted successfully."); catch (Exception e) { LOG.error("MOB data inserted failed ", e); finally { if (table!= null) { try { table.close(); catch (Exception e1) { LOG.error("Close table failed ", e1); LOG.info("Exiting testmobdatainsertion."); 注意事项 读 MOB 数据 功能简介 代码样例 不允许多个线程在同一时间共用同一个 HTable 实例 HTable 是一个非线程安全类, 因此, 同一个 HTable 实例, 不应该被多个线程同时使用, 否则可能会带来并发问题 HBase MOB 数据读出与普通 HBase 数据的读出没有什么区别, 对客户来说是透明的 为了使用 HBase MOB 功能需要在 hbase-site.xml 中添加 HBase MOB 相关的配置项, 具体请参阅 除此之外还需要在指定 column family 上开启 MOB 功能 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testmobdataread 方法中 public void testmobdataread() { LOG.info("Entering testmobdataread."); ResultScanner scanner = null; Table table = null; Admin admin = null; try { // get table object representing table tablename table = conn.gettable(tablename); 文档版本 01 ( ) 31

42 3 HBase 应用开发 admin = conn.getadmin(); admin.flush(table.getname()); Scan scan = new Scan(); // get table scanner scanner = table.getscanner(scan); for (Result result : scanner) { byte[] value = result.getvalue(bytes.tobytes("mobcf"), Bytes.toBytes("cf1")); String string = Bytes.toString(value); LOG.info("value:" + string); LOG.info("MOB data read successfully."); catch (Exception e) { LOG.error("MOB data read failed ", e); finally { if (scanner!= null) { scanner.close(); if (table!= null) { try { // Close table object table.close(); catch (IOException e) { LOG.error("Close table failed ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Close admin failed ", e); LOG.info("Exiting testmobdataread."); Region 的多点分割 功能简介 代码样例 一般通过 org.apache.hadoop.hbase.client.hbaseadmin 进行多点分割 说明 分割操作只对空 Region 起作用 本例使用 multisplit 进行多点分割将 HBase 表按照 A~D D~F F~H H~Z 分为四个 Region 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 testmultisplit 方法中 public void testmultisplit() { LOG.info("Entering testmultisplit."); Table table = null; Admin admin = null; try { admin = conn.getadmin(); // initilize a HTable object table = conn.gettable(tablename); Set<HRegionInfo> regionset = new HashSet<HRegionInfo>(); 文档版本 01 ( ) 32

43 3 HBase 应用开发 List<HRegionLocation> regionlist = conn.getregionlocator(tablename).getallregionlocations(); for (HRegionLocation hrl : regionlist) { regionset.add(hrl.getregioninfo()); byte[][] sk = new byte[4][]; sk[0] = "A".getBytes(); sk[1] = "D".getBytes(); sk[2] = "F".getBytes(); sk[3] = "H".getBytes(); for (HRegionInfo regioninfo : regionset) { ((HBaseAdmin) admin).multisplit(regioninfo.getregionname(), sk); LOG.info("MultiSplit successfully."); catch (Exception e) { LOG.error("MultiSplit failed ", e); finally { if (table!= null) { try { // Close table object table.close(); catch (IOException e) { LOG.error("Close table failed ", e); if (admin!= null) { try { // Close the Admin object. admin.close(); catch (IOException e) { LOG.error("Close admin failed ", e); LOG.info("Exiting testmultisplit."); ACL 安全配置 功能简介 代码样例 访问权限控制, 在关系型数据库中是一个已经很成熟的技术,HBase 实现了一个较为简单的特性 这些特性归纳为读 (R) 写 (W) 创建 (C) 执行 (X) 和管理 (A) 等 在普通模式下, 该功能只有在开启 HBase 权限管理时才支持 ACL 的方法定义在工具类 org.apache.hadoop.hbase.security.access.accesscontrolclient 中 以下代码片段在 com.huawei.bigdata.hbase.examples 包的 HBaseSample 类的 grantacl 方法中 public void grantacl() { LOG.info("Entering grantacl."); String user = "huawei"; String permissions = "RW"; String familyname = "info"; String qualifiername = "name"; Table mt = null; Admin hadmin = null; try { 文档版本 01 ( ) 33

44 3 HBase 应用开发 // Create ACL Instance mt = conn.gettable(accesscontrollists.acl_table_name); Permission perm = new Permission(Bytes.toBytes(permissions)); hadmin = conn.getadmin(); HTableDescriptor ht = hadmin.gettabledescriptor(tablename); // Judge whether the table exists if (hadmin.tableexists(mt.getname())) { // Judge whether ColumnFamily exists if (ht.hasfamily(bytes.tobytes(familyname))) { // grant permission AccessControlClient.grant(conn, tablename, user, Bytes.toBytes(familyName), (qualifiername == null? null : Bytes.toBytes(qualifierName)), perm.getactions()); else { // grant permission AccessControlClient.grant(conn, tablename, user, null, null, perm.getactions()); LOG.info("Grant ACL successfully."); catch (Throwable e) { LOG.error("Grant ACL failed ", e); finally { if (mt!= null) { try { // Close mt.close(); catch (IOException e) { LOG.error("Close table failed ", e); if (hadmin!= null) { try { // Close Admin Object hadmin.close(); catch (IOException e) { LOG.error("Close admin failed ", e); LOG.info("Exiting grantacl."); Shell 命令方式 : 命令行 # 赋权 grant <user> <permissions>[ <table>[ <column family>[ <column qualifier> ] ] ] # 撤销权限 revoke <user> <permissions> [ <table> [ <column family> [ <column qualifier> ] ] ] # 设置表所有者 alter <table> {owner => <user> # 显示权限列表 user_permission <table> # displays existing permissions 例如 : grant 'user1', 'RWC' grant 'user2', 'RW', 'tablea' user_permission 'tablea' 文档版本 01 ( ) 34

45 3 HBase 应用开发 3.4 调测程序 安装客户端时编译并运行程序 HBase 应用程序支持在安装 HBase 客户端的 Linux 环境中运行 在程序代码完成开发后, 您可以上传 Jar 包至 Linux 环境中运行应用 前提条件 已安装 HBase 客户端 Linux 环境已安装 JDK, 版本号需要和 Eclipse 导出 Jar 包使用的 JDK 版本一致 当客户端所在主机不是集群中的节点时, 需要在客户端所在节点的 hosts 文件中设置主机名和 IP 地址映射 主机名和 IP 地址请保持一一对应 操作步骤 步骤 1 修改样例代码 1. 当前样例代码中操作 HBase 的接口有三种, 分别是普通接口,HFS 接口,REST 接口 调试不同 API 接口操作 HBase 时可以注释其他接口调用 这里以使用普通接口操作 HBase 为例,main 方法中只包含如下代码段 public static void main(string[] args) { try { init(); login(); catch (IOException e) { LOG.error("Failed to login because ", e); return; // getdefaultconfiguration(); conf = HBaseConfiguration.create(); // test hbase API HBaseExample onesample; try { onesample = new HBaseExample(conf); onesample.test(); catch (Exception e) { LOG.error("Failed to test HBase because ", e); LOG.info(" finish HBase "); 2. 在调用 HFS 接口,REST 接口时需要把样例工程 conf 下 inputfile.txt 和 hbaseclient.properties 文件拷贝到客户端 ( 客户端目录以 /opt/client 为例 ) 的 HBase/ hbase/conf 目录下, 并修改 hbaseclient.properties 文件 userkeytab.path, krb5.conf.path 对应于从步骤 1 获取的文件的地址 若使用 REST 接口时需修改 rest.server.info, 使其对应于 rest server 的 ip:port(port 默认为 21309) rest.server.info= :21309 user.name=hbaseuser userkeytab.path=/opt/client/conf/user.keytab krb5.conf.path=/opt/client/conf/krb5.conf 说明 HIndexExample 样例工程仅在 MRS1.7 及其以后版本中支持, 需注意当前集群版本 步骤 2 执行 mvn package 打出 jar 包, 在工程目录 target 目录下获取, 比如 :hbaseexamples-1.0.jar, 将获取的包上传到 /opt/client/hbase/hbase/lib 目录下 文档版本 01 ( ) 35

46 3 HBase 应用开发 步骤 3 执行 Jar 包 1. 在 Linux 客户端下执行 Jar 包的时候, 需要用安装用户切换到客户端目录 : cd $BIGDATA_CLIENT_HOME/HBase/hbase 说明 2. 然后执行 : $BIGDATA_CLIENT_HOME 指的是客户端安装目录 source $BIGDATA_CLIENT_HOME/bigdata_env 说明 启用多实例功能后, 为其他 HBase 服务实例进行应用程序开发时还需执行以下命令, 切换指定服务实例的客户端 例如 HBase2:source /opt/client/hbase2/component_env 3. 将应用开发环境中生成的 Jar 包和从 准备开发用户中获取的 krb5.conf 和 user.keytab 文件拷贝上传至客户端运行环境的 Hbase/hbase/conf 目录中, 例如 /opt/ client/hbase/hbase/conf, 并赋给该用户执行权限, 然后修改 hbaseclient.properties 文件,user.name 对应新建的用户 hbaseuser,userkeytab.path 和 krb5.conf.path 路径对应于上述中上传的地址 ( 非安全集群可跳过此步 ) user.name=hbaseuser userkeytab.path=/opt/client/hbase/hbase/conf/user.keytab krb5.conf.path=/opt/client/hbase/hbase/conf/krb5.conf 4. 运行如下命令使 Jar 包执行 ---- 结束 hbase com.huawei.bigdata.hbase.examples.testmain 其中,com.huawei.bigdata.hbase.examples.TestMain 为举例, 具体以实际样例代码为准 未安装客户端时编译并运行程序 HBase 应用程序支持在未安装 HBase 客户端的 Linux 环境中运行 在程序代码完成开发后, 您可以上传 Jar 包至 Linux 环境中运行应用 前提条件 Linux 环境已安装 JDK, 版本号需要和 Eclipse 导出 Jar 包使用的 JDK 版本一致 当 Linux 环境所在主机不是集群中的节点时, 需要在节点的 hosts 文件中设置主机名和 IP 地址映射 主机名和 IP 地址请保持一一对应 操作步骤 步骤 1 步骤 2 步骤 3 按安装客户端时编译并运行程序中的方式修改样例 执行 mvn package 打出 jar 包, 在工程目录 target 目录下获取, 比如 :hbaseexamples-1.0.jar 准备依赖的 Jar 包和配置文件 1. 在 Linux 环境新建目录, 例如 /opt/test, 并创建子目录 lib 和 conf 将样例工程编译时依赖的 Jar 包, 以及步骤 2 导出的 Jar 包, 上传到 Linux 的 lib 目录 将服务端中 hbase-site.xml,hdfs-site.xml,core-site.xml 文件拷贝到 /opt/test 中 conf 目录下, 服务端路径以 /opt/bigdata/fusioninsight/etc/ 1_20_RegionServer 为例 2. 将 准备开发用户中获取的 krb5.conf 和 user.keytab 文件拷贝上传至 /opt/test/ conf 目录中, 并修改 hbaseclient.properties 文件 user.name 对应新建的用户 文档版本 01 ( ) 36

47 3 HBase 应用开发 hbaseuser,userkeytab.path 和 krb5.conf.path 路径对应于上述中上传的地址 ( 非安全集群可跳过此步 ) user.name=hbaseuser userkeytab.path=/opt/test/conf/user.keytab krb5.conf.path=/opt/test/conf/krb5.conf 3. 在 /opt/test 根目录新建脚本 run.sh, 修改内容如下并保存 : 其中,com.huawei.bigdata.hbase.examples.TestMain 为举例, 具体以实际样例代码为准 #!/bin/sh BASEDIR=`pwd` cd ${BASEDIR for file in ${BASEDIR/lib/*.jar do i_cp=$i_cp:$file echo "$file" done for file in ${BASEDIR/conf/* do i_cp=$i_cp:$file done java -cp.${i_cp com.huawei.bigdata.hbase.examples.testmain 步骤 4 切换到 /opt/test, 执行以下命令, 运行 Jar 包 sh run.sh ---- 结束 查看调测结果 HBase 应用程序运行完成后, 可直接通过运行结果查看应用程序运行情况, 也可以通过 HBase 日志获取应用运行情况 运行结果会有如下成功信息 : :44:28,068 INFO [main] examples.hbaseexample: Entering droptable :44:28,074 INFO [main] client.hbaseadmin: Started disable of hbase_sample_table :44:30,310 INFO [main] client.hbaseadmin: Disabled hbase_sample_table :44:31,727 INFO [main] client.hbaseadmin: Deleted hbase_sample_table :44:31,727 INFO [main] examples.hbaseexample: Drop table successfully :44:31,727 INFO [main] examples.hbaseexample: Exiting droptable :44:31,727 INFO [main] client.connectionmanager$hconnectionimplementation: Closing master protocol: MasterService :44:31,733 INFO [main] client.connectionmanager$hconnectionimplementation: Closing zookeeper sessionid=0x13002d37b :44:31,736 INFO [main-eventthread] zookeeper.clientcnxn: EventThread shut down for session: 0x13002d37b :44:31,737 INFO [main] zookeeper.zookeeper: Session: 0x13002d37b closed :44:31,750 INFO [main] examples.testmain: finish HBase 更多信息 SQL 查询 功能简介 Phoenix 是构建在 HBase 之上的一个 SQL 中间层, 提供一个客户端可嵌入的 JDBC 驱动, Phoenix 查询引擎将 SQL 输入转换为一个或多个 HBase scan, 编译并执行扫描任务以产生一个标准的 JDBC 结果集 文档版本 01 ( ) 37

48 代码样例 客户端 hbase-example/conf/hbase-site.xml 中配置存放查询中间结果的临时目录, 如果客户端程序在 Linux 上执行临时目录就配置 Linux 上的路径, 如果客户端程序在 Windows 上执行临时目录则配 Windows 上的路径 <property> <name>phoenix.spool.directory</name> <value>[1] 查询中间结果的临时目录 </value> </property> Java 样例 : 使用 JDBC 接口访问 HBase public String geturl(configuration conf) { String phoenix_jdbc = "jdbc:phoenix"; String zkquorum = conf.get("hbase.zookeeper.quorum"); return phoenix_jdbc + ":" + zkquorum; 3 HBase 应用开发 public void testsql() { String tablename = "TEST"; // Create table String createtablesql = "CREATE TABLE IF NOT EXISTS TEST(id integer not null primary key, name varchar, account char(6), birth date)"; // Delete table String droptablesql = "DROP TABLE TEST"; // Insert String upsertsql = "UPSERT INTO TEST VALUES(1,'John','100000', TO_DATE(' ','yyyy-MM-dd'))"; // Query String querysql = "SELECT * FROM TEST WHERE id =?"; // Create the Configuration instance Configuration conf = getconfiguration(); // Get URL String URL = geturl(conf); Connection conn = null; PreparedStatement prestat = null; Statement stat = null; ResultSet result = null; try { // Create Connection conn = DriverManager.getConnection(URL); // Create Statement stat = conn.createstatement(); // Execute Create SQL stat.executeupdate(createtablesql); // Execute Update SQL stat.executeupdate(upsertsql); // Create PrepareStatement prestat = conn.preparestatement(querysql); // Execute query prestat.setint(1,1); result = prestat.executequery(); // Get result while (result.next()) { int id = result.getint("id"); String name = result.getstring(1); catch (Exception e) 文档版本 01 ( ) 38

49 3 HBase 应用开发 { // handler exception finally { if(null!= result){ try { result.close(); catch (Exception e2) { // handler exception if(null!= stat){ try { stat.close(); catch (Exception e2) { // handler exception if(null!= conn){ try { conn.close(); catch (Exception e2) { // handler exception 注意事项 需要在 hbase-site.xml 中配置用于存放中间查询结果的临时目录路径, 该目录大小限制可查询结果集大小 ; Phoenix 实现了大部分 java.sql 接口,SQL 紧跟 ANSI SQL 标准 MRS 1.6 之后的版本需要按 Sqlline 中下载和配置开源的 phoenix 包 配置 HBase 文件存储 HBase 文件存储模块 (HBase FileStream, 简称 HFS) 是 HBase 的独立模块, 它作为对 HBase 与 HDFS 接口的封装, 应用在 MRS 的上层应用, 为上层应用提供文件的存储 读取 删除等功能 在 Hadoop 生态系统中, 无论是 HDFS, 还是 HBase, 在面对海量文件存储的时候, 在某些场景下, 都会存在一些很难解决的问题 : 如果把海量小文件直接保存在 HDFS 中, 会给 NameNode 带来极大的压力 由于 HBase 接口以及内部机制的原因, 一些较大的文件也不适合直接保存到 HBase 中 HFS 的出现, 就是为了解决需要在 Hadoop 中存储海量小文件, 同时也要存储一些大文件的混合场景 简单来说, 就是在 HBase 表中, 需要存放大量的小文件 (10MB 以下 ), 同时又需要存放一些比较大的文件 (10MB 以上 ) HFS 为以上场景提供了统一的操作接口, 这些操作接口与 HBase 的函数接口类似 必须在 HBase 的配置参数 hbase.coprocessor.master.classes 中增加一个值 : org.apache.hadoop.hbase.filestream.coprocessor.filestreammasterobserver 文档版本 01 ( ) 39

50 3 HBase 应用开发 注意 如果只有小文件, 确定不会有大文件的场景下, 建议使用 HBase 的原始接口进行操作 HFS 接口需要同时对 HBase 和 HDFS 进行操作, 所以客户端用户需要同时拥有这两个组件的操作权限 直接存放在 HDFS 中的大文件,HFS 在存储时会加入一些元数据信息, 所以存储的文件不是直接等于原文件的 不能直接从 HDFS 中移动出来使用, 而需要用 HFS 的接口进行读取 使用 HFS 接口存储在 HDFS 中的数据, 暂不支持备份与容灾 操作步骤 步骤 1 登录 MRS Manager 步骤 2 步骤 3 步骤 4 单击 服务管理 > HBase > 服务配置, 参数类别 类型选择 全部配置, 然后在左边窗口选择 HMaster > 系统 在 hbase.coprocessor.master.classes 配置项中增加值 org.apache.hadoop.hbase.filestream.coprocessor.filestreammasterobserver 单击 保存配置, 在弹出窗口中勾选 重新启动受影响的服务或实例 选项, 然后单击 确定, 重启 HBase 服务 ---- 结束 HFS 的 JAVA API 主要类说明 : 接口 org.apache.hadoop.hbase.filestream.client.fstableinterface 常用接口说明 : 方法 void put(fsput fsput) void put(list<fsput> fsputs) FSResult get(fsget fsget) FSResult[] get(list<fsget> fsgets) void delete(fsdelete fsdelete) void delete(list<fsdelete> fsdeletes) void close() 说明向 HFS 表中插入数据向 HFS 表中批量插入数据从 HFS 表中读取数据从 HFS 表中读取多行数据从 HFS 表中删除数据从 HFS 表中删除多行数据关闭表对象 org.apache.hadoop.hbase.filestream.client.fstable 是 org.apache.hadoop.hbase.filestream.client.fstableinterface 接口的实现类 org.apache.hadoop.hbase.filestream.client.fshcolumndescriptor 继承自 org.apache.hadoop.hbase.hcolumndescriptor, 新增如下接口 : 文档版本 01 ( ) 40

51 3 HBase 应用开发 方法 public void setfilecolumn() public void setfilethreshold(int filethreshold) 说明 设置这个列族为存储文件的列族 设置存储文件大小的阈值 org.apache.hadoop.hbase.filestream.client.fstabledescriptor 继承自 org.apache.hadoop.hbase.htabledescriptor, 没有新增接口, 但是如果要使用 JAVA 接口创建 HFS 表来存储文件, 必须使用该类 org.apache.hadoop.hbase.filestream.client.fsput 继承自 org.apache.hadoop.hbase.put, 新增如下接口 : 方法 public FSPut(byte[] row) public FSPut(byte[] row, long timestamp) public void addfile(string name, byte[] value) public void addfile(string name, byte[] value, long ts) public void addfile(string name, InputStream inputstream) public void addfile(string name, InputStream inputstream, long ts) 说明 构造函数 通过 rowkey 来构造对象 构造函数 通过 rowkey 和时间戳来构造对象 向 HFS 表中的存储文件的列族中插入一个文件, 以 name 为列名,value 为文件内容 向 HFS 表中的存储文件的列族中插入一个文件, 以 name 为列名,value 为文件内容,ts 为指定的时间戳 向 HFS 表中的存储文件的列族中插入一个文件, 以 name 为列名,inputStream 为文件的输入流对象 输入流对象需要调用者自行关闭 向 HFS 表中的存储文件的列族中插入一个文件, 以 name 为列名,inputStream 为文件的输入流对象,ts 为指定的时间戳 输入流对象需要调用者自行关闭 org.apache.hadoop.hbase.filestream.client.fsget 继承自 org.apache.hadoop.hbase.get, 新增如下接口 方法 public FSGet(byte[] row) public void addfile(string filename) public void addfiles(list<string> filenames) 说明构造函数 根据 rowkey 构造对象 指定需要返回的文件 指定需要返回的多个文件 文档版本 01 ( ) 41

52 3 HBase 应用开发 org.apache.hadoop.hbase.filestream.client.fsresult 继承自 org.apache.hadoop.hbase.result, 新增如下接口 : 方法 public FSFile getfile(string filename) 说明 从查询结果中返回指定文件名的 FSFile 文件对象 org.apache.hadoop.hbase.filestream.client.fsfile 接口 : 方法 public InputStream createinputstream() 说明 从 FSFile 对象中获取文件的输入流对象 HBase 接口 Shell 您可以使用 Shell 在服务端直接对 HBase 进行操作 HBase 的 Shell 接口同开源社区版本保持一致, 请参见 Shell 命令执行方法 : 进入 HBase 客户端任意目录, 执行以下命令 hbase shell 进入 HBase 命令行运行模式 ( 也称为 CLI 客户端连接 ), 如下所示 hbase(main):001:0> 您可以在命令行运行模式中运行 help 命令获取 HBase 的命令参数的帮助信息 获取 HBase replication 指标的命令 通过 Shell 命令 status 可以获取到所有需要的指标 查看 replication source 指标的命令 hbase(main):019:0> status 'replication', 'source' 输出结果如下 : version live servers BLR : SOURCE: PeerID=1, SizeOfLogQueue=0, ShippedBatches=0, ShippedOps=0, ShippedBytes=0, LogReadInBytes=1389, LogEditsRead=4, LogEditsFiltered=4, SizeOfLogToReplicate=0, TimeForLogToReplicate=0, ShippedHFiles=0, SizeOfHFileRefsQueue=0, AgeOfLastShippedOp=0, TimeStampsOfLastShippedOp=Wed May 25 20:44:42 CST 2016, Replication Lag=0 PeerID=3, SizeOfLogQueue=0, ShippedBatches=0, ShippedOps=0, ShippedBytes=0, LogReadInBytes=1389, LogEditsRead=4, LogEditsFiltered=4, SizeOfLogToReplicate=0, TimeForLogToReplicate=0, ShippedHFiles=0, SizeOfHFileRefsQueue=0, AgeOfLastShippedOp=0, TimeStampsOfLastShippedOp=Wed May 25 20:44:42 CST 2016, Replication Lag=0 FailedReplicationAttempts=0 文档版本 01 ( ) 42

53 3 HBase 应用开发 Java API 新增或修改的接口 查看 replication sink 指标的命令 hbase(main):020:0> status 'replication', 'sink' 输出结果如下 : version live servers BLR : SINK : AppliedBatches=0, AppliedOps=0, AppliedHFiles=0, AgeOfLastAppliedOp=0, TimeStampsOfLastAppliedOp=Wed May 25 17:55:21 CST 2016 同时查看 replication source 和 replication sink 指标的命令 hbase(main):018:0> status 'replication' 输出结果如下 : version live servers BLR : SOURCE: PeerID=1, SizeOfLogQueue=0, ShippedBatches=0, ShippedOps=0, ShippedBytes=0, LogReadInBytes=1389, LogEditsRead=4, LogEditsFiltered=4, SizeOfLogToReplicate=0, TimeForLogToReplicate=0, ShippedHFiles=0, SizeOfHFileRefsQueue=0, AgeOfLastShippedOp=0, TimeStampsOfLastShippedOp=Wed May 25 20:43:24 CST 2016, Replication Lag=0 PeerID=3, SizeOfLogQueue=0, ShippedBatches=0, ShippedOps=0, ShippedBytes=0, LogReadInBytes=1389, LogEditsRead=4, LogEditsFiltered=4, SizeOfLogToReplicate=0, TimeForLogToReplicate=0, ShippedHFiles=0, SizeOfHFileRefsQueue=0, AgeOfLastShippedOp=0, TimeStampsOfLastShippedOp=Wed May 25 20:43:24 CST 2016, Replication Lag=0 FailedReplicationAttempts=0 SINK : AppliedBatches=0, AppliedOps=0, AppliedHFiles=0, AgeOfLastAppliedOp=0, TimeStampsOfLastAppliedOp=Wed May 25 17:55:21 CST 2016 HBase 采用的接口与 Apache HBase 保持一致, 请参见 index.html HBase 建议使用 org.apache.hadoop.hbase.cell 作为 Key-value 数据对象, 而不是 HBase 0.94 的 org.apache.hadoop.hbase.keyvalue HBase 建议使用 HConnection connection = HConnectionManager.createConnection(conf) 来创建连接池, 废弃 HTablePool 新的 EndPoint 接口, 参见 org.apache.hadoop.hbase.client.scan 中新增反向扫描方法设置 isreversed() 和 setreversed(boolean reversed) HBase 0.98 到 1.0 的 API 变更, 请参考 : hbase HBase 1.0 建议不要使用 org.apache.hadoop.hbase.mapred, 建议使用 org.apache.hadoop.hbase.mapreduce 版本详细的信息请参考 : start_of_a_new_era 获取 HBase replication metrics 新增的 API 接口 文档版本 01 ( ) 43

54 3 HBase 应用开发 表 3-6 org.apache.hadoop.hbase.client.replication.replicationadmin 方法 getsourcemetricssummary(string id) getsourcemetrics(string id) getsinkmetrics() getpeersinkmetrics(string id) 描述 参数类型 :String. 需要获取对端 id 的源指标汇总 返回类型 :Map<String, String> 返回 : 一个 Map, 其中键是 RegionServer 的名称, 值是指定对端 id 的源集群指标的汇总 汇总指标是 'sizeoflogtoreplicate' 和 'timeforlogtoreplicate' 参数类型 :String 需要获取对端 id 的源指标汇总 返回类型 :Map<String, String> 返回 : 一个 Map, 其中键是 RegionServer 的名称, 值是指定对端 id 源集群的指标 返回类型 :Map<String, String> 返回 : 一个 Map, 其中键是 RegionServer 的名称, 值是指定对端 id 源集群的 sink 指标 参数类型 :String 需要获取对端 id 的源指标汇总 返回类型 :Map<String, String> 返回 : 一个 Map, 其中键是 RegionServer 的名称, 值是指定对端 id 源集群的 sink 指标 说明 所有方法返回一个 Map, 其中键是 RegionServer 名称 (IP/Host) 和 string 类型的值, 包含了所有指标, 其格式是 'Metric Name'='Metric Value' [, 'Metric Name'= 'Metric Value']* 举例 :SizeOfHFileRefsQueue=0, AgeOfLastShippedOp=0 表 3-7 org.apache.hadoop.hbase.replication.replicationloadsource 方法 getpeerid() getageoflastshippedop() 描述 返回类型 :String 返回 : 对端集群的 id 返回类型 :long 返回 : 上次成功的 replication 请求持续的毫秒数 文档版本 01 ( ) 44

55 3 HBase 应用开发 方法 getsizeoflogqueue() gettimestampoflastshippedop() getreplicationlag() getshippedops() getshippedbytes() getshippedbatches() getlogreadinbytes() getlogeditsread() getsizeoflogtoreplicate() gettimeforlogtoreplicate() getshippedhfiles() getsizeofhfilerefsqueue() getlogeditsfiltered() getfailedreplicationattempts() 描述 返回类型 :long 返回 : 队列中等待 replication 的 WALs 返回类型 :long 返回 : 上次成功的 replication 请求的时间戳 返回类型 :long 返回 : 当前时间和上次成功的 replication 请求的时间间隔 返回类型 :long 返回 : 输送的数据 ops 总数 返回类型 :long 返回 : 输送的总的数据字节数 返回类型 :long 返回 : 输送的总的数据批数 返回类型 :long 返回 : 从 wal 日志读取的总字节数 返回类型 :long 返回 : 从 wal 日志读取的总编辑数 返回类型 :long 返回 : 在队列中等待 replicate 的总 wal 日志大小 返回类型 :long 返回 : 在队列中 replicate wal 日志需要花的秒数 返回类型 :long 返回 : 输送的 HFile 总数 返回类型 :long 返回 : 等待 replicate 的 HFile 总数 返回类型 :long 返回 : 过滤的 wal 编辑总数 返回类型 :long 返回 : 在一次请求中不能复制数据的次数 文档版本 01 ( ) 45

56 3 HBase 应用开发 表 3-8 org.apache.hadoop.hbase.replication.replicationloadsink 方法 getageoflastappliedop() gettimestampsoflastappliedop() getappliedbatches() getappliedops() getappliedhfiles() 描述 返回类型 :long 返回 : 上次成功的应用 wal 编辑的持续毫秒数 返回类型 :long 返回 : 上次成功的应用 wal 编辑的时间戳 返回类型 :long 返回 : 应用的数据总批数 返回类型 :long 返回 : 应用数据 ops 的总数 返回类型 :long 返回 : 应用的 HFile 总数 说明 Replication Admin 新的接口, 从 HMaster 获取指标值 每个 Region Server 在每一个心跳周期 ( 默认是 3 秒 ) 上报状态给 HMaster 所以 API 通过 Region Server 在最后一个心跳时上报最新的指标值 如果需要当前最新的指标值, 使用由 Region Server 提供的 JMX 接口 1.3.1(MRS1.6) 版本的接口变更 表 3-9 org.apache.hadoop.hbase.client.admin 中移除了以下接口 方法 compactmob 描述 返回类型 :void 增强了 ACL 客户端 API, 以便根据特定用户的 namespace, 表名 (table name), 列族 (column family) 和列限定符 (column qualifier) 进行查询权限 客户端还可以验证指定的用户是否具有对所述表, 列族或列限定符执行操作的权限 文档版本 01 ( ) 46

57 3 HBase 应用开发 表 3-10 org.apache.hadoop.hbase.security.access.accesscontrolclient 方法 getuserpermissions(connection connection, String tableregex, String username) getuserpermissions(connection connection, String tableregex, byte[] columnfamily) 描述 参数类型 :Connection HBase 集群连接 参数类型 :String 表名称模式, 基于将被检索的权限 当 tableregex 为 null 时, 使用全局权限, 而如果 tableregex 作为第一个字符, 则使用 namespace 权限, 否则将检索表权限 tableregex 可以是表和 namespace 的正则表达式 参数类型 :String 将被过滤权限的用户名 当 username ( 用户名 ) 为空时, 将检索所有用户权限 返回类型 :List<UserPermission> 返回 : 用户权限列表 (List of user permissions) 参数类型 : Connection HBase 集群连接 参数类型 :String 表名称模式, 基于将被检索的权限 表名称不能为 null 或空, 也不能是 namespace 名称 参数类型 :byte[] 列族, 基于将被检索的权限 当 columnfamily 为空时, 将检索所有列族权限 返回类型 :List<UserPermission> 返回 : 用户权限列表 文档版本 01 ( ) 47

58 3 HBase 应用开发 方法 getuserpermissions(connection connection, String tableregex, byte[] columnfamily, String username) getuserpermissions(connection connection, String tableregex, byte[] columnfamily, byte[] columnqualifier) 描述 参数类型 :Connection HBase 集群连接 参数类型 :String 表名称模式, 基于将被检索的权限 表名称不能为 null 或空, 也不能是 namespace 名称 参数类型 :byte[] 列族, 基于将被检索的权限 当 columnfamily 为空时, 将检索所有列族权限 参数类型 :String 将被过滤权限的用户名 当 username 为空时, 将检索所有用户权限 返回类型 :List<UserPermission> 返回 : 用户权限列表 参数类型 :Connection HBase 集群连接 参数类型 :String 表名称模式, 基于将被检索的权限 表名称不能为 null 或空, 也不能是 namespace 名称 参数类型 :byte[] 列族, 基于将被检索的权限 当 columnfamily 为空时, 将检索所有列族权限 参数类型 :byte[] 列限定符, 基于将被检索的权限 当 columnqualifier 为空时, 将检索所有列限定符权限 返回类型 : List<UserPermission> 返回 : 用户权限列表 文档版本 01 ( ) 48

59 3 HBase 应用开发 方法 getuserpermissions(connection connection, String tableregex, byte[] columnfamily, byte[] columnqualifier, String username) haspermission(connection connection, String tablename, String columnfamily, String columnqualifier, String username, Permission.Action... actions) 描述 参数类型 : Connection HBase 集群连接 参数类型 :String 表名称模式, 基于将被检索的权限 表名称不能为 null 或空, 也不能是 namespace 名称 参数类型 :byte[] 列族, 基于将被检索的权限 当 columnfamily 为空时, 将检索所有列族权限 参数类型 :byte[] 列限定符, 基于将被检索的权限 当 columnqualifier 为空时, 将检索所有列限定符权限 参数类型 :String 将被过滤权限的用户名 当 username 为空时, 将检索所有用户权限 返回类型 :List<UserPermission> 返回 : 用户权限列表 验证指定用户是否具有对所述表, 列族或列限定符执行操作的权限 参数类型 :Connection HBase 集群连接 参数类型 :String 表名, 表名不能为 null 或空 参数类型 :String 列族, 当 columnfamily 为空时, 将在表级进行验证 参数类型 : String 列限定符, 当 columnqualifier 为空时, 则在表和列族级别进行验证 如果 columnfamily 被传递为 null 或为空, 则不会考虑 columnqualifier 参数值 参数类型 :String 将被过滤权限的用户名 当 username 为空时, 将检索所有用户的权限 参数类型 :Permission.Action 指定的用户希望在所述表, 列族或列限定符上执行的操作 文档版本 01 ( ) 49

60 3 HBase 应用开发 方法 haspermission(connection connection, String tablename, byte[] columnfamily, byte[] columnqualifier, String username, Permission.Action... actions) 描述 验证指定用户是否具有对所述表, 列族或列限定符执行操作的权限 参数类型 :Connection HBase 集群连接 参数类型 :String 表名, 表名不能为 null 或空 参数类型 :byte[] 列族, 当 columnfamily 为空时, 将在表级进行验证 参数类型 :byte[] 列限定符, 当 columnqualifier 为空时, 将在表和列族级别进行验证 如果 columnfamily 被传递为 null 或为空, 则不会考虑 columnqualifier 参数值 参数类型 :String 将被过滤权限的用户名 当 username 为空时, 将检索所有用户的权限 参数类型 :Action 指定的用户希望在所述表, 列族或列限定符上执行的操作 1.3.1(MRS1.7) 版本的接口变更 新增 HIndex API 表 3-11 org.apache.hadoop.hbase.hindex.client.hindexadmin 方法 addindices(tablename tablename,tableindices tableindices) addindiceswithdata(tablename tablename,tableindices tableindices) 描述参数 :TableName 用户想要添加指定索引的表的名称 参数 :TableIndices 要添加到表中的表索引返回类型 :viod 参数 :TableName 用户想要添加指定索引的表的名称参数 :TableIndices 要添加到表中的表索引返回类型 :void 文档版本 01 ( ) 50

61 3 HBase 应用开发 方法 dropindices(tablename tablename,list <String> list) dropindiceswithdata(tablename tablename,list <String> list) disableindices(tablename tablename,list <String> list) enableindices(tablename tablename,list <String> list) listindices(tablename tablename) 描述 参数 :TableName 用户想要删除索引的表的名称 参数 : List<String> 包含要删除的索引名称的列表 返回类型 :void 参数 :TableName 用户想要删除指定索引的表的名称 参数 :List <String> 包含要删除的索引名称的列表 返回类型 :void 参数 :TableName 用户想要禁用指定索引的表的名称 参数 :List <String> 包含要禁用的索引名称的列表 返回类型 :void 参数 :TableName 用户希望启用指定索引的表的名称 参数 :List <String> 包含要启用的索引名称的列表 返回类型 :void 参数 :TableName 用户想要列出所有索引的表的名称 返回类型 :List <Pair <HIndexSpecification,IndexState >> 返回 : 返回二级索引列表, 第一个元素是索引规范, 第二个元素是该索引的当前状态 Sqlline 您可以直接使用 sqlline.py 在服务端对 HBase 进行 SQL 操作 Phoenix 的 sqlline 接口与开源社区保持一致, 请参见 配置方式 MRS1.6 之后的版本需要去官网下载第三方的 phoenix 包来进行如下配置 : 1. 从官网 ( 下载 phoenix 二进制包并解压 (apache-phoenix hbase-1.3-bin.tar.gz) tar xvf apache-phoenix hbase-1.3-bin.tar.gz 文档版本 01 ( ) 51

62 3 HBase 应用开发 2. 进入 apache-phoenix hbase-1.3-bin 中, 把 phoenix hbase-1.3-server.jar 和 phoenix-core hbase-1.3.jar 两个 jar 包拷贝到集群服务端的 lib 下并修改相应的权限 如下所示, cp phoenix hbase-1.3-server.jar /opt/bigdata/mrs/fusioninsight-hbase /hbase/ lib/ cp phoenix-core hbase-1.3.jar /opt/bigdata/mrs/fusioninsight-hbase /hbase/ lib/ chmod 700 /opt/bigdata/mrs/fusioninsight-hbase /hbase/lib/phoenix hbase-1.3- server.jar chmod 700 /opt/bigdata/mrs/fusioninsight-hbase /hbase/lib/phoenix-core HBase-1.3.jar chown omm:wheel /opt/bigdata/mrs/fusioninsight-hbase /hbase/lib/phoenix HBase-1.3-server.jar chown omm:wheel /opt/bigdata/mrs/fusioninsight-hbase /hbase/lib/phoenixcore hbase-1.3.jar 3. 参数配置 ( 非安全集群可跳过此步骤 ) 3.1. 配置 phoenix 连接时使用的认证信息 进入 $PHOENIX_HOME/bin, 编辑 hbasesite.xml 文件, 需配置参数如表 1 Phoenix 参数配置所示 表 3-12 Phoenix 参数配置 参数描述默认值 phoenix.queryserver.keytab.file keytab 文件地址未设置 phoenix.queryserver.kerberos.pr incipal hbase.security.authentication 当前使用 keytab 的 principal 初始化 Phoenix 连接时所采用的认证方式 未设置 kerberos 可配置参数 如下所示, <property> <name>phoenix.queryserver.keytab.file</name> <value>/opt/bigdata/mrs/etc/1_9_regionserver/hbase.keytab</value> </property> <property> <name>phoenix.queryserver.kerberos.principal</name> <value>hbase/zkclient.hadoop.com</value> </property> <property> <name>hbase.security.authentication</name> <value>kerberos</value> </property> 3.2. 修改 sqlline.py 脚本, 添加 hbase 客户端的相关依赖并添加 zookeeper 的认证信息如图 3-7 所示 图 3-7 Phoenix 依赖及 zookeeper 认证 文档版本 01 ( ) 52

63 3 HBase 应用开发 详细配置 如下所示, 添加 hbase client 的 lib 包 (eg,/opt/client/hbase/hbase/lib/*:) 添加 zookeeper 相关认证 (eg," -Djava.security.auth.login.config=file:/opt/Bigdata/MRS/etc/ 1_17_quorumpeer/jaas.conf -Djava.security.krb5.conf=/opt/Bigdata/MRS/etc/1_14_KerberosClient/ kdc.conf -Dzookeeper.kinit=file:/opt/client/KrbClient/kerberos/bin/kinit" + \) 使用方法 Phoenix 支持 SQL 的方式来操作 HBase 以下简单介绍使用 SQL 语句建表 / 插入数据 / 查询数据 / 删表等操作,Phoenix 同样支持以 JDBC 的方式来操作 HBase, 具体请参见 SQL 查询 1. 连接 Phoenix: cd $PHOENIX_HOME bin/sqlline.py zookeerip:2181 说明 MRS 及以下版本 ZooKeeper 端口号为 24002, 详见 MRS Manager 的 Zookeeper 集群配置 2. 建表 : CREATE TABLE TEST (id VARCHAR PRIMARY KEY, name VARCHAR); 3. 插入数据 : UPSERT INTO TEST(id,name) VALUES ('1','jamee'); 4. 查询数据 : SELECT * FROM TEST; 5. 删表 : DROP TABLE TEST; REST MRS1.6 之后, 支持采用 REST 的方式来对 HBASE 进行相应的业务操作,REST API 支持 curl 命令和 Java client 来操作 HBase, 有关 curl 命令的详细使用方法与 Apache HBase 保持一致, 具体请参见 说明 由于当前默认使用 SSL protocols 为 TLSv1.1,TLSv1.2, 所以在启用 CURL 调用 REST 时需判断当前环境支持的 SSL protocols 使用 curl 命令 非安全集群 在非安全集群中执行 curl 命令时增加以下参数 例如, curl -vi POST -H "Accept: text/xml" -H "Content-Type: text/xml" -d '<?xml version="1.0" encoding="utf-8"?> <TableSchema name="users"><columnschema name="cf" /> </TableSchema>' " 安全集群 在安全集群中执行 curl 命令时, 请遵循以下步骤 : a. 进行 kerberos 认证 例如, /opt/hbase/master/bin> kinit hdfs/hadoop Password for hdfs/hadoop@hadoop.com: b. 在 curl 命令中需在请求类型之前添加 --negotiate -u: 参数 例如, curl -vi --negotiate -u: POST -H "Accept: text/xml" -H "Content-Type: text/xml" -d '<? xml version="1.0" encoding="utf-8"?> <TableSchema name="users"><columnschema name="cf" /> </TableSchema>' " 文档版本 01 ( ) 53

64 3 HBase 应用开发 使用 Java Client 使用 Java 调用 REST API, 请按照以下步骤 1. 进行 kerberos 认证 ( 非安全集群可以跳过此步骤 ) 2. 创建一个 org.apache.hadoop.hbase.rest.client.cluster 类的集群对象, 通过调用集群类的 add 方法和 REST server 的集群 IP 和端口来添加集群 Cluster cluster = new Cluster(); cluster.add(" :21309"); 3. 使用在步骤 2 中添加的集群初始化类 org.apache.hadoop.hbase.rest.client.client 的客户端对象, 调用 doas 来操作 HBase Client client = new Client(cluster, true); UserGroupInformation.getLoginUser().doAs(new PrivilegedAction() { public Object run() { ); // Rest client code /* Sample code to list all the tables client.get("/") */ return null; 4. 可以参考如下的使用方式来了解如何调用不同的 Rest API 使用纯文本的方式获取命名空间 1. 以包含命名空间的路径作为参数, 使用 client 去调用 get 方法获取命名空间 响应将被 org.apache.hadoop.hbase.rest.client.response 类的对象捕获 例如 : Response response; String namespacepath = "/namespaces/" + "namespacename"; response = client.get(namespacepath); System.out.println(Bytes.toString(response.getBody())); 创建或修改命名空间 1. 在创建或修改命名空间时, 都是使用 NamespacesInstanceModel 创建模型并使用 buildtestmodel() 方法构建模型, 如下所示 这里创建模型, 使模型包含要创建的命名空间的属性信息 Map<String, String> NAMESPACE1_PROPS = new HashMap<String, String>(); NAMESPACE1_PROPS.put("key1", "value1"); NamespacesInstanceModel model = buildtestmodel(namespace1,namespace1_props); private static NamespacesInstanceModel buildtestmodel(string namespace, Map<String, String> properties) { NamespacesInstanceModel model = new NamespacesInstanceModel(); for (String key : properties.keyset()) { model.addproperty(key, properties.get(key)); return model; 说明 在使用 POST/PUT 请求创建 / 修改表时,TableSchemaModel 是用来创建模型的类 2. 检查以下步骤以了解如何使用不同的方式创建和修改命名空间 使用 xml 的方式创建命名空间 1. 在使用 NamespacesInstanceModel 创建模型后, 以包含命名空间的路径, 内容类型 ( 调用类为 'org.apache.hadoop.hbase.rest.constants', 这里调用的参数为 Constants.MIMETYPE_XML) 和内容 ( 在这里我们需要将内容转换为 xml, 使用下 文档版本 01 ( ) 54

65 3 HBase 应用开发 面显示的 toxml() 方法 ) 作为参数, 使用 client 去调用 post 方法创建命名空间 响应将被 'org.apache.hadoop.hbase.rest.client.response' 类的对象捕获 例如 : Response response; String namespacepath = "/namespaces/" + "namespacename"; response = client.post(namespacepath, Constants.MIMETYPE_XML, toxml(model)); private static byte[] toxml(namespacesinstancemodel model) throws JAXBException { StringWriter writer = new StringWriter(); context.createmarshaller().marshal(model, writer); return Bytes.toBytes(writer.toString()); 2. 在使用 xml 方式进行 Get 请求时, 可使用如下所示的 fromxml() 方法, 从响应中获取模型, 并从模型中获取创建的命名空间的名称 private static <T> T fromxml(byte[] content) throws JAXBException { return (T) context.createunmarshaller().unmarshal(new ByteArrayInputStream(content)); 使用 json 的方式修改命名空间 1. 在使用 NamespacesInstanceModel 创建模型后, 调用客户端类的 put 方法来创建命名空间, 参数包含命名空间的路径, 内容类型 ( 调用类为 org.apache.hadoop.hbase.rest.constants, 这里调用的参数为 Constants.MIMETYPE_JSON) 和内容 ( 在这里我们需要转换内容到 json, 使用 jsonmapper 作为参数 ) 响应被 'org.apache.hadoop.hbase.rest.client.response' 类的对象捕获 例如 : ObjectMapper jsonmapper = new JacksonProvider().locateMapper(NamespacesInstanceModel.class, MediaType.APPLICATION_JSON_TYPE); Response response; String namespacepath = "/namespaces/" + "namespacename"; String jsonstring = jsonmapper.writevalueasstring(model); response = client.put(namespacepath, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString)); 2. 在使用 json 方式进行 Get 请求时,jsonMapper 具有如下所示的 readvalue() 方法, 用于从响应中获取模型, 并能从模型中获取创建的命名空间 jsonmapper.readvalue(response.getbody(), NamespacesInstanceModel.class); /*Here second argument should be according to API, if its be TableSchemaModel.class*/ **related to table it should 使用 protobuf 的方式修改命名空间 1. 在使用 NamespacesInstanceModel 创建模型后, 调用客户端类的 put 方法来创建命名空间, 其参数包含命名空间路径, 内容类型 ( 调用类为 org.apache.hadoop.hbase.rest.constants', 这里调用的参数为 Constants.MIMETYPE_PROTOBUF) 和内容 ( 在这里我们需要转换的内容如下所示, 使用 createprotobufoutput 来创建 protobuf) 响应将被 'org.apache.hadoop.hbase.rest.client.response' 类的对象捕获 例如 : Response response; String namespacepath = "/namespaces/" + "namespacename"; response = client.put(namespacepath, Constants.MIMETYPE_PROTOBUF, model.createprotobufoutput()); model.getobjectfrommessage(response.getbody()); 2. 在使用 protobuf 的方式进行 Get 请求时, 可使用如下所示的 getobjectfrommessage 方法, 用于从响应中获取模型, 并从模型中获取创建的命名空间 model.getobjectfrommessage(response.getbody()); 3.6 FAQ 文档版本 01 ( ) 55

66 3 HBase 应用开发 运行 HBase 应用开发程序产生异常 提示信息包含 org.apache.hadoop.hbase.ipc.controller.serverrpccontrollerfactory 的解决办法 步骤 1 步骤 2 步骤 3 检查应用开发工程的配置文件 hbase-site.xml 中是否包含配置项 hbase.rpc.controllerfactory.class <name>hbase.rpc.controllerfactory.class</name> <value>org.apache.hadoop.hbase.ipc.controller.serverrpccontrollerfactory</value> 如果当前的应用开发工程配置项中包含该配置项, 则应用开发程序还需要引入 Jar 包 phoenix-core hbase-1.0.jar 此 Jar 包可以从 HBase 客户端安装目录下的 HBase/hbase/lib 获取 如果不想引入该 Jar 包, 请将应用开发工程的配置文件 hbase-site.xml 中的配置 hbase.rpc.controllerfactory.class 删除掉 ---- 结束 bulkload 和 put 应用场景 HBase 支持使用 bulkload 和 put 方式加载数据, 在大部分场景下 bulkload 提供了更快的数据加载速度, 但 bulkload 并不是没有缺点的, 在使用时需要关注 bulkload 和 put 适合在哪些场景使用 bulkload 是通过启动 MapReduce 任务直接生成 HFile 文件, 再将 HFile 文件注册到 HBase, 因此错误的使用 bulkload 会因为启动 MapReduce 任务而占用更多的集群内存和 CPU 资源, 也可能会生成大量很小的 HFile 文件频繁的触发 Compaction, 导致查询速度急剧下降 错误的使用 put, 会造成数据加载慢, 当分配给 RegionServer 内存不足时会造成 RegionServer 内存溢出从而导致进程退出 下面给出 bulkload 和 put 适合的场景 : bulkload 适合的场景 : 大量数据一次性加载到 HBase 对数据加载到 HBase 可靠性要求不高, 不需要生成 WAL 文件 使用 put 加载大量数据到 HBase 速度变慢, 且查询速度变慢时 加载到 HBase 新生成的单个 HFile 文件大小接近 HDFS block 大小 put 适合的场景 : 每次加载到单个 Region 的数据大小小于 HDFS block 大小的一半 数据需要实时加载 加载数据过程不会造成用户查询速度急剧下降 3.7 开发规范 文档版本 01 ( ) 56

67 3 HBase 应用开发 规则 Configuration 实例的创建 该类应该通过调用 HBaseConfiguration 的 Create() 方法来实例化 否则, 将无法正确加载 HBase 中的相关配置项 正确示例 : // 该部分, 应该是在类成员变量的声明区域声明 private Configuration hbaseconfig = null; // 最好在类的构造函数中, 或者初始化方法中实例化该类 hbaseconfig = HBaseConfiguration.create(); 错误示例 : hbaseconfig = new Configuration(); 共享 Configuration 实例 HTable 实例的创建 HBase 客户端代码通过创建一个与 Zookeeper 之间的 HConnection, 来获取与一个 HBase 集群进行交互的权限 一个 Zookeeper 的 HConnection 连接, 对应着一个 Configuration 实例, 已经创建的 HConnection 实例, 会被缓存起来 也就是说, 如果客户端需要与 HBase 集群进行交互的时候, 会传递一个 Configuration 实例过去,HBase Client 部分通过已缓存的 HConnection 实例, 来判断属于这个 Configuration 实例的 HConnection 实例是否存在, 如果不存在, 就会创建一个新的, 如果存在, 就会直接返回相应的实例 因此, 如果频繁创建 Configuration 实例, 会导致创建很多不必要的 HConnection 实例, 很容易达到 Zookeeper 的连接数上限 建议在整个客户端代码范围内, 都共用同一个 Configuration 对象实例 HTable 类有多种构造函数, 如 : 1. public HTable(final String tablename) 2. public HTable(final byte [] tablename) 3. public HTable(Configuration conf, final byte [] tablename) 4. public HTable(Configuration conf, final String tablename) 5. public HTable(final byte[] tablename, final HConnection connection, final ExecutorService pool) 建议采用第 5 种构造函数 之所以不建议使用前面的 4 种, 是因为 : 前两种方法实例化一个 HTable 时, 没有指定 Configuration 实例, 那么, 在实例化的时候, 就会自动创建一个 Configuration 实例 如果需要实例化过多的 HTable 实例, 那么, 就可能会出现很多不必要的 HConnection( 关于这一点, 前面部分已经有讲述 ) 因此, 而对于第 3 4 种构造方法, 每个实例都可能会创建一个新的线程池, 也可能会创建新的连接, 导致性能偏低 正确示例 : private HTable table = null; public inittable(configuration config, byte[] tablename) { // sharedconn 和 pool 都已经是事先实例化好的 建议在一个进程中共享相同的 connection 和 pool // 初始化 HConnection 的方法 : 文档版本 01 ( ) 57

68 3 HBase 应用开发 // HConnection sharedconn = // HConnectionManager.createConnection(this.config); table = new HTable(config, tablename, sharedconn, pool); 错误示例 : private HTable table = null; public inittable(string tablename) { table = new HTable(tableName); public inittable(byte[] tablename) { table = new HTable(tableName); 不允许多个线程在同一时间共用同一个 HTable 实例 HTable 实例缓存 HTable 是一个非线程安全类, 因此, 同一个 HTable 实例, 不应该被多个线程同时使用, 否则可能会带来并发问题 如果一个 HTable 实例可能会被长时间且被同一个线程固定且频繁的用到, 例如, 通过一个线程不断的往一个表内写入数据, 那么这个 HTable 在实例化后, 就需要缓存下来, 而不是每一次插入操作, 都要实例化一个 HTable 对象 ( 尽管提倡实例缓存, 但也不是在一个线程中一直沿用一个实例, 个别场景下依然需要重构, 可参见下一条规则 ) 正确示例 : 说明 注意该实例中提供的以 Map 形式缓存 HTable 实例的方法, 未必通用 这与多线程多 HTable 实例的设计方案有关 如果确定一个 HTable 实例仅仅可能会被用于一个线程, 而且该线程也仅有一个 HTable 实例的话, 就无须使用 Map 这里提供的思路仅供参考 // 该 Map 中以 TableName 为 Key 值, 缓存所有已经实例化的 HTable private Map<String, HTable> demotables = new HashMap<String, HTable>(); // 所有的 HTable 实例, 都将共享这个 Configuration 实例 private Configuration democonf = null; /** * < 初始化一个 HTable 类 > * < 功能详细描述 > tablename IOException [ 类 类 # 方法 类 # 成员 ] */ private HTable initnewtable(string tablename) throws IOException { return new HTable(demoConf, tablename); /** * < 获取 HTable 实例 > * < 功能详细描述 > [ 类 类 # 方法 类 # 成员 ] */ private HTable gettable(string tablename) { if (demotables.containskey(tablename)) { return demotables.get(tablename); else { HTable table = null; 文档版本 01 ( ) 58

69 3 HBase 应用开发 try { table = initnewtable(tablename); demotables.put(tablename, table); catch (IOException e) { // TODO Auto-generated catch block e.printstacktrace(); return table; /** * < 写数据 > * < 这里未涉及到多线程多 HTable 实例在设计模式上的优化. 这里只所以采用同步方法, * 是考虑到同一个 HTable 是非线程安全的. 通常, 我们建议一个 HTable 实例, 在同一 * 时间只能被用在一个写数据的线程中 > datalist tablename [ 类 类 # 方法 类 # 成员 ] */ public void putdata(list<put> datalist, String tablename) { HTable table = gettable(tablename); // 关于这里的同步 : 如果在采用的设计方案中, 不存在多线程共用同一个 HTable 实例 // 的可能的话, 就无须同步了 这里需要注意的一点, 就是 HTable 实例是非线程安全的 synchronized (table) { try { table.put(datalist); table.notifyall(); catch (IOException e) { // 在捕获到 IOE 时, 需要将缓存的实例重构 try { // 关闭之前的 Connection. table.close(); // 重新创建这个实例. table = new HTable(this.config, "jeason"); catch (IOException e1) { // TODO 错误示例 : public void putdataincorrect(list<put> datalist, String tablename) { HTable table = null; try { // 每次写数据, 都创建一个 HTable 实例 table = new HTable(demoConf, tablename); table.put(datalist); catch (IOException e1) { // TODO Auto-generated catch block e1.printstacktrace(); finally { table.close(); 文档版本 01 ( ) 59

70 3 HBase 应用开发 HTable 实例写数据的异常处理 尽管在前一条规则中提到了提倡 HTable 实例的重构, 但是, 并非提倡一个线程自始至终要沿用同一个 HTable 实例, 当捕获到 IOException 时, 依然需要重构 HTable 实例 示例代码可参考上一个规则的示例 另外, 勿轻易调用如下两个方法 : Configuration#clear: 这个方法, 会清理掉所有的已经加载的属性, 那么, 对于已经在使用这个 Configuration 的类或线程而言, 可能会带来潜在的问题 ( 例如, 假如 HTable 还在使用这个 Configuration, 那么, 调用这个方法后,HTable 中的这个 Configuration 的所有的参数, 都被清理掉了 ), 也就是说 : 只要还有对象或者线程在使用这个 Configuration, 我们就不应该调用这个 clear 方法, 除非, 所有的类或线程, 都已经确定不用这个 Configuration 了 那么, 这个操作, 可以在所有的线程要退出的时候来做, 而不是每一次 因此, 不要每次实例化一个 HTable 就调用此方法, 只有当所有线程都要结束时再调用 HConnectionManager#deleteAllConnections: 写入失败的数据要做相应的处理 这个可能会导致现有的正在使用的连接被从连接集合中清理掉, 同时, 因为在 HTable 中保存了原有连接的引用, 可能会导致这个连接无法关闭, 进而可能会造成泄漏 因此, 这个方法不建议使用 在写数据的过程中, 如果进程异常或一些其它的短暂的异常, 可能会导致一些写入操作失败 因此, 对于操作的数据, 需要将其记录下来 在集群恢复正常后, 重新将其写入到 HBase 数据表中 另外, 有一点需要注意 :HBase Client 返回写入失败的数据, 是不会自动重试的, 仅仅会告诉接口调用者哪些数据写入失败了 对于写入失败的数据, 一定要做一些安全的处理, 例如可以考虑将这些失败的数据, 暂时写在文件中, 或者, 直接缓存在内存中 正确示例 : private List<Row> errorlist = new ArrayList<Row>(); /** * < 采用 PutList 的模式插入数据 > * < 如果不是多线程调用该方法, 可不采用同步 > put 一条数据记录 IOException [ 类 类 # 方法 类 # 成员 ] */ public synchronized void putdata(put put) { // 暂时将数据缓存在该 List 中 datalist.add(put); // 当 datalist 的大小达到 PUT_LIST_SIZE 之后, 就执行一次 Put 操作 if (datalist.size() >= PUT_LIST_SIZE) { try { demotable.put(datalist); catch (IOException e) { // 如果是 RetriesExhaustedWithDetailsException 类型的异常, // 说明这些数据中有部分是写入失败的这通常都是因为 // HBase 集群的进程异常引起, 当然有时也会因为有大量 文档版本 01 ( ) 60

71 3 HBase 应用开发 // 的 Region 正在被转移, 导致尝试一定的次数后失败 if (e instanceof RetriesExhaustedWithDetailsException) { RetriesExhaustedWithDetailsException ree = (RetriesExhaustedWithDetailsException)e; int failures = ree.getnumexceptions(); for (int i = 0; i < failures; i++) { errorlist.add(ree.getrow(i)); datalist.clear(); 资源释放 Scan 时的容错处理 关于 ResultScanner 和 HTable 实例, 在用完之后, 需要调用它们的 Close 方法, 将资源释放掉 Close 方法, 要放在 finally 块中, 来确保一定会被调用到 正确示例 : ResultScanner scanner = null; try { scanner = demotable.getscanner(s); //Do Something here. finally { scanner.close(); 错误示例 : 1. 在代码中未调用 scanner.close() 方法释放相关资源 2. scanner.close() 方法未放置在 finally 块中 ResultScanner scanner = null; scanner = demotable.getscanner(s); //Do Something here. scanner.close(); Scan 时不排除会遇到异常, 例如, 租约过期 在遇到异常时, 建议 Scan 应该有重试的操作 事实上, 重试在各类异常的容错处理中, 都是一种优秀的实践, 这一点, 可以应用在各类与 HBase 操作相关的接口方法的容错处理过程中 不用 HBaseAdmin 时, 要及时关闭,HBaseAdmin 实例不应常驻内存 HBaseAdmin 的示例应尽量遵循 用时创建, 用完关闭 的原则 不应该长时间缓存同一个 HBaseAdmin 实例 暂时不建议使用 HTablePool 获取 HTable 实例 因为当前的 HTablePool 实现中可能会带来泄露 创建 HTable 实例的方法, 参考上面的不允许多个线程在同一时间共用同一个 HTable 实例 文档版本 01 ( ) 61

72 3 HBase 应用开发 多线程安全登录方式 如果有多线程进行 login 的操作, 当应用程序第一次登录成功后, 所有线程再次登录时应该使用 relogin 的方式 login 的代码样例 : private Boolean login(configuration conf){ boolean flag = false; UserGroupInformation.setConfiguration(conf); try { UserGroupInformation.loginUserFromKeytab(conf.get(PRINCIPAL), conf.get(keytab)); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; relogin 的代码样例 : public Boolean relogin(){ boolean flag = false; try { UserGroupInformation.getLoginUser().reloginFromKeytab(); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; 建议 不要调用 HBaseAdmin 的 closeregion 方法关闭一个 Region 采用 PutList 模式写数据 HBaseAdmin 中, 提供了关闭一个 Region 的接口 : //hostandport 可以指定, 也可以不指定 public void closeregion(final String regionname, final String hostandport) 通过该方法关闭一个 Region,HBase Client 端会直接发 RPC 请求到 Region 所在的 RegionServer 上, 整个流程对 Master 而言, 是不感知的 也就是说, 尽管 RegionServer 关闭了这个 Region, 但是, 在 Master 侧, 还以为该 Region 是在该 RegionServer 上面打开的 假如, 在执行 Balance 的时候,Master 计算出恰好要转移这个 Region, 那么, 这个 Region 将无法被关闭, 本次转移操作将无法完成 ( 关于这个问题, 在当前的 HBase 版本中的处理的确还欠缺妥当 ) 因此, 暂时不建议使用该方法关闭一个 Region HTable 类中提供了两种写数据的接口 : 文档版本 01 ( ) 62

73 3 HBase 应用开发 1. public void put(final Put put) throws IOException 2. public void put(final List<Put> puts) throws IOException 第 1 种方法较之第 2 种方法, 在性能上有明显的弱势 因此, 写数据时应该采用第 2 种方法 Scan 时指定 StartKey 和 EndKey 一个有确切范围的 Scan, 在性能上会带来较大的好处 代码示例 : Scan scan = new Scan(); scan.addcolumn(bytes.tobytes("familyname"),bytes.tobytes("columnname")); scan.setstartrow( Bytes.toBytes("rowA")); // 假设起始 Key 为 rowa scan.setstoprow( Bytes.toBytes("rowB")); // 假设 EndKey 为 rowb for(result result : demotable.getscanner(scan)) { // process Result instance 不要关闭 WAL WAL 是 Write-Ahead-Log 的简称, 是指数据在入库之前, 首先会写入到日志文件中, 借此来确保数据的安全性 WAL 功能默认是开启的, 但是, 在 Put 类中提供了关闭 WAL 功能的接口 : public void setwritetowal(boolean write) 因此, 不建议调用该方法将 WAL 关闭 ( 即将 writetowal 设置为 False), 因为可能会造成最近 1S( 该值由 RegionServer 端的配置参数 hbase.regionserver.optionallogflushinterval 决定, 默认为 1S) 内的数据丢失 但如果在实际应用中, 对写入的速率要求很高, 并且可以容忍丢失最近 1S 内的数据的话, 可以将该功能关闭 创建一张表或 Scan 时设定 blockcache 为 true 示例 HBase 客户端建表和 scan 时, 设置 blockcache=true 需要根据具体的应用需求来设定它的值, 这取决于有些数据是否会被反复的查询到, 如果存在较多的重复记录, 将这个值设置为 true 可以提升效率, 否则, 建议关闭 建议按默认配置, 默认就是 true, 只要不强制设置成 false 就可以, 例如 : HColumnDescriptor fieldadesc = new HColumnDescriptor("value".getBytes()); fieldadesc.setblockcacheenabled(false); Configuration 可以设置的参数 为了能够建立一个 HBase Client 端到 HBase Server 端的连接, 需要设置如下几个参数 hbase.zookeeper.quorum: ZooKeeper 的 IP 多个 ZooKeeper 节点的话, 中间用, 隔开 hbase.zookeeper.property.clientport: Zookeeper 的端口 文档版本 01 ( ) 63

74 3 HBase 应用开发 说明 通过 HBaseConfiguration.create() 创建的 Configuration 实例, 会自动加载如下配置文件中的配置项 : 1. core-default.xml 2. core-site.xml 3. hbase-default.xml 4. hbase-site.xml 因此, 这四个配置文件, 应该要放置在 Source Folder 下面 ( 将一个文件夹设置为 Source Folder 的方法 : 如果在工程下面建立了一个 resource 的文件夹, 那么, 可以在该文件夹上右键鼠标, 依次选择 Build Path -> Use as Source Folder 即可, 可参考下图 ) 下面是客户端可配置的一些参数集合 说明 在通常情况下, 这些值都不建议修改 参数名 hbase.client.pause hbase.client.retries.number hbase.client.retries.longer.m ultiplier hbase.client.rpc.maxattempt s hbase.regionserver.lease.peri od hbase.client.write.buffer hbase.client.scanner.caching hbase.client.keyvalue.maxsi ze 参数解释 每次异常或者其它情况下重试等待相关的时间参数 ( 实际等待时间将根据该值与已重试次数计算得出 ) 异常或者其它情况下的重试次数 与重试次数有关 RPC 请求不可达时的重试次数 与 Scanner 超时时间有关 ( 单位 ms) 在启用 AutoFlush 的情况下, 该值不起作用 如果未启用 AotoFlush 的话,HBase Client 端会首先缓存写入的数据, 达到设定的大小后才向 HBase 集群下发一次写入操作 Scan 时一次 next 请求获取的行数 一条 keyvalue 数据的最大值 文档版本 01 ( ) 64

75 3 HBase 应用开发 参数名 hbase.htable.threads.max hbase.client.prefetch.limit 参数解释 HTable 实例中与数据操作有关的最大线程数 客户端在写数据或者读取数据时, 需要首先获取对应的 Region 所在的地址 客户端可以预缓存一些 Region 地址, 这个参数就是与缓存的数目有关的配置 正确设置参数的方法 : hbaseconfig = HBaseConfiguration.create(); // 如下参数, 如果在配置文件中已经存在, 则无须再配置 hbaseconfig.set("hbase.zookeeper.quorum", " , , "); hbaseconfig.set("hbase.zookeeper.property.clientport", "2181"); HTablePool 在多线程写入操作中的应用 1. 有多个写数据线程时, 可以采用 HTablePool 现在先简单介绍下该类的使用方法和注意点 : 2. 多个写数据的线程之间, 应共享同一个 HTablePool 实例 实例化 HTablePool 的时候, 应要指定最大的 HTableInterface 实例个数 maxsize, 即需要通过如下构造函数实例化该类 : public HTablePool(final Configuration config, final int maxsize) 关于 maxsize 的值, 可以根据写数据的线程数 Threads 以及涉及到的用户表个数 Tables 来定, 理论上, 不应该超过 (Threads*Tables) 3. 客户端线程通过 HTablePool#getTable(tableName) 的方法, 获取一个表名为 tablename 的 HTableInterface 实例 4. 同一个 HTableInterface 实例, 在同一个时刻只能给一个线程使用 5. 如果 HTableInterface 使用完了, 需要调用 HTablePool#putTable(HTableInterface table) 方法将它放回去 示例代码 : /** * 写数据失败后需要一定的重试次数, 每一次重试的等待时间, 需要根据已经重试的次数而定. */ private static final int[] RETRIES_WAITTIME = {1, 1, 1, 2, 2, 4, 4, 8, 16, 32; /** * 限定的重试次数 */ private static final int RETRIES = 10; /** * 失败后等待的基本时间单位 */ private static final int PAUSE_UNIT = 1000; private static Configuration hadoopconfig; private static HTablePool tablepool; private static String[] tables; /** * < 初始化 HTablePool> * < 功能详细描述 > config [ 类 类 # 方法 类 # 成员 ] */ 文档版本 01 ( ) 65

76 3 HBase 应用开发 public static void inittablepool() { DemoConfig config = DemoConfig.getInstance(); if (hadoopconfig == null) { hadoopconfig = HBaseConfiguration.create(); hadoopconfig.set("hbase.zookeeper.quorum", config.getzookeepers()); hadoopconfig.set("hbase.zookeeper.property.clientport", config.getzookeeperport()); if (tablepool == null) { tablepool = new HTablePool(hadoopConfig, config.gettablepoolmaxsize()); tables = config.gettables().split(","); public void run() { // 初始化 HTablePool. 因为这是多线程间共享的一个实例, 仅被实例化一次. inittablepool(); for (;;) { Map<String, Object> data = DataStorage.takeList(); String tablename = tables[(integer)data.get("table")]; List<Put> list = (List)data.get("list"); // 以 Row 为 Key, 保存 List 中所有的 Put. 该集合仅仅使用于写入失败时查找失败的数据记录. // 因为从 Server 端仅仅返回了失败的数据记录的 Row 值. Map<byte[], Put> rowputmap = null; // 如果失败了 ( 哪怕是部分数据失败 ), 需要重试. 每一次重试, 都仅仅提交失败的数据条目 INNER_LOOP : for (int retry = 0; retry < RETRIES; retry++) { // 从 HTablePool 中获取一个 HTableInterface 实例. 用完后需要放回去. HTableInterface table = tablepool.gettable(tablename); try { table.put(list); // 如果执行到这里, 说明成功了. break INNER_LOOP; catch (IOException e) { // 如果是 RetriesExhaustedWithDetailsException 类型的异常, // 说明这些数据中有部分是写入失败的这通常都是因为 HBase 集群 // 的进程异常引起, 当然有时也会因为有大量的 Region 正在被转移, // 导致尝试一定的次数后失败. // 如果非 RetriesExhaustedWithDetailsException 异常, 则需要将 // list 中的所有数据都要重新插入. if (e instanceof RetriesExhaustedWithDetailsException) { RetriesExhaustedWithDetailsException ree = (RetriesExhaustedWithDetailsException)e; int failures = ree.getnumexceptions(); System.out.println(" 本次插入失败了 [" + failures + "] 条数据."); // 第一次失败且重试时, 实例化该 Map. if (rowputmap == null) { rowputmap = new HashMap<byte[], Put>(failures); for (int m = 0; m < list.size(); m++) { Put put = list.get(m); rowputmap.put(put.getrow(), put); // 先 Clear 掉原数据, 然后将失败的数据添加进来 list.clear(); for (int m = 0; m < failures; m++) { list.add(rowputmap.get(ree.getrow(m))); 文档版本 01 ( ) 66

77 3 HBase 应用开发 finally { // 用完之后, 再将该实例放回去 tablepool.puttable(table); // 如果异常了, 就暂时等待一段时间. 该等待应该在将 HTableInterface 实例放回去之后 try { sleep(getwaittime(retry)); catch (InterruptedException e1) { System.out.println("Interruped"); Put 实例的创建 HBase 是一个面向列的数据库, 一行数据, 可能对应多个列族, 而一个列族又可以对应多个列 通常, 写入数据的时候, 我们需要指定要写入的列 ( 含列族名称和列名称 ): 如果要往 HBase 表中写入一行数据, 需要首先构建一个 Put 实例 Put 中包含了数据的 Key 值和相应的 Value 值,Value 值可以有多个 ( 即可以有多列值 ) 有一点需要注意 : 在往 Put 实例中 add 一条 KeyValue 数据时, 传入的 family,qualifier, value 都是字节数组 在将一个字符串转换为字节数组时, 需要使用 Bytes.toBytes 方法, 不要使用 String.toBytes 方法, 因为后者无法保证编码, 尤其是在 Key 或 Value 中出现中文字符的时候, 就会出现问题 代码示例 : // 列族的名称为 privateinfo private final static byte[] FAMILY_PRIVATE = Bytes.toBytes("privateInfo"); // 列族 privateinfo 中总共有两个列 "name"&"address" private final static byte[] COLUMN_NAME = Bytes.toBytes("name"); private final static byte[] COLUMN_ADDR = Bytes.toBytes("address"); /** * < 创建一个 Put 实例 > * < 在该方法中, 将会创建一个具有 1 个列族,2 列数据的 Put> rowkey Key 值 name 人名 address 地址 [ 类 类 # 方法 类 # 成员 ] */ public Put createput(string rowkey, String name, String address) { Put put = new Put(Bytes.toBytes(rowKey)); put.add(family_private, COLUMN_NAME, Bytes.toBytes(name)); 文档版本 01 ( ) 67

78 3 HBase 应用开发 put.add(family_private, COLUMN_ADDR, Bytes.toBytes(address)); return put; HBaseAdmin 实例的创建以及常用方法 代码示例 : private Configuration democonf = null; private HBaseAdmin hbaseadmin = null; /** * < 构造函数 > * 需要将已经实例化好的 Configuration 实例传递进来 */ public HBaseAdminDemo(Configuration conf) { this.democonf = conf; try { // 实例化 HBaseAdmin hbaseadmin = new HBaseAdmin(this.demoConf); catch (MasterNotRunningException e) { e.printstacktrace(); catch (ZooKeeperConnectionException e) { e.printstacktrace(); /** * < 一些方法使用示例 > * < 更多的方法, 请参考 HBase 接口文档 > IOException ZooKeeperConnectionException MasterNotRunningException [ 类 类 # 方法 类 # 成员 ] */ public void demo() throws MasterNotRunningException, ZooKeeperConnectionException, IOException { byte[] regionname = Bytes.toBytes("mrtest,jjj, fc41d70b84e9f6e91f9f01affdb06703."); byte[] encodename = Bytes.toBytes("fc41d70b84e9f6e91f9f01affdb06703"); // 重新分配一个 Reigon. hbaseadmin.unassign(regionname, false); // 主动触发 Balance. hbaseadmin.balancer(); // 移动一个 Region, 第 2 个参数, 是 RegionServer 的 HostName+StartCode, 例如 : // host187.example.com,60020, 如果将该参数设置为 null, 则会随机移动该 Region hbaseadmin.move(encodename, null); // 判断一个表是否存在 hbaseadmin.tableexists("tablename"); // 判断一个表是否被激活 hbaseadmin.istableenabled("tablename"); /** * < 快速创建一个表的方法 > * < 首先创建一个 HTableDescriptor 实例, 它里面包含了即将要创建的 HTable 的描述信息, 同时, 需要创建相应的列族 列族关联的实例是 HColumnDescriptor 在本示例中, 创建的列族名称为 columnname > tablename 表名 [ 类 类 # 方法 类 # 成员 ] */ public boolean createtable(string tablename) { try { if (hbaseadmin.tableexists(tablename)) { return false; 文档版本 01 ( ) 68

79 3 HBase 应用开发 HTableDescriptor tabledesc = new HTableDescriptor(tableName); HColumnDescriptor fieldadesc = new HColumnDescriptor("columnName".getBytes()); fieldadesc.setblocksize(640 * 1024); tabledesc.addfamily(fieldadesc); hbaseadmin.createtable(tabledesc); catch (Exception e) { e.printstacktrace(); return false; return true; 附录 Scan 时的两个关键参数 Batch 和 Caching Batch: 使用 scan 调用 next 接口每次最大返回的记录数, 与一次读取的列数有关 Caching: 一个 RPC 查询请求最大的返回的 next 数目, 与一次 RPC 获取的行数有关 首先举几个例子, 来介绍这两个参数在 Scan 时所起到的作用 : 假设表 A 的一个 Region 中存在 2 行 (rowkey) 数据, 每行有 1000column, 且每列当前只有一个 version, 即每行就会有 1000 个 key value - Colu A1 Colu A2 Colu A3 Colu A4 Colu N1 Colu N2 Colu N3 Colu N4 Row1 Row2 例 1: 查询参数 : 不设 batch, 设定 caching=2 那么, 一次 RPC 请求, 就会返回 2000 个 KeyValue 例 2: 查询参数 : 设定 batch=500, 设定 caching=2 那么, 一次 RPC 请求, 只能返回 1000 个 KeyValue 例 3: 查询参数 : 设定 batch=300, 设定 caching=4 那么, 一次 RPC 请求, 也只能返回 1000 个 KeyValue 关于 Batch 和 Caching 的进一步解释 : 一次 Caching, 是一次请求数据的机会 同一行数据是否可以通过一次 Caching 读完, 取决于 Batch 的设置, 如果 Batch 的值小于一行的总列数, 那么, 这一行至少需要 2 次 Caching 才可以读完 ( 后面的一次 Caching 的机会, 会继续前面读取到的位置继续读取 ) 一次 Caching 读取, 不能跨行 如果某一行已经读完, 并且 Batch 的值还没有达到设定的大小, 也不会继续读下一行了 那么, 关于例 1 与例 2 的结果, 就很好解释了 : 例 1 的解释 : 不设定 Batch 的时候, 默认会读完改行所有的列 那么, 在 caching 为 2 的时候, 一次 RPC 请求就会返回 2000 个 KeyValue 例 2 的解释 : 文档版本 01 ( ) 69

80 3 HBase 应用开发 设定 Batch 为 500,caching 为 2 的情况下, 也就是说, 每一次 Caching, 最多读取 500 列数据 那么, 第一次 Caching, 读取到 500 列, 剩余的 500 列, 会在第 2 次 Caching 中读取到 因此, 两次 Caching 会返回 1000 个 KeyValue 例 3 的解释 : 设定 Batch 为 300,caching 为 4 的情况下, 读取完 1000 条数据, 正好需要 4 次 caching 因此, 只能返回 1000 条数据 代码示例 : Scan s = new Scan(); // 设置查询的起始 key 和结束 key s.setstartrow(bytes.tobytes(" ")); s.setstoprow(bytes.tobytes(" ")); s.setbatch(1000); s.setcaching(100); ResultScanner scanner = null; try { scanner = tb.getscanner(s); for (Result rr = scanner.next(); rr!= null; rr = scanner.next()) { for (KeyValue kv : rr.raw()) { // 显示查询的结果 System.out.println("key:" + Bytes.toString(kv.getRow()) + "getqualifier:" + Bytes.toString(kv.getQualifier()) + "value" + Bytes.toString(kv.getValue())); catch (IOException e) { System.out.println("error!" + e.tostring()); finally { scanner.close(); 文档版本 01 ( ) 70

81 4 Hive 应用开发 4 Hive 应用开发 4.1 概述 应用开发简介 Hive 简介 常用概念 Hive 是一个开源的, 建立在 Hadoop 上的数据仓库框架, 提供类似 SQL 的 HiveQL 语言操作结构化数据, 其基本原理是将 HiveQL 语言自动转换成 MapReduce 任务或 Spark 任务, 从而完成对 Hadoop 集群中存储的海量数据进行查询和分析 Hive 主要特点如下 : 通过 HiveQL 语言非常容易的完成数据提取 转换和加载 (ETL) 通过 HiveQL 完成海量结构化数据分析 灵活的数据存储格式, 支持 JSON,CSV,TEXTFILE,RCFILE,ORCFILE, SEQUENCEFILE 等存储格式, 并支持自定义扩展 多种客户端连接方式, 支持 JDBC 接口 Hive 的主要应用于海量数据的离线分析 ( 如日志分析, 集群状态分析 ) 大规模的数据挖掘 ( 用户行为分析, 兴趣分区, 区域展示 ) 等场景下 客户端 客户端直接面向用户, 可通过 Java API Thrift API 访问服务端进行 Hive 的相关操作 本文中的 Hive 客户端特指 Hive client 的安装目录, 里面包含通过 Java API 访问 Hive 的样例代码 HiveQL 语言 Hive Query Language, 类 SQL 语句 HCatalog HCatalog 是建立在 Hive 元数据之上的一个表信息管理层, 吸收了 Hive 的 DDL 命令 为 MapReduce 提供读写接口, 提供 Hive 命令行接口来进行数据定义和元数据 文档版本 01 ( ) 71

82 4 Hive 应用开发 开发流程 查询 基于 Hive 的 HCatalog 功能,Hive MapReduce 开发人员能够共享元数据信息, 避免中间转换和调整, 能够提升数据处理的效率 WebHCat WebHCat 运行用户通过 Rest API 来执行 Hive DDL, 提交 MapReduce 任务, 查询 MapReduce 任务执行结果等操作 开发流程中各阶段的说明如图 4-1 和表 4-1 所示 图 4-1 Hive 应用程序开发流程 表 4-1 Hive 应用开发的流程说明 阶段说明参考文档 了解基本概念 准备开发和运行环境 在开始开发应用前, 需要了解 Hive 的基本概念 Hive 的应用程序支持使用 Java Python 两种语言进行开发 推荐使用 Eclipse 工具, 请根据指导完成不同语言的开发环境配置 常用概念 环境准备 文档版本 01 ( ) 72

83 4 Hive 应用开发 阶段说明参考文档 根据场景开发工程 运行程序及查看结果 提供了 Java Python 两种不同语言的样例工程, 还提供了从建表 数据加载到数据查询的样例工程 指导用户将开发好的程序编译提交运行并查看结果 开发程序 调测程序 4.2 环境准备 开发环境简介 在进行应用开发时, 要准备的本地开发环境如表 4-2 所示 同时需要准备运行调测的 Linux 环境, 用于验证应用程序运行正常 表 4-2 开发环境 准备项 操作系统 说明 开发环境 :Windows 系统, 推荐 Windows7 以上版本 运行环境 :Linux 系统 文档版本 01 ( ) 73

84 4 Hive 应用开发 准备项安装 JDK 安装和配置 Eclipse 网络 说明 开发和运行环境的基本配置 版本要求如下 : MRS 集群的服务端和客户端仅支持自带的 Oracle JDK( 版本为 1.8), 不允许替换 对于客户应用需引用 SDK 类的 Jar 包运行在客户应用进程中的, 支持 Oracle JDK 和 IBM JDK Oracle JDK: 支持 1.7 和 1.8 版本 IBM JDK: 推荐 和 版本 说明 : 在 HCatalog 的开发环境中, 基于安全考虑,MRS 服务端只支持 TLS 1.1 和 TLS 1.2 加密协议,IBM JDK 默认 TLS 只支持 1.0, 若使用 IBM JDK, 请配置启动参数 com.ibm.jsse2.overridedefaulttls 为 true, 设置后可以同时支持 TLS1.0/1.1/1.2 详情请参见 : support/knowledgecenter/zh/ SSYKE2_8.0.0/ com.ibm.java.security.component.80.doc/ security-component/jsse2docs/ matchsslcontext_tls.html#matchsslcontex t_tls 用于开发 Hive 应用程序的工具 版本要求如下 : JDK 使用 1.7 版本,Eclipse 使用 及以上版本 JDK 使用 1.8 版本,Eclipse 使用 及以上版本 说明 : 若使用 IBM JDK, 请确保 Eclipse 中的 JDK 配置为 IBM JDK 若使用 Oracle JDK, 请确保 Eclipse 中的 JDK 配置为 Oracle JDK 不同的 Eclipse 不要使用相同的 workspace 和相同路径下的示例工程 确保客户端与 Hive 服务主机在网络上互通 文档版本 01 ( ) 74

85 4 Hive 应用开发 准备环境 选择 Window 开发环境下, 安装 Eclipse, 安装 JDK JDK 使用 1.8 版本,Eclipse 使用 及以上版本 说明 若使用 IBM JDK, 请确保 Eclipse 中的 JDK 配置为 IBM JDK 若使用 Oracle JDK, 请确保 Eclipse 中的 JDK 配置为 Oracle JDK 若使用 ODBC 进行二次开发, 请确保 JDK 版本为 1.8 以上 不同的 Eclipse 不要使用相同的 workspace 和相同路径下的示例工程 准备一个应用程序运行测试的 Linux 环境 准备运行调测环境 步骤 1 步骤 2 步骤 3 步骤 4 步骤 5 在弹性云服务器管理控制台, 申请一个新的弹性云服务器, 用于应用开发运行调测 弹性云服务器的安全组需要和 MRS 集群 Master 节点的安全组相同 弹性云服务器的 VPC 需要与 MRS 集群在同一个 VPC 中 弹性云服务器的网卡需要与 MRS 集群在同一个网段中 申请弹性 IP, 绑定新申请的弹性云主机 IP, 并配置安全组出入规则 登陆客户端下载目标节点, 下载客户端程序 1. 登录 Manager 系统 2. 选择 服务管理 > 下载客户端 > 完整客户端, 下载客户端程序到本地机器 以 root 用户安装集群客户端 1. 执行以下命令解压客户端包 : tar -xvf /opt/mrs_services_client.tar 2. 执行以下命令校验安装文件包 : sha256sum -c /opt/mrs_services_clientconfig.tar.sha256 MRS_Services_ClientConfig.tar:OK 3. 执行以下命令解压安装文件包 : tar -xvf MRS_Services_ClientConfig.tar 4. 执行如下命令安装客户端到指定目录 ( 绝对路径 ), 例如 /opt/client 目录会自动创建 cd /opt/mrs_services_clientconfig sh install.sh /opt/client Components client installation is complete. 在主管理节点准备客户端, 并在 Master1 节点, 将集群配置文件从主管理节点复制到运行调测环境 步骤 6 修改 /etc/hosts 在文件中新添加弹性云服务器的主机名与弹性云服务器私有 IP 地址对应关系 在文件中新添加 OBS 域名与 IP 地址的对应关系 用户在中国华北区申请 MRS 集群, 需添加以下记录 : obs.huawei.com 文档版本 01 ( ) 75

86 4 Hive 应用开发 步骤 7 执行以下命令, 更新客户端配置 : sh /opt/client/refreshconfig.sh 客户端安装目录客户端配置文件压缩包完整路径 例如, 执行命令 sh /opt/client/refreshconfig.sh /opt/client /opt/mrs_services_client.tar 说明 ---- 结束 准备开发用户 前提条件 操作步骤 如果修改了组件的配置参数, 需重新下载客户端配置文件并更新运行调测环境上的客户端 开发用户用于运行样例工程 用户需要有 Hive 权限, 才能运行 Hive 样例工程 MRS 服务集群开启了 Kerberos 认证, 没有开启 Kerberos 认证的集群忽略该步骤 步骤 1 登录 MRS Manager, 在 MRS Manager 界面选择 系统设置 > 角色管理 > 添加角色, 如图 1 添加角色所示 图 4-2 添加角色 1. 填写角色的名称, 例如 hiverole 2. 编辑角色 在 权限 的表格中选择 Hive> Hive Read Write Privileges, 勾选 Select Delete Insert 和 Create, 单击 确定 保存, 如图 4-3 所示 图 4-3 授权 Hive 权限 文档版本 01 ( ) 76

87 4 Hive 应用开发 在 权限 的表格中选择 Yarn > Scheduler Queue > root, 勾选 default 的 Submit 和 Admin, 单击 确定 保存, 如图 4-4 所示 图 4-4 授权 Yarn 权限 步骤 2 步骤 3 步骤 4 单击 系统设置 > 用户组管理 > 添加用户组, 为样例工程创建一个用户组, 例如 hivegroup 单击 系统设置 > 用户管理 > 添加用户, 为样例工程创建一个用户 填写用户名, 例如 hiveuser, 用户类型为 机机 用户, 加入用户组 hivegroup 和 supergroup, 设置其 主组 为 supergroup, 并绑定角色 hiverole 取得权限, 单击 确定, 如图 4-5 所示 图 4-5 添加用户 文档版本 01 ( ) 77

88 4 Hive 应用开发 步骤 5 在 MRS Manager 界面选择 系统设置 > 用户管理, 在用户名中选择 hiveuser, 下载认证凭据文件, 如图 4-6 所示 保存后解压得到用户的 user.keytab 文件与 krb5.conf 文件 用于在样例工程中进行安全认证 图 4-6 下载认证凭据 参考信息 ---- 结束 如果修改了组件的配置参数, 需重新下载客户端配置文件并更新运行调测环境上的客户端 准备 JDBC 客户端开发环境 操作步骤 为了运行 Hive 组件的 JDBC 接口样例代码, 需要完成下面的操作 说明 以在 Windows 环境下开发 JDBC 方式连接 Hive 服务的应用程序为例 步骤 1 步骤 2 步骤 3 步骤 4 在样例工程获取地址获取 Hive 示例工程 在 Hive 示例工程根目录, 执行 mvn install 进行编译 在 Hive 示例工程根目录, 执行 mvn eclipse:eclipse 创建 Eclipse 工程 在应用开发环境中, 导入样例工程到 Eclipse 开发环境 1. 选择 File > Import > General > Existing Projects into Workspace > Next >Browse 显示 浏览文件夹 对话框 2. 选择文件夹 hive-examples, 如图 4-7 所示 Windows 下要求该文件夹的完整路径不包含空格 文档版本 01 ( ) 78

89 4 Hive 应用开发 图 4-7 导入样例工程到 Eclipse 中 单击 Finish 导入成功后,com.huawei.bigdata.hive.example 包下的 JDBCExample 类, 为 JDBC 接口样例代码 步骤 5 设置 Eclipse 的文本文件编码格式, 解决乱码显示问题 1. 在 Eclipse 的菜单栏中, 选择 Window > Preferences 弹出 Preferences 窗口 2. 在左边导航上选择 General > Workspace, 在 Text file encoding 区域, 选中 Other, 并设置参数值为 UTF-8, 单击 Apply 后, 单击 OK, 如图 4-8 所示 文档版本 01 ( ) 79

90 4 Hive 应用开发 图 4-8 设置 Eclipse 的编码格式 步骤 6 修改样例 ( 非安全集群可跳过此步骤 ) 在步骤 5 获取新建开发用户的 krb5.conf 和 user.keytab 文件后, 修改 ExampleMain.java 中的 username 为对应的新建用户, 例如 hiveuser /** * Other way to set conf for zk. If use this way, * can ignore the way in the 'login' method */ if (issecuritymode) { username = "hiveuser"; userkeytabfile = CONF_DIR + "user.keytab"; krb5file = CONF_DIR + "krb5.conf"; conf.set(hadoop_security_authentication, "kerberos"); conf.set(hadoop_security_authorization, "true"); ---- 结束 准备 HCatalog 开发环境 为了运行 Hive 组件的 HCatalog 接口样例代码, 需要完成下面的操作 说明 以在 Windows 环境下开发 HCatalog 方式连接 Hive 服务的应用程序为例 操作步骤 步骤 1 步骤 2 步骤 3 在样例工程获取地址获取 Hive 示例工程 在 Hive 示例工程根目录, 执行 mvn install 进行编译 在 Hive 示例工程根目录, 执行 mvn eclipse:eclipse 创建 Eclipse 工程 文档版本 01 ( ) 80

91 4 Hive 应用开发 步骤 4 在应用开发环境中, 导入样例工程到 Eclipse 开发环境 1. 选择 File > Import > General > Existing Projects into Workspace > Next >Browse 显示 浏览文件夹 对话框 2. 下载工程后选择文件夹 hive-examples, 如图 4-9 所示 Windows 下要求该文件夹的完整路径不包含空格 图 4-9 导入样例工程到 Eclipse 中 单击 Finish 导入成功后,com.huawei.bigdata.hive.example 包下的 HCatalogExample 类, 为 HCatalog 接口样例代码 步骤 5 设置 Eclipse 的文本文件编码格式, 解决乱码显示问题 1. 在 Eclipse 的菜单栏中, 选择 Window > Preferences 弹出 Preferences 窗口 2. 在左边导航上选择 General > Workspace, 在 Text file encoding 区域, 选中 Other, 并设置参数值为 UTF-8, 单击 Apply 后, 单击 OK, 如图 4-10 所示 文档版本 01 ( ) 81

92 4 Hive 应用开发 图 4-10 设置 Eclipse 的编码格式 ---- 结束 4.3 开发程序 典型场景说明 场景说明开发思路 假定用户开发一个 Hive 数据分析应用, 用于管理企业雇员信息, 如表 4-3 表 4-4 所示 步骤 1 数据准备 1. 创建三张表, 雇员信息表 employees_info 雇员联络信息表 employees_contact 雇员信息扩展表 employees_info_extended 雇员信息表 employees_info 的字段为雇员编号 姓名 支付薪水币种 薪水金额 缴税税种 工作地 入职时间, 其中支付薪水币种 R 代表人民币, D 代表美元 雇员联络信息表 employees_contact 的字段为雇员编号 电话号码 e- mail 雇员信息扩展表 employees_info_extended 的字段为雇员编号 姓名 电话号码 支付薪水币种 薪水金额 缴税税种 工作地, 分区字段为入职时间 创建表代码实现请见创建表 文档版本 01 ( ) 82

93 4 Hive 应用开发 2. 加载雇员信息数据到雇员信息表 employees_info 中 加载数据代码实现请见数据加载 雇员信息数据如表 4-3 所示 : 表 4-3 雇员信息数据 编号 姓名 支付薪水 币种 薪水金额缴税税种工作地入职时间 1 Wang R personal income tax& Tom D personal income tax& Jack D personal income tax& Linda D personal income tax& Zhang R personal income tax&0.05 China:Sh enzhen America: NewYork America: Manhatta n America: NewYork China:Sh anghai 加载雇员联络信息数据到雇员联络信息表 employees_contact 中 雇员联络信息数据如表 4-4 所示 : 表 4-4 雇员联络信息数据编号 电话号码 XXXX XXXX xxxx@xx.com XXXX XXXX xxxxx@xx.com.cn XXXX XXXX xxxx@xx.org XXXX XXXX xxxx@xxx.cn XXXX XXXX xxxx@xxxx.cn 步骤 2 数据分析 数据分析代码实现, 请见数据查询 查看薪水支付币种为美元的雇员联系方式 查询入职时间为 2014 年的雇员编号 姓名等字段, 并将查询结果加载进表 employees_info_extended 中的入职时间为 2014 的分区中 文档版本 01 ( ) 83

94 4 Hive 应用开发 统计表 employees_info 中有多少条记录 查询使用以 cn 结尾的邮箱的员工信息 步骤 3 提交数据分析任务, 统计表 employees_info 中有多少条记录 实现请见样例程序指导 ---- 结束 样例代码说明 创建表 功能介绍 本小节介绍了如何使用 HQL 创建内部表 外部表的基本操作 创建表主要有以下三种方式 : 自定义表结构, 以关键字 EXTERNAL 区分创建内部表和外部表 内部表, 如果对数据的处理都由 Hive 完成, 则应该使用内部表 在删除内部表时, 元数据和数据一起被删除 外部表, 如果数据要被多种工具 ( 如 Pig 等 ) 共同处理, 则应该使用外部表, 可避免对该数据的误操作 删除外部表时, 只删除掉元数据 根据已有表创建新表, 使用 CREATE LIKE 句式, 完全复制原有的表结构, 包括表的存储格式 根据查询结果创建新表, 使用 CREATE AS SELECT 句式 这种方式比较灵活, 可以在复制原表表结构的同时指定要复制哪些字段, 不包括表的存储格式 样例代码 -- 创建外部表 employees_info. CREATE EXTERNAL TABLE IF NOT EXISTS employees_info ( id INT, name STRING, usd_flag STRING, salary DOUBLE, deductions MAP<STRING, DOUBLE>, address STRING, entrytime STRING ) -- 指定行中各字段分隔符. -- "delimited fields terminated by" 指定列与列之间的分隔符为 ',',"MAP KEYS TERMINATED BY" 指定 MAP 中键值的分隔符为 '&'. ROW FORMAT delimited fields terminated by ',' MAP KEYS TERMINATED BY '&' -- 指定表的存储格式为 TEXTFILE. STORED AS TEXTFILE; -- 使用 CREATE Like 创建表. CREATE TABLE employees_like LIKE employees_info; -- 使用 DESCRIBE 查看 employees_info employees_like employees_as_select 表结构. DESCRIBE employees_info; DESCRIBE employees_like; 扩展应用 创建分区表 文档版本 01 ( ) 84

95 4 Hive 应用开发 数据加载 功能介绍 一个表可以拥有一个或者多个分区, 每个分区以文件夹的形式单独存在表文件夹的目录下 对分区内数据进行查询, 可缩小查询范围, 加快数据的检索速度和可对数据按照一定的条件进行管理 分区是在创建表的时候用 PARTITIONED BY 子句定义的 CREATE EXTERNAL TABLE IF NOT EXISTS employees_info_extended ( id INT, name STRING, usd_flag STRING, salary DOUBLE, deductions MAP<STRING, DOUBLE>, address STRING ) -- 使用关键字 PARTITIONED BY 指定分区列名及数据类型. PARTITIONED BY (entrytime STRING) STORED AS TEXTFILE; 更新表的结构 一个表在创建完成后, 还可以使用 ALTER TABLE 执行增 删字段, 修改表属性, 添加分区等操作 -- 为表 employees_info_extended 增加 tel_phone 字段. ALTER TABLE employees_info_extended ADD COLUMNS (tel_phone STRING, STRING); 建表时配置 Hive 数据加密 指定表的格式为 RCFile( 推荐使用 ) 或 SequenceFile, 加密算法为 ARC4Codec SequenceFile 是 Hadoop 特有的文件格式,RCFile 是 Hive 优化的文件格式 RCFile 优化了列存储, 在对大表进行查询时, 综合性能表现比 SequenceFile 更优 set hive.exec.compress.output=true; set hive.exec.compress.intermediate=true; set hive.intermediate.compression.codec=org.apache.hadoop.io.encryption.arc4.arc4codec; create table seq_codec (key string, value string) stored as RCFile; HIVE 使用 OBS 存储 需要在 beeline 里面设置指定的参数,AK/SK 可登录 OBS 控制台, 进入 我的凭证 页面获取 set fs.s3a.access.key=ak; set fs.s3a.secret.key=sk; set metaconf:fs.s3a.access.key=ak; set metaconf:fs.s3a.secret.key=sk; 新建表的存储类型为 obs create table obs(c1 string, c2 string) stored as orc location 's3a://obs-lmm/hive/orctest' tblproperties('orc.compress'='snappy'); 本小节介绍了如何使用 HQL 向已有的表 employees_info 中加载数据 从本节中可以掌握如何从集群中加载数据 样例代码 -- 从本地文件系统 /opt/hive_examples_data/ 目录下将 employee_info.txt 加载进 employees_info 表中. LOAD DATA LOCAL INPATH '/opt/hive_examples_data/employee_info.txt' OVERWRITE INTO TABLE employees_info; -- 从 HDFS 上 /user/hive_examples_data/employee_info.txt 加载进 employees_info 表中. LOAD DATA INPATH '/user/hive_examples_data/employee_info.txt' OVERWRITE INTO TABLE employees_info; 文档版本 01 ( ) 85

96 4 Hive 应用开发 说明 加载数据的实质是将数据拷贝到 HDFS 上指定表的目录下 LOAD DATA LOCAL INPATH 命令可以完成从本地文件系统加载文件到 Hive 的需求, 但是当指定 LOCAL 时, 这里的路径指的是当前连接的 HiveServer 的本地文件系统的路径, 同时由于当前的 HiveServer 是集群式部署的, 客户端在连接时是随机连接所有 HiveServer 中的一个, 需要注意当前连接的 HiveServer 的本地文件系统中是否存在需要加载的文件 查看当前连接的 HiveServer, 可通过在客户端环境中连接 hive 客户端 ( 使用 beeline 命令 ), 然后查看 url 中的 ip 即可, 如下 即为当前连接的 HiveServer [INFO] Unable to bind key for unsupported operation: down-history 0: jdbc:hive2:// :21066/> 数据查询 功能介绍 本小节介绍了如何使用 HQL 对数据进行查询分析 从本节中可以掌握如下查询分析方法 : SELECT 查询的常用特性, 如 JOIN 等 加载数据进指定分区 如何使用 Hive 自带函数 如何使用自定义函数进行查询分析, 如何创建 定义自定义函数请见用户自定义函数 样例代码 -- 查看薪水支付币种为美元的雇员联系方式. SELECT a.name, b.tel_phone, b. FROM employees_info a JOIN employees_contact b ON(a.id = b.id) WHERE usd_flag='d'; -- 查询入职时间为 2014 年的雇员编号 姓名等字段, 并将查询结果加载进表 employees_info_extended 中的入职时间为 2014 的分区中. INSERT OVERWRITE TABLE employees_info_extended PARTITION (entrytime = '2014') SELECT a.id, a.name, a.usd_flag, a.salary, a.deductions, a.address, b.tel_phone, b. FROM employees_info a JOIN employees_contact b ON (a.id = b.id) WHERE a.entrytime = '2014'; -- 使用 Hive 中已有的函数 COUNT(), 统计表 employees_info 中有多少条记录. SELECT COUNT(*) FROM employees_info; -- 查询使用以 cn 结尾的邮箱的员工信息. SELECT a.name, b.tel_phone FROM employees_info a JOIN employees_contact b ON (a.id = b.id) WHERE b. like '%cn'; 扩展使用 配置 Hive 中间过程的数据加密 指定表的格式为 RCFile( 推荐使用 ) 或 SequenceFile, 加密算法为 ARC4Codec SequenceFile 是 Hadoop 特有的文件格式,RCFile 是 Hive 优化的文件格式 RCFile 优化了列存储, 在对大表进行查询时, 综合性能表现比 SequenceFile 更优 文档版本 01 ( ) 86

97 4 Hive 应用开发 set hive.exec.compress.output=true; set hive.exec.compress.intermediate=true; set hive.intermediate.compression.codec=org.apache.hadoop.io.encryption.arc4.arc4codec; 自定义函数, 具体内容请参见用户自定义函数 用户自定义函数 功能介绍 样例代码 当 Hive 的内置函数不能满足需要时, 可以通过编写用户自定义函数 UDF(User-Defined Functions) 插入自己的处理代码并在查询中使用它们 按实现方式,UDF 分如下分类 : 普通的 UDF, 用于操作单个数据行, 且产生一个数据行作为输出 用户定义聚集函数 UDAF(User-Defined Aggregating Functions), 用于接受多个输入数据行, 并产生一个输出数据行 用户定义表生成函数 UDTF(User-Defined Table-Generating Functions), 用于操作单个输入行, 产生多个输出行 按使用方法,UDF 有如下分类 : 临时函数, 只能在当前会话使用, 重启会话后需要重新创建 永久函数, 可以在多个会话中使用, 不需要每次创建 下面以编写一个 AddDoublesUDF 为例, 说明 UDF 的编写和使用方法 : AddDoublesUDF 主要用来对两个及多个浮点数进行相加 在该样例中可以掌握如何编写和使用 UDF 说明 一个普通 UDF 必须继承自 org.apache.hadoop.hive.ql.exec.udf 一个普通 UDF 必须至少实现一个 evaluate() 方法,evaluate 函数支持重载 以下为 UDF 示例代码 : package com.huawei.bigdata.hive.example.udf; import org.apache.hadoop.hive.ql.exec.udf; public class AddDoublesUDF extends UDF { public Double evaluate(double... a) { Double total = 0.0; // 处理逻辑部分. for (int i = 0; i < a.length; i++) if (a[i]!= null) total += a[i]; return total; 如何使用 步骤 1 把以上程序打包成 AddDoublesUDF.jar, 并上传到 HDFS 指定目录下 ( 如 /user/ hive_examples_jars/ ) 且创建函数的用户与使用函数的用户有该文件的可读权限 示例语句 : 文档版本 01 ( ) 87

98 4 Hive 应用开发 hdfs dfs -put./hive_examples_jars /user/hive_examples_jars hdfs dfs -chmod 777 /user/hive_examples_jars 步骤 2 执行如下命令 beeline -n Hive 业务用户 步骤 3 在 Hive Server 中定义该函数, 以下语句用于创建永久函数 : CREATE FUNCTION adddoubles AS 'com.huawei.bigdata.hive.example.udf.adddoublesudf' using jar 'hdfs :/user/ hive_examples_jars/adddoublesudf.jar'; 其中 adddoubles 是该函数的别名, 用于 SELECT 查询中使用 以下语句用于创建临时函数 : CREATE TEMPORARY FUNCTION adddoubles AS 'com.huawei.bigdata.hive.example.udf.adddoublesudf' using jar 'hdfs :/user/ hive_examples_jars/adddoublesudf.jar'; adddoubles 是该函数的别名, 用于 SELECT 查询中使用 关键字 TEMPORARY 说明该函数只在当前这个 Hive Server 的会话过程中定义使用 步骤 4 在 Hive Server 中使用该函数, 执行 SQL 语句 : SELECT adddoubles(1,2,3); 说明 若重新连接客户端再使用函数出现 [Error 10011] 的错误, 可执行 reload function; 命令后再使用该函数 步骤 5 在 Hive Server 中删除该函数, 执行 SQL 语句 : DROP FUNCTION adddoubles; ---- 结束 样例程序指导 功能介绍 本小节介绍了如何使用样例程序完成分析任务 样例程序主要有以下方式 : 使用 JDBC 接口提交数据分析任务 使用 HCatalog 接口提交数据分析任务 样例代码 使用 Hive JDBC 接口提交数据分析任务, 参考样例程序中的 JDBCExample.java a. 修改以下变量为 false, 标识连接集群的认证模式为普通模式 // 所连接集群的认证模式是否在安全模式 boolean issecurever = false; b. 设置 ZooKeeper 的 IP 列表和端口, 之间以, 分隔 本例中 ZooKeeper 部署在三个节点上,xxx.xxx.xxx.xxx 指代 3 个节点 IP, 使用的端口默认为 2181 文档版本 01 ( ) 88

99 4 Hive 应用开发 // 其中,zkQuorum 的 "xxx.xxx.xxx.xxx" 为集群中 ZooKeeper 所在节点的 IP, 端口默认是 2181 zkquorum = "xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2181"; 说明 MRS 及以下版本,ZooKeeper 默认端口号为 24002, 详见 MRS 用户指南, 组件端口信息相关章节 c. 定义 HiveQL HiveQL 必须为单条语句, 注意 HiveQL 不能包含 ; // 定义 HQL, 不能包含 ; String[] sqls = {"CREATE TABLE IF NOT EXISTS employees_info(id INT,name STRING)", "SELECT COUNT(*) FROM employees_info", "DROP TABLE employees_info"; d. 拼接 JDBC URL // 拼接 JDBC URL StringBuilder sbuilder = new StringBuilder( "jdbc:hive2://").append(clientinfo.getzkquorum()).append("/"); if (issecuritymode) { // 安全模式 // ZooKeeper 登录认证 sbuilder.append(";servicediscoverymode=").append(clientinfo.getservicediscoverymode()).append(";zookeepernamespace=").append(clientinfo.getzookeepernamespace()).append(";sasl.qop=").append(clientinfo.getsaslqop()).append(";auth=").append(clientinfo.getauth()).append(";principal=").append(clientinfo.getprincipal()).append(";"); else { // 普通模式 sbuilder.append(";servicediscoverymode=").append(clientinfo.getservicediscoverymode()).append(";zookeepernamespace=").append(clientinfo.getzookeepernamespace()).append(";auth=none"); String url = sbuilder.tostring(); 以上是通过 zookeeper 的方式访问 hive 若直连 HiveServer 的方式访问 hive, 需按如下方式拼接 JDBC URL // 拼接 JDBC URL StringBuilder sbuilder = new StringBuilder( "jdbc:hive2://").append(clientinfo.getzkquorum()).append("/"); if (issecuritymode) { // 安全模式 // ZooKeeper 登录认证 sbuilder.append(";sasl.qop=").append(clientinfo.getsaslqop()).append(";auth=").append(clientinfo.getauth()).append(";principal=").append(clientinfo.getprincipal()).append(";"); else { // 普通模式 sbuilder.append(";auth=none"); String url = sbuilder.tostring(); 注 : 直连 hiveserver 时, 若当前连接的 HiveServer 故障则会导致访问 hive 失败 ; 若使用 zookeeper 的访问 hive, 只要有任一个 HiveServer 实例可正常提供服务即可 因此使用 JDBC 时建议通过 zookeeper 的方式访问 hive e. 加载 Hive JDBC 驱动 // 加载 Hive JDBC 驱动 Class.forName(HIVE_DRIVER); 文档版本 01 ( ) 89

100 4 Hive 应用开发 f. 填写正确的用户名, 获取 JDBC 连接, 确认 HQL 的类型 (DDL/DML), 调用对应的接口执行 HiveQL, 输出查询的列名和结果到控制台, 关闭 JDBC 连接 Connection connection = null; try { // 获取 JDBC 连接 // 第二个参数需要填写正确的用户名, 否则会以匿名用户 (anonymous) 登录 connection = DriverManager.getConnection(url, "username", ""); // 建表 // 表建完之后, 如果要往表中导数据, 可以使用 LOAD 语句将数据导入表中, 比如从 HDFS 上将数据导入表 : //load data inpath '/tmp/employees.txt' overwrite into table employees_info; execddl(connection,sqls[0]); System.out.println("Create table success!"); // 查询 execdml(connection,sqls[1]); // 删表 execddl(connection,sqls[2]); System.out.println("Delete table success!"); finally { // 关闭 JDBC 连接 if (null!= connection) { connection.close(); public static void execddl(connection connection, String sql) throws SQLException { PreparedStatement statement = null; try { statement = connection.preparestatement(sql); statement.execute(); finally { if (null!= statement) { statement.close(); public static void execdml(connection connection, String sql) throws SQLException { PreparedStatement statement = null; ResultSet resultset = null; ResultSetMetaData resultmetadata = null; try { // 执行 HQL statement = connection.preparestatement(sql); resultset = statement.executequery(); // 输出查询的列名到控制台 resultmetadata = resultset.getmetadata(); int columncount = resultmetadata.getcolumncount(); for (int i = 1; i <= columncount; i++) { System.out.print(resultMetaData.getColumnLabel(i) + '\t'); System.out.println(); // 输出查询结果到控制台 while (resultset.next()) { for (int i = 1; i <= columncount; i++) { System.out.print(resultSet.getString(i) + '\t'); System.out.println(); 文档版本 01 ( ) 90

101 4 Hive 应用开发 finally { if (null!= resultset) { resultset.close(); if (null!= statement) { statement.close(); 使用 HCatalog 接口提交数据分析任务, 参考样例程序中的 HCatalogExample.java a. 编写 Map 类, 从 Hive 的表中获取数据 public static class Map extends Mapper<LongWritable, HCatRecord, IntWritable, IntWritable> { int protected void map( LongWritable key, HCatRecord value, Context context) throws IOException, InterruptedException { age = (Integer) value.get(0); context.write(new IntWritable(age), new IntWritable(1)); b. 编写 Reduce 类, 对从 hive 表中读取到的数据进行统计 public static class Reduce extends Reducer<IntWritable, IntWritable, IntWritable, HCatRecord> protected void reduce( IntWritable key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; Iterator<IntWritable> iter = values.iterator(); while (iter.hasnext()) { sum++; iter.next(); HCatRecord record = new DefaultHCatRecord(2); record.set(0, key.get()); record.set(1, sum); context.write(null, record); c. 在 run() 方法中配置 job 后, 执行 main() 方法, 提交任务 public int run(string[] args) throws Exception { HiveConf.setLoadMetastoreConfig(true); Configuration conf = getconf(); String[] otherargs = args; String inputtablename = otherargs[0]; String outputtablename = otherargs[1]; String dbname = Job job = new Job(conf, "GroupByDemo"); HCatInputFormat.setInput(job, dbname, inputtablename); job.setinputformatclass(hcatinputformat.class); job.setjarbyclass(hcatalogexample.class); job.setmapperclass(map.class); job.setreducerclass(reduce.class); 文档版本 01 ( ) 91

102 4 Hive 应用开发 job.setmapoutputkeyclass(intwritable.class); job.setmapoutputvalueclass(intwritable.class); job.setoutputkeyclass(writablecomparable.class); job.setoutputvalueclass(defaulthcatrecord.class); null); OutputJobInfo outputjobinfo = OutputJobInfo.create(dbName,outputTableName, HCatOutputFormat.setOutput(job, outputjobinfo); HCatSchema schema = outputjobinfo.getoutputschema(); HCatOutputFormat.setSchema(job, schema); job.setoutputformatclass(hcatoutputformat.class); return (job.waitforcompletion(true)? 0 : 1); public static void main(string[] args) throws Exception { int exitcode = ToolRunner.run(new HCatalogExample(), args); System.exit(exitCode); 4.4 调测程序 JDBC 客户端运行及结果查看 步骤 1 步骤 2 执行 mvn package 打出 jar 包, 在工程目录 target 目录下获取, 比如 :hiveexamples-1.0.jar 在运行调测环境上创建一个目录作为运行目录, 如或 /opt/hive_examples (Linux 环境 ), 并在该目录下创建子目录 conf 将步骤 1 导出的 hive-examples-1.0.jar 拷贝到 /opt/hive_examples 下 将客户端下的配置文件拷贝到 conf 下, 安全集群下把从步骤 5 获取的 user.keytab 和 krb5.conf 拷贝到的 /opt/hive_examples/conf 下, 非安全集群可不必拷贝 user.keytab 和 krb5.conf 文件 cd /opt/hive_examples/conf cp /opt/client/hive/config/hiveclient.properties. 步骤 3 在 Linux 环境下执行 : chmod +x /opt/hive_examples -R cd /opt/hive_examples java -cp.:hive-examples-1.0.jar:/opt/hive_examples/conf:/opt/client/hive/beeline/lib/*:/opt/ client/hdfs/hadoop/lib/* com.huawei.bigdata.hive.example.examplemain 步骤 4 在命令行终端查看样例代码中的 HiveQL 所查询出的结果 Linux 环境运行成功结果会有如下信息 : Create table success! _c0 0 Delete table success! ---- 结束 HCatalog 运行及结果查看 步骤 1 步骤 2 执行 mvn package 打出 jar 包, 在工程目录 target 目录下获取, 比如 :hiveexamples-1.0.jar 将上一步生成的 hive-examples-1.0.jar 上传至运行调测环境的指定路径, 例如 /opt/ hive_examples, 记作 $HCAT_CLIENT, 并确保已经安装好客户端 文档版本 01 ( ) 92

103 4 Hive 应用开发 export HCAT_CLIENT=/opt/hive_examples/ 步骤 3 执行以下命令用于配置环境变量信息 ( 以客户端安装路径为 /opt/client 为例 ): export HADOOP_HOME=/opt/client/HDFS/hadoop export HIVE_HOME=/opt/client/Hive/Beeline export HCAT_HOME=$HIVE_HOME/../HCatalog export LIB_JARS=$HCAT_HOME/lib/hive-hcatalog-core jar,$HCAT_HOME/lib/hivemetastore jar,$HIVE_HOME/lib/hive-exec jar,$HCAT_HOME/lib/libfb jar, $HCAT_HOME/lib/slf4j-api jar,$HCAT_HOME/lib/antlr jar,$HCAT_HOME/lib/jdo-api jar, $HCAT_HOME/lib/antlr-runtime-3.4.jar,$HCAT_HOME/lib/datanucleus-api-jdo jar,$HCAT_HOME/lib/ datanucleus-core jar,$hcat_home/lib/datanucleus-rdbms jar export HADOOP_CLASSPATH=$HCAT_HOME/lib/hive-hcatalog-core jar:$HCAT_HOME/lib/hivemetastore jar:$HIVE_HOME/lib/hive-exec jar:$HCAT_HOME/lib/libfb jar: $HADOOP_HOME/etc/hadoop:$HCAT_HOME/conf:$HCAT_HOME/lib/slf4j-api jar:$HCAT_HOME/lib/ antlr jar:$hcat_home/lib/jdo-api jar:$hcat_home/lib/antlr-runtime-3.4.jar: $HCAT_HOME/lib/datanucleus-api-jdo jar:$HCAT_HOME/lib/datanucleus-core jar: $HCAT_HOME/lib/datanucleus-rdbms jar 注 : 导入上述环境变量前需确认当前引入的 jar 包是否存在 步骤 4 运行前准备 : 1. 使用 Hive 客户端, 在 beeline 中创建源表 t1: create table t1(col1 int); 向 t1 中插入如下数据 : t1.col 创建目的表 t2:create table t2(col1 int,col2 int); 步骤 5 使用 YARN 客户端提交任务 yarn --config $HADOOP_HOME/etc/hadoop jar $HCAT_CLIENT/hiveexamples-1.0.jar com.huawei.bigdata.hive.example.hcatalogexample -libjars $LIB_JARS t1 t2 步骤 6 运行结果查看, 运行后 t2 表数据如下所示 : 0: jdbc:hive2:// :24002, > select * from t2; t2.col1 t2.col 结束 4.5 Hive 接口 JDBC Hive JDBC 接口遵循标准的 JAVA JDBC 驱动标准, 详情请参见 JDK1.7 API 文档版本 01 ( ) 93

104 4 Hive 应用开发 说明 Hive 作为数据仓库类型数据库, 其并不能支持所有的 JDBC 标准 API 例如事务类型的操作 : rollback setautocommit 等, 执行该类操作会产生 Method not supported 的 SQLException 异常 HiveQL HiveQL 支持 Hive 版本中的所有特性, 详情请参见 confluence/display/hive/languagemanual MRS 提供的扩展 Hive 语句如表 4-5 所示 表 4-5 扩展 Hive 语句 扩展语法语法说明语法示例示例说明 CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_na me (col_name data_type [COMMENT col_comment],...) [ROW FORMAT row_format] [STORED AS file_format] STORED BY 'storage.handler.clas s.name' [WITH SERDEPROPERTI ES (...) ]... [TBLPROPERTIES ("groupid"=" group1 ","locatorid"="locat or1")]...; 创建一个 Hive 表, 并指定表数据文件分布的 locator 信息 CREATE TABLE tab1 (id INT, name STRING) row format delimited fields terminated by '\t' stored as RCFILE TBLPROPERTIES( "groupid"=" group1 ","locatorid"="locat or1"); 创建表 tab1, 并制定 tab1 的表数据分布在 locator1 节点上 文档版本 01 ( ) 94

105 4 Hive 应用开发 扩展语法语法说明语法示例示例说明 CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_na me (col_name data_type [COMMENT col_comment],...) [ [ROW FORMAT row_format] [STORED AS file_format] STORED BY 'storage.handler.clas s.name' [WITH SERDEPROPERTI ES (...) ]... [TBLPROPERTIES ('column.encode.co lumns'='col_name1, col_name2' 'column.encode.ind ices'='col_id1,col_id 2', 'column.encode.cla ssname'='encode_cl assname')]...; 创建一个 Hive 表, 并指定表的加密列和加密算法 create table encode_test(id INT, name STRING, phone STRING, address STRING) ROW FORMAT SERDE 'org.apache.hadoop. hive.serde2.lazy.laz ysimpleserde' WITH SERDEPROPERTI ES ('column.encode.indi ces'='2,3', 'column.encode.clas sname'='org.apache. hadoop.hive.serde2. SMS4Rewriter') STORED AS TEXTFILE; 创建表 encode_test, 并制定插入数据时对第 2 3 列加密, 加密算法类为 org.apache.hadoop.h ive.serde2.sms4re writer REMOVE TABLE hbase_tablename [WHERE where_condition]; 删除 Hive on HBase 表中符合条件的数据 remove table hbase_table1 where id = 1; 删除表中符合条件 id =1 的数据 文档版本 01 ( ) 95

106 4 Hive 应用开发 扩展语法语法说明语法示例示例说明 CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_na me (col_name data_type [COMMENT col_comment],...) [ROW FORMAT row_format] STORED AS inputformat 'org.apache.hadoop.hive.contrib.filefor mat.specifieddelim iterinputformat' outputformat 'org.apache.hadoop. hive.ql.io.hiveignor ekeytextoutputfor mat'; 创建 Hive 表, 并设定表可以指定自定义行分隔符 create table blu(time string, num string, msg string) row format delimited fields terminated by ',' stored as inputformat 'org.apache.hadoop.hive.contrib.filefor mat.specifieddelim iterinputformat' outputformat 'org.apache.hadoop. hive.ql.io.hiveignor ekeytextoutputfor mat'; 创建表 blu, 指定 inputformat 为 SpecifiedDelimiterI nputformat, 以便查询时可以指定表的查询行分隔符 WebHCat 说明 以下示例的 IP 为 WebHCat 的业务 IP, 端口为安装时设置的 WebHCat HTTP 端口 除 :version status version version/hive version/hadoop 以外, 其他 API 都需要添加 user.name 参数 1. :version(get) 描述 URL 参数 查询 WebHCat 支持的返回类型列表 参数 :version 描述 WebHCat 版本号 ( 当前必须是 v1) 返回结果 文档版本 01 ( ) 96

107 4 Hive 应用开发 参数 responsetypes 描述 WebHCat 支持的返回类型列表 例子 curl -i -u : --negotiate ' 说明 2. status (GET) 描述 URL 参数 MRS 及以下版本 WebHcat 默认端口号为 21055, 详见 MRS Manager 的 Zookeeper 配置 获取当前服务器的状态 无 返回结果 参数 描述 status WebChat 连接正常, 返回 OK version 字符串, 包含版本号, 比如 v1 例子 curl -i -u : --negotiate ' 3. version (GET) 描述 URL 参数 获取服务器 WebHCat 的版本 无 返回结果 参数 supportedversions version 描述 所有支持的版本 当前服务器 WebHCat 的版本 例子 curl -i -u : --negotiate ' 4. version/hive (GET) 描述 获取服务器 Hive 的版本 文档版本 01 ( ) 97

108 4 Hive 应用开发 URL 参数无 返回结果 参数 module version 描述 hive Hive 的版本 例子 curl -i -u : --negotiate ' 5. version/hadoop (GET) 描述 URL 参数 获取服务器 Hadoop 的版本 无 返回结果 参数 module version 描述 hadoop Hadoop 的版本 例子 curl -i -u : --negotiate ' 6. ddl (POST) 描述 URL 参数 执行 DDL 语句 参数 exec group permissions 描述 需要执行的 HCatalog DDL 语句 当 DDL 是创建表时, 创建表使用的用户组 当 DDL 是创建表时, 创建表使用的权限, 格式为 rwxr-xr-x 文档版本 01 ( ) 98

109 4 Hive 应用开发 返回结果 参数 stdout stderr exitcode 描述 HCatalog 执行时的标准输出值, 可能为空 HCatalog 执行时的错误输出, 可能为空 HCatalog 的返回值 例子 curl -i -u : --negotiate -d exec="show tables" ' ddl?user.name=user1' 7. ddl/database (GET) 描述 URL 参数 列出所有的数据库 参数 like 描述 用来匹配数据库名的正则表达式 返回结果 参数 databases 描述 数据库名 例子 curl -i -u : --negotiate ' user.name=user1' 8. ddl/database/:db (GET) 描述 URL 参数 获取指定数据库的详细信息 参数 :db 描述 数据库名 返回结果 文档版本 01 ( ) 99

110 4 Hive 应用开发 参数 location comment database owner ownertype 描述 数据库位置 数据库的备注, 如果没有备注则其参数值不存在 数据库名 数据库的所有者 数据库所有者的类型 例子 curl -i -u : --negotiate ' user.name=user1' 9. ddl/database/:db (PUT) 描述 URL 参数 创建数据库 参数 :db group permission location comment properties 描述数据库名创建数据库时使用的用户组创建数据库时使用的权限数据库的位置数据库的备注, 比如描述数据库属性 返回结果 参数 database 描述 新创建的数据库的名字 例子 curl -i -u : --negotiate -X PUT -HContent-type:application/json -d '{"location": "/tmp/ a", "comment": "my db", "properties": {"a": "b"' ' templeton/v1/ddl/database/db2?user.name=user1' 10. ddl/database/:db (DELETE) 描述 URL 删除数据库 文档版本 01 ( ) 100

111 4 Hive 应用开发 参数 参数 :db ifexists 描述 数据库名 如果指定数据库不存在,Hive 会返回错误, 除非设置了 ifexists 为 true option 将参数设置成 cascade 或者 restrict 如果选择 cascade, 将清除一切, 包括数据和定义 如果选择 restrict, 表格内容为空, 模式也将不存在 返回结果 参数 database 描述 删除的数据库名字 例子 curl -i -u : --negotiate -X DELETE ' db3?ifexists=true&user.name=user1' 11. ddl/database/:db/table (GET) 描述 URL 参数 列出数据库下的所有表 参数 :db like 描述 数据库名 用来匹配表名的正则表达式 返回结果 参数 database tables 描述 数据库名字 数据库下表名列表 例子 curl -i -u : --negotiate ' table?user.name=user1' 12. ddl/database/:db/table/:table (GET) 描述 获取表的详细信息 文档版本 01 ( ) 101

112 4 Hive 应用开发 URL format=extended 参数 参数 :db :table format 描述 数据库名 表名 设置 "format=extended" 可查看表的更多信息 ( 其作用相当于 HQL: show table extended like tablename ) 返回结果 参数 columns database table partitioned location outputformat inputformat owner partitioncolumns 描述 列名和类型 数据库名 表名 是否分区表, 只有 extended 下才会显示 表的位置, 只有 extended 下才会显示 输出形式, 只有 extended 下才会显示 输入形式, 只有 extended 下才会显示 表的属主, 只有 extended 下才会显示 分区的列, 只有 extended 下才会显示 例子 curl -i -u : --negotiate ' table/t1?format=extended&user.name=user1' 13. ddl/database/:db/table/:table (PUT) 描述 创建表 URL 文档版本 01 ( ) 102

113 4 Hive 应用开发 参数 参数 :db :table group permissions external ifnotexists comment columns partitionedby clusteredby format location 描述 数据库名 新建表名 创建表时使用的用户组 创建表时使用的权限 指定位置,hive 不使用表的默认位置 设置为 true, 当表存在时不会报错 备注 列描述, 包括列名, 类型和可选备注 分区列描述, 用于划分表格 参数 columns 列出了列名, 类型和可选备注 分桶列描述, 参数包括 columnnames sortedby 和 numberofbuckets 参数 columnnames 包括 columnname 和排列顺序 (ASC 为升序,DESC 为降序 ) 存储格式, 参数包括 rowformat, storedas, 和 storedby HDFS 路径 tableproperties 表属性和属性值 (name-value 对 ) 返回结果 参数 database table 描述 数据库名 表名 例子 curl -i -u : --negotiate -X PUT -HContent-type:application/json -d '{"columns": [{"name": "id", "type": "int", {"name": "name","type": "string"], "comment": "hello","format": {"storedas": "orc" ' ' database/db3/table/tbl1?user.name=user1' 14. ddl/database/:db/table/:table (POST) 描述 重命名表 文档版本 01 ( ) 103

114 4 Hive 应用开发 URL 参数 参数 :db :table rename 描述数据库名已有表名新表表名 返回结果 参数 database table 描述 数据库名 新表表名 例子 curl -i -u : --negotiate -d rename=table1 ' database/default/table/tbl1?user.name=user1' 15. ddl/database/:db/table/:table (DELETE) 描述 删除表 URL 参数 参数 :db :table ifexists 描述数据库名表名当设置为 true 时, 不报错 返回结果 参数 database table 描述 数据库名 表名 例子 curl -i -u : --negotiate -X DELETE ' default/table/table2?ifexists=true&user.name=user1' 16. ddl/database/:db/table/:existingtable/like/:newtable (PUT) 描述 文档版本 01 ( ) 104

115 4 Hive 应用开发 创建一张和已经存在的表一样的表 URL like/:newtable 参数 参数 :db :existingtable :newtable group permissions external 描述 数据库名 已有表名 新表名 创建表时使用的用户组 创建表时使用的权限 指定位置,hive 不使用表的默认位置 ifnotexists 当设置为 true 时, 如果表已经存在, Hive 不报错 location HDFS 路径 返回结果 参数 database table 描述 数据库名 表名 例子 curl -i -u : --negotiate -X PUT -HContent-type:application/json -d '{"ifnotexists": "true"' ' user.name=user1' 17. ddl/database/:db/table/:table/partition(get) 描述 URL 参数 列出表的分区信息 参数 :db :table 描述 数据库名 表名 返回结果 文档版本 01 ( ) 105

116 4 Hive 应用开发 参数 database table partitions 描述数据库名表名分区属性值和分区名 例子 curl -i -u : --negotiate table/x1/partition?user.name=user1 18. ddl/database/:db/table/:table/partition/:partition(get) 描述 URL 列出表的某个具体分区的信息 partition/:partition 参数 参数 :db :table :partition 描述 数据库名 表名 分区名, 解码 http 引用时, 需当心 比如 country=%27algeria%27 返回结果 参数 database table partition partitioned location outputformat columns owner partitioncolumns inputformat totalnumberfiles totalfilesize 描述数据库名表名分区名如果设置为 true, 为分区表表的存储路径输出格式列名, 类型, 备注所有者分区的列输入格式分区下文件个数分区下文件总大小 文档版本 01 ( ) 106

117 4 Hive 应用开发 参数 maxfilesize minfilesize lastaccesstime lastupdatetime 描述最大文件大小最小文件大小最后访问时间最后更新时间 例子 curl -i -u : --negotiate table/x1/partition/dt=1?user.name=user1 19. ddl/database/:db/table/:table/partition/:partition(put) 描述 URL 增加一个表分区 partition/:partition 参数 参数 :db :table group permissions location 描述数据库名 表名 创建新分区时使用的用户组 创建新分区时用户的权限 新分区的存放位置 ifnotexists 如果设置为 true, 当分区已经存在, 系统报错 返回结果 参数 database table partitions 描述数据库名表名分区名 例子 curl -i -u : --negotiate -X PUT -HContent-type:application/json -d '{' :50111/templeton/v1/ddl/database/default/table/x1/partition/dt=10? user.name=user1 20. ddl/database/:db/table/:table/partition/:partition(delete) 描述 删除一个表分区 文档版本 01 ( ) 107

118 4 Hive 应用开发 URL partition/:partition 参数 参数 :db :table group permissions ifexists 描述 数据库名 表名 删除新分区时使用的用户组 删除新分区时用户的权限, 格式为 rwxrw-r-x 如果指定分区不存在,Hive 报错 参数值设置为 true 除外 返回结果 参数 database table partitions 描述数据库名表名分区名 例子 curl -i -u : --negotiate -X DELETE -HContent-type:application/json -d '{' :50111/templeton/v1/ddl/database/default/table/x1/partition/dt=10? user.name=user1 21. ddl/database/:db/table/:table/column(get) 描述 URL 参数 获取表的 column 列表 参数 :db :table 描述 数据库名 表名 返回结果 参数 database table 描述 数据库名 表名 文档版本 01 ( ) 108

119 4 Hive 应用开发 参数 columns 描述 列名字和类型 例子 curl -i -u : --negotiate table/t1/column?user.name=user1 22. ddl/database/:db/table/:table/column/:column(get) 描述 URL 获取表的某个具体的 column 的信息 column/:column 参数 参数 :db :table :column 描述数据库名表名列名 返回结果 参数 database table column 描述数据库名表名列名字和类型 例子 curl -i -u : --negotiate table/t1/column/id?user.name=user1 23. ddl/database/:db/table/:table/column/:column(put) 描述 URL 增加表的一列 column/:column 参数 参数 :db :table 描述 数据库名 表名 文档版本 01 ( ) 109

120 4 Hive 应用开发 参数 :column type comment 描述列名列类型, 比如 string 和 int 列备注, 比如描述 返回结果 参数 database table column 描述数据库名表名列名 例子 curl -i -u : --negotiate -X PUT -HContent-type:application/json -d '{"type": "string", "comment": "new column"' table/t1/column/name?user.name=user1 24. ddl/database/:db/table/:table/property(get) 描述 URL 参数 获取表的 property 参数 :db :table 描述 数据库名 表名 返回结果 参数 database table properties 描述数据库名表名属性列表 例子 curl -i -u : --negotiate table/t1/property?user.name=user1 25. ddl/database/:db/table/:table/property/:property(get) 描述 获取表的某个具体的 property 的值 文档版本 01 ( ) 110

121 4 Hive 应用开发 URL property/:property 参数 参数 :db :table :property 描述数据库名表名属性名 返回结果 参数 database table property 描述数据库名表名属性列表 例子 curl -i -u : --negotiate table/t1/property/last_modified_by?user.name=user1 26. ddl/database/:db/table/:table/property/:property(put) 描述 URL 增加表的 property 的值 property/:property 参数 参数 :db :table :property value 描述数据库名表名属性名属性值 返回结果 参数 database table property 描述数据库名表名属性名 文档版本 01 ( ) 111

122 4 Hive 应用开发 例子 curl -i -u : --negotiate -X PUT -HContent-type:application/json -d '{"value": "my value"' mykey?user.name=user1 27. mapreduce/jar(post) 描述 URL 参数 执行 MR 任务, 在执行之前, 需要将 MR 的 jar 包上传到 HDFS 中 参数 描述 jar 需要执行的 MR 的 jar 包 class libjars files arg 需要执行的 MR 的分类 需要加入的 classpath 的 jar 包名, 以逗号分隔 需要拷贝到 MR 集群的文件名, 以逗号分隔 Main 类接受的输入参数 define 设置 hadoop 的配置, 格式为 : define=name=value statusdir enablelog callback WebHCat 会将执行的 MR 任务的状态写入到 statusdir 中 如果设置了这个值, 那么需要用户手动进行删除 如果 statusdir 设置,enablelog 设置为 true, 收集 Hadoop 任务配置和日志到 $statusdir/logs 此后, 成功和失败的尝试, 都将记录进日志 $statusdir/ logs 下, 子目录布局为 : logs/$job_id (directory for $job_id) logs/$job_id/job.xml.html logs/$job_id/$attempt_id (directory for $attempt_id) logs/$job_id/$attempt_id/stderr logs/$job_id/$attempt_id/stdout logs/$job_id/$attempt_id/syslog 仅支持 Hadoop 1.X 在 MR 任务执行完的回调地址, 使用 $jobid, 将任务 ID 嵌入回调地址 在回调地址中, 任务 ID 替换该 $jobid 文档版本 01 ( ) 112

123 4 Hive 应用开发 返回结果 参数 id 描述 任务 ID, 类似 job_ _0001 例子 curl -i -u : --negotiate -d jar="/tmp/word.count snapshot.jar" -d class=com.huawei.word.count.wd -d statusdir="/output" " templeton/v1/mapreduce/jar?user.name=user1" 28. mapreduce/streaming(post) 描述 URL 参数 以 Streaming 方式提交 MR 任务 参数 input 描述 Hadoop 中 input 的路径 output 存储 output 的路径 如没有规定, WebChat 将 output 储存在使用队列资源可以发现到的路径 mapper reducer files mapper 程序位置 reducer 程序位置 HDFS 文件添加到分布式缓存中 arg 设置 argument define 设置 hadoop 的配置变量, 格式 : define=name=value cmdenv 设置环境变量, 格式 : cmdenv=name=value statusdir WebHCat 会将执行的 MR 任务的状态写入到 statusdir 中 如果设置了这个值, 那么需要用户手动进行删除 文档版本 01 ( ) 113

124 4 Hive 应用开发 参数 enablelog callback 描述 如果 statusdir 设置,enablelog 设置为 true, 收集 Hadoop 任务配置和日志到 $statusdir/logs 此后, 成功和失败的尝试, 都将记录进日志 $statusdir/ logs 下, 子目录布局为 : logs/$job_id (directory for $job_id) logs/$job_id/job.xml.html logs/$job_id/$attempt_id (directory for $attempt_id) logs/$job_id/$attempt_id/stderr logs/$job_id/$attempt_id/stdout logs/$job_id/$attempt_id/syslog 仅支持 Hadoop 1.X 在 MR 任务执行完的回调地址, 使用 $jobid, 将任务 ID 嵌入回调地址 在回调地址中, 任务 ID 将替换该 $jobid 返回结果 参数 id 描述 任务 ID, 类似 job_ _0001 例子 curl -i -u : --negotiate -d input=/input -d output=/oooo -d mapper=/bin/cat -d reducer="/usr/bin/wc -w" -d statusdir="/output" ' mapreduce/streaming?user.name=user1' 说明 29. /hive(post) 本接口的使用需要前置条件, 请参阅规则 描述 URL 参数 执行 Hive 命令 参数 execute file 描述 hive 命令, 包含整个和短的 Hive 命令 包含 hive 命令的 HDFS 文件 文档版本 01 ( ) 114

125 4 Hive 应用开发 参数 files 描述 需要拷贝到 MR 集群的文件名, 以逗号分隔 arg 设置 argument define 设置 hadoop 的配置, 格式 : define=key=value statusdir enablelog callback WebHCat 会将执行的 MR 任务的状态写入到 statusdir 中 如果设置了这个值, 那么需要用户手动进行删除 如果 statusdir 设置,enablelog 设置为 true, 收集 Hadoop 任务配置和日志到 $statusdir/logs 此后, 成功和失败的尝试, 都将记录进日志 $statusdir/ logs 下, 子目录布局为 : logs/$job_id (directory for $job_id) logs/$job_id/job.xml.html logs/$job_id/$attempt_id (directory for $attempt_id) logs/$job_id/$attempt_id/stderr logs/$job_id/$attempt_id/stdout logs/$job_id/$attempt_id/syslog 在 MR 任务执行完的回调地址, 使用 $jobid, 将任务 ID 嵌入回调地址 在回调地址中, 任务 ID 将替换该 $jobid 返回结果 参数 id 描述 任务 ID, 类似 job_ _0001 例子 curl -i -u : --negotiate -d execute="select count(*) from t1" -d statusdir="/output" " 30. jobs(get) 描述 URL 参数 获取所有的 job id 文档版本 01 ( ) 115

126 4 Hive 应用开发 参数 fields jobid numrecords showall 描述 如果设置成 *, 那么会返回每个 job 的详细信息 如果没设置, 只返回任务 ID 现在只能设置成 *, 如设置成其他值, 将出现异常 如果设置了 jobid, 那么只有字典顺序比 jobid 大的 job 才会返回 比如, 如果 jobid 为 "job_ _0001", 只有大于该值的 job 才能返回 返回的 job 的个数, 取决于 numrecords 如果设置了 numrecords 和 jobid,jobid 列表按字典顺序排列, 待 jobid 返回后, 可以得到 numrecords 的最大值 如果 jobid 没有设置, 而 numrecords 设置了参数值,jobid 按字典顺序排列后, 可以得到 numrecords 的最大值 相反, 如果 numrecords 没有设置, 而 jobid 设置了参数值, 所有大于 jobid 的 job 都将返回 如果设置为 true, 用户可以获取所有 job, 如果设置为 false, 则只获取当前用户提交的 job 默认为 false 返回结果 参数 id detail 描述 Job id 如果 showall 为 true, 那么显示 detail 信息, 否则为 null 例子 curl -i -u : --negotiate " 31. jobs/:jobid(get) 描述 URL 参数 获取指定 job 的信息 参数 jobid 描述 Job 创建后的 Jobid 返回结果 文档版本 01 ( ) 116

127 4 Hive 应用开发 参数 status profile 描述 包含 job 状态信息的 json 对象 包含 job 信息的 json 对象 WebHCat 解析 JobProfile 对象中的信息, 该对象因 Hadoop 版本不同而不同 id Job 的 id percentcomplete user 完成百分比, 比如 75% complete, 如果完成后则为 null 创建 job 的用户 callback 回调 URL( 如果有 ) userargs exitvalue 用户提交 job 时的 argument 参数和参数值 job 退出值 例子 curl -i -u : --negotiate " job_ _0255?user.name=user1" 32. jobs/:jobid(delete) 描述 kill 任务 URL 参数 参数 :jobid 描述 删除的 Job 的 ID 返回结果 参数 user status profile 描述 提交 Job 的用户 包含 Job 状态信息的 JSON 对象 包含 job 信息的 json 对象 WebHCat 解析 JobProfile 对象中的信息, 该对象因 Hadoop 版本不同而不同 id Job 的 id callback 回调的 URL( 如果有 ) 例子 文档版本 01 ( ) 117

128 4 Hive 应用开发 curl -i -u : --negotiate -X DELETE " job_ _0265?user.name=user1" 4.6 开发规范 规则 Hive JDBC 驱动的加载 客户端程序以 JDBC 的形式连接 HiveServer 时, 需要首先加载 Hive 的 JDBC 驱动类 org.apache.hive.jdbc.hivedriver 故在客户端程序的开始, 必须先使用当前类加载器加载该驱动类 如果 classpath 下没有相应的 jar 包, 则客户端程序抛出 Class Not Found 异常并退出 如下 : Class.forName("org.apache.hive.jdbc.HiveDriver").newInstance(); 获取数据库连接 使用 JDK 的驱动管理类 java.sql.drivermanager 来获取一个 Hive 的数据库连接 Hive 的数据库 URL 为 url="jdbc:hive2://xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx: 2181,xxx.xxx.xxx.xxx: 2181/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver;sasl.qop=authconf;auth=KERBEROS;principal=hive/ hadoop.hadoop.com@hadoop.com;user.principal=hive/ hadoop.hadoop.com;user.keytab=conf/hive.keytab"; 以上已经经过安全认证, 所以 Hive 数据库的用户名和密码为 null 或者空 说明 如下 : MRS 及以下版本 ZooKeeper 默认端口号为 24002, 详见 MRS Manager 的 Zookeeper 配置 // 建立连接 connection = DriverManager.getConnection(url, "", ""); 执行 HQL 执行 HQL, 注意 HQL 不能以 ";" 结尾 正确示例 : String sql = "SELECT COUNT(*) FROM employees_info"; Connection connection = DriverManager.getConnection(url, "", ""); PreparedStatement statement = connection.preparestatement(sql); resultset = statement.executequery(); 错误示例 : String sql = "SELECT COUNT(*) FROM employees_info;"; Connection connection = DriverManager.getConnection(url, "", ""); PreparedStatement statement = connection.preparestatement(sql); resultset = statement.executequery(); 文档版本 01 ( ) 118

129 4 Hive 应用开发 关闭数据库连接 客户端程序在执行完 HQL 之后, 注意关闭数据库连接, 以免内存泄露, 同时这是一个良好的编程习惯 需要关闭 JDK 的两个对象 statement 和 connection 如下 : finally { if (null!= statement) { statement.close(); // 关闭 JDBC 连接 if (null!= connection) { connection.close(); HQL 语法规则之判空 判断字段是否为 空, 即没有值, 使用 is null ; 判断不为空, 即有值, 使用 is not null 要注意的是, 在 HQL 中 String 类型的字段若是空字符串, 即长度为 0, 那么对它进行 IS NULL 的判断结果是 False 此时应该使用 col = '' 来判断空字符串 ; 使用 col!= '' 来判断非空字符串 正确示例 : select * from default.tbl_src where id is null; select * from default.tbl_src where id is not null; select * from default.tbl_src where name = ''; select * from default.tbl_src where name!= ''; 错误示例 : select * from default.tbl_src where id = null; select * from default.tbl_src where id!= null; select * from default.tbl_src where name is null; select * from default.tbl_src where name is not null; 注 : 表 tbl_src 的 id 字段为 Int 类型,name 字段为 String 类型 客户端配置参数需要与服务端保持一致 当集群的 Hive YARN HDFS 服务端配置参数发生变化时, 客户端程序对应的参数会被改变, 用户需要重新审视在配置参数变更之前提交到 HiveServer 的配置参数是否和服务端配置参数一致, 如果不一致, 需要用户在客户端重新调整并提交到 HiveServer 例如下面的示例中, 如果修改了集群中的 YARN 配置参数时,Hive 客户端 示例程序都需要审视并修改之前已经提交到 HiveServer 的配置参数 : 初始状态 : 集群 YARN 的参数配置如下 : mapreduce.reduce.java.opts=-xmx2048m 客户端的参数配置如下 : mapreduce.reduce.java.opts=-xmx2048m 集群 YARN 修改后, 参数配置如下 : mapreduce.reduce.java.opts=-xmx1024m 文档版本 01 ( ) 119

130 4 Hive 应用开发 多线程安全登录方式 如果此时客户端程序不做调整修改, 则还是以客户端参数有效, 会导致 reducer 内存不足而使 MR 运行失败 如果有多线程进行 login 的操作, 当应用程序第一次登录成功后, 所有线程再次登录时应该使用 relogin 的方式 login 的代码样例 : private Boolean login(configuration conf){ boolean flag = false; UserGroupInformation.setConfiguration(conf); try { UserGroupInformation.loginUserFromKeytab(conf.get(PRINCIPAL), conf.get(keytab)); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; relogin 的代码样例 : public Boolean relogin(){ boolean flag = false; try { UserGroupInformation.getLoginUser().reloginFromKeytab(); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; 使用 WebHCat 的 REST 接口以 Streaming 方式提交 MR 任务的前置条件 本接口需要依赖 hadoop 的 streaming 包, 在以 Streaming 方式提交 MR 任务给 WebHCat 前, 需要将 hadoop-streaming jar 包上传到 HDFS 的指定路径下 : hdfs:///apps/ templeton/hadoop-streaming jar 首先登录到安装有客户端和 Hive 服务的节点上, 以客户端安装路径为 /opt/client 为例 : source /opt/client/bigdata_env 使用 kinit 登录集群的人机用户或者机机用户 hdfs dfs -put ${BIGDATA_HOME/FusionInsight/FusionInsight-Hadoop-2.7.2/hadoop/ share/hadoop/tools/lib/hadoop-streaming jar /apps/templeton/ 其中 /apps/templeton/ 需要根据不同的实例进行修改, 默认实例使用 /apps/templeton/, Hive1 实例使用 /apps1/templeton/, 以此类推 避免对同一张表同时进行读写操作 目前的版本中,Hive 不支持并发操作, 需要避免对同一张表同时进行读写操作, 否则会出现查询结果不准确, 甚至任务失败的情况 文档版本 01 ( ) 120

131 4 Hive 应用开发 分桶表不支持 insert into 分桶表 (bucket table) 不支持 insert into, 仅支持 insert overwrite, 否则会导致文件个数与桶数不一致 使用 WebHCat 的部分 REST 接口的前置条件 Hive 授权说明 WebHCat 的部分 REST 接口使用依赖于 MapReduce 的 JobHistoryServer 实例, 具体接口如下 : mapreduce/jar(post) mapreduce/streaming(post) hive(post) jobs(get) jobs/:jobid(get) jobs/:jobid(delete) Hive 授权 ( 数据库 表或者视图 ) 推荐通过 Manager 授权界面进行授权, 不推荐使用命令行授权, 除了 alter databases databases_name set owner='user_name' 场景以外 不允许创建 Hive on HBase 的分区表 Hive on HBase 表将实际数据存储在 HBase 上 由于 HBase 会将表划分为多个分区, 将分区散列在 RegionServer 上, 因此不允许在 Hive 中创建 Hive on HBase 分区表 Hive on HBase 表不支持 INSERT OVERWRITE 建议 HQL 编写之隐式类型转换 HBase 中使用 rowkey 作为一行记录的唯一标识 在插入数据时, 如果 rowkey 相同, 则 HBase 会覆盖该行的数据 如果在 Hive 中对一张 Hive on HBase 表执行 INSERT OVERWRITE, 会将相同 rowkey 的行进行覆盖, 不相关的数据不会被覆盖 查询语句使用字段的值做过滤时, 不建议通过 Hive 自身的隐式类型转换来编写 HQL 因为隐式类型转换不利于代码的阅读和移植 建议示例 : select * from default.tbl_src where id = 10001; select * from default.tbl_src where name = 'TestName'; 不建议示例 : select * from default.tbl_src where id = '10001'; select * from default.tbl_src where name = TestName; 说明 表 tbl_src 的 id 字段为 Int 类型,name 字段为 String 类型 文档版本 01 ( ) 121

132 4 Hive 应用开发 HQL 编写之对象名称长度 HQL 编写之记录个数统计 JDBC 超时限制 示例 JDBC 二次开发示例代码一 HQL 的对象名称, 包括表名 字段名 视图名 索引名等, 其长度建议不要超过 30 个字节 Oracle 中任何对象名称长度不允许超过 30 个字节, 超过时会报错 为了兼容 Oracle, 对对象的名称进行了限制, 不允许超过 30 个字节 太长不利于阅读 维护 移植 统计某个表所有的记录个数, 建议使用 select count(1) from table_name 统计某个表某个字段有效的记录个数, 建议使用 select count(column_name) from tbale_name Hive 提供的 JDBC 实现有超时限制, 默认是 5 分钟, 用户可以通过 java.sql.drivermanager.setlogintimeout(int seconds) 设置,seconds 的单位为秒 以下示例代码主要功能如下 : 1. 在 JDBC URL 地址中提供用户名和密钥文件路径, 程序自动完成安全登录 建立 Hive 连接 说明 MRS 及以下版本 ZooKeeper 端口号默认为 24002, 详见 MRS Manager 的 Zookeeper 配置 2. 执行创建表 查询和删除三类 HQL 语句 package com.huawei.bigdata.hive.example; import java.io.file; import java.io.fileinputstream; import java.io.ioexception; import java.io.inputstream; import java.sql.connection; import java.sql.drivermanager; import java.sql.preparedstatement; import java.sql.resultset; import java.sql.resultsetmetadata; import java.sql.sqlexception; import java.util.properties; import org.apache.hadoop.conf.configuration; import com.huawei.bigdata.security.loginutil; public class JDBCExample { private static final String HIVE_DRIVER = "org.apache.hive.jdbc.hivedriver"; private static final String ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME = "Client"; private static final String ZOOKEEPER_SERVER_PRINCIPAL_KEY = "zookeeper.server.principal"; private static final String ZOOKEEPER_DEFAULT_SERVER_PRINCIPAL = "zookeeper/hadoop"; private static Configuration CONF = null; 文档版本 01 ( ) 122

133 4 Hive 应用开发 private static String KRB5_FILE = null; private static String USER_NAME = null; private static String USER_KEYTAB_FILE = null; private static String zkquorum = null;//zookeeper 节点 ip 和端口列表 private static String auth = null; private static String sasl_qop = null; private static String zookeepernamespace = null; private static String servicediscoverymode = null; private static String principal = null; private static void init() throws IOException{ CONF = new Configuration(); Properties clientinfo = null; String userdir = System.getProperty("user.dir") + File.separator + "conf" + File.separator; System.out.println(userdir); InputStream fileinputstream = null; try{ clientinfo = new Properties(); //"hiveclient.properties" 为客户端配置文件, 如果使用多实例特性, 需要把该文件换成对应实例客户端下的 "hiveclient.properties" //"hiveclient.properties" 文件位置在对应实例客户端安裝包解压目录下的 config 目录下 String hiveclientprop = userdir + "hiveclient.properties" ; File propertiesfile = new File(hiveclientProp); fileinputstream = new FileInputStream(propertiesFile); clientinfo.load(fileinputstream); catch (Exception e) { throw new IOException(e); finally{ if(fileinputstream!= null){ fileinputstream.close(); fileinputstream = null; //zkquorum 获取后的格式为 "xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2181"; //"xxx.xxx.xxx.xxx" 为集群中 ZooKeeper 所在节点的业务 IP, 端口默认是 2181 zkquorum = clientinfo.getproperty("zk.quorum"); auth = clientinfo.getproperty("auth"); sasl_qop = clientinfo.getproperty("sasl.qop"); zookeepernamespace = clientinfo.getproperty("zookeepernamespace"); servicediscoverymode = clientinfo.getproperty("servicediscoverymode"); principal = clientinfo.getproperty("principal"); // 设置新建用户的 USER_NAME, 其中 "xxx" 指代之前创建的用户名, 例如创建的用户为 user, 则 USER_NAME 为 user USER_NAME = "userx"; if ("KERBEROS".equalsIgnoreCase(auth)) { // 设置客户端的 keytab 和 krb5 文件路径 USER_KEYTAB_FILE = userdir + "user.keytab"; KRB5_FILE = userdir + "krb5.conf"; LoginUtil.setJaasConf(ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME, USER_NAME, USER_KEYTAB_FILE); LoginUtil.setZookeeperServerPrincipal(ZOOKEEPER_SERVER_PRINCIPAL_KEY, ZOOKEEPER_DEFAULT_SERVER_PRINCIPAL); // 安全模式 // Zookeeper 登录认证 LoginUtil.login(USER_NAME, USER_KEYTAB_FILE, KRB5_FILE, CONF); /** * 本示例演示了如何使用 Hive JDBC 接口来执行 HQL 命令 <br> * <br> * ClassNotFoundException IllegalAccessException InstantiationException 文档版本 01 ( ) 123

134 4 Hive 应用开发 SQLException IOException */ public static void main(string[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException, IOException{ // 参数初始化 init(); // 定义 HQL,HQL 为单条语句, 不能包含 ; String[] sqls = {"CREATE TABLE IF NOT EXISTS employees_info(id INT,name STRING)", "SELECT COUNT(*) FROM employees_info", "DROP TABLE employees_info"; // 拼接 JDBC URL StringBuilder sbuilder = new StringBuilder( "jdbc:hive2://").append(zkquorum).append("/"); if ("KERBEROS".equalsIgnoreCase(auth)) { sbuilder.append(";servicediscoverymode=").append(servicediscoverymode).append(";zookeepernamespace=").append(zookeepernamespace).append(";sasl.qop=").append(sasl_qop).append(";auth=").append(auth).append(";principal=").append(principal).append(";"); else { // 普通模式 sbuilder.append(";servicediscoverymode=").append(servicediscoverymode).append(";zookeepernamespace=").append(zookeepernamespace).append(";auth=none"); String url = sbuilder.tostring(); // 加载 Hive JDBC 驱动 Class.forName(HIVE_DRIVER); Connection connection = null; try { System.out.println(url); // 获取 JDBC 连接 // 如果使用的是普通模式, 那么第二个参数需要填写正确的用户名, 否则会以匿名用户 (anonymous) 登录 connection = DriverManager.getConnection(url, "", ""); // 建表 // 表建完之后, 如果要往表中导数据, 可以使用 LOAD 语句将数据导入表中, 比如从 HDFS 上将数据导入表 : // load data inpath '/tmp/employees.txt' overwrite into table employees_info; execddl(connection,sqls[0]); System.out.println("Create table success!"); // 查询 execdml(connection,sqls[1]); // 删表 execddl(connection,sqls[2]); System.out.println("Delete table success!"); finally { // 关闭 JDBC 连接 if (null!= connection) { connection.close(); 文档版本 01 ( ) 124

135 4 Hive 应用开发 public static void execddl(connection connection, String sql) throws SQLException { PreparedStatement statement = null; try { statement = connection.preparestatement(sql); statement.execute(); finally { if (null!= statement) { statement.close(); public static void execdml(connection connection, String sql) throws SQLException { PreparedStatement statement = null; ResultSet resultset = null; ResultSetMetaData resultmetadata = null; try { // 执行 HQL statement = connection.preparestatement(sql); resultset = statement.executequery(); // 输出查询的列名到控制台 resultmetadata = resultset.getmetadata(); int columncount = resultmetadata.getcolumncount(); for (int i = 1; i <= columncount; i++) { System.out.print(resultMetaData.getColumnLabel(i) + '\t'); System.out.println(); // 输出查询结果到控制台 while (resultset.next()) { for (int i = 1; i <= columncount; i++) { System.out.print(resultSet.getString(i) + '\t'); System.out.println(); finally { if (null!= resultset) { resultset.close(); if (null!= statement) { statement.close(); JDBC 二次开发示例代码二 以下示例代码主要功能如下 : 1. 用户自行进行安全登录, 不在 JDBC URL 地址中提供用户和密钥文件路径, 建立 Hive 连接 2. 执行创建表 查询和删除三类 HQL 语句 说明 程序在访问 ZooKeeper 时会使用 jaas 配置文件, 例如 user.hive.jaas.conf, 具体信息如下 Client { com.sun.security.auth.module.krb5loginmodule required usekeytab=true 文档版本 01 ( ) 125

136 4 Hive 应用开发 keytab="d:\\workspace\\jdbc-examples\\conf\\user.keytab" useticketcache=false storekey=true debug=true; ; 用户需要根据实际环境, 修改上述配置文件的 keytab 路径 ( 绝对路径 ) 和 principal, 并设置环境变量 java.security.auth.login.config 指向该文件所在路径 package com.huawei.bigdata.hive.example; import static org.apache.hadoop.fs.commonconfigurationkeyspublic.hadoop_security_authentication; import static org.apache.hadoop.fs.commonconfigurationkeyspublic.hadoop_security_authorization; import java.io.file; import java.io.fileinputstream; import java.io.ioexception; import java.io.inputstream; import java.sql.connection; import java.sql.drivermanager; import java.sql.preparedstatement; import java.sql.resultset; import java.sql.resultsetmetadata; import java.sql.sqlexception; import java.util.properties; import org.apache.hadoop.conf.configuration; import org.apache.hadoop.security.securityutil; import org.apache.hadoop.security.usergroupinformation; public class JDBCExamplePreLogin { private static final String HIVE_DRIVER = "org.apache.hive.jdbc.hivedriver"; /** * 本示例演示了如何使用 Hive JDBC 接口来执行 HQL 命令 <br> * <br> * ClassNotFoundException IllegalAccessException InstantiationException SQLException */ public static void main(string[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException,IOException{ Properties clientinfo = null; String userdir = System.getProperty("user.dir") + File.separator + "conf" + File.separator; InputStream fileinputstream = null; try{ clientinfo = new Properties(); //"hiveclient.properties" 为客户端配置文件, 如果使用多实例特性, 需要把该文件换成对应实例客户端下的 "hiveclient.properties" //"hiveclient.properties" 文件位置在对应实例客户端安裝包解压目录下的 config 目录下 String hiveclientprop = userdir + "hiveclient.properties" ; File propertiesfile = new File(hiveclientProp); fileinputstream = new FileInputStream(propertiesFile); clientinfo.load(fileinputstream); catch (Exception e) { throw new IOException(e); finally{ if(fileinputstream!= null){ fileinputstream.close(); fileinputstream = null; //zkquorum 获取后的格式为 "xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2181"; //"xxx.xxx.xxx.xxx" 为集群中 ZooKeeper 所在节点的业务 IP, 端口默认是 2181 String zkquorum = clientinfo.getproperty("zk.quorum"); 文档版本 01 ( ) 126

137 4 Hive 应用开发 String auth = clientinfo.getproperty("auth"); String sasl_qop = clientinfo.getproperty("sasl.qop"); String zookeepernamespace = clientinfo.getproperty("zookeepernamespace"); String servicediscoverymode = clientinfo.getproperty("servicediscoverymode"); String principal = clientinfo.getproperty("principal"); // 定义 HQL,HQL 为单条语句, 不能包含 ; String[] sqls = {"CREATE TABLE IF NOT EXISTS employees_info(id INT,name STRING)", "SELECT COUNT(*) FROM employees_info", "DROP TABLE employees_info"; // 拼接 JDBC URL StringBuilder sbuilder = new StringBuilder( "jdbc:hive2://").append(zkquorum).append("/"); if ("KERBEROS".equalsIgnoreCase(auth)) { // 设置属性 java.security.krb5.conf, 以此指定将访问的安全服务的信息 System.setProperty("java.security.krb5.conf", "conf/krb5.conf"); // 设置需要使用的 jaas 配置文件, 请根据实际情况修改 user.hive.jaas.conf 中的 keytab 和 principal System.setProperty("java.security.auth.login.config", "conf/user.hive.jaas.conf"); Configuration conf = new Configuration(); conf.set(hadoop_security_authentication, "kerberos"); conf.set(hadoop_security_authorization, "true"); String PRINCIPAL = "username.client.kerberos.principal"; String KEYTAB = "username.client.keytab.file"; // 设置客户端的 keytab 文件路径 conf.set(keytab, "conf/user.keytab"); // 设置新建用户的 userprincipal, 此处填写为带域名的用户名, 例如创建的用户为 user, 域为 HADOOP.COM, 则其 userprincipal 则为 user@hadoop.com conf.set(principal, "xxx@xxx"); // 进行登录认证 UserGroupInformation.setConfiguration(conf); SecurityUtil.login(conf, KEYTAB, PRINCIPAL); // 安全模式 sbuilder.append(";servicediscoverymode=").append(servicediscoverymode).append(";zookeepernamespace=").append(zookeepernamespace).append(";sasl.qop=").append(sasl_qop).append(";auth=").append(auth).append(";principal=").append(principal).append(";"); else { // 普通模式 sbuilder.append(";servicediscoverymode=").append(servicediscoverymode).append(";zookeepernamespace=").append(zookeepernamespace).append(";auth=none"); String url = sbuilder.tostring(); // 加载 Hive JDBC 驱动 Class.forName(HIVE_DRIVER); Connection connection = null; try { // 获取 JDBC 连接 // 如果使用的是普通模式, 那么第二个参数需要填写正确的用户名, 否则会以匿名用户 (anonymous) 登录 connection = DriverManager.getConnection(url, "", ""); // 建表 文档版本 01 ( ) 127

138 4 Hive 应用开发 // 表建完之后, 如果要往表中导数据, 可以使用 LOAD 语句将数据导入表中, 比如从 HDFS 上将数据导入表 : // load data inpath '/tmp/employees.txt' overwrite into table employees_info; execddl(connection,sqls[0]); System.out.println("Create table success!"); // 查询 execdml(connection,sqls[1]); // 删表 execddl(connection,sqls[2]); System.out.println("Delete table success!"); finally { // 关闭 JDBC 连接 if (null!= connection) { connection.close(); public static void execddl(connection connection, String sql) throws SQLException { PreparedStatement statement = null; try { statement = connection.preparestatement(sql); statement.execute(); finally { if (null!= statement) { statement.close(); public static void execdml(connection connection, String sql) throws SQLException { PreparedStatement statement = null; ResultSet resultset = null; ResultSetMetaData resultmetadata = null; try { // 执行 HQL statement = connection.preparestatement(sql); resultset = statement.executequery(); // 输出查询的列名到控制台 resultmetadata = resultset.getmetadata(); int columncount = resultmetadata.getcolumncount(); for (int i = 1; i <= columncount; i++) { System.out.print(resultMetaData.getColumnLabel(i) + '\t'); System.out.println(); // 输出查询结果到控制台 while (resultset.next()) { for (int i = 1; i <= columncount; i++) { System.out.print(resultSet.getString(i) + '\t'); System.out.println(); finally { if (null!= resultset) { resultset.close(); if (null!= statement) { statement.close(); 文档版本 01 ( ) 128

139 4 Hive 应用开发 HCatalog 二次开发示例代码 以下示例代码演示了如何使用 HCatalog 提供的 HCatInputFormat 和 HCatOutputFormat 接口提交 MapReduce 任务 public class HCatalogExample extends Configured implements Tool { public static class Map extends Mapper<LongWritable, HCatRecord, IntWritable, IntWritable> { int protected void map( LongWritable key, HCatRecord value, org.apache.hadoop.mapreduce.mapper<longwritable, HCatRecord, IntWritable, IntWritable>.Context context) throws IOException, InterruptedException { age = (Integer) value.get(0); context.write(new IntWritable(age), new IntWritable(1)); public static class Reduce extends Reducer<IntWritable, IntWritable, IntWritable, HCatRecord> protected void reduce( IntWritable key, java.lang.iterable<intwritable> values, org.apache.hadoop.mapreduce.reducer<intwritable, IntWritable, IntWritable, HCatRecord>.Context context) throws IOException, InterruptedException { int sum = 0; Iterator<IntWritable> iter = values.iterator(); while (iter.hasnext()) { sum++; iter.next(); HCatRecord record = new DefaultHCatRecord(2); record.set(0, key.get()); record.set(1, sum); context.write(null, record); public int run(string[] args) throws Exception { Configuration conf = getconf(); String[] otherargs = args; String inputtablename = otherargs[0]; String outputtablename = otherargs[1]; String dbname = Job job = new Job(conf, "GroupByDemo"); HCatInputFormat.setInput(job, dbname, inputtablename); job.setinputformatclass(hcatinputformat.class); job.setjarbyclass(hcatalogexample.class); job.setmapperclass(map.class); job.setreducerclass(reduce.class); job.setmapoutputkeyclass(intwritable.class); job.setmapoutputvalueclass(intwritable.class); job.setoutputkeyclass(writablecomparable.class); 文档版本 01 ( ) 129

140 4 Hive 应用开发 job.setoutputvalueclass(defaulthcatrecord.class); OutputJobInfo outputjobinfo = OutputJobInfo.create(dbName,outputTableName, null); HCatOutputFormat.setOutput(job, outputjobinfo); HCatSchema schema = outputjobinfo.getoutputschema(); HCatOutputFormat.setSchema(job, schema); job.setoutputformatclass(hcatoutputformat.class); return (job.waitforcompletion(true)? 0 : 1); public static void main(string[] args) throws Exception { int exitcode = ToolRunner.run(new HCatalogExample(), args); System.exit(exitCode); 文档版本 01 ( ) 130

141 5 MapReduce 应用开发 5 MapReduce 应用开发 5.1 概述 MapReduce 简介 常用概念 Hadoop MapReduce 是一个使用简易的并行计算软件框架, 基于它写出来的应用程序能够运行在由上千个服务器组成的大型集群上, 并以一种可靠容错的方式并行处理上 T 级别的数据集 一个 MapReduce 作业 (application/job) 通常会把输入的数据集切分为若干独立的数据块, 由 map 任务 (task) 以完全并行的方式来处理 框架会对 map 的输出先进行排序, 然后把结果输入给 reduce 任务, 最后返回给客户端 通常作业的输入和输出都会被存储在文件系统中 整个框架负责任务的调度和监控, 以及重新执行已经失败的任务 MapReduce 主要特点如下 : 大规模并行计算 适用于大型数据集 高容错性和高可靠性 合理的资源调度 Hadoop shell 命令 Hadoop 基本 shell 命令, 包括提交 MapReduce 作业,kill MapReduce 作业, 进行 HDFS 文件系统各项操作等 MapReduce 输入输出 (InputFormat,OutputFormat) MapReduce 框架根据用户指定的 InputFormat 切割数据集, 读取数据, 并提供给 map 任务多条键值对进行处理, 决定并行启动的 map 任务数目 MapReduce 框架根据用户指定的 OutputFormat, 把生成的键值对输出为特定格式的数据 map reduce 两个阶段都处理在 <key,value> 键值对上, 也就是说, 框架把作业的输入作为一组 <key,value> 键值对, 同样也产出一组 <key,value> 键值对做为作业的输出, 这两组键值对的类型可能不同 对单个 map 和 reduce 而言, 对键值对的处理为单线程串行处理 文档版本 01 ( ) 131

142 5 MapReduce 应用开发 开发流程 框架需要对 key 和 value 的类 (classes) 进行序列化操作, 因此, 这些类需要实现 Writable 接口 另外, 为了方便框架执行排序操作,key 类必须实现 WritableComparable 接口 一个 MapReduce 作业的输入和输出类型如下所示 : (input)<k1,v1> > map > <k2,v2> > 汇总数据 > <k2,list(v2)> > reduce > <k3,v3>(output) 业务核心 应用程序通常只需要分别继承 Mapper 类和 Reducer 类, 并重写其 map 和 reduce 方法来实现业务逻辑, 它们组成作业的核心 MapReduce WebUI 界面 用于监控正在运行的或者历史的 MapReduce 作业在 MapReduce 框架各个阶段的细节, 以及提供日志显示, 帮助用户更细粒度地去开发 配置和调优作业 归档 混洗 映射 用来保证所有映射的键值对中的每一个共享相同的键组 从 Map 任务输出的数据到 Reduce 任务的输入数据的过程称为 Shuffle 用来把一组键值对映射成一组新的键值对 开发流程中各阶段的说明如图 5-1 和表 5-1 所示 文档版本 01 ( ) 132

143 5 MapReduce 应用开发 图 5-1 MapReduce 应用程序开发流程 表 5-1 MapReduce 应用开发的流程说明 阶段说明参考文档 了解基本概念 准备开发环境 准备运行环境 获取并导入样例工程 或者新建工程 根据场景开发工程 在开始开发应用前, 需要了解 MapReduce 的基本概念 使用 Eclipse 工具, 请根据指导完成开发环境配置 MapReduce 的运行环境即 MapReduce 客户端, 请根据指导完成客户端的安装和配置 MapReduce 提供了不同场景下的样例程序, 您可以导入样例工程进行程序学习 或者您可以根据指导, 新建一个 MapReduce 工程 提供了样例工程 帮助用户快速了解 MapReduce 各部件的编程接口 常用概念 准备 Eclipse 与 JDK 准备 Eclipse 与 JDK 获取并导入样例工程 开发程序 文档版本 01 ( ) 133

144 5 MapReduce 应用开发 阶段说明参考文档 编译并运行程序 查看程序运行结果 指导用户将开发好的程序编译并提交运行 程序运行结果会写在用户指定的路径下 用户还可以通过 UI 查看应用运行情况 编译并运行程序 查看调测结果 5.2 环境准备 开发环境简介 在进行应用开发时, 要准备的开发环境如表 5-2 所示 同时需要准备运行调测的 Linux 环境, 用于验证应用程序运行正常 表 5-2 开发环境 准备项 说明 安装 Eclipse 开发环境的基本配置 版本要求 :4.2 安装 JDK 版本要求 :1.8 版本 准备开发用户 开发用户用于运行样例工程 用户需要有组件权限, 才能运行样例工程 前提条件 操作步骤 MRS 服务集群开启了 Kerberos 认证, 没有开启 Kerberos 认证的集群忽略该步骤 步骤 1 登录 MRS Manager, 在 MRS Manager 界面选择 系统设置 > 角色管理 > 添加角色 1. 填写角色的名称, 例如 mrrole 2. 编辑角色, 在 权限 的表格中选择 Yarn > Scheduler Queue > root, 勾选 Submit Admin, 单击 确定 保存 3. 在 权限 表格中选择 HBase > HBase Scope, 勾选 global 的 Create Read Write Execute 单击 确定 保存 4. 编辑角色, 在 权限 的表格中选择 HDFS > File System > hdfs://hacluster/, 勾选 Read Write 和 Execute, 单击 确定 保存 5. 编辑角色, 在 权限 的表格中选择 Hive > Hive Read Write Privileges, 勾选 default 的 Create Select Delete Insert, 单击 确定 保存 步骤 2 单击 系统设置 > 用户组管理 > 添加用户组, 为样例工程创建一个用户组, 例如 mrgroup 文档版本 01 ( ) 134

145 步骤 3 步骤 4 步骤 5 单击 系统设置 > 用户管理 > 添加用户, 为样例工程创建一个用户 5 MapReduce 应用开发 填写用户名, 例如 test, 用户类型为 机机 用户, 加入用户组 mrgroup 和 supergroup, 设置其 主组 为 supergroup, 并绑定角色 mrrole 取得权限, 单击 确定 在 MRS Manager 界面选择 系统设置 > 用户管理, 在用户名中选择 test, 单击操作中 下载认证凭据文件, 保存后解压得到用户的 keytab 文件与 krb5.conf 文件 用于在样例工程中进行安全认证, 具体使用请参考 准备 kerberos 认证 ---- 结束 准备 Eclipse 与 JDK 选择 Window 开发环境下, 安装 Eclipse, 安装 JDK 步骤 1 开发环境安装 Eclipse 程序, 安装要求如下 : Eclipse 使用 4.2 或以上版本 步骤 2 开发环境安装 JDK 程序, 安装要求如下 : JDK 使用 1.8 版本 说明 ---- 结束 若使用 IBM JDK, 请确保 Eclipse 中的 JDK 配置为 IBM JDK 若使用 Oracle JDK, 请确保 Eclipse 中的 JDK 配置为 Oracle JDK 不同的 Eclipse 不要使用相同的 workspace 和相同路径下的示例工程 准备 Linux 客户端运行环境 操作步骤 MapReduce 的运行环境可以部署在 Linux 环境下 您可以按照如下操作完成运行环境准备 步骤 1 确认服务端 YARN 组件和 MapReduce 组件已经安装, 并正常运行 步骤 2 客户端运行环境已安装 1.7 或 1.8 版本的 JDK 步骤 3 客户端机器的时间与 Hadoop 集群的时间要保持一致, 时间差小于 5 分钟 MRS 集群的时间可通过登录主管理节点 ( 集群管理 IP 地址所在节点 ) 运行 date 命令查询 步骤 4 步骤 5 下载 MapReduce 客户端程序到客户端机器中 1. 登录 MRS Manager 系统 在浏览器地址栏中输入访问地址, 地址格式为 Manager 系统的 WebService 浮动 IP 地址 :8080/web 例如, 在 IE 浏览器地址栏中, 输入 2. 选择 服务管理 > 下载客户端, 下载客户端程序到客户端机器 解压缩客户端文件包 MRS_Services_Client.tar 安装包为 tar 格式, 执行如下命令解压两次 文档版本 01 ( ) 135

146 5 MapReduce 应用开发 tar -xvf MRS_Services_Client.tar tar -xvf MRS_Service_ClientConfig.tar 步骤 6 为运行环境设置环境变量, 假设安装包解压路径为 MRS_Services_ClientConfig/ 进入解压文件夹, 执行如下命令安装客户端 sh install.sh {client_install_home 步骤 7 进入客户端安装目录, 执行如下命令初始化环境变量 source bigdata_env 步骤 8 将 准备开发用户中下载的 user.keytab 和 krb5.conf 文件拷贝到 Linux 环境, 例如 /srv/client/conf 目录下, 可参考 编译并运行程序 说明 在二次开发过程中,PRINCIPAL 需要用到的用户名, 应该填写为带域名的用户名, 例如创建的用户为 test, 域名为 HADOOP.COM, 则其 PRINCIPAL 用户名则为 test@hadoop.com, 代码举例 : conf.set(principal, "test@hadoop.com"); 步骤 9 执行命令 kinit -kt /srv/client/conf/user.keytab test 说明 这里的 user.keytab 文件路径为 Linux 机器上配置文件的存放路径, 后面的 test 用户名可以更改为 准备开发用户中新建的用户名 ---- 结束 获取并导入样例工程 操作步骤 MapReduce 针对多个场景提供样例工程, 帮助客户快速学习 MapReduce 工程 以下操作步骤以导入 MapReduce 样例代码为例 步骤 1 步骤 2 步骤 3 参照第二章 MRS 服务样例工程获取方式, 下载样例工程到本地 导入样例工程到 Eclipse 开发环境 1. 打开 Eclipse, 选择 File > Import 显示 Import 窗口, 选择 Existing Maven Projects, 点击 next 按钮 2. 在 Import Maven Projects 窗口单击 Browse 显示 Select Root Folder 对话框 3. 选择样例工程文件夹 mapreduce-example, 单击 确定 按钮 4. 在 Import Maven Projects 窗口单击 Finish 按钮 设置 Eclipse 的文本文件编码格式, 解决乱码显示问题 1. 在 Eclipse 的菜单栏中, 选择 Window > Preferences 弹出 Preferences 窗口 2. 在左边导航上选择 General > Workspace, 在 Text file encoding 区域, 选中 Other, 并设置参数值为 UTF-8, 单击 Apply 后, 单击 OK, 如图 5-2 所示 文档版本 01 ( ) 136

147 5 MapReduce 应用开发 图 5-2 设置 Eclipse 的编码格式 ---- 结束 准备 kerberos 认证 场景说明 安全认证代码 在 kerberos 认证集群环境下, 各个组件之间的相互通信不能够简单的互通, 而需要在通信之前进行相互认证, 以确保通信的安全性 用户在提交 MapReduce 应用程序时, 需要与 Yarn HDFS 等之间进行通信 那么提交 MapReduce 的应用程序中需要写入安全认证代码, 确保 MapReduce 程序能够正常运行 安全认证有两种方式 : 命令行认证 : 提交 MapReduce 应用程序运行前, 在 MapReduce 客户端执行如下命令获得认证 kinit 组件业务用户 代码认证 : 通过获取客户端的 principal 和 keytab 文件在应用程序中进行认证 目前是统一调用 LoginUtil 类进行安全认证 在 MapReduce 样例工程代码中,test@HADOOP.COM user.keytab 和 krb5.conf 为示例, 实际操作时请联系管理员获取相应帐号对应权限的 keytab 文件和 krb5.conf 文件, 并将 keytab 文件和 krb5.conf 文件放入到 conf 目录, 安全登录方法如下代码所示 文档版本 01 ( ) 137

148 5 MapReduce 应用开发 public static final String PRINCIPAL= public static final String KEYTAB = FemaleInfoCollector.class.getClassLoader().getResource("user.keytab").getPath(); public static final String KRB = FemaleInfoCollector.class.getClassLoader().getResource("krb5.conf").getPath(); // 判断是否为安全模式 if("kerberos".equalsignorecase(conf.get("hadoop.security.authentication"))){ // 安全登录 System.setProperty("java.security.krb5.conf", KRB); LoginUtil.login(PRINCIPAL, KEYTAB, KRB, conf); 5.3 开发程序 MapReduce 统计样例程序 典型场景说明 假定用户有某个周末网民网购停留时间的日志文本, 基于某些业务要求, 要求开发 MapReduce 应用程序实现如下功能 : 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 周末两天的日志文件第一列为姓名, 第二列为性别, 第三列为本次停留时间, 单位为分钟, 分隔符为, log1.txt: 周六网民停留日志 LiuYang,female,20 YuanJing,male,10 GuoYijun,male,5 CaiXuyu,female,50 Liyuan,male,20 FangBo,female,50 LiuYang,female,20 YuanJing,male,10 GuoYijun,male,50 CaiXuyu,female,50 FangBo,female,60 log2.txt: 周日网民停留日志 LiuYang,female,20 YuanJing,male,10 CaiXuyu,female,50 FangBo,female,50 GuoYijun,male,5 CaiXuyu,female,50 Liyuan,male,20 CaiXuyu,female,50 FangBo,female,50 LiuYang,female,20 YuanJing,male,10 FangBo,female,50 GuoYijun,male,50 CaiXuyu,female,50 FangBo,female,60 数据规划 首先需要把原日志文件放置在 HDFS 系统里 文档版本 01 ( ) 138

149 5 MapReduce 应用开发 1. 本地新建两个文本文件, 将 log1.txt 中的内容复制保存到 input_data1.txt, 将 log2.txt 中的内容复制保存到 input_data2.txt 2. 在 HDFS 上建立一个文件夹, /tmp/input, 并上传 input_data1.txt, input_data2.txt 到此目录, 命令如下 : a. 在 Linux 系统 HDFS 客户端使用命令 hdfs dfs -mkdir /tmp/input b. 在 Linux 系统 HDFS 客户端使用命令 hdfs dfs -put local_filepath /tmp/input 开发思路 样例代码说明 功能介绍 代码样例 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 主要分为四个部分 : 读取原文件数据 筛选女性网民上网时间数据信息 汇总每个女性上网总时间 筛选出停留总时间大于两个小时的女性网民信息 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 主要分为三个部分 : 从原文件中筛选女性网民上网时间数据信息, 通过类 CollectionMapper 继承 Mapper 抽象类实现 汇总每个女性上网时间, 并输出时间大于两个小时的女性网民信息, 通过类 CollectionReducer 继承 Reducer 抽象类实现 main 方法提供建立一个 MapReduce job, 并提交 MapReduce 作业到 hadoop 集群 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.mapreduce.examples.femaleinfocollector 类 : 样例 1: 类 CollectionMapper 定义 Mapper 抽象类的 map() 方法和 setup() 方法 public static class CollectionMapper extends Mapper<Object, Text, Text, IntWritable> { // 分隔符 String delim; // 性别筛选 String sexfilter; // 姓名信息 private Text nameinfo = new Text(); // 输出的 key,value 要求是序列化的 private IntWritable timeinfo = new IntWritable(1); /** * 分布式计算 * 文档版本 01 ( ) 139

150 5 MapReduce 应用开发 key Object : 原文件位置偏移量 value Text : 原文件的一行字符数据 context Context : 出参 IOException, InterruptedException */ public void map(object key, Text value, Context context) throws IOException, InterruptedException { String line = value.tostring(); if (line.contains(sexfilter)) { // 读取的一行字符串数据 String name = line.substring(0, line.indexof(delim)); nameinfo.set(name); // 获取上网停留时间 String time = line.substring(line.lastindexof(delim) + 1, line.length()); timeinfo.set(integer.parseint(time)); // map 输出 key,value 键值对 context.write(nameinfo, timeinfo); /** * map 调用, 做一些初始工作 * context Context */ public void setup(context context) throws IOException, InterruptedException { // 通过 Context 可以获得配置信息 delim = context.getconfiguration().get("log.delimiter", ","); sexfilter = delim + context.getconfiguration().get("log.sex.filter", "female") + delim; 样例 2: 类 CollectionReducer 定义 Reducer 抽象类的 reduce() 方法 { public static class CollectionReducer extends Reducer<Text, IntWritable, Text, IntWritable> // 统计结果 private IntWritable result = new IntWritable(); // 总时间门槛 private int timethreshold; { /** key Text : Mapper 后的 key 项 values Iterable : 相同 key 项的所有统计结果 context Context IOException, InterruptedException */ public void reduce(text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException int sum = 0; for (IntWritable val : values) { sum += val.get(); 文档版本 01 ( ) 140

151 5 MapReduce 应用开发 { // 如果时间小于门槛时间, 不输出结果 if (sum < timethreshold) return; result.set(sum); // reduce 输出为 key: 网民的信息,value: 该网民上网总时间 context.write(key, result); /** * setup() 方法只在进入 map 任务的 map() 方法之前或者 reduce 任务的 reduce() 方法之前调用一次 * context Context IOException, InterruptedException */ public void setup(context context) throws IOException, InterruptedException { // Context 可以获得配置信息 timethreshold = context.getconfiguration().getint( "log.time.threshold", 120); 样例 3:main() 方法创建一个 job, 指定参数, 提交作业到 hadoop 集群 public static void main(string[] args) throws Exception { // 初始化环境变量 Configuration conf = new Configuration(); // 获取入参 String[] otherargs = new GenericOptionsParser(conf, args).getremainingargs(); if (otherargs.length!= 2) { System.err.println("Usage: collect female info <in> <out>"); System.exit(2); // 判断是否为安全模式 if("kerberos".equalsignorecase(conf.get("hadoop.security.authentication"))){ //security mode System.setProperty("java.security.krb5.conf", KRB); LoginUtil.login(PRINCIPAL, KEYTAB, KRB, conf); // 初始化 Job 任务对象 Job job = Job.getInstance(conf, "Collect Female Info"); job.setjarbyclass(femaleinfocollector.class); // 设置运行时执行 map,reduce 的类, 也可以通过配置文件指定 job.setmapperclass(collectionmapper.class); job.setreducerclass(collectionreducer.class); // 设置 combiner 类, 默认不使用, 使用时通常使用和 reduce 一样的类 // Combiner 类需要谨慎使用, 也可以通过配置文件指定 job.setcombinerclass(collectioncombiner.class); // 设置作业的输出类型 job.setoutputkeyclass(text.class); job.setoutputvalueclass(intwritable.class); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); // 提交任务交到远程环境上执行 System.exit(job.waitForCompletion(true)? 0 : 1); 文档版本 01 ( ) 141

152 5 MapReduce 应用开发 样例 4: 类 CollectionCombiner 实现了在 map 端先合并一下 map 输出的数据, 减少 map 和 reduce 之间传输的数据量 /** * Combiner class */ public static class CollectionCombiner extends Reducer<Text, IntWritable, Text, IntWritable> { // Intermediate statistical results private IntWritable intermediateresult = new IntWritable(); /** key Text : key after Mapper values Iterable : all results with the same key in this map task context Context IOException, InterruptedException */ public void reduce(text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); intermediateresult.set(sum); // In the output information, key indicates netizen information, // and value indicates the total online time of the netizen in this map task. context.write(key, intermediateresult); MapReduce 访问多组件样例程序 典型场景说明 场景说明 该样例以 MapReduce 访问 HDFS HBase Hive 为例, 介绍如何编写 MapReduce 作业访问多个服务组件 帮助用户理解认证 配置加载等关键使用方式 该样例逻辑过程如下 : 以 HDFS 文本文件为输入数据 : log1.txt: 数据输入文件 YuanJing,male,10 GuoYijun,male,5 Map 阶段 : 1. 获取输入数据的一行并提取姓名信息 2. 查询 HBase 一条数据 3. 查询 Hive 一条数据 4. 将 HBase 查询结果与 Hive 查询结果进行拼接作为 Map 输出 Reduce 阶段 : 文档版本 01 ( ) 142

153 5 MapReduce 应用开发 1. 获取 Map 输出中的最后一条数据 2. 将数据输出到 HBase 3. 将数据保存到 HDFS 数据规划 样例代码说明 功能介绍 1. 创建 HDFS 数据文件 a. 在 Linux 系统上新建文本文件, 将 log1.txt 中的内容复制保存到 data.txt b. 在 HDFS 上创建一个文件夹, /tmp/examples/multi-components/mapreduce/ input/, 并上传 data.txt 到此目录, 命令如下 : i. 在 Linux 系统 HDFS 客户端使用命令 hdfs dfs -mkdir -p /tmp/examples/multicomponents/mapreduce/input/ ii. 在 Linux 系统 HDFS 客户端使用命令 hdfs dfs -put data.txt /tmp/examples/ multi-components/mapreduce/input/ 2. 创建 HBase 表并插入数据 a. 在 Linux 系统 HBase 客户端使用命令 hbase shell b. 在 HBase shell 交互窗口创建数据表 table1, 该表有一个列族 cf, 使用命令 create 'table1', 'cf' c. 插入一条 rowkey 为 1 列名为 cid 数据值为 123 的数据, 使用命令 put 'table1', '1', 'cf:cid', '123' d. 执行命令 quit 退出 3. 创建 Hive 表并载入数据 a. 在 Linux 系统 Hive 客户端使用命令 beeline b. 在 Hive beeline 交互窗口创建数据表 person, 该表有 3 个字段 :name/gender/ staytime, 使用命令 CREATE TABLE person(name STRING, gender STRING, staytime INT) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' stored as textfile; c. 在 Hive beeline 交互窗口加载数据文件,LOAD DATA INPATH '/tmp/ examples/multi-components/mapreduce/input/' OVERWRITE INTO TABLE person; d. 执行命令!q 退出 4. 由于 Hive 加载数据将 HDFS 对应数据目录清空, 所以需再次执行 1 该样例主要分为三个部分 : 从 HDFS 原文件中抽取 name 信息, 查询 HBase Hive 相关数据, 并进行数据拼接, 通过类 MultiComponentMapper 继承 Mapper 抽象类实现 获取拼接后的数据取最后一条输出到 HBase HDFS, 通过类 MultiComponentReducer 继承 Reducer 抽象类实现 main 方法提供建立一个 MapReduce job, 并提交 MapReduce 作业到 Hadoop 集群 文档版本 01 ( ) 143

154 5 MapReduce 应用开发 代码样例 下面代码片段仅为演示, 具体代码请参见 com.huawei.bigdata.mapreduce.examples.multicomponentexample 类 : 样例 1: 类 MultiComponentMapper 定义 Mapper 抽象类的 map 方法 private static class MultiComponentMapper extends Mapper<Object, Text, Text, Text> { Configuration protected void map(object key, Text value, Context context) throws IOException, InterruptedException { String name = ""; String line = value.tostring(); // 加载配置文件 conf = context.getconfiguration(); setjaasinfo("krb5.conf", "jaas.conf"); LoginUtil.setJaasConf(ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME, "test", KEYTAB); LoginUtil.setZookeeperServerPrincipal(ZOOKEEPER_SERVER_PRINCIPAL_KEY, ZOOKEEPER_DEFAULT_SERVER_PRINCIPAL); // 准备 hive query // 加载 parameter Properties clientinfo = null; InputStream fileinputstream = null; try { clientinfo = new Properties(); File propertiesfile = new File(hiveClientProperties); fileinputstream = new FileInputStream(propertiesFile); clientinfo.load(fileinputstream); catch (Exception e) { finally { if (fileinputstream!= null) { fileinputstream.close(); String zkquorum = clientinfo.getproperty("zk.quorum"); String zookeepernamespace = clientinfo.getproperty("zookeepernamespace"); String servicediscoverymode = clientinfo.getproperty("servicediscoverymode"); // 创建 Hive 鉴权信息 // Read this carefully: // MapReduce can only use Hive through JDBC. // Hive will submit another MapReduce Job to execute query. // So we run Hive in MapReduce is not recommended. final String driver = "org.apache.hive.jdbc.hivedriver"; String sql = "select name,sum(staytime) as " + "staytime from person where name =? group by name"; StringBuilder sbuilder = new StringBuilder("jdbc:hive2://").append(zkQuorum).append("/"); // in map or reduce, use 'auth=delegationtoken' sbuilder.append(";servicediscoverymode=").append(servicediscoverymode).append(";zookeepernamespace=").append(zookeepernamespace).append(";auth=delegationtoken;"); String url = sbuilder.tostring(); try { Class.forName(driver); hiveconn = DriverManager.getConnection(url, "", ""); 文档版本 01 ( ) 144

155 5 MapReduce 应用开发 statement = hiveconn.preparestatement(sql); catch (Exception e) { LOG.error("Init jdbc driver failed.", e); // 创建 hbase 连接 try { // Create a HBase connection hbaseconn = ConnectionFactory.createConnection(conf); // get table table = hbaseconn.gettable(tablename.valueof(hbase_table_name)); catch (IOException e) { LOG.error("Exception occur when connect to HBase", e); throw e; if (line.contains("male")) { name = line.substring(0, line.indexof(",")); // 1. 读取 HBase 数据 String hbasedata = readhbase(); // 2. 读取 Hive 数据 String hivedata = readhive(name); // Map 输出键值对, 内容为 HBase 与 Hive 数据拼接的字符串 context.write(new Text(name), new Text("hbase:" + hbasedata + ", hive:" + hivedata)); 样例 2:HBase 数据读取的 readhbase 方法 private String readhbase() { String tablename = "table1"; String columnfamily = "cf"; String hbasekey = "1"; String hbasevalue; Configuration hbaseconfig = HBaseConfiguration.create(conf); org.apache.hadoop.hbase.client.connection conn = null; try { // 创建一个 HBase Get 请求实例 Get get = new Get(hbaseKey.getBytes()); // 提交 Get 请求 Result result = table.get(get); hbasevalue = Bytes.toString(result.getValue(columnFamily.getBytes(), "cid".getbytes())); return hbasevalue; catch (IOException e) { LOG.warn("Exception occur ", e); finally { if (hbaseconn!= null) { try { hbaseconn.close(); catch (Exception e1) { LOG.error("Failed to close the connection ", e1); return ""; 样例 3:Hive 数据读取的 readhive 方法 private int readhive(string name) { ResultSet resultset = null; try { 文档版本 01 ( ) 145

156 5 MapReduce 应用开发 statement.setstring(1, name); resultset = statement.executequery(); if (resultset.next()) { return resultset.getint("staytime"); catch (SQLException e) { LOG.warn("Exception occur ", e); finally { if (null!= resultset) { try { resultset.close(); catch (SQLException e) { // handle exception if (null!= statement) { try { statement.close(); catch (SQLException e) { // handle exception if (null!= hiveconn) { try { hiveconn.close(); catch (SQLException e) { // handle exception return 0; 样例 4: 类 MultiComponentReducer 定义 Reducer 抽象类的 reduce 方法 public void reduce(text key, Iterable<Text> values, Context context) throws IOException, InterruptedException { Text finalvalue = new Text(""); setjaasinfo("krb5.conf", "jaas.conf"); LoginUtil.setJaasConf(ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME, "test", KEYTAB); LoginUtil.setZookeeperServerPrincipal(ZOOKEEPER_SERVER_PRINCIPAL_KEY, ZOOKEEPER_DEFAULT_SERVER_PRINCIPAL); conf = context.getconfiguration(); try { // 创建 hbase 连接 conn = ConnectionFactory.createConnection(conf); // 得到表 table = conn.gettable(tablename.valueof(hbase_table_name)); catch (IOException e) { LOG.error("Exception occur when connect to HBase", e); throw e; for (Text value : values) { finalvalue = value; // 将结果输出到 HBase writehbase(key.tostring(), finalvalue.tostring()); // 将结果保存到 HDFS context.write(key, finalvalue); 样例 5: 结果输出到 HBase 的 writehbase 方法 文档版本 01 ( ) 146

157 5 MapReduce 应用开发 private void writehbase(string rowkey, String data) { try { // 创建一个 HBase Put 请求实例 List<Put> list = new ArrayList<Put>(); byte[] row = Bytes.toBytes("1"); Put put = new Put(row); byte[] family = Bytes.toBytes("cf"); byte[] qualifier = Bytes.toBytes("value"); byte[] value = Bytes.toBytes(data); put.addcolumn(family, qualifier, value); list.add(put); // 执行 Put 请求 table.put(list); catch (IOException e) { LOG.warn("Exception occur ", e); finally { if (conn!= null) { try { conn.close(); catch (Exception e1) { LOG.error("Failed to close the connection ", e1); 样例 6:main() 方法创建一个 job, 配置相关依赖, 配置相关鉴权信息, 提交作业到 hadoop 集群 public static void main(string[] args) throws Exception { // 清理所需目录 MultiComponentExample.cleanupBeforeRun(); // 查找 Hive 依赖 jar 包 Class hivedriverclass = Class.forName("org.apache.hive.jdbc.HiveDriver"); Class thriftclass = Class.forName("org.apache.thrift.TException"); Class thriftcliclass = Class.forName("org.apache.hive.service.cli.thrift.TCLIService"); Class hiveconfclass = Class.forName("org.apache.hadoop.hive.conf.HiveConf"); Class hivetransclass = Class.forName("org.apache.thrift.transport.HiveTSaslServerTransport"); Class hivemetaclass = Class.forName("org.apache.hadoop.hive.metastore.api.MetaException"); Class hiveshimclass = Class.forName("org.apache.hadoop.hive.thrift.HadoopThriftAuthBridge23"); // 添加 Hive 运行依赖到 Job JarFinderUtil.addDependencyJars(config, hivedriverclass, thriftcliclass, thriftclass, hiveconfclass, hivetransclass, hivemetaclass, hiveshimclass); // 安全集群登录 if("kerberos".equalsignorecase(config.get("hadoop.security.authentication"))){ //security mode LoginUtil.setJaasConf(ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME, PRINCIPAL, KEYTAB); LoginUtil.setZookeeperServerPrincipal(ZOOKEEPER_SERVER_PRINCIPAL_KEY, ZOOKEEPER_DEFAULT_SERVER_PRINCIPAL); System.setProperty("java.security.krb5.conf", KRB); LoginUtil.login(PRINCIPAL, KEYTAB, KRB, config); // 添加 Hive 配置文件 config.addresource("hive-site.xml"); // 添加 HBase 配置文件 Configuration conf = HBaseConfiguration.create(config); // 实例化 Job Job job = Job.getInstance(conf); job.setjarbyclass(multicomponentexample.class); 文档版本 01 ( ) 147

158 5 MapReduce 应用开发 // 设置 mapper&reducer 类 job.setmapperclass(multicomponentmapper.class); job.setreducerclass(multicomponentreducer.class); // 设置 Job 输入输出路径 FileInputFormat.addInputPath(job, new Path(baseDir, INPUT_DIR_NAME + File.separator + "data.txt")); FileOutputFormat.setOutputPath(job, new Path(baseDir, OUTPUT_DIR_NAME)); // 设置输出键值类型 job.setoutputkeyclass(text.class); job.setoutputvalueclass(text.class); // HBase 提供工具类添加 HBase 运行依赖到 Job TableMapReduceUtil.addDependencyJars(job); // 安全模式下必须要执行这个操作 // HBase 添加鉴权信息到 Job,map 或 reduce 任务将会使用此处的鉴权信息 TableMapReduceUtil.initCredentials(job); // 创建 Hive 鉴权信息 Properties clientinfo = null; InputStream fileinputstream = null; try { clientinfo = new Properties(); File propertiesfile = new File(hiveClientProperties); fileinputstream = new FileInputStream(propertiesFile); clientinfo.load(fileinputstream); catch (Exception e) { finally { if (fileinputstream!= null) { fileinputstream.close(); String zkquorum = clientinfo.getproperty("zk.quorum");//zookeeper 节点 ip 和端口列表 String zookeepernamespace = clientinfo.getproperty("zookeepernamespace"); String servicediscoverymode = clientinfo.getproperty("servicediscoverymode"); String principal = clientinfo.getproperty("principal"); String auth = clientinfo.getproperty("auth"); String sasl_qop = clientinfo.getproperty("sasl.qop"); StringBuilder sbuilder = new StringBuilder("jdbc:hive2://").append(zkQuorum).append("/"); sbuilder.append(";servicediscoverymode=").append(servicediscoverymode).append(";zookeepernamespace= ").append(zookeepernamespace).append(";sasl.qop=").append(sasl_qop).append(";auth=").append(auth).append(";principal=").append(principal).append(";"); String url = sbuilder.tostring(); Connection connection = DriverManager.getConnection(url, "", ""); String tokenstr = ((HiveConnection) connection).getdelegationtoken(usergroupinformation.getcurrentuser().getshortusername(), PRINCIPAL); connection.close(); Token<DelegationTokenIdentifier> hive2token = new Token<DelegationTokenIdentifier>(); hive2token.decodefromurlstring(tokenstr); // 添加 Hive 鉴权信息到 Job job.getcredentials().addtoken(new Text("hive.server2.delegation.token"), hive2token); job.getcredentials().addtoken(new Text(HiveAuthFactory.HS2_CLIENT_TOKEN), hive2token); // 提交作业 System.exit(job.waitForCompletion(true)? 0 : 1); 文档版本 01 ( ) 148

159 5 MapReduce 应用开发 说明 样例中所有 zkquorum 对象需替换为实际 ZooKeeper 集群节点信息 5.4 调测程序 编译并运行程序 在程序代码完成开发后, 可以在 Linux 环境中运行应用 操作步骤 步骤 1 生成 MapReduce 应用可执行包 执行 mvn package 打出 jar 包, 在工程目录 target 目录下获取, 比如 mapreduceexamples-1.0.jar 步骤 2 步骤 3 步骤 4 上传生成的应用包 mapreduce-examples-1.0.jar 到 Linux 客户端上 例如 /opt 目录 如果集群开启 kerberos, 参考 准备开发用户获得的 user.keytab krb5.conf 文件需要在 Linux 环境上创建文件夹保存这些配置文件, 例如 /srv/client/conf 并在 linux 环境上获得 core-site.xml hdfs-site.xml 文件放入上述文件夹里 样例程序如果指定 OBS 为输入输出的目标文件系统 ( 如 s3a://<bucketname>/input/), 需要进行以下配置 : 在 $YARN_CONF_DIR/core-site.xml 中添加 AK 配置项 fs.s3a.access.key 和 SK 配置项 fs.s3a.secret.key,ak/sk 可登录 OBS 控制台, 进入 我的凭证 页面获取 <property> <name>fs.s3a.access.key</name> <value>xxxxxxxxxxxxxxxx</value> </property> <property> <name>fs.s3a.secret.key</name> <value>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</value> </property> 步骤 5 在 Linux 环境下运行样例工程 对于 MapReduce 统计样例程序, 执行如下命令 a. 如果集群开启 kerberos, 在 Linux 环境中添加样例工程运行所需的 classpath, 例如 export YARN_USER_CLASSPATH=/srv/client/conf/ b. 执行如下命令 : cd /opt yarn jar mapreduce-examples-1.0.jar com.huawei.hadoop.mapreduce.examples.femaleinfocollector <inputpath> <outputpath> 此命令包含了设置参数和提交 job 的操作, 其中 <inputpath> 指 HDFS 文件系统中 input 的路径,<outputPath> 指 HDFS 文件系统中 output 的路径 文档版本 01 ( ) 149

160 5 MapReduce 应用开发 说明 在执行 yarn jar mapreduce-examples-1.0.jar com.huawei.hadoop.mapreduce.examples.femaleinfocollector <inputpath> <outputpath> 命令之前, 需要把 log1.txt 和 log2.txt 这两个文件上传到 HDFS 的 <inputpath> 目录下 在执行 yarn jar mapreduce-examples-1.0.jar com.huawei.hadoop.mapreduce.examples.femaleinfocollector <inputpath> <outputpath> 命令之前,<outputPath> 目录必须不存在, 否则会报错 在 MapReduce 任务运行过程中禁止重启 HDFS 服务, 否则可能会导致任务失败 对于 MapReduce 访问多组件样例程序, 操作步骤如下 ---- 结束 查看调测结果 a. 获取 hbase-site.xml hiveclient.properties 和 hive-site.xml 文件, 如果是安全模式集群, 还需要同时获取 user.keytab krb5.conf, 并在 Linux 环境上创建文件夹保存这些配置文件, 例如 /srv/client/conf 说明 请联系管理员获取相应帐号对应权限的 user.keytab 和 krb5.conf 文件, hbasesite.xml 从 HBase 客户端获取, hiveclient.properties 和 hive-site.xml 从 Hive 客户端获取 b. 对于安全模式集群, 在新建的文件夹中创建文件 jaas_mr.conf, 文件内容如下 : Client { com.sun.security.auth.module.krb5loginmodule required usekeytab=true keytab="user.keytab" principal="test@hadoop.com" useticketcache=false storekey=true debug=true; ; 说明 1. 文件内容中的 test@hadoop.com 为示例, 实际操作时请做相应修改 2. 非安全集群略过此步骤 c. 在 Linux 环境中添加样例工程运行所需的 classpath, 例如 ( 以客户端安装路径为 /opt/client 为例 ) export YARN_USER_CLASSPATH=/srv/client/conf/:/opt/client/HBase/ hbase/lib/*:/opt/client/hive/beeline/lib/*:/opt/client/hbase/adapter/client/ controller/publiclib/fiber/lib/slf4j-api jar d. 提交 MapReduce 任务, 执行如下命令, 运行样例工程 yarn jar mapreduce-examples-1.0.jar com.huawei.bigdata.mapreduce.examples.multicomponentexample MapReduce 应用程序运行完成后, 可以通过 WebUI 查看应用程序运行情况, 也可以通过 MapReduce 日志获取应用运行情况 通过 MapReduce 服务的 WebUI 进行查看 登录 MRS Manager, 单击 服务管理 > MapReduce > JobHistoryServer 进入 Web 界面后查看任务执行状态 文档版本 01 ( ) 150

161 5 MapReduce 应用开发 图 5-3 JobHistory Web UI 界面 通过 YARN 服务的 WebUI 进行查看 登录 MRS Manager, 单击 服务管理 > Yarn > ResourceManager( 主 ) 进入 Web 界面后查看任务执行状态 图 5-4 ResourceManager Web UI 页面 查看 MapReduce 应用运行结果数据 当用户在 Linux 环境下执行 yarn jar mapreduce-example.jar 命令后, 可以通过执行结果显示正在执行的应用的运行情况 例如 : yarn jar mapreduce-example.jar /tmp/mapred/example/input/ /tmp/root/output/1 16/07/12 17:07:16 INFO hdfs.peercache: SocketCache disabled. 16/07/12 17:07:17 INFO input.fileinputformat: Total input files to process : 2 16/07/12 17:07:18 INFO mapreduce.jobsubmitter: number of splits:2 16/07/12 17:07:18 INFO mapreduce.jobsubmitter: Submitting tokens for job: job_ _ /07/12 17:07:18 INFO impl.yarnclientimpl: Submitted application application_ _ /07/12 17:07:18 INFO mapreduce.job: The url to track the job: :26000/proxy/application_ _0006/ 16/07/12 17:07:18 INFO mapreduce.job: Running job: job_ _ /07/12 17:07:31 INFO mapreduce.job: Job job_ _0006 running in uber mode : false 16/07/12 17:07:31 INFO mapreduce.job: map 0% reduce 0% 16/07/12 17:07:41 INFO mapreduce.job: map 50% reduce 0% 16/07/12 17:07:43 INFO mapreduce.job: map 100% reduce 0% 16/07/12 17:07:51 INFO mapreduce.job: map 100% reduce 100% 16/07/12 17:07:51 INFO mapreduce.job: Job job_ _0006 completed successfully 16/07/12 17:07:51 INFO mapreduce.job: Counters: 49 File System Counters FILE: Number of bytes read=75 FILE: Number of bytes written= FILE: Number of read operations=0 FILE: Number of large read operations=0 FILE: Number of write operations=0 HDFS: Number of bytes read=674 HDFS: Number of bytes written=23 HDFS: Number of read operations=9 HDFS: Number of large read operations=0 HDFS: Number of write operations=2 Job Counters Launched map tasks=2 Launched reduce tasks=1 Data-local map tasks=2 Total time spent by all maps in occupied slots (ms)= Total time spent by all reduces in occupied slots (ms)=56280 文档版本 01 ( ) 151

162 5 MapReduce 应用开发 Total time spent by all map tasks (ms)=18123 Total time spent by all reduce tasks (ms)=7035 Total vcore-milliseconds taken by all map tasks=18123 Total vcore-milliseconds taken by all reduce tasks=7035 Total megabyte-milliseconds taken by all map tasks= Total megabyte-milliseconds taken by all reduce tasks= Map-Reduce Framework Map input records=26 Map output records=16 Map output bytes=186 Map output materialized bytes=114 Input split bytes=230 Combine input records=16 Combine output records=6 Reduce input groups=3 Reduce shuffle bytes=114 Reduce input records=6 Reduce output records=2 Spilled Records=12 Shuffled Maps =2 Failed Shuffles=0 Merged Map outputs=2 GC time elapsed (ms)=202 CPU time spent (ms)=2720 Physical memory (bytes) snapshot= Virtual memory (bytes) snapshot= Total committed heap usage (bytes)= Shuffle Errors BAD_ID=0 CONNECTION=0 IO_ERROR=0 WRONG_LENGTH=0 WRONG_MAP=0 WRONG_REDUCE=0 File Input Format Counters Bytes Read=444 File Output Format Counters Bytes Written=23 在 Linux 环境下执行 yarn application -status <ApplicationId>, 可以通过执行结果显示正在执行的应用的运行情况 例如 : yarn application -status application_ _0006 Application Report : Application-Id : application_ _0006 Application-Name : Collect Female Info Application-Type : MAPREDUCE User : root Queue : default Start-Time : Finish-Time : Progress : 100% State : FINISHED Final-State : SUCCEEDED Tracking-URL : RPC Port : AM Host : Aggregate Resource Allocation : MB-seconds, 64 vcore-seconds Log Aggregation Status : SUCCEEDED Diagnostics : Application finished execution. Application Node Label Expression : <Not set> AM container Node Label Expression : <DEFAULT_PARTITION> 查看 MapReduce 日志获取应用运行情况 您可以查看 MapReduce 日志了解应用运行情况, 并根据日志信息调整应用程序 文档版本 01 ( ) 152

163 5 MapReduce 应用开发 5.5 MapReduce 接口 MapReduce 常用接口 MapReduce 中常见的类如下 : org.apache.hadoop.mapreduce.job: 用户提交 MR 作业的接口, 用于设置作业参数 提交作业 控制作业执行以及查询作业状态 org.apache.hadoop.mapred.jobconf:mapreduce 作业的配置类, 是用户向 Hadoop 提交作业的主要配置接口 表 5-3 类 org.apache.hadoop.mapreduce.job 的常用接口 功能 Job(Configuration conf, String jobname), Job(Configuration conf) setmapperclass(class<extend s Mapper> cls) setreducerclass(class<exten ds Reducer> cls) setcombinerclass(class<exte nds Reducer> cls) setinputformatclass(class<ex tends InputFormat> cls) setjarbyclass(class< > cls) setjar(string jar) setoutputformatclass(class< extends OutputFormat> theclass) 说明 新建一个 MapReduce 客户端, 用于配置作业属性, 提交作业 核心接口, 指定 MapReduce 作业的 Mapper 类, 默认为空 也可以在 mapred-site.xml 中配置 mapreduce.job.map.class 项 核心接口, 指定 MapReduce 作业的 Reducer 类, 默认为空 也可以在 mapred-site.xml 中配置 mapreduce.job.reduce.class 项 指定 MapReduce 作业的 Combiner 类, 默认为空 也可以在 mapred-site.xml 中配置 mapreduce.job.combine.class 项 需要保证 reduce 的输入输出 key,value 类型相同才可以使用, 谨慎使用 核心接口, 指定 MapReduce 作业的 InputFormat 类, 默认为 TextInputFormat 也可以在 mapred-site.xml 中配置 mapreduce.job.inputformat.class 项 该设置用来指定处理不同格式的数据时需要的 InputFormat 类, 用来读取数据, 切分数据块 核心接口, 指定执行类所在的 jar 包本地位置 java 通过 class 文件找到执行 jar 包, 该 jar 包被上传到 HDFS 指定执行类所在的 jar 包本地位置 直接设置执行 jar 包所在位置, 该 jar 包被上传到 HDFS 与 setjarbyclass(class< > cls) 选择使用一个 也可以在 mapred-site.xml 中配置 mapreduce.job.jar 项 核心接口, 指定 MapReduce 作业的 OutputFormat 类, 默认为 TextOutputFormat 也可以在 mapredsite.xml 中配置 mapred.output.format.class 项, 指定输出结果的数据格式 例如默认的 TextOutputFormat 把每条 key,value 记录写为文本行 通常场景不配置特定的 OutputFormat 文档版本 01 ( ) 153

164 5 MapReduce 应用开发 功能 setoutputkeyclass(class< > theclass) setoutputvalueclass(class< > theclass) setpartitionerclass(class<exte nds Partitioner> theclass) setsortcomparatorclass(class <extends RawComparator> cls) setpriority(jobpriority priority) 说明 核心接口, 指定 MapReduce 作业的输出 key 的类型, 也可以在 mapred-site.xml 中配置 mapreduce.job.output.key.class 项 核心接口, 指定 MapReduce 作业的输出 value 的类型, 也可以在 mapred-site.xml 中配置 mapreduce.job.output.value.class 项 指定 MapReduce 作业的 Partitioner 类 也可以在 mapred-site.xml 中配置 mapred.partitioner.class 项 该方法用来分配 map 的输出结果到哪个 reduce 类, 默认使用 HashPartitioner, 均匀分配 map 的每条键值对记录 例如在 hbase 应用中, 不同的键值对应的 region 不同, 这就需要设定特殊的 partitioner 类分配 map 的输出结果 指定 MapReduce 作业的 map 任务的输出结果压缩类, 默认不使用压缩 也可以在 mapred-site.xml 中配置 mapreduce.map.output.compress 和 mapreduce.map.output.compress.codec 项 当 map 的输出数据大, 减少网络压力, 使用压缩传输中间数据 指定 MapReduce 作业的优先级, 共有 5 个优先级别, VERY_HIGH,HIGH,NORMAL,LOW,VERY_LOW, 默认级别为 NORMAL 也可以在 mapred-site.xml 中配置 mapreduce.job.priority 项 表 5-4 类 org.apache.hadoop.mapred.jobconf 的常用接口 方法 setnummaptasks(int n) setnumreducetasks(int n) setqueuename(string queuename) 说明 核心接口, 指定 MapReduce 作业的 map 个数 也可以在 mapred-site.xml 中配置 mapreduce.job.maps 项 说明指定的 InputFormat 类用来控制 map 任务个数, 注意该类是否支持客户端设定 map 个数 核心接口, 指定 MapReduce 作业的 reduce 个数 默认只启动 1 个 也可以在 mapred-site.xml 中配置 mapreduce.job.reduces 项 reduce 个数由用户控制, 通常场景 reduce 个数是 map 个数的 1/4 指定 MapReduce 作业的提交队列 默认使用 default 队列 也可以在 mapredsite.xml 中配置 mapreduce.job.queuename 项 文档版本 01 ( ) 154

165 5 MapReduce 应用开发 5.6 FAQ 提交 MapReduce 任务时客户端长时间无响应 问题 回答 向 YARN 服务器提交 MapReduce 任务后, 客户端长时间无响应 对于上述出现的问题,ResourceManager 在其 WebUI 上提供了 MapReduce 作业关键步骤的诊断信息, 对于一个已经提交到 YARN 上的 MapReduce 任务, 用户可以通过该诊断信息获取当前作业的状态以及处于该状态的原因 具体操作 : 在公有云管理控制台, 选择 基本信息 > Yarn 监控信息 进入 Web 界面, 单击提交的 MapReduce 任务, 在打开的页面中查看诊断信息, 根据诊断信息再采取相应的措施 或者也可以通过查看 MapReduce 日志了解应用运行情况, 并根据日志信息调整应用程序 5.7 开发规范 规则 继承 Mapper 抽象类实现 在 MapReduce 任务的 Map 阶段, 会执行 map() 及 setup() 方法 正确示例 : public static class MapperClass extends Mapper<Object, Text, Text, IntWritable> { /** * map 的输入,key 为原文件位置偏移量,value 为原文件的一行字符数据 * 其 map 的输入 key,value 为文件分割方法 InputFormat 提供, 用户不设置, 默认 * 使用 TextInputFormat */ public void map(object key, Text value, Context context) throws IOException, InterruptedException { // 自定义的实现 /** * setup() 方法只在进入 map 任务的 map() 方法之前或者 reduce 任务的 reduce() 方法之前调用一次 */ public void setup(context context) throws IOException, InterruptedException { // 自定义的实现 继承 Reducer 抽象类实现 在 MapReduce 任务的 Reduce 阶段, 会执行 reduce() 及 setup() 方法 文档版本 01 ( ) 155

166 5 MapReduce 应用开发 正确示例 : public static class ReducerClass extends Reducer<Text, IntWritable, Text, IntWritable> { /** 输入为一个 key 和 value 值集合迭代器 * 由各个 map 汇总相同的 key 而来 reduce 方法汇总相同 key 的个数 * 并调用 context.write(key, value) 输出到指定目录 * 其 reduce 的输出的 key,value 由 Outputformat 写入文件系统 * 默认使用 TextOutputFormat 写入 HDFS */ public void reduce(text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { // 自定义实现 /** * setup() 方法只在进入 map 任务的 map() 方法之前或者 reduce 任务的 reduce() 方法之前调用一次 */ public void setup(context context) throws IOException, InterruptedException { // 自定义实现,Context 可以获得配置信息 提交一个 MapReduce 任务 main() 方法创建一个 job, 指定参数, 提交作业到 hadoop 集群 正确示例 : public static void main(string[] args) throws Exception { Configuration conf = getconfiguration(); // main 方法输入参数 :args[0] 为样例 MR 作业输入路径,args[1] 为样例 MR 作业输出路径 String[] otherargs = new GenericOptionsParser(conf, args).getremainingargs(); if (otherargs.length!= 2) { System.err.println("Usage: <in> <out>"); System.exit(2); Job job = new Job(conf, "job name"); // 设置找到主任务所在的 jar 包 job.setjar("d:\\job-examples.jar"); // job.setjarbyclass(testwordcount.class); // 设置运行时执行 map,reduce 的类, 也可以通过配置文件指定 job.setmapperclass(tokenizermapperv1.class); job.setreducerclass(intsumreducerv1.class); // 设置 combiner 类, 默认不使用, 使用时通常使用和 reduce 一样的类,Combiner 类需要谨慎使用, 也可以通过配置文件指定 job.setcombinerclass(intsumreducerv1.class); // 设置作业的输出类型, 也可以通过配置文件指定 job.setoutputkeyclass(text.class); job.setoutputvalueclass(intwritable.class); // 设置该 job 的输入输出路径, 也可以通过配置文件指定 Path outputpath = new Path(otherArgs[1]); FileSystem fs = outputpath.getfilesystem(conf); // 如果输出路径已存在, 删除该路径 if (fs.exists(outputpath)) { fs.delete(outputpath, true); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); 文档版本 01 ( ) 156

167 5 MapReduce 应用开发 System.exit(job.waitForCompletion(true)? 0 : 1); 对资源消耗较大的操作不要放到 map 或 reduce 函数中 多线程安全登录方式 建议 对资源消耗较大的操作比如创建数据库链接, 打开关闭文件等, 不要放到 map 或 reduce 函数中 如果有多线程进行 login 的操作, 当应用程序第一次登录成功后, 所有线程再次登录时应该使用 relogin 的方式 login 的代码样例 : private Boolean login(configuration conf){ boolean flag = false; UserGroupInformation.setConfiguration(conf); try { UserGroupInformation.loginUserFromKeytab(conf.get(PRINCIPAL), conf.get(keytab)); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; relogin 的代码样例 : public Boolean relogin(){ boolean flag = false; try { UserGroupInformation.getLoginUser().reloginFromKeytab(); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; 全局使用的配置项, 在 mapred-site.xml 中指定 如下给出接口所对应的 mapred-site.xml 中的配置项 : setmapperclass(class <extends Mapper> cls) -> mapreduce.job.map.class setreducerclass(class<extends Reducer> cls) -> mapreduce.job.reduce.class setcombinerclass(class<extends Reducer> cls) -> mapreduce.job.combine.class setinputformatclass(class<extends InputFormat> cls) - > mapreduce.job.inputformat.class setjar(string jar) -> mapreduce.job.jar setoutputformat(class< extends OutputFormat> theclass) -> mapred.output.format.class setoutputkeyclass(class<> theclass) -> mapreduce.job.output.key.class 文档版本 01 ( ) 157

168 5 MapReduce 应用开发 setoutputvalueclass(class<> theclass) -> mapreduce.job.output.value.class setpartitionerclass(class<extends Partitioner> theclass) -> mapred.partitioner.class setmapoutputcompressorclass(class<extends CompressionCodec> codecclass) -> mapreduce.map.output.compress & mapreduce.map.output.compress.codec setjobpriority(jobpriority prio) -> mapreduce.job.priority setqueuename(string queuename) -> mapreduce.job.queuename setnummaptasks(int n) -> mapreduce.job.maps setnumreducetasks(int n) -> mapreduce.job.reduces 示例 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 主要分为三个部分 : 1. 从原文件中筛选女性网民上网时间数据信息, 通过类 MapperClass 继承 Mapper 抽象类实现 2. 汇总每个女性上网时间, 并输出时间大于两个小时的女性网民信息, 通过类 ReducerClass 继承 Reducer 抽象类实现 3. main 方法提供建立一个 MapReduce job, 并提交 MapReduce 作业到 hadoop 集群 样例 1: 类 MapperClass 定义 Mapper 抽象类的 map() 方法和 setup() 方法 public static class MapperClass extends Mapper<Object, Text, Text, IntWritable> { // 分隔符 String delim; // 性别筛选 String sexfilter; private final static IntWritable timeinfo = new IntWritable(1); private Text nameinfo = new Text(); /** * map 的输入,key 为原文件位置偏移量,value 为原文件的一行字符数据 * 其 map 的输入 key,value 为文件分割方法 InputFormat 提供, 用户不设置, 默认使用 TextInputFormat */ public void map(object key, Text value, Context context) throws IOException, InterruptedException { // 读取的一行字符串数据 String line = value.tostring(); if (line.contains(sexfilter)) { // 获取姓名 String name = line.substring(0, line.indexof(delim)); nameinfo.set(name); // 获取上网停留时间 String time = line.substring(line.lastindexof(delim), line.length()); timeinfo.set(integer.parseint(time)); // map 输出 key,value 键值对 context.write(nameinfo, timeinfo); /** * setup() 方法只在进入 map 任务的 map() 方法之前或者 reduce 任务的 reduce() 方法之前调用一次 */ public void setup(context context) throws IOException, InterruptedException { 文档版本 01 ( ) 158

169 5 MapReduce 应用开发 // 通过 Context 可以获得配置信息 sexfilter = delim + context.getconfiguration().get("log.sex.filter", "female") + delim; 样例 2: 类 ReducerClass 定义 Reducer 抽象类的 reduce() 方法 public static class ReducerClass extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); // 总时间门槛 private int timethreshold; /** 输入为一个 key 和 value 值集合迭代器 * 由各个 map 汇总相同的 key 而来 reduce 方法汇总相同 key 的个数 * 并调用 context.write(key, value) 输出到指定目录 * 其 reduce 的输出的 key,value 由 Outputformat 写入文件系统 * 默认使用 TextOutputFormat 写入 HDFS */ public void reduce(text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); // 如果时间小于门槛时间, 不输出结果 if (sum < timethreshold) { return; result.set(sum); // reduce 输出为 key: 网民的信息,value: 该网民上网总时间 context.write(key, result); /** * setup() 方法只在进入 map 任务的 map() 方法之前或者 reduce 任务的 reduce() 方法之前调用一次 */ public void setup(context context) throws IOException, InterruptedException { // Context 可以获得配置信息 timethreshold = context.getconfiguration().getint( "log.time.threshold", 120); 样例 3:main() 方法创建一个 job, 指定参数, 提交作业到 hadoop 集群 public static void main(string[] args) throws Exception { Configuration conf = getconfiguration(); // main 方法输入参数 :args[0] 为样例 MR 作业输入路径,args[1] 为样例 MR 作业输出路径 String[] otherargs = new GenericOptionsParser(conf, args).getremainingargs(); if (otherargs.length!= 2) { System.err.println("Usage: <in> <out>"); System.exit(2); Job job = new Job(conf, "Collect Female Info"); // 设置找到主任务所在的 jar 包 job.setjar("d:\\mapreduce-examples\\hadoop-mapreduce-examples\\mapreduce-examples.jar"); // job.setjarbyclass(testwordcount.class); // 设置运行时执行 map,reduce 的类, 也可以通过配置文件指定 job.setmapperclass(tokenizermapperv1.class); job.setreducerclass(intsumreducerv1.class); // 设置 combiner 类, 默认不使用, 使用时通常使用和 reduce 一样的类,Combiner 类需要谨慎使用, 也可以通过配置文件指定 job.setcombinerclass(intsumreducerv1.class); // 设置作业的输出类型, 也可以通过配置文件指定 job.setoutputkeyclass(text.class); job.setoutputvalueclass(intwritable.class); // 设置该 job 的输入输出路径, 也可以通过配置文件指定 Path outputpath = new Path(otherArgs[1]); 文档版本 01 ( ) 159

170 5 MapReduce 应用开发 FileSystem fs = outputpath.getfilesystem(conf); // 如果输出路径已存在, 删除该路径 if (fs.exists(outputpath)) { fs.delete(outputpath, true); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); System.exit(job.waitForCompletion(true) 0 : 1); 文档版本 01 ( ) 160

171 6 HDFS 应用开发 6 HDFS 应用开发 6.1 概述 HDFS 简介 HDFS 简介 HDFS 开发接口简介 常用概念 DataNode NameNode HDFS(Hadoop Distribute FileSystem) 是一个适合运行在通用硬件之上, 具备高度容错特性, 支持高吞吐量数据访问的分布式文件系统, 适合大规模数据集应用 HDFS 适用于如下场景 : 处理海量数据 (TB 或 PB 级别以上 ) 需要很高的吞吐量 需要高可靠性 需要很好的可扩展能力 HDFS 支持使用 Java 语言进行程序开发, 具体的 API 接口内容请参考 Java API 将文件切分成大小相同的块 ( 称为 数据块 ), 存储在不同的 DataNode 上, 并且周期性地向 NameNode 报告该 DataNode 的数据存放情况 用于管理文件系统的命名空间 目录结构 元数据信息以及提供备份机制等, 分为 : Active NameNode: 主 NameNode, 管理文件系统的命名空间 维护文件系统的目录结构树以及元数据信息 ; 记录写入的每个 数据块 与其归属文件的对应关系 文档版本 01 ( ) 161

172 6 HDFS 应用开发 Standby NameNode: 备 NameNode, 与主 NameNode 中的数据保持同步 ; 随时准备在主 NameNode 出现异常时接管其服务 Journalnode 高可用性 (High availability,ha) 集群下, 用于同步主备 NameNode 之间的元数据信息 ZKFC ZKFC 是需要和 NameNode 一一对应的服务, 即每个 NameNode 都需要部署 ZKFC 它负责监控 NameNode 的状态, 并及时把状态写入 Zookeeper ZKFC 有选择哪个 NameNode 作为主 NameNode 的权利 Colocation 同分布 (Colocation) 功能是将存在关联关系的数据或可能要进行关联操作的数据存储在相同的存储节点上 HDFS 文件同分布的特性是将那些需进行关联操作的文件存放在相同的数据节点上, 在进行关联操作计算时, 避免了到别的数据节点上获取数据的动作, 降低了网络带宽的占用 Client HDFS Client 主要包括五种方式 :JAVA API C API Shell HTTP REST API WEB UI Java API 提供 HDFS 文件系统的应用接口, 本开发指南主要介绍如何使用 Java APIJava API 进行 HDFS 文件系统的应用开发 C API 提供 HDFS 文件系统的应用接口, 使用 C 语言开发的用户可参考 C 接口 C API 的描述进行应用开发 Shell 提供 shell 命令 Shell 命令介绍完成 HDFS 文件系统的基本操作 HTTP REST API 提供除 Shell Java API 和 C API 以外的其他接口, 可通过此接口 HTTP REST API 监控 HDFS 状态等信息 WEB UI 提供 Web 可视化组件管理界面 keytab 文件 开发流程 存放用户信息的密钥文件 应用程序采用此密钥文件在 MRS Hadoop 组件中进行 API 方式认证 开发流程中各阶段的说明如图 6-1 和表 6-1 所示 文档版本 01 ( ) 162

173 6 HDFS 应用开发 图 6-1 HDFS 应用程序开发流程 表 6-1 HDFS 应用开发的流程说明 阶段说明参考文档 了解基本概念 准备开发环境 准备运行环境 下载并导入样例工程 在开始开发应用前, 需要了解 HDFS 的基本概念 使用 Eclipse 工具, 请根据指导完成开发环境配置 HDFS 的运行环境即 HDFS 客户端, 请根据指导完成客户端的安装和配置 HDFS 提供了不同场景下的样例程序, 可以导入样例工程进行程序学习 常用概念 准备 Eclipse 与 JDK 准备 Linux 客户端运行环境 获取并导入样例工程 文档版本 01 ( ) 163

174 6 HDFS 应用开发 阶段说明参考文档 根据场景开发工程 编译并运行程序 查看程序运行结果 提供样例工程, 帮助用户快速了解 HDFS 各部件的编程接口 指导用户将开发好的程序编译并提交运行 程序运行结果会写在用户指定的路径下 用户还可以通过 UI 查看应用运行情况 开发程序 Linux: 安装客户端时编译并运行程序 Linux: 查看调测结果 6.2 环境准备 开发环境简介 在进行应用开发时, 要准备的开发环境如表 6-2 所示 表 6-2 开发环境 准备项 Eclipse JDK 说明 开发环境的基本配置 版本要求 :4.2 或以上 JDK 使用 1.7 或者 1.8 版本 说明基于安全考虑,MRS 集群服务端只支持 TLS 1.1 和 TLS 1.2 加密协议,IBM JDK 默认 TLS 只支持 1.0, 若使用 IBM JDK, 请配置启动参数 com.ibm.jsse2.overridedefaulttls 为 true, 设置后可以同时支持 TLS1.0/1.1/1.2 详情请参见 : SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/ jsse2docs/matchsslcontext_tls.html#matchsslcontext_tls 准备开发用户 前提条件操作场景操作步骤 MRS 服务集群开启了 Kerberos 认证, 没有开启 Kerberos 认证的集群忽略该步骤 开发用户用于运行样例工程 用户需要有 HDFS 权限, 才能运行 HDFS 样例工程 步骤 1 登录 MRS Manager, 在 MRS Manager 界面选择 系统设置 > 角色管理 > 添加角色 文档版本 01 ( ) 164

175 6 HDFS 应用开发 1. 填写角色的名称, 例如 hdfsrole 2. 编辑角色, 在 权限 的表格中选择 HDFS > File System > hdfs://hacluster/, 勾选 Read Write 和 Execute, 单击 确定 保存 步骤 2 步骤 3 步骤 4 步骤 5 单击 系统设置 > 用户组管理 > 添加用户组, 为样例工程创建一个用户组, 例如 hdfsgroup 单击 系统设置 > 用户管理 > 添加用户, 为样例工程创建一个用户 填写用户名, 例如 hdfsuser, 用户类型为 机机 用户, 加入用户组 hdfsgroup 和 supergroup, 设置其 主组 为 supergroup, 并绑定角色 hdfsrole 取得权限, 单击 确定 在 MRS Manager 界面选择 系统设置 > 用户管理, 在用户名中选择 hdfsuser, 单击操 作中下载认证凭据文件, 保存后解压得到用户的 keytab 文件与 krb5.conf 文件 用于在样例工程中进行安全认证 ---- 结束 准备 Eclipse 与 JDK 前提条件操作场景操作步骤 MRS 服务集群开启了 Kerberos 认证在 Windows 环境下需要安装 Eclipse 和 JDK 步骤 1 开发环境安装 Eclipse 程序, 版本要求 : Eclipse 使用 4.2 或以上版本 步骤 2 开发环境安装 JDK 程序, 版本要求 : JDK 使用 1.7 或者 1.8 版本 说明 若使用 IBM JDK, 请确保 Eclipse 中的 JDK 配置为 IBM JDK 若使用 Oracle JDK, 请确保 Eclipse 中的 JDK 配置为 Oracle JDK 不同的 Eclipse 不要使用相同的 workspace 和相同路径下的示例工程 ---- 结束 准备 Linux 客户端运行环境 前提条件 1. 确认服务端 HDFS 组件已经安装, 并正常运行 2. 客户端运行环境已安装 1.7 或 1.8 版本的 JDK 3. 获取客户端安装包 MRS_Services_Client.tar 文档版本 01 ( ) 165

176 6 HDFS 应用开发 操作场景 在 Windows 上安装客户端 操作步骤 步骤 1 客户端机器的时间与 Hadoop 集群的时间要保持一致 ( 手动修改客户端机器或者集群的时间 ), 时间差小于 5 分钟 MRS 集群的时间可通过登录主管理节点 ( 集群管理 IP 地址所在节点 ) 运行 date 命令查询 步骤 2 下载 MapReduce 客户端程序到客户端机器中 1. 登录 MRS Manager 系统 在浏览器地址栏中输入访问地址, 地址格式为 Manager 系统的 WebService 浮动 IP 地址 :8080/web 例如, 在 IE 浏览器地址栏中, 输入 2. 选择 服务管理 > 下载客户端, 下载客户端程序到客户端机器 步骤 3 解压缩客户端文件包 MRS_Services_Client.tar 安装包为 tar 格式, 执行如下命令解压两次 tar -xvf MRS_Services_Client.tar tar -xvf MRS_Service_ClientConfig.tar 步骤 4 为运行环境设置环境变量, 假设安装包解压路径为 MRS_Services_ClientConfig/ 进入解压文件夹, 执行如下命令安装客户端 sh install.sh {client_install_home 步骤 5 进入客户端安装目录, 执行如下命令初始化环境变量 source bigdata_env 步骤 6 从服务端拷贝如下文件至 jar 包 ( 样例代码导出的 jar 包可参安装客户端时编译并运行程序 ) 同目录的 conf 目录下 表 6-3 配置文件 文件名称作用获取地址 core-site.xml 配置 HDFS 详细参数 ${HADOOP_HOME/etc/hadoop/ core-site.xml hdfs-site.xml 配置 HDFS 详细参数 ${HADOOP_HOME/etc/hadoop/ hdfs-site.xml user.keytab krb5.conf 对于 Kerberos 安全认证提供 HDFS 用户信息 Kerberos server 配置信息 如果是安全模式集群, 您可以联系管理员获取相应帐号对应权限的 keytab 文件和 krb5 文件 文档版本 01 ( ) 166

177 6 HDFS 应用开发 说明 ---- 结束 获取并导入样例工程 操作场景 操作步骤 表 6-3 中 ${HADOOP_HOME 表示服务端 Hadoop 的安装目录 keytab 文件会过期, 一般是 24 小时有效 因此超过 24 小时需要重新找管理员获取新下载的 keytab 文件 样例代码中 PRNCIPAL_NAME 的用户名要与获取 keytab 文件和 krb5 文件的账户名一致 不同集群的 user.keytab krb5.conf 不能共用 注意样例代码中, System.getProperty("user.dir") + File.separator + "conf" + File.separator + "user.keytab" 处使用的 keytab 文件需与用户的 keytab 一致 conf 目录下的 log4j.properties 文件客户根据自己的需要进行配置 HDFS 针对多个场景提供样例工程, 帮助客户快速学习 HDFS 工程 以下操作步骤以导入 HDFS 样例代码为例 步骤 1 步骤 2 导入样例工程到 Eclipse 开发环境 1. 第一种方法 : 打开 Eclipse, 选择 File > New > Java Project 2. 去掉对 Use default location 的勾选, 单击 Browse 显示 浏览文件夹 对话框 3. 选择样例工程文件夹 hdfs-examples, 单击 确定 4. 在 New Java Project 窗口单击 Finish 5. 第二种方法 : 打开 Eclipse, 依次选择 File > Import > Existing maven Projects into Workspace > Next, 在下一个页面单击 Browse, 显示 浏览文件夹 对话框 6. 选择样例工程文件夹 hdfs-examples, 单击 确定 7. 在 Import 窗口单击 Finish 设置 Eclipse 的文本文件编码格式, 解决乱码显示问题 1. 在 Eclipse 的菜单栏中, 选择 Window > Preferences 弹出 Preferences 窗口 2. 在左边导航上选择 General > Workspace, 在 Text file encoding 区域, 选中 Other, 并设置参数值为 UTF-8, 单击 Apply 后, 单击 OK, 如图 6-2 所示 文档版本 01 ( ) 167

178 6 HDFS 应用开发 图 6-2 设置 Eclipse 的编码格式 ---- 结束 6.3 开发程序 场景说明 通过典型场景, 可以快速学习和掌握 HDFS 的开发过程, 并对关键的接口函数有所了解 HDFS 的业务操作对象是文件, 代码样例中所涉及的文件操作主要包括创建文件夹 写文件 追加文件内容 读文件和删除文件 / 文件夹 ;HDFS 还有其他的业务处理, 例如设置文件权限等, 其他操作可以在掌握本代码样例之后, 再扩展学习 本代码样例讲解顺序为 : 1. HDFS 初始化 HDFS 初始化 2. 写文件写文件 3. 追加文件内容追加文件内容 4. 读文件读文件 5. 删除文件删除文件 6. Colocation Colocation 7. 设置存储策略设置存储策略 8. 设置标签表达式设置标签表达式 9. 访问 OBS 访问 OBS 文档版本 01 ( ) 168

179 6 HDFS 应用开发 开发思路 开发思路 样例代码说明 根据前述场景说明进行功能分解, 以上传一个新员工的信息为例, 对该员工的信息进行查询 追加 删除等, 可分为以下七部分 : 1. 通过 kerberos 认证 2. 调用 filesystem 中的 mkdir 接口创建目录 3. 调用 HdfsWriter 的 dowrite 接口写入信息 4. 调用 filesystem 中的 open 接口读取文件 5. 调用 HdfsWriter 的 doappend 接口追加信息 6. 调用 filesystem 中的 deleteonexit 接口删除文件 7. 调用 filesystem 中的 delete 接口删除文件夹 HDFS 初始化 功能简介 配置文件介绍 在使用 HDFS 提供的 API 之前, 需要先进行 HDFS 初始化操作 过程为 : 1. 加载 HDFS 服务配置文件, 并进行 kerberos 安全认证 2. 认证通过后, 实例化 Filesystem 3. 使用 HDFS 的 API 说明 此处 kerberos 安全认证需要使用到的 keytab 文件, 请提前准备 登录 HDFS 时会使用到如表 6-4 所示的配置文件 这些文件均已导入到 hdfs-example 工程的 conf 目录 表 6-4 配置文件 文件名称作用获取地址 core-site.xml 配置 HDFS 详细参数 MRS_Services_ClientConfig\HDFS \config\core-site.xml hdfs-site.xml 配置 HDFS 详细参数 MRS_Services_ClientConfig\HDFS \config\hdfs-site.xml user.keytab krb5.conf 对于 Kerberos 安全认证提供 HDFS 用户信息 Kerberos server 配置信息 如果是安全模式集群, 您可以联系管理员获取相应帐号对应权限的 keytab 文件和 krb5 文件 文档版本 01 ( ) 169

180 6 HDFS 应用开发 说明 不同集群的 user.keytab krb5.conf 不能共用 conf 目录下的 log4j.properties 文件客户根据自己的需要进行配置 代码样例 如下是代码片段, 详细代码请参考 com.huawei.bigdata.hdfs.examples 中的 HdfsMain 类 在 Linux 客户端运行应用和在 Windows 环境下运行应用的初始化代码相同, 代码样例如下所示 /** * 初始化, 获取一个 FileSystem 实例 * IOException */ private void init() throws IOException { confload(); authentication(); instancebuild(); /** * * 如果程序运行在 Linux 上, 则需要 core-site.xml hdfs-site.xml 的路径, * 修改为在 Linux 下客户端文件的绝对路径 * */ private void confload() throws IOException { conf = new Configuration(); // conf file conf.addresource(new Path(PATH_TO_HDFS_SITE_XML)); conf.addresource(new Path(PATH_TO_CORE_SITE_XML)); /** * kerberos security authentication * 如果程序运行在 Linux 上, 则需要 krb5.conf 和 keytab 文件的路径, * 修改为在 Linux 下客户端文件的绝对路径 并且需要将样例代码中的 keytab 文件和 principal 文件 * 分别修改为当前用户的 keytab 文件名和用户名 * */ private void authentication() throws IOException { // 安全模式 if ("kerberos".equalsignorecase(conf.get("hadoop.security.authentication"))) { System.setProperty("java.security.krb5.conf", PATH_TO_KRB5_CONF); LoginUtil.login(PRNCIPAL_NAME, PATH_TO_KEYTAB, PATH_TO_KRB5_CONF, conf); /** * build HDFS instance */ private void instancebuild() throws IOException { // get filesystem fsystem = FileSystem.get(conf); 在 Windows 环境和 Linux 环境下都需要运行 login 的代码样例, 用于第一次登录使用, 详细代码请参考 com.huawei.hadoop.security 中的 LoginUtil 类 public synchronized static void login(string userprincipal, String userkeytabpath, String krb5confpath, Configuration conf) throws IOException { // 1. 检查放入的参数 if ((userprincipal == null) (userprincipal.length() <= 0)) { LOG.error("input userprincipal is invalid."); 文档版本 01 ( ) 170

181 6 HDFS 应用开发 throw new IOException("input userprincipal is invalid."); if ((userkeytabpath == null) (userkeytabpath.length() <= 0)) { LOG.error("input userkeytabpath is invalid."); throw new IOException("input userkeytabpath is invalid."); if ((krb5confpath == null) (krb5confpath.length() <= 0)) { LOG.error("input krb5confpath is invalid."); throw new IOException("input krb5confpath is invalid."); if ((conf == null)) { LOG.error("input conf is invalid."); throw new IOException("input conf is invalid."); // 2. 检查文件是否存在 File userkeytabfile = new File(userKeytabPath); if (!userkeytabfile.exists()) { LOG.error("userKeytabFile(" + userkeytabfile.getabsolutepath() + ") does not exsit."); throw new IOException("userKeytabFile(" + userkeytabfile.getabsolutepath() + ") does not exsit."); if (!userkeytabfile.isfile()) { LOG.error("userKeytabFile(" + userkeytabfile.getabsolutepath() + ") is not a file."); throw new IOException("userKeytabFile(" + userkeytabfile.getabsolutepath() + ") is not a file."); File krb5conffile = new File(krb5ConfPath); if (!krb5conffile.exists()) { LOG.error("krb5ConfFile(" + krb5conffile.getabsolutepath() + ") does not exsit."); throw new IOException("krb5ConfFile(" + krb5conffile.getabsolutepath() + ") does not exsit."); if (!krb5conffile.isfile()) { LOG.error("krb5ConfFile(" + krb5conffile.getabsolutepath() + ") is not a file."); throw new IOException("krb5ConfFile(" + krb5conffile.getabsolutepath() + ") is not a file."); // 3. 设置并检查 krb5config setkrb5config(krb5conffile.getabsolutepath()); setconfiguration(conf); // 4. 检查是否需要登录 if (checkneedlogin(userprincipal)) { // 5. 登录 hadoop 并检查 loginhadoop(userprincipal, userkeytabfile.getabsolutepath()); // 6. 检查重新登录 checkauthenticateoverkrb(); System.out.println("Login success!!!!!!!!!!!!!!"); 写文件 功能简介 写文件过程为 : 文档版本 01 ( ) 171

182 6 HDFS 应用开发 1. 实例化一个 FileSystem 2. 由此 FileSystem 实例获取写文件的各类资源 3. 将待写内容写入到 HDFS 的指定文件中 说明在写完文件后, 需关闭所申请资源 代码样例 如下是写文件的代码片段, 详细代码请参考 com.huawei.bigdata.hdfs.examples 中的 HdfsMain 类和 HdfsWriter 类 /** * 创建文件, 写文件 * IOException ParameterException */ private void write() throws IOException, ParameterException { final String content = "hi, I am bigdata. It is successful if you can see me."; InputStream in = (InputStream) new ByteArrayInputStream( content.getbytes()); try { HdfsWriter writer = new HdfsWriter(fSystem, DEST_PATH + File.separator + FILE_NAME); writer.dowrite(in); System.out.println("success to write."); finally { // 务必要关闭流资源 close(in); 追加文件内容功能简介 追加文件内容, 是指在 HDFS 的某个指定文件后面, 追加指定的内容 过程为 : 1. 实例化一个 FileSystem 2. 由此 FileSystem 实例获取各类相关资源 3. 将待追加内容添加到 HDFS 的指定文件后面 说明在完成后, 需关闭所申请资源 代码样例 如下是代码片段, 详细代码请参考 com.huawei.bigdata.hdfs.examples 中的 HdfsMain 类和 HdfsWriter 类 /** * 追加文件内容 * IOException */ private void append() throws Exception { final String content = "I append this content."; InputStream in = (InputStream) new ByteArrayInputStream( 文档版本 01 ( ) 172

183 6 HDFS 应用开发 content.getbytes()); try { HdfsWriter writer = new HdfsWriter(fSystem, DEST_PATH + File.separator + FILE_NAME); writer.doappend(in); System.out.println("success to append."); finally { // 务必要关闭流资源. close(in); 读文件 功能简介 获取 HDFS 上某个指定文件的内容 说明 在完成后, 需关闭所申请资源 代码样例 如下是读文件的代码片段, 详细代码请参考 com.huawei.bigdata.hdfs.examples 中的 HdfsMain 类 /** * 读文件 * IOException */ private void read() throws IOException { String strpath = DEST_PATH + File.separator + FILE_NAME; Path path = new Path(strPath); FSDataInputStream in = null; BufferedReader reader = null; StringBuffer strbuffer = new StringBuffer(); try { in = fsystem.open(path); reader = new BufferedReader(new InputStreamReader(in)); String stemponeline; // 写文件 while ((stemponeline = reader.readline())!= null) { strbuffer.append(stemponeline); System.out.println("result is : " + strbuffer.tostring()); System.out.println("success to read."); finally { // 务必要关闭资源. close(reader); close(in); 删除文件 功能简介 删除 HDFS 上某个指定文件或者文件夹 文档版本 01 ( ) 173

184 6 HDFS 应用开发 说明 被删除的文件或文件夹, 会被放在当前用户目录下的.Trash/Current 文件夹中 若发生误删除, 可从该文件夹中恢复 代码样例 如下是删除文件的代码片段, 详细代码请参考 com.huawei.bigdata.hdfs.examples 中的 HdfsMain 类 /** * 删除文件 * IOException */ private void delete() throws IOException { Path bedeletedpath = new Path(DEST_PATH + File.separator + FILE_NAME); fsystem.deleteonexit(bedeletedpath); System.out.println("succee to delete the file " + DEST_PATH + File.separator + FILE_NAME); Colocation 功能简介 同分布 (Colocation) 功能是将存在关联关系的数据或可能要进行关联操作的数据存储在相同的存储节点上 HDFS 文件同分布的特性, 将那些需进行关联操作的文件存放在相同数据节点上, 在进行关联操作计算时避免了到别的数据节点上获取数据, 大大降低网络带宽的占用 在使用 Colocation 功能之前, 建议用户对 Colocation 的内部机制有一定了解, 包括 : Colocation 分配节点原理 Colocation 为 locator 分配数据节点的时候,locator 的分配算法会根据已分配的情况, 进行均衡的分配数据节点 说明 locator 分配算法的原理是, 查询目前存在的所有 locators, 读取所有 locators 所分配的数据节点, 并记录其使用次数 根据使用次数, 对数据节点进行排序, 使用次数少的排在前面, 优先选择排在前面的节点 每次选择一个节点后, 计数加 1, 并重新排序, 选择后续的节点 扩容与 Colocation 分配 集群扩容之后, 为了平衡地使用所有的数据节点, 使新的数据节点的分配频率与旧的数据节点趋于一致, 有如下两种策略可以选择, 如表 6-5 所示 文档版本 01 ( ) 174

185 6 HDFS 应用开发 表 6-5 分配策略 编号策略说明 1 删除旧的 locators, 为集群中所有数据节点重新创建 locators 2 创建一批新的 locators, 并重新规划数据存放方式 1. 在未扩容之前分配的 locators, 平衡的使用了所有数据节点 当扩容后, 新加入的数据节点并未分配到已经创建的 locators 中, 所以使用 Colocation 来存储数据的时候, 只会往旧的数据节点存储数据 2. 由于 locators 与特定数据节点相关, 所以当集群进行扩容的时候, 就需要对 Colocation 的 locators 分配进行重新规划 旧的 locators 使用的是旧的数据节点, 而新创建的 locators 偏重使用新的数据节点, 所以需要根据实际业务对数据的使用需求, 重新规划 locators 的使用 说明 一般的, 建议用户在进行集群扩容之后采用策略一来重新分配 locators, 可以避免数据偏重使用新的数据节点 Colocation 与数据节点容量 由于使用 Colocation 进行存储数据的时候, 会固定存储在指定的 locator 所对应的数据节点上面, 所以如果不对 locator 进行规划, 会造成数据节点容量不均衡 下面总结了保证数据节点容量均衡的两个主要的使用原则, 如表 6-6 所示 表 6-6 使用原则 编号使用原则说明 1 所有的数据节点在 locators 中出现的频率一样 2 对于所有 locators 的使用需要进行合理的数据存放规划, 让数据均匀的分布在这些 locators 中 如何保证频率一样 : 假如数据节点有 N 个, 则创建 locators 的数量应为 N 的整数倍 (N 个 2N 个...) - HDFS 的二次开发过程中, 可以获取 DFSColocationAdmin 和 DFSColocationClient 实例, 进行从 location 创建 group 删除 group 写文件和删除文件的操作 文档版本 01 ( ) 175

186 6 HDFS 应用开发 说明 使用 Colocation 功能, 用户指定了 DataNode, 会造成某些节点上数据量很大 数据倾斜严重, 导致 HDFS 写任务失败 由于数据倾斜, 导致 MapReduce 只会在某几个节点访问, 造成这些节点上负载很大, 而其他节点闲置 针对单个应用程序任务, 只能使用一次 DFSColocationAdmin 和 DFSColocationClient 实例 如果每次对文件系统操作都获取此实例, 会创建过多 HDFS 链接, 消耗 HDFS 资源 如果需要对 colocation 上传的文件做 balance 操作, 为避免 colocation 失效, 可以通过 MRS Manager 界面中的 oi.dfs.colocation.file.pattern 参数进行设置, 设置该参数值为对应数据文件块的路径, 多个路径之间以逗号分开 例如 /test1,/test2 代码样例 完整样例代码可参考 com.huawei.bigdata.hdfs.examples.colocationexample 说明 在运行 Colocation 工程时, 需要将 HDFS 用户绑定 supergroup 用户组 1. 初始化 使用 Colocation 前需要进行 kerberos 安全认证 private static void init() throws IOException { LoginUtil.login(PRNCIPAL_NAME, PATH_TO_KEYTAB, PATH_TO_KRB5_CONF, conf); 2. 获取实例 样例 :Colocation 的操作使用 DFSColocationAdmin 和 DFSColocationClient 实例, 在进行创建 group 等操作前需获取实例 public static void main(string[] args) throws IOException { init(); dfsadmin = new DFSColocationAdmin(conf); dfs = new DFSColocationClient(); dfs.initialize(uri.create(conf.get("fs.defaultfs")), conf); creategroup(); put(); delete(); deletegroup(); dfs.close(); dfsadmin.close(); 3. 创建 group 样例 : 创建一个 gid01 组, 组中包含 3 个 locator private static void creategroup() throws IOException { dfsadmin.createcolocationgroup(colocation_group_group01, Arrays.asList(new String[] { "lid01", "lid02", "lid03" )); 4. 写文件, 写文件前必须创建对应的 group 样例 : 写入 testfile.txt 文件 private static void put() throws IOException { FSDataOutputStream out = dfs.create(new Path("/testfile.txt"), true, COLOCATION_GROUP_GROUP01, "lid01"); // 待写入到 HDFS 的数据. byte[] readbuf = "Hello World".getBytes("UTF-8"); out.write(readbuf, 0, readbuf.length); out.close(); 5. 删除文件 样例 : 删除 testfile.txt 文件 文档版本 01 ( ) 176

187 6 HDFS 应用开发 设置存储策略 功能简介 public static void delete() throws IOException { dfs.delete(new Path("/testfile.txt")); 6. 删除 group 样例 : 删除 gid01 private static void deletegroup() throws IOException { dfsadmin.deletecolocationgroup(colocation_group_group01); 为 HDFS 上某个文件或文件夹指定存储策略 代码样例 1. 在 ${HADOOP_HOME/etc/hadoop/ 下的 Hdfs-site.xml 中设置如下参数 : <name>dfs.storage.policy.enabled</name> <value>true</value> 2. 重启 HDFS, 如图 6-3 所示 图 6-3 重启 hdfs 3. 登录 MRSManager, 选择 服务管理 > HDFS > 服务配置, 将 参数类别 设置为 全部配置 4. 搜索并查看 dfs.storage.policy.enabled 的参数值是否为 true, 如果不是, 修改为 true, 并单击 保存配置, 重启 HDFS 5. 查看代码 如下是代码片段, 详细代码请参考 com.huawei.bigdata.hdfs.examples 中的 HdfsMain 类 /** * 设置存储策略 policyname * 策略名称能够被接受 : * <li>hot * <li>warm * <li>cold * <li>lazy_persist * <li>all_ssd * <li>one_ssd IOException */ private void setstoragepolicy(string policyname) throws IOException{ if(fsystem instanceof DistributedFileSystem) { DistributedFileSystem dfs = (DistributedFileSystem) fsystem; Path destpath = new Path(DEST_PATH); Boolean flag = false; mkdir(); BlockStoragePolicySpi[] storage = dfs.getstoragepolicies(); for (BlockStoragePolicySpi bs : storage) { if (bs.getname().equals(policyname)) { flag = true; System.out.println("StoragePolicy:" + bs.getname()); 文档版本 01 ( ) 177

188 6 HDFS 应用开发 if (!flag) { policyname = storage[0].getname(); dfs.setstoragepolicy(destpath, policyname); System.out.println("success to set Storage Policy path " + DEST_PATH); rmdir(); else{ System.out.println("Storage Policy is only supported in HDFS."); 设置标签表达式 功能简介 在启用了 HDFS 基于标签的摆放策略 (HDFS Nodelabel) 后, 用户对有写权限的目录或者文件可以设置标签表达式, 设置后, 该文件或者该目录下的文件均会选择符合标签表达式的节点来存放数据块 标签表达式 标签表达式由多个子表达式组成, 每个子表达式代表了一个或者多个副本对应的标签, 可包含备份数和 fallback 策略两个属性 图 6-4 标签表达式结构 HDFS 文件通常默认有 3 个副本, 子表达式描述了其中 1 个或者多个副本的存放标签逻辑规则, 其中子表达式的 replica 属性指定了使用这个子表达式的副本数, fallback 表示了当这个标签逻辑规则对应的节点不足以满足副本数时所采取的策略 可使用 EBNF(Extended Backus-Naur Form, 扩展的巴克斯范式 ) 定义描述标签以及标签表达式的字符集范围 EBNF 定义详细描述请参考 wiki/extended_backus-naur_form 标签表达式 EBNF 定义 : digit = "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" ; upper = "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" ; lower = "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" ; seperat = "-" "_" allow_symbol = "!" ";" "<" ">" "-" "?" "@" "_" "/" "\" "#" "&" "*" "." alpha = upper lower ; 文档版本 01 ( ) 178

189 6 HDFS 应用开发 alphanum = upper lower digit ; label_first_part = alpha, { alphanum label_part = { alphanum label = label_first_part, {seperat, label_part intersection_op = "&&" union_op = " " except_op = "!" set_op = intersection_op union_op min_part = label except_op, label prop_value = { alphanum seperat allow_symbol prop_key = alpha, { alphanum, [{ ".", { alphanum ] property = prop_key, "=", prop_value properties = "[", property, [{ "," property], "]" sub_expression = min_part, { set_op, min_part, [ properties ] label_expression = sub_expression, [{ "," sub_expression ] Fallback 策略有 3 种 : NONE: 不进行 fallback,namenode 选择副本位置失败, 抛出异常 NEXT: 将当前剩余 replica 数加入到下一个子表达式中, 如果没有下一个子表达式, 则采用 GLOBAL 的 fallback 策略 GLOBAL: 从所有 datanode 节点中选择剩余的 replica 数 replica 参数表示子表达式的副本数 : 用户在子表达式中指定的副本数, 有可能小于文件副本数, 此时剩余的副本会在标签表达式中选择最后的那个子表达式 另外, 当 Fallback 策略为 NONE 时, 如果副本数没有达到目标的 replica 数, 也不会继续进行副本选择 表达式继承关系 标签表达式和存储策略一样, 默认情况下, 文件继承父目录的表达式 ; 当文件被指定了标签表达式后, 则该文件和其父目录可以拥有不同的标签表达式 清除标签表达式操作, 只是将对应文件或者目录的表达式清除 ; 清除后, 该文件或者目录仍然继承父目录的表达式 和 Favored Nodes 的关系 HDFS 允许用户给出副本放置节点的建议, 这些节点被称作 Favored Node 当启用 NodeLabel 特性时, 若 Favored Node 指定的节点没有在文件标签表达式对应的节点范围内, 则 Favored Node 不会被 HDFS 采纳 HDFS 会尽力在 Favored Nodes 和标签表达式对应节点范围的交集中选择节点 NodeLabel 策略块选择倾向 NodeLabel 特性下, 块选择倾向与默认块分配策略一致, 优先选择本地的节点, 倾向于将 3 个副本尽量分布在多个机架上 代码样例 给目标目录设置标签表达式 /** * 设置标签表达式, 对已存在标签的对象设置表达式时, 会替换掉原有表达式 src 目标目录 expression 表达式 fs filesystem 对象 IOException */ public static void setlabelexpression(string src, String expression, FileSystem fs) throws IOException { if(fs instanceof DistributedFileSystem) { DistributedFileSystem dfs = (DistributedFileSystem) fs; DFSClient client = dfs.getclient(); 文档版本 01 ( ) 179

190 6 HDFS 应用开发 client.setlabelexpression(src, expression); else{ System.out.println("Nodelabel is only supported in HDFS."); /** * 清除标签表达式, 清除后, 对象采用父目录的标签表达式 src 目标目录 fs filesystem 对象 IOException */ public static void clearlabelexpression(string src, FileSystem fs) throws IOException { if(fs instanceof DistributedFileSystem) { DistributedFileSystem dfs = (DistributedFileSystem) fs; DFSClient client = dfs.getclient(); // 设置为 null 或者空字符串即为清除 client.setlabelexpression(src, null); else{ System.out.println("Nodelabel is only supported in HDFS."); 说明 使用标签表达式功能前, 必须进行一些配置来使用这个功能, 请参考配置 HDFS NodeLabel 章节 访问 OBS 功能简介 代码样例 访问过程为 : 1. 设置 fs.s3a.access.key 和 fs.s3a.secret.key 2. 实例化一个 FileSystem 3. 由此 FileSystem 实例可以读取 新增和删除各类资源 说明 不支持追加操作 如下是实例化 FileSystem 的代码片段, 详细代码请参考 com.huawei.bigdata.hdfs.examples 中的 HdfsMain 类 /** * * Add configuration file if the application run on the linux,then need make * the path of the core-site.xml and hdfs-site.xml to in the linux client file * */ private void confload() throws IOException { conf = new Configuration(); // conf file conf.addresource(new Path(PATH_TO_HDFS_SITE_XML)); 文档版本 01 ( ) 180

191 6 HDFS 应用开发 conf.addresource(new Path(PATH_TO_CORE_SITE_XML)); conf.set("fs.s3a.access.key","*** Provide your Access Key ***"); conf.set("fs.s3a.secret.key","*** Provide your Secret Key ***"); /** * build HDFS instance */ private void instancebuild() throws IOException { // get filesystem // fsystem = FileSystem.get(conf); fsystem = FileSystem.get(URI.create("s3a://[BuketName]"),conf); 配置 HDFS NodeLabel 配置场景 用户需要通过数据特征灵活配置 HDFS 文件数据块的存储节点 通过设置 HDFS 目录 / 文件对应一个标签表达式, 同时设置每个 Datanode 对应一个或多个标签, 从而给文件的数据块存储指定了特定范围的 Datanode 当使用基于标签的数据块摆放策略, 为指定的文件选择 DataNode 节点进行存放时, 会根据文件的标签表达式选择出 Datanode 节点范围, 然后在这些 Datanode 节点范围内, 选择出合适的存放节点 场景 1 DataNodes 分区场景 场景说明 : 用户需要让不同的应用数据运行在不同的节点, 分开管理, 就可以通过标签表达式, 来实现不同业务的分离, 指定业务存放到对应的节点上 通过配置 NodeLabel 特性使得 : /HBase 下的数据存储在 DN1 DN2 DN3 DN4 节点上 /Spark 下的数据存储在 DN5 DN6 DN7 DN8 节点上 图 6-5 DataNode 分区场景 文档版本 01 ( ) 181

192 6 HDFS 应用开发 说明 通过 hdfs nodelabel -setlabelexpression -expression 'LabelA[fallback=NONE]' -path / Hbase 命令, 给 Hbase 目录设置表达式, 从图 6-5 中可知, /Hbase 文件的数据块副本会被放置在有 LabelA 标签的节点上, 即 DN1 DN2 DN3 DN4 同理, 在 /Spark 目录下文件对应的数据块副本只能放置到 LabelB 标签上的节点, 如 DN5 DN6 DN7 DN8 设置标签表达式参考设置标签表达式章节 设置数据节点的标签参考配置描述 如果同一个集群上存在多个机架, 每个标签下最好有多个机架的 datanodes, 以确保数据块摆放的可靠性 场景 2 多机架下指定副本位置场景 场景说明 : 在异构集群中, 客户需要分配一些特定的具有高可靠性的节点用以存放重要的商业数据, 可以通过标签表达式指定副本位置, 指定文件数据块的其中一个副本存放到高可靠性的节点上 /data 目录下的数据块, 默认三副本情况下, 其中至少有一个副本会被存放到 RACK1 或 RACK2 机架的节点上 (RACK1 和 RACK2 机架的节点为高可靠性节点 ), 另外两个副本会被分别存放到 RACK3 和 RACK4 机架的节点上 图 6-6 场景样例 说明 通过 hdfs nodelabel -setlabelexpression -expression 'LabelA LabelB[fallback=NONE],LabelC,LabelD' -path /data 命令给 /data 目录设置表达式 当向 /data 目录下写数据时, 至少有一个数据块副本存放在 LabelA 或者 LabelB 标签的节点中, 剩余的两个数据块副本会被存放在有 LabelC 和 LabelD 标签的节点上 配置描述 Datanode 节点标签配置 在 NameNode 的 $HADOOP_HOME/etc/hadoop/hdfs-site.xml 配置文件中配置如下参数 登录 MRS Manager, 选择 服务管理 > HDFS > 服务配置, 在 参数类别 中选择 全部配置 文档版本 01 ( ) 182

193 6 HDFS 应用开发 表 6-7 参数说明 参数描述默认值 dfs.nodelab el.enabled dfs.nodelab el.host2labe ls.file dfs.block.re plicator.clas sname host2tags 在块放置策略中考虑节点标签, 并允许用户设置文件和目录的标签表达式 指定存放 DataNode 标签配置信息的文件路径 每一个 DataNode 可以配置一个或多个标签, 也可以不配置标签 说明 : DataNode 的表示方式可以是 hostname,ip 地址, 或者 IP:PORT 的其中之一 因为 : 是特殊符号, 所以当使用 IP:PORT 时, 必须添加转义符, 例如 : \:50010 = label-1 标签由大小写字母和数字组成, 中间可以有 - 表示段落, 首字母必须是英文字母 支持 IP 地址范围表示方式, 即支持 [start-end] 指定 range 范围 如 :[10-20] [5-12], 数字之间使用 - 支持主机名正则表达式, 如 :/datanode-\\d{2/ 正则表达式需要符合 Java 正则规范 指定 NameNode 使用的块副本位置选择策略, 只能配置为默认值或 org.apache.hadoop.hdfs.server.blockmanagement.av ailablespaceblockplacementpolicy, 不能配置成其他值, 否则会造成 NameNode 启动失败 配置 DataNode 主机与标签的对应关系 主机名称支持配置 IP 扩展表达式 ( 如 [1-128] 或者 [2-3].[1-128]), 或者为前后加上 / 的主机名的正则表达式 ( 如 /datanode-[123]/ 或者 /datanode-\d{2/) 标签配置名称不允许包含 = / \ 字符 false true - org.apache.h adoop.hdfs.s erver.blockm anagement.b lockplaceme ntpolicydefa ult 默认为空 文档版本 01 ( ) 183

194 6 HDFS 应用开发 分布式 Node Label 说明 由参数 dfs.nodelabel.host2labels.file 指定的, 存放 DataNode 标签配置信息的文件 host2labels 文件格式内容详细说明 :host2tags 配置项内容详细说明 : 假如有一套集群, 有 20 个 Datanode:dn-1 到 dn-20, 对应的 IP 地址为 到 , dfs.nodelabel.host2labels.file 配置文件内容可以使用如下的表示方式 主机名正则表达式 /dn-\d/ = label-1 表示 dn-1 到 dn-9 对应的标签为 label-1, 即 dn-1 = label-1,dn-2 = label-1,...dn-9 = label-1 /dn-((1[0-9]$) (20$))/ = label-2 表示 dn-10 到 dn-20 对应的标签为 label-2, 即 dn-10 = label-2,dn-11 = label-2,...dn-20 = label-2 IP 地址范围表示方式 [1-9] = label-1 表示 到 对应的标签为 label-1, 即 = label-1, = label-1, = label [10-20] = label-2 表示 到 对应的标签为 label-2, 即 = label-2, = label-2, = label-2 基于标签的数据块摆放策略支持扩容减容场景 : 当集群中新增加 DataNode 节点时, 如果该 DataNode 对应的 IP 匹配 dfs.nodelabel.host2labels.file 配置项指定的 host2labels 文件中的 IP 地址范围, 或者该 DataNode 的主机名匹配 dfs.nodelabel.host2labels.file 配置项中的主机名正则表达式, 则该 DataNode 节点会被设置成对应的标签 当集群中新增加 DataNode 节点时, 如果该 DataNode 对应的 IP 匹配 host2tag 配置项中的 IP 地址范围, 或者该 DataNode 的主机名匹配 host2tag 配置项中的主机名正则表达式, 则该 DataNode 节点会被设置成对应的标签 例如 dfs.nodelabel.host2labels.file host2tags 配置值为 [1-9] = label-1, 而当前集群只有 到 三个数据节点 进行扩容后, 又添加了 这个数据节点, 则该数据节点会被设置成 label-1 的标签 ; 如果 这个数据节点被删除或者退出服务后, 数据块不会再被分配到该节点上 设置目录 / 文件的标签表达式 标签表达式的含义请参考设置标签表达式 在 HDFS 参数配置页面配置 path2expression, 配置 HDFS 目录与标签的对应关系, 该目录必须为已存在目录 设置了标签的目录被删除后, 新增一个同名目录, 原有的对应关系不会继承 Java API 设置方式通过 DistributedFileSystem 实例化对象调用 setlabelexpression(string src, String labelexpression) 方法 src 为 HDFS 上的目录或文件路径, labelexpression 为标签表达式 开启 NodeLabel 特性后, 可以通过 Namenode WebUI 的 Datanodes 页面查看每个 Datanode 的标签信息 集中式的 NodeLabel 由 host2labels 文件在 NameNode 端配置, 用于 host 到 label 的映射 分布式的 NodeLabel 由 DataNode 进行解析, 该 DataNode 是基于已配置的 nodelabel providers 的 当 DataNode 获得 register 时, 该 DataNode 会将其 label 报告给 NameNode 文档版本 01 ( ) 184

195 6 HDFS 应用开发 表 6-8 分布式 Node Label 配置 参数描述默认值 dfs.datanode.nodelabel.prov ider.class dfs.datanode.nodelabel.label s 以逗号分隔的 Node Label provider 类 将为该节点考虑由标签的每个 provider 类提供的所有综合列表 以逗号分隔的可以配置的标签名称, 该参数在参数 dfs.datanode.nodelabel.pr ovider.class 配置为默认值 org.apache.hadoop.hdfs.s erver.datanode.configuredn odelabelprovider 有效 org.apache.hadoop.hdfs.serv er.datanode.configurednode LabelProvider - 说明 用户可以通过实现以下接口来配置新的 NodeLabel provider 接口 : org.apache.hadoop.hdfs.server.datanode.nodelabelprovider /** * Interface which will be responsible for fetching the labels * from a Node. */ public interface NodeLabelProvider { /** Return the labels identified by this provider */ public Set<String> getnodelabels(); /** * Initialize the Provider with configurations. */ void init(configuration config); 如果需要修改 datanode 的 nodelabel, 需要修改表 6-8 中 datanode 的配置参数并重启该 datanode Node Label ACLs NodeLabel ACL 的功能是限制用户访问一些标签 如果没有 ACL 配置, 所有节点标签都可以在没有任何权限控制的标签表达式中使用 在多租户场景中, 需要 ACL 控制 仅当用户对相关标签具有访问权限时, 用户才能设置标签表达式, 否则操作将失败且提示 AccessControlException 创建新文件或追加到现有 hdfs 文件也需验证带有 ACL 的标签表达式, 如果任何标签都不可访问, 则操作失败, 提示 AccessControlException Node Label ACLs 将引入两个新的属性参数 表 6-9 参数描述 参数描述默认值 dfs.nodelabel.acl.file Node Label Acl 文件的文件名 - 文档版本 01 ( ) 185

196 6 HDFS 应用开发 参数描述默认值 dfs.nodelabel.acl.check.s uperuser.enable 默认情况下,super user 可以访问所有标签 如果该参数设置为 true, 将对 super user 进行 ACL 检查 ( 即使 super user 也需要配置 ACL 配置 ) false ACLs 文件格式如下 : <label1,lable2,...>=user:user1,user2,...;group:group1,group2,... 说明 示例 : 用户可以以此格式配置逗号分隔的用户和组列表 用逗号分隔的用户和组列表是可选的 如果用户和组列表为空, 则任何用户都无法访问该标签 若使用 * 配置用户和组, 则所有用户访问均可访问对应标签 在 host2label 文件中进行配置但未在 label2acl 文件中进行配置的标签, 可被所有用户访问 表示其默认权限是 *, 表示所有人都有权限 如果 2 个标签 ACL 是相同的, 那么可以配置用逗号分隔标签 ( 参见下例中的 label6 和 label7). 如果标签名称在后续的行中重复, 将考虑将最后一行的标签名称作为 ACLs label1 = USER:hdfs;GROUP:hadoop label2 = USER:;GROUP:hadoop label3 = USER:;GROUP: label4= USER:*;GROUP: label5= USER:;GROUP:* label6,lable7 = USER:hive,hbase;GROUP:nonsql 描述 : 属于 hadoop 组的 hdfs 用户可访问 label1 属于 hadoop 组的用户均可访问 label2 由于配置用户和组为空, 所以没有用户可以访问 label3 label4,label5 可以被任意用户访问 属于 nonsql 组的 hive,hbase 用户可以访问 label6 和 label7 没有在这里进行配置的标签可以被任意一个用户访问 说明 如果一个 DataNode 配置了多个标签, 则用户必须能够访问该 DataNode 的所有标签, 进而该 DataNode 被认为是可访问的 如果用户不能访问该 DataNode 的任何一个标签, 则对于用户而言该 DataNode 被认为是不可访问 如果文件已经被写入 HDFS 并且稍后标签上的权限被移除, 则 re-replication,mover 和 balancer 可能将不能管理 blocks Mover 和 balancer 还需要解析用户组映射, 因此 hadoop.security.group.mapping 配置参数应该可配置 ( 该配置参数在开源文档中, 其链接为 hadoop-project-dist/hadoop-common/core-default.xml) 如果我们使用 org.apache.hadoop.security.shellbasedunixgroupsmapping 作为参数 hadoop.security.group.mapping 的值, 那么所有的集群组应该在运行 mover 或 balancer 的本地机器上可用 Mover 和 balancer 还取决于 ACLs 的配置文件路径 ( 使用参数 dfs.nodelabel.acl.file 进行配置 ) 因此, 该文件路径应该存在于运行 mover 或 balancer 的本地机器中 文档版本 01 ( ) 186

197 6 HDFS 应用开发 块副本位置选择 多余块副本删除选择 与 NodeTag 特性不同,Nodelabel 支持对各个副本的摆放采用不同的策略, 如表达式 label-1,label-2,label-3, 表示 3 个副本分别放到含有 label-1 label-2 label-3 的 Datanode 中, 不同的副本策略用逗号分隔 如果 label-1, 希望放 2 个副本, 可以这样设置表达式 : label-1[replica=2],label-2,label-3 这种情况下, 如果默认副本数是 3, 则会选择 2 个带有 label-1 和一个 label-2 的节点 ; 如果默认副本数是 4, 会选择 2 个带有 label-1 一个 label-2 以及一个 label-3 的节点 可以注意到, 副本数是从左到右依次满足各个副本策略的, 但也有副本数超过表达式表述的情况, 当默认副本数为 5 时, 多出来的一个副本会放到最后一个节点中, 也就是 label-3 的节点里 当启用 ACLs 功能并且用户无权访问表达式中使用的标签时, 将不会为副本选择属于该标签的 DataNode 如果块副本数超过参数 dfs.replication 值 ( 即用户指定的文件副本数 ),hdfs 会删除多余块副本来保证集群资源利用率 删除规则如下 : 优先删除不满足任何表达式的副本 示例 : 文件默认副本数为 3 /test 标签表达式为 LA[replica=1],LB[replica=1],LC[replica=1], /test 文件副本分布的四个节点 (D1~D4) 以及对应标签 (LA~LD): D1:LA D2:LB D3:LC D4:LD 则选择删除 D4 节点上的副本块 如果所有副本都满足表达式, 删除多于表达式指定的数量的副本 示例 : 文件默认副本数为 3 /test 标签表达式为 LA[replica=1],LB[replica=1],LC[replica=1], /test 文件副本分布的四个节点以及对应标签 : D1:LA D2:LA D3:LB D4:LC 则选择删除 D1 或者 D2 上的副本块 如果文件所有者或文件所有者的组不能访问某个标签, 则优先删除映射到该标签的 DataNode 中的副本 基于标签的数据块摆放策略样例 假如有一套集群, 有六个 DataNode:dn-1,dn-2,dn-3,dn-4,dn-5 以及 dn-6, 对应的 IP 为 [1-6] 有六个目录需要配置标签表达式,Block 默认备份数为 3 下面给出 3 种 Datanode 标签信息在 host2labels 文件中的表示方式, 其作用是一样的 主机名正则表达式 /dn-[1456]/ = label-1,label-2 /dn-[26]/ = label-1,label-3 文档版本 01 ( ) 187

198 6 HDFS 应用开发 /dn-[3456]/ = label-1,label-4 /dn-5/ = label-5 IP 地址范围表示方式 [1-6] = label = label = label [3-6] = label [4-6] = label = label = label-3 普通的主机名表达式 /dn-1/ = label-1, label-2 /dn-2/ = label-1, label-3 /dn-3/ = label-1, label-4 /dn-4/ = label-1, label-2, label-4 /dn-5/ = label-1, label-2, label-4, label-5 /dn-6/ = label-1, label-2, label-3, label-4 目录的标签表达式设置结果如下 : /dir1 = label-1 /dir2 = label-1 && label-3 /dir3 = label-2 label-4[replica=2] /dir4 = (label-2 label-3) && label-4 /dir5 =!label-1 /sdir2.txt = label-1 && label-3[replica=3,fallback=none] /dir6 = label-4[replica=2],label-2 文件的数据块存放结果如下 : /dir1 目录下文件的数据块可存放在 dn-1,dn-2,dn-3,dn-4,dn-5 和 dn-6 六个节点中的任意一个 /dir2 目录下文件的数据块可存放在 dn-2 和 dn-6 节点上 Block 默认备份数为 3, 表达式只匹配了两个 datanode 节点, 第三个副本会在集群上剩余的节点中选择一个 datanode 节点存放 /dir3 目录下文件的数据块可存放在 dn-1,dn-3,dn-4,dn-5 和 dn-6 中的任意三个节点上 /dir4 目录下文件的数据块可存放在 dn-4,dn-5 和 dn-6 /dir5 目录下文件的数据块没有匹配到任何一个 DataNode, 会从整个集群中任意选择三个节点存放 ( 和默认选块策略行为一致 ) /sdir2.txt 文件的数据块, 两个副本存放在 dn-2 和 dn-6 节点上, 虽然还缺失一个备份节点, 但由于使用了 fallback=none 参数, 所以只存放两个备份 /dir6 目录下文件的数据块在具备 label-4 的节点中选择 2 个节点 (dn-3 -- dn-6), 然后在 label-2 中选择一个节点, 如果用户指定 /dir6 下文件副本数大于 3, 则多出来的副本均在 label-2 使用限制 配置文件中, key value 是以 = : 及空白字符作为分隔的 因此, key 对应的主机名中间请勿包含以上字符, 否则会被误认为分隔符 NodeLabel 特性是替代 RackGroup 和 NodeTag 的功能,NodeLabel 特性不能与 RackGroup 或 NodeTag 同时使用 6.4 调测程序 在 Linux 中调测程序 文档版本 01 ( ) 188

199 6 HDFS 应用开发 安装客户端时编译并运行程序 操作场景 HDFS 应用程序支持在安装 HDFS 客户端的 Linux 环境中运行 在程序代码完成开发后, 可以上传 Jar 包至 Linux 客户端环境中运行应用 前提条件 已安装 HDFS 客户端 当客户端所在主机不是集群中的节点时, 需要在客户端所在节点的 hosts 文件中设置主机名和 IP 地址映射 主机名和 IP 地址请保持一一对应 操作步骤 步骤 1 步骤 2 执行 mvn package 打出 jar 包, 在工程目录 target 目录下获取, 比如 :hdfsexamples-1.0.jar 将导出的 Jar 包拷贝上传至 Linux 客户端运行环境的任意目录下, 例如 /opt/ hadoop_client, 然后在该目录下创建 conf 目录, 将 user.keytab 拷贝至 conf 目录 可参考准备 Linux 客户端运行环境步骤 9 步骤 3 配置环境变量 : cd /opt/hadoop_client source bigdata_env 步骤 4 执行如下命令, 运行 Jar 包 hadoop jar hdfs-examples-1.0.jar com.huawei.bigdata.hdfs.examples.hdfsmain hadoop jar hdfs-examples-1.0.jar com.huawei.bigdata.hdfs.examples.colocationexample 说明 运行命令时, 需保持客户端 Yarn/config/hdfs-site.xml 中的 kerberos 相关信息和 HDFS/ hadoop/etc/hadoop/hdfs-site.xml 中的 kerberos 相关信息一致 hdfs-site.xml 中 kerberos 的配置 mapred 改为 hdfs, 需要修改的地方如图 6-7 所示 : 图 6-7 hdfs-site.xml ---- 结束 文档版本 01 ( ) 189

200 6 HDFS 应用开发 查看调测结果 操作场景 HDFS 应用程序运行完成后, 可直接通过运行结果查看应用程序运行情况, 也可以通过 HDFS 日志获取应用运行情况 操作步骤 1. 查看运行结果获取应用运行情况 HdfsMain Linux 样例程序运行结果如下所示 linux1:/opt/hdfs_client/hadoop/hdfs/lib # hadoop jar hdfs-example.jar WARNING: Use "yarn jar" to launch YARN applications. 16/02/26 18:21:48 INFO security.loginutil: JaasConfiguration logincontextname=client principal=hdfs/hadoop.hadoop.com@hadoop.com useticketcache=false keytabfile=/tmp/hadoop-unjar /hdfs.keytab 16/02/26 18:21:49 INFO security.usergroupinformation: Login successful for user hdfs/ hadoop.hadoop.com@hadoop.com using keytab file hdfs.keytab Login success!!!!!!!!!!!!!! 16/02/26 18:21:49 INFO hdfs.peercache: SocketCache disabled. success to create path /hdfs-examples success to write. success to append. result is : hi, I am bigdata. It is successful if you can see me.i append this content. success to read. success to delete the file /hdfs-examples/test.txt success to delete path /hdfs-examples begin to set Storage Policy success to create path /hdfs-examples StoragePolicy:FROZEN StoragePolicy:COLD StoragePolicy:WARM StoragePolicy:HOT StoragePolicy:ONE_SSD StoragePolicy:ALL_SSD StoragePolicy:LAZY_PERSIST success to set Storage Policy path /hdfs-examples success to delete path /hdfs-examples set Storage Policy end :/opt/hadoop_client # hadoop jar hdfs-example.jar WARNING: Use "yarn jar" to launch YARN applications. 16/04/12 21:32:43 INFO security.usergroupinformation: Login successful for user test using keytab file user.keytab 16/04/12 21:32:43 INFO security.loginutil: Login success!!!!!!!!!!!!!! 16/04/12 21:32:44 INFO hdfs.peercache: SocketCache disabled. success to create path /user/hdfs-examples success to write. success to append. result is : hi, I am bigdata. It is successful if you can see me.i append this content. success to read. success to delete the file /user/hdfs-examples/test.txt success to delete path /user/hdfs-examples begin to set Storage Policy success to create path /user/hdfs-examples StoragePolicy:FROZEN StoragePolicy:COLD StoragePolicy:WARM StoragePolicy:HOT StoragePolicy:ONE_SSD StoragePolicy:ALL_SSD StoragePolicy:LAZY_PERSIST success to set Storage Policy path /user/hdfs-examples success to delete path /user/hdfs-examples set Storage Policy end ColocationExample Linux 样例程序运行结果如下所示 文档版本 01 ( ) 190

201 6 HDFS 应用开发 linux1:/opt/hdfs_client/hadoop/hdfs/lib # hadoop jar colocation-example.jar WARNING: Use "yarn jar" to launch YARN applications. 16/02/26 16:02:09 INFO security.loginutil: JaasConfiguration logincontextname=client principal=hdfs/hadoop.hadoop.com@hadoop.com useticketcache=false keytabfile=/tmp/hadoopunjar /hdfs.keytab 16/02/26 16:02:10 INFO security.usergroupinformation: Login successful for user hdfs/ hadoop.hadoop.com@hadoop.com using keytab file hdfs.keytab Login success!!!!!!!!!!!!!! 16/02/26 16:02:10 INFO hdfs.peercache: SocketCache disabled. 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:zookeeper.version=v100r002cxx10, built on 02/15/ :52 GMT 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:host.name=linux1 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:java.version=1.8.0_66 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:java.vendor=oracle Corporation 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:java.home=/opt/ client/jdk/jdk/jre 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:java.library.path=/opt/ client//hdfs/hadoop/lib/native 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:java.io.tmpdir=/tmp 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:java.compiler=<na> 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:os.name=linux 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:os.arch=amd64 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:os.version= default 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:user.name=root 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:user.home=/root 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:user.dir=/opt/ HDFS_client/Hadoop/hdfs/lib 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:os.memory.free=270mb 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:os.memory.max=455mb 16/02/26 16:02:10 INFO zookeeper.zookeeper: Client environment:os.memory.total=360mb 16/02/26 16:02:10 INFO zookeeper.zookeeper: Initiating client connection, connectstring=szv :24002,szv :24002,szv :24002, linux1:24002,linux2:24002,linux3:24002 sessiontimeout=20000 watcher=com.huawei.hadoop.oi.colocation.zookeeperwatcher@ee86bcb 16/02/26 16:02:10 INFO zookeeper.clientcnxn: zookeeper.request.timeout is not configured. Using default value /02/26 16:02:10 INFO client.fourletterwordmain: connecting to szv /02/26 16:02:10 INFO zookeeper.login: successfully logged in. 16/02/26 16:02:10 INFO zookeeper.login: TGT refresh thread started. 16/02/26 16:02:10 INFO zookeeper.login: TGT valid starting at: Fri Feb 26 16:02:10 CST /02/26 16:02:10 INFO zookeeper.login: TGT expires: Sat Feb 27 16:02:10 CST /02/26 16:02:10 INFO zookeeper.login: TGT refresh sleeping until: Sat Feb 27 11:41:09 CST /02/26 16:02:10 INFO client.zookeepersaslclient: Client will use GSSAPI as SASL mechanism. 16/02/26 16:02:10 INFO zookeeper.clientcnxn: Opening socket connection to server szv / : Will attempt to SASL-authenticate using Login Context section 'Client' 16/02/26 16:02:10 INFO zookeeper.clientcnxn: Socket connection established, initiating session, client: / :42214, server: szv / : /02/26 16:02:10 INFO zookeeper.clientcnxn: Session establishment complete on server szv / :24002, sessionid = 0x1a00d9350b210047, negotiated timeout = /02/26 16:02:11 INFO colocation.zkutil: ZooKeeper colocation znode : /hadoop/ colocationdetails. Will publish colocation details under this znode hierarchy. 16/02/26 16:02:11 INFO zookeeper.zookeeper: Initiating client connection, connectstring=szv :24002,szv :24002,szv :24002, linux1:24002,linux2:24002,linux3:24002 sessiontimeout=20000 watcher=com.huawei.hadoop.oi.colocation.zookeeperwatcher@7096b474 16/02/26 16:02:11 INFO zookeeper.clientcnxn: zookeeper.request.timeout is not configured. Using default value /02/26 16:02:11 INFO client.fourletterwordmain: connecting to linux /02/26 16:02:11 INFO client.zookeepersaslclient: Client will use GSSAPI as SASL mechanism. 文档版本 01 ( ) 191

202 6 HDFS 应用开发 16/02/26 16:02:11 INFO zookeeper.clientcnxn: Opening socket connection to server linux2/ : Will attempt to SASL-authenticate using Login Context section 'Client' 16/02/26 16:02:11 INFO zookeeper.clientcnxn: Socket connection established, initiating session, client: / :52756, server: linux2/ : /02/26 16:02:11 INFO zookeeper.clientcnxn: Session establishment complete on server linux2/ :24002, sessionid = 0x1d00fcf4a39c0049, negotiated timeout = /02/26 16:02:11 INFO colocation.zkutil: ZooKeeper colocation znode : /hadoop/ colocationdetails. Will publish colocation details under this znode hierarchy. 16/02/26 16:02:11 WARN hdfs.datastreamer: These favored nodes were specified but not chosen: [szv :25009, szv :25009, szv :25009] Specified favored nodes: [szv :25009, szv :25009, szv :25009] 16/02/26 16:02:11 INFO zookeeper.clientcnxn: EventThread shut down for session: 0x1a00d9350b /02/26 16:02:11 WARN zookeeper.login: TGT renewal thread has been interrupted and will exit. 16/02/26 16:02:11 INFO zookeeper.zookeeper: Session: 0x1a00d9350b closed 16/02/26 16:02:11 INFO zookeeper.zookeeper: Session: 0x1d00fcf4a39c0049 closed 16/02/26 16:02:11 INFO zookeeper.clientcnxn: EventThread shut down for session: 0x1d00fcf4a39c :/opt/hadoop_client # hadoop jar colocation-example.jar 16/04/12 21:29:52 INFO colocation.zkutil: ZooKeeper colocation znode : /hadoop/ colocationdetails. Will publish colocation details under this znode hierarchy. Create Group is running... 16/04/12 21:29:52 INFO zookeeper.zookeeper: Initiating client connection, connectstring= :24002, :24002, :24002 sessiontimeout=20000 watcher=com.huawei.hadoop.oi.colocation.zookeeperwatcher@2e9fda69 16/04/12 21:29:52 INFO zookeeper.clientcnxn: zookeeper.request.timeout is not configured. Using default value /04/12 21:29:52 INFO client.fourletterwordmain: connecting to /04/12 21:29:52 INFO client.zookeepersaslclient: Client will use GSSAPI as SASL mechanism. 16/04/12 21:29:52 INFO zookeeper.clientcnxn: Opening socket connection to server / : Will attempt to SASL-authenticate using Login Context section 'Client' 16/04/12 21:29:52 INFO zookeeper.clientcnxn: Socket connection established, initiating session, client: / :52443, server: / : /04/12 21:29:52 INFO zookeeper.clientcnxn: Session establishment complete on server / :24002, sessionid = 0x a, negotiated timeout = /04/12 21:29:52 INFO colocation.zkutil: ZooKeeper colocation znode : /hadoop/ colocationdetails. Will publish colocation details under this znode hierarchy. Create Group has finished. Put file is running... Put file has finished. Delete file is running... Delete file has finished. Delete Group is running... Delete Group has finished. 2. 查看 HDFS 日志获取应用运行情况 您可以查看 HDFS 的 namenode 日志了解应用运行情况, 并根据日志信息调整应用程序 6.5 HDFS 接口 Java API HDFS 完整和详细的接口可以直接参考官方网站上的描述 : docs/r2.7.2/api/index.html 文档版本 01 ( ) 192

203 6 HDFS 应用开发 HDFS 常用接口 HDFS 常用的 Java 类有以下几个 : FileSystem: 是客户端应用的核心类 常用接口参见表 6-10 FileStatus: 记录文件和目录的状态信息 常用接口参见表 6-11 DFSColocationAdmin: 管理 colocation 组信息的接口 常用接口参见表 6-12 DFSColocationClient: 操作 colocation 文件的接口 常用接口参见表 6-13 说明 系统中不保留文件与 LocatorId 的映射关系, 只保留节点与 LocatorId 的映射关系 当文件使用 Colocation 接口创建时, 系统会将文件创建在 LocatorId 所对应的节点上 文件创建和写入要求使用 Colocation 相关接口 文件写入完毕后, 后续对该文件的相关操作不限制使用 Colocation 接口, 也可以使用开源接口进行操作 DFSColocationClient 类继承于开源的 DistributedFileSystem 类, 包含其常用接口 建议使用 DFSColocationClient 进行 Colocation 相关文件操作 表 6-10 类 FileSystem 常用接口说明 接口 public static FileSystem get(configuration conf) public FSDataOutputStream create(path f) public void copyfromlocalfile(path src, Path dst) public boolean mkdirs(path f) public abstract boolean rename(path src, Path dst) public abstract boolean delete(path f, boolean recursive) public boolean exists(path f) public FileStatus getfilestatus(path f) public BlockLocation[] getfileblocklocations(fil estatus file, long start, long len) 说明 Hadoop 类库中最终面向用户提供的接口类是 FileSystem, 该类是个抽象类, 只能通过该类的 get 方法得到具体类 get 方法存在几个重载版本, 常用的是这个 通过该接口可在 HDFS 上创建文件, 其中 f 为文件的完整路径 通过该接口可将本地文件上传到 HDFS 的指定位置上, 其中 src 和 dst 均为文件的完整路径 通过该接口可在 HDFS 上创建文件夹, 其中 f 为文件夹的完整路径 通过该接口可为指定的 HDFS 文件重命名, 其中 src 和 dst 均为文件的完整路径 通过该接口可删除指定的 HDFS 文件, 其中 f 为需要删除文件的完整路径,recuresive 用来确定是否进行递归删除 通过该接口可查看指定 HDFS 文件是否存在, 其中 f 为文件的完整路径 通过该接口可以获取文件或目录的 FileStatus 对象, 该对象记录着该文件或目录的各种状态信息, 其中包括修改时间 文件目录等等 通过该接口可查找指定文件在 HDFS 集群上块的位置, 其中 file 为文件的完整路径,start 和 len 来标识查找文件的块的范围 文档版本 01 ( ) 193

204 6 HDFS 应用开发 接口 public FSDataInputStream open(path f) public FSDataOutputStream create(path f, boolean overwrite) public FSDataOutputStream append(path f) 说明 通过该接口可以打开 HDFS 上指定文件的输出流, 并可通过 FSDataInputStream 类提供接口进行文件的读出, 其中 f 为文件的完整路径 通过该接口可以在 HDFS 上创建指定文件的输入流, 并可通过 FSDataOutputStream 类提供的接口进行文件的写入, 其中 f 为文件的完整路径,overwrite 为 true 时表示如果文件已经存在, 则重写文件 ; 如果为 false, 当文件已经存在时, 则抛出异常 通过该接口可以打开 HDFS 上已经存在的指定文件的输入流, 并可通过 FSDataOutputStream 类提供的接口进行文件的写入, 其中 f 为文件的完整路径 表 6-11 类 FileStatus 常用接口说明 接口 public long getmodificationtime() public Path getpath() 说明 通过该接口可查看指定 HDFS 文件的修改时间 通过该接口可查看指定 HDFS 中某个目录下所有文件 表 6-12 类 DFSColocationAdmin 常用接口说明 接口 public Map<String, List<DatanodeInfo>> createcolocationgroup(st ring groupid,string file) public Map<String, List<DatanodeInfo>> createcolocationgroup(st ring groupid,list<string> locators) public void deletecolocationgroup(st ring groupid) public List<String> listcolocationgroups() 说明 根据文件 file 中的 locatorids 信息, 创建 group file 为文件路径 使用内存中 List 的 locatorids 信息, 创建 group 删除 group 返回 colocation 所有组信息, 返回的组 Id 数组按创建时间排序 文档版本 01 ( ) 194

205 6 HDFS 应用开发 接口 public List<DatanodeInfo> getnodesforlocator(strin g groupid, String locatorid) 说明 获取该 locator 中所有节点列表 表 6-13 类 DFSColocationClient 常用接口说明 接口 public FSDataOutputStream create(path f, boolean overwrite, String groupid,string locatorid) public FSDataOutputStream create(final Path f, final FsPermission permission, final EnumSet<CreateFlag> cflags, final int buffersize, final short replication, final long blocksize, final Progressable progress, final ChecksumOpt checksumopt, final String groupid, final String locatorid) public void close() 说明 用 colocation 模式, 创建一个 FSDataOutputStream, 从而允许用户在 f 路径写文件 f 为 HDFS 路径 overwrite 表示如果文件已存在是否允许覆盖 用户指定文件所属的 groupid 和 locatorid 必须已经存在 功能与 FSDataOutputStream create(path f, boolean overwrite, String groupid,string locatorid) 相同, 只是允许用户自定义 checksum 选项 使用完毕后关闭连接 表 6-14 HDFS 客户端 WebHdfsFileSystem 接口说明 接口 public RemoteIterator<FileStatu s> liststatusiterator(final Path) 说明 该 API 有助于通过使用远程迭代的多个请求获取子文件和文件夹信息, 从而避免在获取大量子文件和文件夹信息时, 用户界面变慢 文档版本 01 ( ) 195

206 6 HDFS 应用开发 基于 API 的 Glob 路径模式以获取 LocatedFileStatus 和从 FileStatus 打开文件 在 DistributedFileSystem 中添加了以下 API, 以获取具有块位置的 FileStatus, 并从 FileStatus 对象打开文件 这些 API 将减少从客户端到 Namenode 的 RPC 调用的数量 表 6-15 FileSystem API 接口说明 Interface 接口 public LocatedFileStatus[] globlocatedstatus(path, PathFilter, boolean) throws IOException public FSDataInputStream open(filestatus stat) throws IOException Description 说明 返回一个 LocatedFileStatus 对象数组, 其对应文件路径符合路径过滤规则 如果 stat 对象是 LocatedFileStatusHdfs 的实例, 该实例已具有位置信息, 则直接创建 InputStream 而不联系 Namenode C API 功能简介 C 语言应用开发代码样例中所涉及的文件操作主要包括创建文件 读写文件 追加文件 删除文件 完整和详细的接口请直接参考官网上的描述以了解其使用方法 : hadoop.apache.org/docs/r2.7.2/hadoop-project-dist/hadoop-hdfs/libhdfs.html 代码样例 下面代码片段仅为演示, 具体代码请参见 HDFS 的 C 样例代码 hdfs_test.c MRS_Services_ClientConfig/HDFS/hdfs-c-example/hdfs_test.c 文件 1. 设置 HDFS NameNode 参数, 建立 HDFS 文件系统连接 hdfsfs fs = hdfsconnect("default", 0); fprintf(stderr, "hdfsconnect- SUCCESS!\n"); 2. 创建 HDFS 目录 const char* dir = "/nativetest"; int exitcode = hdfscreatedirectory(fs, dir); if( exitcode == -1 ){ fprintf(stderr, "Failed to create directory %s \n", dir ); exit(-1); fprintf(stderr, "hdfscreatedirectory- SUCCESS! : %s\n", dir); 3. 写文件 const char* file = "/nativetest/testfile.txt"; hdfsfile writefile = openfile(fs, (char*)file, O_WRONLY O_CREAT, 0, 0, 0); fprintf(stderr, "hdfsopenfile- SUCCESS! for write : %s\n", file); if(!hdfsfileisopenforwrite(writefile)){ fprintf(stderr, "Failed to open %s for writing.\n", file); exit(-1); char* buffer = "Hadoop HDFS Native file write!"; hdfswrite(fs, writefile, (void*)buffer, strlen(buffer)+1); fprintf(stderr, "hdfswrite- SUCCESS! : %s\n", file); printf("flushing file data...\n"); 文档版本 01 ( ) 196

207 6 HDFS 应用开发 if (hdfsflush(fs, writefile)) { fprintf(stderr, "Failed to 'flush' %s\n", file); exit(-1); hdfsclosefile(fs, writefile); fprintf(stderr, "hdfsclosefile- SUCCESS! : %s\n", file); 4. 读文件 hdfsfile readfile = openfile(fs, (char*)file, O_RDONLY, 100, 0, 0); fprintf(stderr, "hdfsopenfile- SUCCESS! for read : %s\n", file); if(!hdfsfileisopenforread(readfile)){ fprintf(stderr, "Failed to open %s for reading.\n", file); exit(-1); buffer = (char *) malloc(100); tsize num_read = hdfsread(fs, readfile, (void*)buffer, 100); fprintf(stderr, "hdfsread- SUCCESS!, Byte read : %d, File contant : %s \n", num_read,buffer); hdfsclosefile(fs, readfile); 5. 指定位置开始读文件 buffer = (char *) malloc(100); readfile = openfile(fs, file, O_RDONLY, 100, 0, 0); if (hdfsseek(fs, readfile, 10)) { fprintf(stderr, "Failed to 'seek' %s\n", file); exit(-1); num_read = hdfsread(fs, readfile, (void*)buffer, 100); fprintf(stderr, "hdfsseek- SUCCESS!, Byte read : %d, File seek contant : %s \n", num_read,buffer); hdfsclosefile(fs, readfile); 6. 拷贝文件 const char* destfile = "/nativetest/testfile1.txt"; if (hdfscopy(fs, file, fs, destfile)) { fprintf(stderr, "File copy failed, src : %s, des : %s \n", file, destfile); exit(-1); fprintf(stderr, "hdfscopy- SUCCESS!, File copied, src : %s, des : %s \n", file, destfile); 7. 移动文件 const char* mvfile = "/nativetest/testfile2.txt"; if (hdfsmove(fs, destfile, fs, mvfile )) { fprintf(stderr, "File move failed, src : %s, des : %s \n", destfile, mvfile); exit(-1); fprintf(stderr, "hdfsmove- SUCCESS!, File moved, src : %s, des : %s \n", destfile, mvfile); 8. 重命名文件 const char* renamefile = "/nativetest/testfile3.txt"; if (hdfsrename(fs, mvfile, renamefile)) { fprintf(stderr, "File rename failed, Old name : %s, New name : %s \n", mvfile, renamefile); exit(-1); fprintf(stderr, "hdfsrename- SUCCESS!, File renamed, Old name : %s, New name : %s \n", mvfile, renamefile); 9. 删除文件 if (hdfsdelete(fs, renamefile, 0)) { fprintf(stderr, "File delete failed : %s \n", renamefile); exit(-1); fprintf(stderr, "hdfsdelete- SUCCESS!, File deleted : %s\n",renamefile); 10. 设置副本数 if (hdfssetreplication(fs, file, 10)) { fprintf(stderr, "Failed to set replication : %s \n", file ); exit(-1); 文档版本 01 ( ) 197

208 6 HDFS 应用开发 fprintf(stderr, "hdfssetreplication- SUCCESS!, Set replication 10 for %s\n",file); 11. 设置用户 用户组 if (hdfschown(fs, file, "root", "root")) { fprintf(stderr, "Failed to set chown : %s \n", file ); exit(-1); fprintf(stderr, "hdfschown- SUCCESS!, Chown success for %s\n",file); 12. 设置权限 if (hdfschmod(fs, file, S_IRWXU S_IRWXG S_IRWXO)) { fprintf(stderr, "Failed to set chmod: %s \n", file ); exit(-1); fprintf(stderr, "hdfschmod- SUCCESS!, Chmod success for %s\n",file); 13. 设置文件时间 struct timeval now; gettimeofday(&now, NULL); if (hdfsutime(fs, file, now.tv_sec, now.tv_sec)) { fprintf(stderr, "Failed to set time: %s \n", file ); exit(-1); fprintf(stderr, "hdfsutime- SUCCESS!, Set time success for %s\n",file); 14. 获取文件信息 hdfsfileinfo *fileinfo = NULL; if((fileinfo = hdfsgetpathinfo(fs, file))!= NULL) { printfileinfo(fileinfo); hdfsfreefileinfo(fileinfo, 1); fprintf(stderr, "hdfsgetpathinfo - SUCCESS!\n"); 15. 变量目录 hdfsfileinfo *filelist = 0; int numentries = 0; if((filelist = hdfslistdirectory(fs, dir, &numentries))!= NULL) { int i = 0; for(i=0; i < numentries; ++i) { printfileinfo(filelist+i); hdfsfreefileinfo(filelist, numentries); fprintf(stderr, "hdfslistdirectory- SUCCESS!, %s\n", dir); 16. stream builder 接口 buffer = (char *) malloc(100); struct hdfsstreambuilder *builder= hdfsstreambuilderalloc(fs, (char*)file, O_RDONLY); hdfsstreambuildersetbuffersize(builder,100); hdfsstreambuildersetreplication(builder,20); hdfsstreambuildersetdefaultblocksize(builder, ); readfile = hdfsstreambuilderbuild(builder); num_read = hdfsread(fs, readfile, (void*)buffer, 100); fprintf(stderr, "hdfsstreambuilderbuild- SUCCESS! File read success. Byte read : %d, File contant : %s \n", num_read,buffer); struct hdfsreadstatistics *stats = NULL; hdfsfilegetreadstatistics(readfile, &stats); fprintf(stderr, "hdfsfilegetreadstatistics- SUCCESS! totalbytesread : %" PRId64 ", totallocalbytesread : %" PRId64 ", totalshortcircuitbytesread : %" PRId64 ", totalzerocopybytesread : %" PRId64 "\n", stats->totalbytesread, stats->totallocalbytesread, stats->totalshortcircuitbytesread, stats->totalzerocopybytesread); hdfsfilefreereadstatistics(stats); free(buffer); 17. 断开 HDFS 文件系统连接 hdfsdisconnect(fs); 文档版本 01 ( ) 198

209 6 HDFS 应用开发 准备运行环境 在节点上安装客户端, 例如安装到 /opt/client 目录, 安装方法可参考 MapReduce 服务用户指南 的 客户端管理 章节 1. 确认服务端 HDFS 组件已经安装, 并正常运行 2. 客户端运行环境已安装 1.7 或 1.8 版本的 JDK 3. 获取并解压缩安装 MRS_HDFS_Client.tar 包 执行如下命令解压 tar -xvf MRS_HDFS_Client.tar tar -xvf MRS_HDFS_ClientConfig.tar 说明 Linux 中编译并运行程序 由于不兼容老版本客户端, 建议用户获取与服务端集群相同版本的客户端安装包进行安装部署 4. 进入解压文件夹, 即 MRS_HDFS_ClientConfig.tar, 执行下列命令安装客户端 sh install.sh /opt/client 其中 /opt/client 为用户自定义路径, 此处仅为举例 5. 进入客户端安装目录 /opt/client, 执行下列命令初始化环境变量 source bigdata_env 说明 从目录 Datasight_V100R002CXX_CodeExample/hdfs-example-normal/src/com/huawei/hdfs/ nativeclient 中复制 C 源文件, 并上传 C 源文件 hdfs_test.c 和 makefile 文件至 Linux 客户端环境中运行应用, 上传路径为 /opt/client/hdfs/hadoop/hdfs-c-example/hdfs_test.c 和 /opt/client/hdfs/ hadoop/hdfs-c-example/makefile 如果该路径下 hdfs_test.c 文件和 makefile 文件已存在, 则将其覆盖 1. 进入 Linux 客户端目录, 运行如下命令导入公共环境变量 : cd /opt/client source bigdata_env 2. 在该目录下用 hdfs 用户进行命令行认证 kinit hdfs 说明 kinit 一次票据时效 24 小时 24 小时后再次运行样例, 需要重新 kinit 命令 3. 进入 /opt/client/hdfs/hadoop/hdfs-c-example 目录下, 运行如下命令导入 C 客户端环境变量 cd /opt/client/hdfs/hadoop/hdfs-c-example source component_env_c_example 4. 清除之前运行生成的目标文件和可执行文件, 运行如下命令 make clean 执行结果如下 : [root@ hdfs-c-example]# make clean rm -f hdfs_test.o rm -f hdfs_test 5. 编译生成新的目标和可执行文件, 运行如下命令 文档版本 01 ( ) 199

210 6 HDFS 应用开发 make( 或 make all) 执行结果如下 : [root@ hdfs-c-example]# make all cc -c -I/opt/client/HDFS/hadoop/include -Wall -o hdfs_test.o hdfs_test.c cc -o hdfs_test hdfs_test.o -lhdfs 6. 运行文件以实现创建文件 读写追加文件和删除文件的功能, 运行如下命令 make run 执行结果如下 : [root@ hdfs-c-example]# make run./hdfs_test hdfsconnect- SUCCESS! hdfscreatedirectory- SUCCESS! : /nativetest hdfsopenfile- SUCCESS! for write : /nativetest/testfile.txt hdfswrite- SUCCESS! : /nativetest/testfile.txt Flushing file data... hdfsclosefile- SUCCESS! : /nativetest/testfile.txt hdfsopenfile- SUCCESS! for read : /nativetest/testfile.txt hdfsread- SUCCESS!, Byte read : 31, File contant : Hadoop HDFS Native file write! hdfsseek- SUCCESS!, Byte read : 21, File seek contant : S Native fi ²* rite! hdfscopy- SUCCESS!, File copied, src : /nativetest/testfile.txt, des : /nativetest/ testfile1.txt hdfsmove- SUCCESS!, File moved, src : /nativetest/testfile1.txt, des : /nativetest/ testfile2.txt hdfsrename- SUCCESS!, File renamed, Old name : /nativetest/testfile2.txt, New name : / nativetest/testfile3.txt hdfsdelete- SUCCESS!, File deleted : /nativetest/testfile3.txt hdfssetreplication- SUCCESS!, Set replication 10 for /nativetest/testfile.txt hdfschown- SUCCESS!, Chown success for /nativetest/testfile.txt hdfschmod- SUCCESS!, Chmod success for /nativetest/testfile.txt hdfsutime- SUCCESS!, Set time success for /nativetest/testfile.txt Name: hdfs://hacluster/nativetest/testfile.txt, Type: F, Replication: 10, BlockSize: , Size: 31, LastMod: , Owner: root, Group: root, Permissions: 511 (rwxrwxrwx) hdfsgetpathinfo - SUCCESS! Name: hdfs://hacluster/nativetest/testfile.txt, Type: F, Replication: 10, BlockSize: , Size: 31, LastMod: , Owner: root, Group: root, Permissions: 511 (rwxrwxrwx) hdfslistdirectory- SUCCESS!, /nativetest hdfstruncatefile- SUCCESS!, /nativetest/testfile.txt Block Size : hdfsgetdefaultblocksize- SUCCESS! Block Size : for file /nativetest/testfile.txt hdfsgetdefaultblocksizeatpath- SUCCESS! HDFS Capacity : hdfsgetcapacity- SUCCESS! HDFS Used : hdfsgetcapacity- SUCCESS! hdfsexists- SUCCESS! /nativetest/testfile.txt hdfsconfgetstr- SUCCESS : hdfs://hacluster hdfsstreambuilderbuild- SUCCESS! File read success. Byte read : 31, File contant : Hadoop HDFS Native file write! hdfsfilegetreadstatistics- SUCCESS! totalbytesread : 31, totallocalbytesread : 31, totalshortcircuitbytesread : 0, totalzerocopybytesread : 0 [root@ hdfs-c-example]# make run./hdfs_test hdfsconnect- SUCCESS! hdfscreatedirectory- SUCCESS! : /nativetest hdfsopenfile- SUCCESS! for write : /nativetest/testfile.txt hdfswrite- SUCCESS! : /nativetest/testfile.txt Flushing file data... hdfsclosefile- SUCCESS! : /nativetest/testfile.txt hdfsopenfile- SUCCESS! for read : /nativetest/testfile.txt hdfsread- SUCCESS!, Byte read : 31, File contant : Hadoop HDFS Native file write! hdfsseek- SUCCESS!, Byte read : 21, File seek contant : S Native file write! hdfspread- SUCCESS!, Byte read : 10, File pead contant : S Native f hdfscopy- SUCCESS!, File copied, src : /nativetest/testfile.txt, des : /nativetest/ 文档版本 01 ( ) 200

211 6 HDFS 应用开发 testfile1.txt hdfsmove- SUCCESS!, File moved, src : /nativetest/testfile1.txt, des : /nativetest/ testfile2.txt hdfsrename- SUCCESS!, File renamed, Old name : /nativetest/testfile2.txt, New name : / nativetest/testfile3.txt hdfsdelete- SUCCESS!, File deleted : /nativetest/testfile3.txt hdfssetreplication- SUCCESS!, Set replication 10 for /nativetest/testfile.txt hdfschown- SUCCESS!, Chown success for /nativetest/testfile.txt hdfschmod- SUCCESS!, Chmod success for /nativetest/testfile.txt hdfsutime- SUCCESS!, Set time success for /nativetest/testfile.txt Name: hdfs://hacluster/nativetest/testfile.txt, Type: F, Replication: 10, BlockSize: , Size: 31, LastMod: , Owner: root, Group: root, Permissions: 511 (rwxrwxrwx) hdfsgetpathinfo - SUCCESS! Name: hdfs://hacluster/nativetest/testfile.txt, Type: F, Replication: 10, BlockSize: , Size: 31, LastMod: , Owner: root, Group: root, Permissions: 511 (rwxrwxrwx) hdfslistdirectory- SUCCESS!, /nativetest hdfstruncatefile- SUCCESS!, /nativetest/testfile.txt Block Size : hdfsgetdefaultblocksize- SUCCESS! Block Size : for file /nativetest/testfile.txt hdfsgetdefaultblocksizeatpath- SUCCESS! HDFS Capacity : hdfsgetcapacity- SUCCESS! HDFS Used : hdfsgetcapacity- SUCCESS! hdfsexists- SUCCESS! /nativetest/testfile.txt hdfsconfgetstr- SUCCESS : hdfs://hacluster hdfsstreambuilderbuild- SUCCESS! File read success. Byte read : 31, File contant : Hadoop HDFS Native file write! hdfsfilegetreadstatistics- SUCCESS! totalbytesread : 31, totallocalbytesread : 0, totalshortcircuitbytesread : 0, totalzerocopybytesread : 0 7. 进入 debug 模式 ( 可选 ) make gdb 执行结果如下 : [root@ hdfs-c-example]# make gdb gdb hdfs_test GNU gdb (GDB) SUSE ( ) Copyright (C) 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later < This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-suse-linux". For bug reporting instructions, please see: < Reading symbols from /opt/hadoop-client/hdfs/hadoop/hdfs-c-example/hdfs_test...done. (gdb) [root@ hdfs-c-example]# make gdb gdb hdfs_test GNU gdb (GDB) SUSE ( ) Copyright (C) 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later < This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-suse-linux". For bug reporting instructions, please see: < Reading symbols from /opt/client/hdfs/hadoop/hdfs-c-example/hdfs_test...done. (gdb) 文档版本 01 ( ) 201

212 6 HDFS 应用开发 HTTP REST API 功能简介 准备运行环境 REST 应用开发代码样例中所涉及的文件操作主要包括创建文件 读写文件 追加文件 删除文件 完整和详细的接口请参考官网上的描述以了解其使用 : hadoop.apache.org/docs/r2.7.2/hadoop-project-dist/hadoop-hdfs/webhdfs.html 步骤 1 安装客户端 在节点上安装客户端, 如安装到 /opt/client 目录, 可安装方法可参考 MapReduce 服务用户指南 的 客户端管理 章节 1. 确认服务端 HDFS 组件已经安装, 并正常运行 2. 客户端运行环境已安装 1.7 或 1.8 版本的 JDK 3. 获取并解压缩安装包 MRS_HDFS_Client.tar 执行如下命令解压 tar -xvf MRS_HDFS_Client.tar tar -xvf MRS_HDFS_ClientConfig.tar 说明 由于不兼容老版本客户端, 建议用户获取与服务端集群相同版本的客户端安装包进行安装部署 4. 进入解压文件夹, 即 MRS_HDFS_ClientConfig, 执行下列命令安装客户端 sh install.sh /opt/client 其中 /opt/client 为用户自定义路径, 此处仅为举例 5. 进入客户端安装目录 /opt/client, 执行下列命令初始化环境变量 source bigdata_env 6. 执行下列命令进行用户认证, 这里以 hdfs 为例, 用户可根据实际用户名修改 kinit hdfs 说明 kinit 一次的时效 24 小时 24 小时后再次运行样例, 需要重新执行 kinit 7. 在客户端目录准备文件 testfile 和 testfileappend, 文件内容分别 Hello, webhdfs user! 和 Welcome back to webhdfs!, 执行如下命令准备文件 touch testfile vi testfile 写入 Hello, webhdfs user! 保存退出 touch testfileappend vi testfileappend 写入 Welcome back to webhdfs! 保存退出 步骤 2 步骤 3 MRS 集群默认只支持 HTTPS 服务访问, 若使用 HTTPS 服务访问, 执行步骤 3; 若使用 HTTP 服务访问, 执行步骤 4 与 HTTP 服务访问相比, 以 HTTPS 方式访问 HDFS 时, 由于使用了 SSL 安全加密, 需要确保 Curl 命令所支持的 SSL 协议在集群中已添加支持 若不支持, 可对应修改集群中 SSL 协议 例如, 若 Curl 仅支持 TLSv1 协议, 修改方法如下 : 登录 MRS Manager 页面, 单击 服务管理 > HDFS > 服务配置, 在 参数类别 选择 全部配置, 在 搜索 框里搜索 hadoop.ssl.enabled.protocols, 查看参数值是否 文档版本 01 ( ) 202

213 6 HDFS 应用开发 包含 TLSv1, 若不包含, 则在配置项 hadoop.ssl.enabled.protocols 中追加,TLSv1 清空 ssl.server.exclude.cipher.list 配置项的值, 否则以 HTTPS 访问不了 HDFS 单击 保存配置, 并勾选 重新启动受影响的服务或实例, 单击 确定, 重启 HDFS 服务 说明 TLSv1 协议存在安全漏洞, 请谨慎使用 步骤 4 登录 MRS Manager 页面, 单击 服务管理 > HDFS > 服务配置, 在 参数类别 选择 全部配置, 在 搜索 框里搜索 dfs.http.policy, 然后勾选 HTTP_AND_HTTPS, 单击 保存配置, 并勾选 重新启动受影响的服务或实例, 单击 确定, 重启 HDFS 服务 ---- 结束 操作步骤 步骤 1 登录 MRS Manager, 单击 服务管理, 选择 HDFS, 单击进入 HDFS 服务状态页面 说明 由于 webhdfs 是 http/https 访问的, 需要主 NameNode 的 IP 和 http/https 端口 1. 单击 实例, 进入图 6-8 界面, 找到 NameNode(hacluster, 主 ) 的主机名 (host) 和对应的 IP 图 6-8 HDFS 实例 文档版本 01 ( ) 203

214 6 HDFS 应用开发 2. 单击 服务配置, 进入图 6-9 界面, 找到 namenode.http.port (9870) 和 namenode.https.port (9871) 说明 MRS 及以下版本, 上述端口默认值分别为 和 25003, 详见 MRS 用户指南, 组件端口信息章节 图 6-9 HDFS 服务配置 步骤 2 参考如下链接, 创建目录 WebHDFS.html#Make_a_Directory 单击链接, 如图 6-10 所示 图 6-10 创建目录样例命令 进入到客户端的安装目录下, 此处为 /opt/client, 创建名为 huawei 的目录 1. 执行下列命令, 查看当前是否存在名为 huawei 的目录 hdfs dfs -ls / 执行结果如下 : linux1:/opt/client # hdfs dfs -ls / 16/04/22 16:10:02 INFO hdfs.peercache: SocketCache disabled. Found 7 items -rw-r--r-- 3 hdfs supergroup :03 /PRE_CREATE_DIR.SUCCESS drwxr-x--- - flume hadoop :02 /flume 文档版本 01 ( ) 204

215 6 HDFS 应用开发 drwx hbase hadoop :19 /hbase drwxrwxrwx - mapred hadoop :02 /mr-history drwxrwxrwx - spark supergroup :19 /sparkjobhistory drwxrwxrwx - hdfs hadoop :51 /tmp drwxrwxrwx - hdfs hadoop :50 /user 当前路径下不存在 huawei 目录 2. 执行图 6-10 中的命令创建以 huawei 为名的目录 其中, 用步骤 1 中查找到的主机名或 IP 和端口分别替代命令中的 <HOST> 和 <PORT>, 在 <PATH> 中输入想要创建的目录 huawei 说明 用主机名或 IP 代替 <HOST> 都是可以的, 要注意 HTTP 和 HTTPS 的端口不同 执行下列命令访问 HTTP: linux1:/opt/client # curl -i -X PUT --negotiate -u: " huawei?op=mkdirs" 其中用 linux1 代替 <HOST>, 用 9870 代替 <PORT> 运行结果 : HTTP/ Authentication required Date: Thu, 05 May :10:09 GMT Pragma: no-cache Date: Thu, 05 May :10:09 GMT Pragma: no-cache X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate Set-Cookie: hadoop.auth=; Path=/; Expires=Thu, 01-Jan :00:00 GMT; HttpOnly Content-Length: 0 HTTP/ OK Cache-Control: no-cache Expires: Thu, 05 May :10:09 GMT Date: Thu, 05 May :10:09 GMT Pragma: no-cache Expires: Thu, 05 May :10:09 GMT Date: Thu, 05 May :10:09 GMT Pragma: no-cache Content-Type: application/json X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate YGoGCSqGSIb3EgECAgIAb1swWaADAgEFoQMCAQ +ittbloamcarkirarcarhuv39ttp6lhblg3b0jamfjv9welp+sgfi+t2hsehn6p4uvwkky/kd9dkegnmlydu/ o7ytzs0cqmxnsi69wbn5h Set-Cookie: hadoop.auth="u=hdfs&p=hdfs@hadoop.com&t=kerberos&e= &s=wirf4rdtwpm3tdst+a/ Sy0lwgA4="; Path=/; Expires=Thu, 05-May :10:09 GMT; HttpOnly Transfer-Encoding: chunked {"boolean":truelinux1:/opt/client # 返回值 {"boolean":true 说明创建成功 执行下列命令访问 HTTPS: linux1:/opt/client # curl -i -k -X PUT --negotiate -u: " webhdfs/v1/huawei?op=mkdirs" 其中用 IP 代替 <HOST>, 用 9871 代替 <PORT> 运行结果 : HTTP/ Authentication required Date: Fri, 22 Apr :13:37 GMT Pragma: no-cache Date: Fri, 22 Apr :13:37 GMT Pragma: no-cache X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate Set-Cookie: hadoop.auth=; Path=/; Expires=Thu, 01-Jan :00:00 GMT; Secure; HttpOnly Content-Length: 0 HTTP/ OK Cache-Control: no-cache 文档版本 01 ( ) 205

216 6 HDFS 应用开发 Expires: Fri, 22 Apr :13:37 GMT Date: Fri, 22 Apr :13:37 GMT Pragma: no-cache Expires: Fri, 22 Apr :13:37 GMT Date: Fri, 22 Apr :13:37 GMT Pragma: no-cache Content-Type: application/json X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate YGoGCSqGSIb3EgECAgIAb1swWaADAgEFoQMCAQ+iTTBLoAMCARKiRARCugB +yt3y+z8ycrmyjhxf84o1cycfjq157+nzn1gu7d7yhmulnjr+7buudeczkewfr7ud+drimy3akg3ogu45xq9r Set-Cookie: Aknoz410yJPTLHg="; Path=/; Expires=Fri, 22-Apr :13:37 GMT; Secure; HttpOnly Transfer-Encoding: chunked {"boolean":truelinux1:/opt/client # 返回值 {"boolean":true 说明创建成功 3. 再执行下列命令进行查看, 可以看到路径下出现 huawei 目录 linux1:/opt/client # hdfs dfs -ls / 16/04/22 16:14:25 INFO hdfs.peercache: SocketCache disabled. Found 8 items -rw-r--r-- 3 hdfs supergroup :03 /PRE_CREATE_DIR.SUCCESS drwxr-x--- - flume hadoop :02 /flume drwx hbase hadoop :19 /hbase drwxr-xr-x - hdfs supergroup :13 /huawei drwxrwxrwx - mapred hadoop :02 /mr-history drwxrwxrwx - spark supergroup :12 /sparkjobhistory drwxrwxrwx - hdfs hadoop :51 /tmp drwxrwxrwx - hdfs hadoop :10 /user 步骤 3 创建请求上传命令, 获取集群分配的可写入 DataNode 节点地址的信息 Location 执行如下命令访问 HTTP: linux1:/opt/client # curl -i -X PUT --negotiate -u: " testhdfs?op=create" 运行结果 : HTTP/ Authentication required Date: Thu, 05 May :09:48 GMT Pragma: no-cache Date: Thu, 05 May :09:48 GMT Pragma: no-cache X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate Set-Cookie: hadoop.auth=; Path=/; Expires=Thu, 01-Jan :00:00 GMT; HttpOnly Content-Length: 0 HTTP/ TEMPORARY_REDIRECT Cache-Control: no-cache Expires: Thu, 05 May :09:48 GMT Date: Thu, 05 May :09:48 GMT Pragma: no-cache Expires: Thu, 05 May :09:48 GMT Date: Thu, 05 May :09:48 GMT Pragma: no-cache Content-Type: application/octet-stream X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate YGoGCSqGSIb3EgECAgIAb1swWaADAgEFoQMCAQ+iTTBLoAMCARKiRARCzQ6w +9pNzWCTJEdoU3z9xKEyg1JQNka0nYaB9TndvrL5S0neAoK2usnictTFnqIincAjwB6SnTtht8Q16WDlHJX/ Set-Cookie: hadoop.auth="u=hdfs&p=hdfs@hadoop.com&t=kerberos&e= &s=qry87vayyzsn9vss6rm6vklhke U="; Path=/; Expires=Thu, 05-May :09:48 GMT; HttpOnly Location: op=create&delegation=hgafywrtaw4fywrtaw4aigfuf4lzdiobvkov3xqocbsyxvfap92alcrs4j- KNulnN6wUoBJXRUJIREZTIGRlbGVnYXRpb24UMTAuMTIwLjE3Mi4xMDk6MjUwMDA&namenoderpcaddress=hacluster &overwrite=false Content-Length: 0 执行如下命令访问 HTTPS: 文档版本 01 ( ) 206

217 6 HDFS 应用开发 linux1:/opt/client # curl -i -k -X PUT --negotiate -u: " huawei/testhdfs?op=create" 运行结果 : HTTP/ Authentication required Date: Thu, 05 May :46:18 GMT Pragma: no-cache Date: Thu, 05 May :46:18 GMT Pragma: no-cache X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate Set-Cookie: hadoop.auth=; Path=/; Expires=Thu, 01-Jan :00:00 GMT; Secure; HttpOnly Content-Length: 0 HTTP/ TEMPORARY_REDIRECT Cache-Control: no-cache Expires: Thu, 05 May :46:18 GMT Date: Thu, 05 May :46:18 GMT Pragma: no-cache Expires: Thu, 05 May :46:18 GMT Date: Thu, 05 May :46:18 GMT Pragma: no-cache Content-Type: application/octet-stream X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate YGoGCSqGSIb3EgECAgIAb1swWaADAgEFoQMCAQ +ittbloamcarkirarczmyr8ggukn7ppzaooyzd5hxzltrz71anguhkubw2wc/18m9/ OOZstGQ6M1wH2pGriipuCNsKIfwP93eO2Co0fQF3 Set-Cookie: hadoop.auth="u=hdfs&p=hdfs@hadoop.com&t=kerberos&e= &s=f4rxuweevhzze3pr8txkzcv7rq Q="; Path=/; Expires=Thu, 05-May :46:18 GMT; Secure; HttpOnly Location: op=create&delegation=hgafywrtaw4fywrtaw4aigfufwx3t4obvkmse7cccbsfjti9j7x64qwnsz59tgfpkff7ghnt V0VCSERGUyBkZWxlZ2F0aW9uFDEwLjEyMC4xNzIuMTA5OjI1MDAw&namenoderpcaddress=hacluster&overwrite=f alse Content-Length: 0 步骤 4 根据获取的 Location 地址信息, 可在 HDFS 文件系统上创建 /huawei/testhdfs 文件, 并将本地 testfile 中的内容上传至 testhdfs 文件 执行如下命令访问 HTTP: linux1:/opt/client # curl -i -X PUT -T testfile --negotiate -u: " webhdfs/v1/huawei/testhdfs? op=create&delegation=hgafywrtaw4fywrtaw4aigfuf4lzdiobvkov3xqocbsyxvfap92alcrs4j- KNulnN6wUoBJXRUJIREZTIGRlbGVnYXRpb24UMTAuMTIwLjE3Mi4xMDk6MjUwMDA&namenoderpcaddress=hacluster &overwrite=false" 运行结果 : HTTP/ Continue HTTP/ Created Location: hdfs://hacluster/huawei/testhdfs Content-Length: 0 Connection: close 执行如下命令访问 HTTPS: linux1:/opt/client # curl -i -k -X PUT -T testfile --negotiate -u: " huawei/testhdfs?op=create"x1:25011/webhdfs/v1/huawei/testhdfs? op=create&delegation=hgafywrtaw4fywrtaw4aigfufwx3t4obvkmse7cccbsfjti9j7x64qwnsz59tgfpkff7ghnt V0VCSERGUyBkZWxlZ2F0aW9uFDEwLjEyMC4xNzIuMTA5OjI1MDAw&namenoderpcaddress=hacluster&overwrite=f alse" 运行结果 : HTTP/ Continue HTTP/ Created Location: hdfs://hacluster/huawei/testhdfs Content-Length: 0 Connection: close 步骤 5 打开 /huawei/testhdfs 文件, 并读取文件中上传写入的内容 执行如下命令访问 HTTP: linux1:/opt/client # curl -L --negotiate -u: " op=open" 文档版本 01 ( ) 207

218 6 HDFS 应用开发 运行结果 : Hello, webhdfs user! 执行如下命令访问 HTTPS: linux1:/opt/client # curl -k -L --negotiate -u: " testhdfs?op=open" 运行结果 : Hello, webhdfs user! 步骤 6 创建请求追加文件的命令, 获取集群为已存在 /huawei/testhdfs 文件分配的可写入 DataNode 节点地址信息 Location 执行如下命令访问 HTTP: linux1:/opt/client # curl -i -X POST --negotiate -u: " testhdfs?op=append" 运行结果 : HTTP/ Authentication required Cache-Control: must-revalidate,no-cache,no-store Date: Thu, 05 May :35:02 GMT Pragma: no-cache Date: Thu, 05 May :35:02 GMT Pragma: no-cache Content-Type: text/html; charset=iso X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate Set-Cookie: hadoop.auth=; Path=/; Expires=Thu, 01-Jan :00:00 GMT; HttpOnly Content-Length: 1349 HTTP/ TEMPORARY_REDIRECT Cache-Control: no-cache Expires: Thu, 05 May :35:02 GMT Date: Thu, 05 May :35:02 GMT Pragma: no-cache Expires: Thu, 05 May :35:02 GMT Date: Thu, 05 May :35:02 GMT Pragma: no-cache Content-Type: application/octet-stream X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate YGoGCSqGSIb3EgECAgIAb1swWaADAgEFoQMCAQ+iTTBLoAMCARKiRARCTYvNX/ 2JMXhzsVPTw3Sluox6s/gEroHH980xMBkkYlCnO3W+0fM32c4/F98U5bl5dzgoolQoBvqq/EYXivvR12WX Set-Cookie: hadoop.auth="u=hdfs&p=hdfs@hadoop.com&t=kerberos&e= &s=et1okviod7dwj/ LdhzNeS2wQEEY="; Path=/; Expires=Thu, 05-May :35:02 GMT; HttpOnly Location: op=append&delegation=hgafywrtaw4fywrtaw4aigfuf2mghoobvkn2ch4kcbrzjm3jwsmlaowxb4dhqfkb5rt-8hjx RUJIREZTIGRlbGVnYXRpb24UMTAuMTIwLjE3Mi4xMDk6MjUwMDA&namenoderpcaddress=hacluster Content-Length: 0 执行如下命令访问 HTTPS: linux1:/opt/client # curl -i -k -X POST --negotiate -u: " huawei/testhdfs?op=append" 运行结果 : HTTP/ Authentication required Cache-Control: must-revalidate,no-cache,no-store Date: Thu, 05 May :20:41 GMT Pragma: no-cache Date: Thu, 05 May :20:41 GMT Pragma: no-cache Content-Type: text/html; charset=iso X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate Set-Cookie: hadoop.auth=; Path=/; Expires=Thu, 01-Jan :00:00 GMT; Secure; HttpOnly Content-Length: 1349 HTTP/ TEMPORARY_REDIRECT Cache-Control: no-cache Expires: Thu, 05 May :20:41 GMT Date: Thu, 05 May :20:41 GMT Pragma: no-cache 文档版本 01 ( ) 208

219 6 HDFS 应用开发 Expires: Thu, 05 May :20:41 GMT Date: Thu, 05 May :20:41 GMT Pragma: no-cache Content-Type: application/octet-stream X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate YGoGCSqGSIb3EgECAgIAb1swWaADAgEFoQMCAQ +ittbloamcarkirarcxgdjzuoxlhgtm1oyrpcxk95/ Y869eMfXIQV5UdEwBZ0iQiYaOdf5+Vk7a7FezhmzCABOWYXPxEQPNugbZ/yD5VLT Set-Cookie: 0="; Path=/; Expires=Thu, 05-May :20:41 GMT; Secure; HttpOnly Location: op=append&delegation=hgafywrtaw4fywrtaw4aigfuf1xi_4obvkno5v8hcbse3fg0f_ewtfkklodkqsm2t32cjhnt V0VCSERGUyBkZWxlZ2F0aW9uFDEwLjEyMC4xNzIuMTA5OjI1MDAw&namenoderpcaddress=hacluster 步骤 7 步骤 8 步骤 9 根据获取的 Location 地址信息, 可将本地 testfileappend 文件中的内容追加到 HDFS 文件系统上的 /huawei/testhdfs 文件 执行如下命令访问 HTTP: linux1:/opt/client # curl -i -X POST -T testfileappend --negotiate -u: " webhdfs/v1/huawei/testhdfs? op=append&delegation=hgafywrtaw4fywrtaw4aigfuf2mghoobvkn2ch4kcbrzjm3jwsmlaowxb4dhqfkb5rt-8hjx RUJIREZTIGRlbGVnYXRpb24UMTAuMTIwLjE3Mi4xMDk6MjUwMDA&namenoderpcaddress=hacluster" 运行结果 : HTTP/ Continue HTTP/ OK Content-Length: 0 Connection: close 执行如下命令访问 HTTPS: linux1:/opt/client # curl -i -k -X POST -T testfileappend --negotiate -u: " linux1:9865/webhdfs/v1/huawei/testhdfs? op=append&delegation=hgafywrtaw4fywrtaw4aigfuf1xi_4obvkno5v8hcbse3fg0f_ewtfkklodkqsm2t32cjhnt V0VCSERGUyBkZWxlZ2F0aW9uFDEwLjEyMC4xNzIuMTA5OjI1MDAw&namenoderpcaddress=hacluster" 运行结果 : HTTP/ Continue HTTP/ OK Content-Length: 0 Connection: close 打开 /huawei/testhdfs 文件, 并读取文件中全部的内容 执行如下命令访问 HTTP: linux1:/opt/client # curl -L --negotiate -u: " op=open" 运行结果 : Hello, webhdfs user! Welcome back to webhdfs! 执行如下命令访问 HTTPS: linux1:/opt/client # curl -k -L --negotiate -u: " testhdfs?op=open" 运行结果 : Hello, webhdfs user! Welcome back to webhdfs! 可列出文件系统上 huawei 目录下所有目录和文件的详细信息 LISTSTATUS 将在一个请求中返回所有子文件和文件夹的信息 执行如下命令访问 HTTP: linux1:/opt/client # curl --negotiate -u: " op=liststatus" 运行结果 : {"FileStatuses":{"FileStatus":[ {"accesstime": ,"blocksize": ,"childrennum":0,"fileid": 17680,"group":"supergroup","length":70,"modificationTime": 文档版本 01 ( ) 209

220 6 HDFS 应用开发 ,"owner":"hdfs","pathSuffix":"","permission":"755","replication": 3,"storagePolicy":0,"type":"FILE" ] 执行如下命令访问 HTTPS: linux1:/opt/client # curl -k --negotiate -u: " op=liststatus" 运行结果 : {"FileStatuses":{"FileStatus":[ {"accesstime": ,"blocksize": ,"childrennum":0,"fileid": 17680,"group":"supergroup","length":70,"modificationTime": ,"owner":"hdfs","pathSuffix":"","permission":"755","replication": 3,"storagePolicy":0,"type":"FILE" ] 带有大小参数和 startafter 参数的 LISTSTATUS 将有助于通过多个请求获取子文件和文件夹信息, 从而避免获取大量子文件和文件夹信息时, 用户界面变慢 执行如下命令访问 HTTP: linux1:/opt/client # curl --negotiate -u: " op=liststatus&startafter=sparkjobhistory&size=1" 运行结果 : {"FileStatuses":{"FileStatus":[ {"accesstime": ,"blocksize": ,"childrennum":0,"fileid": 17680,"group":"supergroup","length":70,"modificationTime": ,"owner":"hdfs","pathSuffix":"testHdfs","permission":"755","replication": 3,"storagePolicy":0,"type":"FILE" ] 执行如下命令访问 HTTPS: linux1:/opt/client # curl -k --negotiate -u: " op=liststatus&startafter=sparkjobhistory&size=1" 运行结果 : {"FileStatuses":{"FileStatus":[ {"accesstime": ,"blocksize": ,"childrennum":0,"fileid": 17680,"group":"supergroup","length":70,"modificationTime": ,"owner":"hdfs","pathSuffix":"testHdfs","permission":"755","replication": 3,"storagePolicy":0,"type":"FILE" ] 步骤 10 删除 HDFS 上的文件 /huawei/testhdfs 执行如下命令访问 HTTP: linux1:/opt/client # curl -i -X DELETE --negotiate -u: " huawei/testhdfs?op=delete" 运行结果 : HTTP/ Authentication required Date: Thu, 05 May :54:37 GMT Pragma: no-cache Date: Thu, 05 May :54:37 GMT Pragma: no-cache X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate Set-Cookie: hadoop.auth=; Path=/; Expires=Thu, 01-Jan :00:00 GMT; HttpOnly Content-Length: 0 HTTP/ OK Cache-Control: no-cache Expires: Thu, 05 May :54:37 GMT Date: Thu, 05 May :54:37 GMT Pragma: no-cache Expires: Thu, 05 May :54:37 GMT Date: Thu, 05 May :54:37 GMT Pragma: no-cache Content-Type: application/json X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate YGoGCSqGSIb3EgECAgIAb1swWaADAgEFoQMCAQ+iTTBLoAMCARKiRARC9k0/ v6ed8vluby3kut0b4rkqknmcrdevslgqouqrorkzwi3wu+xljumklmzawpp+bpzpx8o2od81mlbgdi8soklw 文档版本 01 ( ) 210

221 6 HDFS 应用开发 Set-Cookie: I="; Path=/; Expires=Thu, 05-May :54:37 GMT; HttpOnly Transfer-Encoding: chunked {"boolean":truelinux1:/opt/client # 执行如下命令访问 HTTPS: linux1:/opt/client # curl -i -k -X DELETE --negotiate -u: " huawei/testhdfs?op=delete" 运行结果 : HTTP/ Authentication required Date: Thu, 05 May :20:10 GMT Pragma: no-cache Date: Thu, 05 May :20:10 GMT Pragma: no-cache X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate Set-Cookie: hadoop.auth=; Path=/; Expires=Thu, 01-Jan :00:00 GMT; Secure; HttpOnly Content-Length: 0 HTTP/ OK Cache-Control: no-cache Expires: Thu, 05 May :20:10 GMT Date: Thu, 05 May :20:10 GMT Pragma: no-cache Expires: Thu, 05 May :20:10 GMT Date: Thu, 05 May :20:10 GMT Pragma: no-cache Content-Type: application/json X-Frame-Options: SAMEORIGIN WWW-Authenticate: Negotiate YGoGCSqGSIb3EgECAgIAb1swWaADAgEFoQMCAQ +ittbloamcarkirarcly5vrvmgsih2vwrypc30izgffruf4nxnahcwni3tiduotl+s+hfjatsbo/+uayqi/ 6k9jAfaJrvFIfxqppFtofpp Set-Cookie: hadoop.auth="u=hdfs&p=hdfs@hadoop.com&t=kerberos&e= &s=kgd2sbh/ EUSaaeVKCb5zPzGBRKo="; Path=/; Expires=Thu, 05-May :20:10 GMT; Secure; HttpOnly Transfer-Encoding: chunked {"boolean":truelinux1:/opt/client # ---- 结束 密钥管理系统通过 HTTP REST API 对外提供密钥管理服务, 接口请参考官网 : 说明 Shell 命令介绍 HDFS Shell 由于 REST API 接口做了安全加固, 防止脚本注入攻击 通过 REST API 的接口, 无法创建包含 "<script ", "<iframe", "<frame", "javascript:" 这些关键字的目录和文件名 您可以使用 HDFS Shell 命令对 HDFS 文件系统进行操作, 例如读文件 写文件等操作 执行 HDFS Shell 的方法 : 进入 HDFS 客户端如下目录, 直接输入命令即可 例如 : cd /opt/client/hdfs/hadoop/bin./hdfs dfs -mkdir /tmp/input 执行如下命令查询 HDFS 命令的帮助./hdfs --help 文档版本 01 ( ) 211

222 6 HDFS 应用开发 HDFS 命令行参考请参见官网 : FileSystemShell.html 表 6-16 透明加密相关命令 场景操作命令描述 hadoo p shell 命令管理密钥 创建密钥 hadoop key create <keyname> [-cipher <cipher>] [-size <size>] [- description <description>] [-attr <attribute=value>] [-provider <provider>] [-help] create 子命令为 provider 中 <keyname> 参数指定的 name 创建一个新的密钥,provider 是由 - provider 参数指定 用户可以使用参数 - cipher 定义一个密码 目前默认的密码为 "AES/CTR/NoPadding" 默认密钥的长度为 128 用户可以使用参数 - size 定义需要的密钥的长度 任意的 attribute=value 类型属性可以用参数 -attr 定义 每一个属性,-attr 可以被定义很多次 回滚操作 hadoop key roll <keyname> [- provider <provider>] [-help] roll 子命令为 provider 中指定的 key 创建一个新的版本,provider 是由 -provider 参数指定 删除密钥 hadoop key delete <keyname> [- provider <provider>] [-f] [- help] delete 子命令删除 key 的所有版本,key 是由 provider 中的 <keyname> 参数指定,provider 是由 -provider 参数指定 除非 -f 被指定否则该命令需要用户确认 查看密钥 hadoop key list [- provider <provider>] [- metadata] [-help] list 子命令显示 provider 中所有的密钥名, 这个 provider 由用户在 core-site.xml 中配置或者由 -provider 参数指定 -metadata 参数显示的是元数据 6.6 开发规范 规则 HDFS NameNode 元数据存储路径 NameNode 元数据信息的默认存储路径为 ${BIGDATA_DATA_HOME/namenode/ data, 该参数用于确定 HDFS 文件系统的元数据信息的保存路径 HDFS 需要开启 NameNode 镜像备份 NameNode 的镜像备份参数为 fs.namenode.image.backup.enable, 需要设置该值为 true, 系统即可定期备份 NameNode 的数据 文档版本 01 ( ) 212

223 6 HDFS 应用开发 HDFS 需要开启 DataNode 数据存储路径 DataNode 默认存储路径配置为 :${BIGDATA_DATA_HOME/hadoop/dataN/dn/datadir (N 1),N 为数据存放的目录个数 例如 :${BIGDATA_DATA_HOME/hadoop/data1/dn/datadir $ {BIGDATA_DATA_HOME/hadoop/data2/dn/datadir 设置后, 数据会存储到节点上每个挂载磁盘的对应目录下面 HDFS 提高读取写入性能方式 写入数据流程 :HDFS Client 收到业务数据后, 从 NameNode 获取到数据块编号 位置信息后, 联系 DataNode, 并将需要写入数据的 DataNode 建立起流水线, 完成后, 客户端再通过自有协议写入数据到 Datanode1, 再有 DataNode1 复制到 DataNode2 DataNode3( 三备份 ) 写完的数据, 将返回确认信息给 HDFS Client 1. 合理设置块大小, 如设置 dfs.blocksize 为 ( 即 256MB) 2. 对于一些不可能重用的大数据, 缓存在操作系统的缓存区是无用的 可将以下两参数设置为 false: MapReduce 中间文件存放路径 dfs.datanode.drop.cache.behind.reads 和 dfs.datanode.drop.cache.behind.writes MapReduce 默认中间文件夹存放路径只有一个,${hadoop.tmp.dir/mapred/local, 建议修改为每个磁盘下均可存放中间文件 例如 :/hadoop/hdfs/data1/mapred/local /hadoop/hdfs/data2/mapred/local /hadoop/hdfs/ data3/mapred/local 等, 不存在的目录会自动忽略 JAVA 开发时, 申请资源须在 finally 释放 HDFS 文件操作 API 概述 申请的 HDFS 资源需要在 try/finally 中释放, 而不能只在 try 语句之外释放, 否则会导致异常情况下的资源泄漏 Hadoop 中关于文件操作类基本上全部是在 "org.apache.hadoop.fs" 包中, 这些 API 能够支持的操作包含 : 打开文件, 读写文件, 删除文件等 Hadoop 类库中最终面向用户提供的接口类是 FileSystem, 该类是个抽象类, 只能通过来类的 get 方法得到具体类 get 方法存在几个重载版本, 常用的是这个 : static FileSystem get(configuration conf); 该类封装了几乎所有的文件操作, 例如 mkdir,delete 等 综上基本可以得出操作文件的程序库框架 : operator() { 得到 Configuration 对象得到 FileSystem 对象进行文件操作 HDFS 初始化方法 HDFS 初始化是指在使用 HDFS 提供的 API 之前, 需要做的必要工作 文档版本 01 ( ) 213

224 6 HDFS 应用开发 大致过程为 : 加载 HDFS 服务配置文件, 并进行 Kerberos 安全认证, 认证通过后再实例化 Filesystem, 之后使用 HDFS 的 API 此处 Kerberos 安全认证需要使用到的 keytab 文件, 请提前准备, 可参考准备开发用户 正确示例 : private void init() throws IOException { Configuration conf = new Configuration(); // 读取配置文件 conf.addresource("user-hdfs.xml"); // 安全模式下, 先进行安全认证 if ("kerberos".equalsignorecase(conf.get("hadoop.security.authentication"))) { String PRINCIPAL = "username.client.kerberos.principal"; String KEYTAB = "username.client.keytab.file"; // 设置 keytab 密钥文件 conf.set(keytab, System.getProperty("user.dir") + File.separator + "conf"?????+ File.separator + conf.get(keytab)); // 设置 kerberos 配置文件路径 */ String krbfilepath = System.getProperty("user.dir") + File.separator + "conf"?????+ File.separator + "krb5.conf"; System.setProperty("java.security.krb5.conf", krbfilepath); // 进行登录认证 */ SecurityUtil.login(conf, KEYTAB, PRINCIPAL); // 实例化文件系统对象 fsystem = FileSystem.get(conf); HDFS 上传本地文件 通过 FileSystem.copyFromLocalFile(Path src,patch dst) 可将本地文件上传到 HDFS 的制定位置上, 其中 src 和 dst 均为文件的完整路径 正确示例 : public class CopyFile { public static void main(string[] args) throws Exception { Configuration conf=new Configuration(); FileSystem hdfs=filesystem.get(conf); // 本地文件 Path src =new Path("D:\\HebutWinOS"); //HDFS 为止 Path dst =new Path("/"); hdfs.copyfromlocalfile(src, dst); System.out.println("Upload to"+conf.get("fs.default.name")); FileStatus files[]=hdfs.liststatus(dst); for(filestatus file:files){ System.out.println(file.getPath()); HDFS 创建目录 通过 "FileSystem.mkdirs(Path f)" 可在 HDFS 上创建目录, 其中 f 为目录的完整路径 正确示例 : public class CreateDir { public static void main(string[] args) throws Exception{ Configuration conf=new Configuration(); FileSystem hdfs=filesystem.get(conf); Path dfs=new Path("/TestDir"); hdfs.mkdirs(dfs); 文档版本 01 ( ) 214

225 6 HDFS 应用开发 查看 HDFS 文件的最后修改时间 通过 FileSystem.getModificationTime() 可查看指定 HDFS 文件的修改时间 正确示例 : public static void main(string[] args) throws Exception { Configuration conf=new Configuration(); FileSystem hdfs=filesystem.get(conf); Path fpath =new Path("/user/hadoop/test/file1.txt"); FileStatus filestatus=hdfs.getfilestatus(fpath); long moditime=filestatus.getmodificationtime(); System.out.println("file1.txt 的修改时间是 "+moditime); 读取 HDFS 某个目录下的所有文件 通过 FileStatus.getPath() 可查看指定 HDFS 中某个目录下所有文件 正确示例 : public static void main(string[] args) throws Exception { Configuration conf=new Configuration(); FileSystem hdfs=filesystem.get(conf); Path listf =new Path("/user/hadoop/test"); FileStatus stats[]=hdfs.liststatus(listf); for(int i = 0; i < stats.length; + +i) { System.out.println(stats[i].getPath().toString()) hdfs.close(); 查找某个文件在 HDFS 集群的位置 通过 FileSystem.getFileBlockLocation(FileStatus file,long start,long len) 可查找指定文件在 HDFS 集群上的位置, 其中 file 为文件的完整路径,start 和 len 来标识查找文件的路径 正确示例 : public static void main(string[] args) throws Exception { Configuration conf=new Configuration(); FileSystem hdfs=filesystem.get(conf); Path fpath=new Path("/user/hadoop/cygwin"); FileStatus filestatus = hdfs.getfilestatus(fpath); BlockLocation[] blklocations = hdfs.getfileblocklocations(filestatus, 0, filestatus.getlen()); int blocklen = blklocations.length; for(int i=0;i < blocklen;i++){ String[] hosts = blklocations[i].gethosts(); System.out.println("block_"+i+"_location:"+hosts[0]); 获取 HDFS 集群上所有节点名称信息 通过 DatanodeInfo.getHostName() 可获取 HDFS 集群上的所有节点名称 文档版本 01 ( ) 215

226 6 HDFS 应用开发 正确示例 : public static void main(string[] args) throws Exception { Configuration conf=new Configuration(); FileSystem fs=filesystem.get(conf); DistributedFileSystem hdfs = (DistributedFileSystem)fs; DatanodeInfo[] datanodestats = hdfs.getdatanodestats(); for(int i=0;i < datanodestats.length;i++){ System.out.println("DataNode_"+i+"_Name:"+dataNodeStats[i].getHostName()); 多线程安全登录方式 建议 如果有多线程进行 login 的操作, 当应用程序第一次登录成功后, 所有线程再次登录时应该使用 relogin 的方式 login 的代码样例 : private Boolean login(configuration conf){ boolean flag = false; UserGroupInformation.setConfiguration(conf); try { UserGroupInformation.loginUserFromKeytab(conf.get(PRINCIPAL), conf.get(keytab)); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; relogin 的代码样例 : public Boolean relogin(){ boolean flag = false; try { HDFS 的读写文件注意点 UserGroupInformation.getLoginUser().reloginFromKeytab(); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; HDFS 不支持随机读和写 HDFS 追加文件内容只能在文件末尾添加, 不能随机添加 只有存储在 HDFS 文件系统中的数据才支持 append,edit.log 以及数据元文件不支持 Append Append 追加文件时, 需要将 hdfs-site.xml 中的 dfs.support.append 参数值设置为 true 文档版本 01 ( ) 216

227 6 HDFS 应用开发 说明 dfs.support.append 参数在开源社区版本中默认值是关闭, 在 MRS 版本默认值是开启 该参数为服务器端参数 建议开启, 开启后才能使用 Append 功能 不适用 HDFS 场景可以考虑使用其他方式来存储数据, 如 HBase HDFS 不适用于存储大量小文件 HDFS 不适用于存储大量的小文件, 因为大量小文件的元数据会占用 NameNode 的大量内存 HDFS 中数据的备份数量 3 份即可 DataNode 数据备份数量 3 份即可, 增加备份数量不能提升系统效率, 只会提升系统数据的安全系数 ; 在某个节点损坏时, 该节点上的数据会被均衡到其他节点上 文档版本 01 ( ) 217

228 7 Spark 应用开发 7 Spark 应用开发 7.1 概述 应用开发简介 Spark 简介 Spark 是分布式批处理框架, 提供分析挖掘与迭代式内存计算能力, 支持多种语言 (Scala/Java/Python) 的应用开发 适用以下场景 : 数据处理 (Data Processing): 可以用来快速处理数据, 兼具容错性和可扩展性 迭代计算 (Iterative Computation): 支持迭代计算, 有效应对多步的数据处理逻辑 数据挖掘 (Data Mining): 在海量数据基础上进行复杂的挖掘分析, 可支持各种数据挖掘和机器学习算法 流式处理 (Streaming Processing): 支持秒级延迟的流式处理, 可支持多种外部数据源 查询分析 (Query Analysis): 支持标准 SQL 查询分析, 同时提供 DSL (DataFrame), 并支持多种外部输入 Apache Spark 部件架构如图 7-1 所示 本文档重点介绍 Spark Spark SQL 和 Spark Streaming 应用开发指导 MLlib 和 GraghX 的详细指导请参见 Spark 官方网站 : 图 7-1 Spark 架构 文档版本 01 ( ) 218

229 7 Spark 应用开发 Spark 开发接口简介 Spark 支持使用 Scala Java 和 Python 语言进行程序开发, 由于 Spark 本身是由 Scala 语言开发出来的, 且 Scala 语言具有简洁易懂的特性, 推荐用户使用 Scala 语言进行 Spark 应用程序开发 按不同的语言分,Spark 的 API 接口如表 7-1 所示 表 7-1 Spark API 接口 接口 Scala API 说明 提供 Scala 语言的 API 由于 Scala 语言的简洁易懂, 推荐用户使用 Scala 接口进行程序开发 Java API 提供 Java 语言的 API Python API 提供 Python 语言的 API 常用概念 按不同的模块分,Spark Core 和 Spark Streaming 使用上表中的 API 接口进行程序开发 而 SparkSQL 模块, 支持 CLI 或者 Thrift Server 两种方式访问 其中 Thrift Server 的连接方式也有 Beeline 和 JDBC 客户端代码两种 说明 spark-sql 脚本 spark-shell 脚本和 spark-submit 脚本 ( 运行的应用中带 SQL 操作 ), 不支持使用 proxy user 参数去提交任务 基本概念 RDD 即弹性分布数据集 (Resilient Distributed Dataset), 是 Spark 的核心概念 指的是一个只读的, 可分区的分布式数据集, 这个数据集的全部或部分可以缓存在内存中, 在多次计算间重用 RDD 的生成 : 从 HDFS 输入创建, 或从与 Hadoop 兼容的其他存储系统中输入创建 从父 RDD 转换得到新 RDD 从数据集合转换而来, 通过编码实现 RDD 的存储 : 用户可以选择不同的存储级别缓存 RDD 以便重用 (RDD 有 11 种存储级别 ) 当前 RDD 默认是存储于内存, 但当内存不足时,RDD 会溢出到磁盘中 Dependency(RDD 的依赖 ) RDD 的依赖分别为 : 窄依赖和宽依赖 文档版本 01 ( ) 219

230 7 Spark 应用开发 图 7-2 RDD 的依赖 窄依赖 : 指父 RDD 的每一个分区最多被一个子 RDD 的分区所用 宽依赖 : 指子 RDD 的分区依赖于父 RDD 的所有分区 窄依赖对优化很有利 逻辑上, 每个 RDD 的算子都是一个 fork/join( 此 join 非上文的 join 算子, 而是指同步多个并行任务的 barrier): 把计算 fork 到每个分区, 算完后 join, 然后 fork/join 下一个 RDD 的算子 如果直接翻译到物理实现, 是很不经济的 : 一是每一个 RDD( 即使是中间结果 ) 都需要物化到内存或存储中, 费时费空间 ; 二是 join 作为全局的 barrier, 是很昂贵的, 会被最慢的那个节点拖死 如果子 RDD 的分区到父 RDD 的分区是窄依赖, 就可以实施经典的 fusion 优化, 把两个 fork/ join 合为一个 ; 如果连续的变换算子序列都是窄依赖, 就可以把很多个 fork/join 并为一个, 不但减少了大量的全局 barrier, 而且无需物化很多中间结果 RDD, 这将极大地提升性能 Spark 把这个叫做流水线 (pipeline) 优化 Transformation 和 Action(RDD 的操作 ) 对 RDD 的操作包含 Transformation( 返回值还是一个 RDD) 和 Action( 返回值不是一个 RDD) 两种 RDD 的操作流程如图 7-3 所示 其中 Transformation 操作是 Lazy 的, 也就是说从一个 RDD 转换生成另一个 RDD 的操作不是马上执行,Spark 在遇到 Transformations 操作时只会记录需要这样的操作, 并不会去执行, 需要等到有 Actions 操作的时候才会真正启动计算过程进行计算 Actions 操作会返回结果或把 RDD 数据写到存储系统中 Actions 是触发 Spark 启动计算的动因 文档版本 01 ( ) 220

231 7 Spark 应用开发 图 7-3 RDD 操作示例 RDD 看起来与 Scala 集合类型没有太大差别, 但数据和运行模型大相迥异 val file = sc.textfile("hdfs://...") val errors = file.filter(_.contains("error")) errors.cache() errors.count() a. textfile 算子从 HDFS 读取日志文件, 返回 file( 作为 RDD) b. filter 算子筛出带 ERROR 的行, 赋给 errors( 新 RDD) filter 算子是一个 Transformation 操作 c. cache 算子缓存下来以备未来使用 d. count 算子返回 errors 的行数 count 算子是一个 Action 操作 Transformation 操作可以分为如下几种类型 : 视 RDD 的元素为简单元素 输入输出一对一, 且结果 RDD 的分区结构不变, 主要是 map 输入输出一对多, 且结果 RDD 的分区结构不变, 如 flatmap(map 后由一个元素变为一个包含多个元素的序列, 然后展平为一个个的元素 ) 输入输出一对一, 但结果 RDD 的分区结构发生了变化, 如 union( 两个 RDD 合为一个, 分区数变为两个 RDD 分区数之和 ) coalesce( 分区减少 ) 从输入中选择部分元素的算子, 如 filter distinct( 去除重复元素 ) subtract ( 本 RDD 有 其他 RDD 无的元素留下来 ) 和 sample( 采样 ) 文档版本 01 ( ) 221

232 7 Spark 应用开发 视 RDD 的元素为 Key-Value 对 对单个 RDD 做一对一运算, 如 mapvalues( 保持源 RDD 的分区方式, 这与 map 不同 ); 对单个 RDD 重排, 如 sort partitionby( 实现一致性的分区划分, 这个对数据本地性优化很重要 ); 对单个 RDD 基于 key 进行重组和 reduce, 如 groupbykey reducebykey; 对两个 RDD 基于 key 进行 join 和重组, 如 join cogroup 说明 后三种操作都涉及重排, 称为 shuffle 类操作 Action 操作可以分为如下几种 : 生成标量, 如 count( 返回 RDD 中元素的个数 ) reduce fold/aggregate( 返回几个标量 ) take( 返回前几个元素 ) 生成 Scala 集合类型, 如 collect( 把 RDD 中的所有元素倒入 Scala 集合类型 ) lookup( 查找对应 key 的所有值 ) 写入存储, 如与前文 textfile 对应的 saveastextfile 还有一个检查点算子 checkpoint 当 Lineage 特别长时 ( 这在图计算中时常发生 ), 出错时重新执行整个序列要很长时间, 可以主动调用 checkpoint 把当前数据写入稳定存储, 作为检查点 Shuffle Shuffle 是 MapReduce 框架中的一个特定的 phase, 介于 Map phase 和 Reduce phase 之间, 当 Map 的输出结果要被 Reduce 使用时, 每一条输出结果需要按 key 哈希, 并且分发到对应的 Reducer 上去, 这个过程就是 shuffle 由于 shuffle 涉及到了磁盘的读写和网络的传输, 因此 shuffle 性能的高低直接影响到了整个程序的运行效率 下图清晰地描述了 MapReduce 算法的整个流程 图 7-4 算法流程 概念上 shuffle 就是一个沟通数据连接的桥梁, 实际上 shuffle 这一部分是如何实现的呢, 下面就以 Spark 为例讲一下 shuffle 在 Spark 中的实现 Shuffle 操作将一个 Spark 的 Job 分成多个 Stage, 前面的 stages 会包括一个或多个 ShuffleMapTasks, 最后一个 stage 会包括一个或多个 ResultTask Spark Application 的结构 Spark Application 的结构可分为两部分 : 初始化 SparkContext 和主体程序 初始化 SparkContext: 构建 Spark Application 的运行环境 文档版本 01 ( ) 222

233 7 Spark 应用开发 Spark SQL 常用概念 构建 SparkContext 对象, 如 : new SparkContext(master, appname, [SparkHome], [jars]) 参数介绍 : master: 连接字符串, 连接方式有 local yarn-cluster yarn-client 等 appname: 构建的 Application 名称 SparkHome: 集群中安装 Spark 的目录 jars: 应用程序代码和依赖包 主体程序 : 处理数据 提交 Application 的描述请参见 : Spark shell 命令 Spark 基本 shell 命令, 支持提交 Spark 应用 命令为 :./bin/spark-submit \ --class <main-class> \ --master <master-url> \... # other options <application-jar> \ [application-arguments] 参数解释 : --class:spark 应用的类名 --master:spark 用于所连接的 master, 如 yarn-client,yarn-cluster 等 application-jar:spark 应用的 jar 包的路径 application-arguments: 提交 Spark 应用的所需要的参数 ( 可以为空 ) Spark JobHistory Server 用于监控正在运行的或者历史的 Spark 作业在 Spark 框架各个阶段的细节以及提供日志显示, 帮助用户更细粒度地开发 配置和调优作业 DataFrame Spark Streaming 常用概念 DataFrame 是一个由多个列组成的结构化的分布式数据集合, 等同于关系数据库中的一张表, 或者是 R/Python 中的 Data Frame DataFrame 是 Spark SQL 中的最基本的概念, 可以通过多种方式创建, 例如结构化的数据集 Hive 表 外部数据库或者 RDD Spark SQL 的程序入口是 SQLContext 类 ( 或其子类 ), 创建 SQLContext 时需要一个 SparkContext 对象作为其构造参数 SQLContext 其中一个子类是 HiveContext, 相较于其父类,HiveContext 添加了 HiveQL 的 parser UDF 以及读取存量 Hive 数据的功能等 但注意,HiveContext 并不依赖运行时的 Hive, 只是依赖 Hive 的类库 由 SQLContext 及其子类可以方便的创建 SparkSQL 中的基本数据集 DataFrame, DataFrame 向上提供多种多样的编程接口, 向下兼容多种不同的数据源, 例如 Parquet JSON Hive 数据 Database HBase 等等, 这些数据源都可以使用统一的语法来读取 Dstream DStream( 又称 Discretized Stream) 是 Spark Streaming 提供的抽象概念 文档版本 01 ( ) 223

234 7 Spark 应用开发 DStream 表示一个连续的数据流, 是从数据源获取或者通过输入流转换生成的数据流 从本质上说, 一个 DStream 表示一系列连续的 RDD RDD 一个只读的 可分区的分布式数据集 DStream 中的每个 RDD 包含了一个区间的数据 如图 7-5 所示 图 7-5 DStream 与 RDD 关系 应用到 DStream 上的所有算子会被转译成下层 RDD 的算子操作, 如图 7-6 所示 这些下层的 RDD 转换会通过 Spark 引擎进行计算 DStream 算子隐藏大部分的操作细节, 并且提供了方便的 High-level API 给开发者使用 图 7-6 DStream 算子转译 开发流程 Spark 包含 Spark Core Spark SQL 和 Spark Streaming 三个组件, 其应用开发流程相同 开发流程中各阶段的说明如图 7-7 和表 7-2 所示 文档版本 01 ( ) 224

235 7 Spark 应用开发 图 7-7 Spark 应用程序开发流程 表 7-2 Spark 应用开发的流程说明 阶段说明参考文档 了解基本概念 在开始开发应用前, 需要了解 Spark 的基本概念, 根据实际场景选择需要了解的概念, 分为 Spark Core 基本概念 Spark SQL 基本概念和 Spark Streaming 基本概念 常用概念 文档版本 01 ( ) 225

236 7 Spark 应用开发 阶段说明参考文档 准备开发环境 准备运行环境 获取并导入样例工程 或者新建工程 根据场景开发工程 编译并运行程序 查看程序运行结果 调优程序 Spark 的应用程序支持使用 Scala Java Python 三种语言进行开发 推荐使用 IDEA 工具, 请根据指导完成不同语言的开发环境配置 Spark 的运行环境即 Spark 客户端, 请根据指导完成客户端的安装和配置 Spark 提供了不同场景下的样例程序, 您可以导入样例工程进行程序学习 或者您可以根据指导, 新建一个 Spark 工程 提供了 Scala Java Python 三种不同语言的样例工程, 还提供了 Streaming SQL JDBC 客户端程序以及 Spark on HBase 四种不同场景的样例工程 帮助用户快速了解 Spark 各部件的编程接口 指导用户将开发好的程序编译并提交运行 程序运行结果会写在用户指定的路径下 用户还可以通过 UI 查看应用运行情况 您可以根据程序运行情况, 对程序进行调优, 使其性能满足业务场景诉求 调优完成后, 请重新进行编译和运行 准备开发环境 准备运行环境 下载并导入样例工程 开发程序 编包并运行程序 查看调测结果 调优程序 7.2 环境准备 环境简介 在进行应用开发时, 要准备的开发环境如表 7-3 所示 同时需要准备运行调测的 Linux 环境, 用于验证应用程序运行正常 表 7-3 开发环境 准备项 说明 安装 JDK 开发环境的基本配置 版本要求 :1.7 或者 1.8 说明基于安全考虑,MRS 服务端只支持 TLS 1.1 和 TLS 1.2 加密协议,IBM JDK 默认 TLS 只支持 1.0, 若使用 IBM JDK, 请配置启动参数 com.ibm.jsse2.overridedefaulttls 为 true, 设置后可以同时支持 TLS1.0/1.1/1.2 详情请参见 : SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/securitycomponent/jsse2Docs/matchsslcontext_tls.html#matchsslcontext_tls 文档版本 01 ( ) 226

237 7 Spark 应用开发 准备项安装和配置 IDEA 安装 Scala 安装 Scala 插件 说明 用于开发 Spark 应用程序的工具 版本要求 : 及以上版本 Scala 开发环境的基本配置 版本要求 : 及以上版本 Scala 开发环境的基本配置 版本要求 : 及以上版本 准备开发用户 前提条件 操作场景 操作步骤 MRS 服务集群开启了 Kerberos 认证, 没有开启 Kerberos 认证的集群忽略该步骤 开发用户用于运行样例工程 用户需要有 HDFS YARN 和 Hive 权限, 才能运行 Spark 样例工程 步骤 1 登录 MRS Manager, 在 MRS Manager 界面选择 系统设置 > 角色管理 > 添加角色 1. 填写角色的名称, 例如 sparkrole 2. 在 权限 表格中选择 HBase > HBase Scope > global, 勾选 default 的 create, 单击 确定 保存 3. 编辑角色, 在 权限 的表格中选择 HBase > HBase Scope > global > hbase, 勾选 hbase:meta 的 execute, 单击 确定 保存 4. 编辑角色, 在 权限 的表格中选择 HDFS > File System > hdfs://hacluster/ > user >hive, 勾选 Execute, 单击 确定 保存 5. 编辑角色, 在 权限 的表格中选择 HDFS > File System > hdfs://hacluster/ >user >hive > warehouse, 勾选 Read Write 和 Execute, 单击 确定 保存 6. 编辑角色, 在 权限 的表格中选择 Hive > Hive Read Write Privileges, 勾选 default 的 CREATE, 单击 确定 保存 7. 编辑角色, 在 权限 的表格中选择 Yarn > Scheduler Queue > root, 勾选 default 的 Submit, 单击 确定 保存 步骤 2 步骤 3 步骤 4 在 MRS Manager 界面选择 系统设置 > 用户组管理 > 添加用户组, 为样例工程创建一个用户组, 例如 sparkgroup 在 MRS Manager 界面选择 系统设置 > 用户管理 > 添加用户, 为样例工程创建一个用户 填写用户名例如 sparkuser, 用户类型为 机机 用户, 加入用户组 sparkgroup, 并绑定角色 sparkrole 取得权限, 单击 确定 在 MRS Manager 界面选择 系统设置 > 用户管理, 在用户名中选择 sparkuser, 单击操作中下载认证凭据文件, 保存后解压得到用户的 keytab 文件与 krb5.conf 文件 用于在样例工程中进行安全认证, 具体使用请参考准备认证机制代码 ---- 结束 文档版本 01 ( ) 227

238 7 Spark 应用开发 准备开发环境 准备 Java 开发环境 操作场景 操作步骤 Java 开发环境可以搭建在 Windows 环境下, 而运行环境 ( 即客户端 ) 只能部署在 Linux 环境下 步骤 1 对于 Java 开发环境, 推荐使用 IDEA 工具, 安装要求如下 : JDK 使用 1.7 版本 ( 或 1.8 版本 ) IntelliJ IDEA( 版本 :13.1.6) 说明 Spark 不支持当客户端程序使用 IBM JDK 1.7 运行时, 使用 yarn-client 模式向服务端提交 Spark 任务 Oracle JDK 需进行安全加固, 具体操作如下 : 1. 到 Oracle 官方网站获取与 JDK 版本对应的 JCE(Java Cryptography Extension) 文件 JCE 文件解压后包含 local_policy.jar 和 US_export_policy.jar 拷贝 jar 包到如下路径 : Linux:JDK 安装目录 /jre/lib/security Windows:JDK 安装目录 \jre\lib\security 2. 将 客户端安装目录 /JDK/jdk/jre/lib/ext/ 目录下 SMS4JA.jar 拷贝到如下路径 : Linux:JDK 安装目录 /jre/lib/ext/ Windows:JDK 安装目录 \jre\lib\ext\ 步骤 2 安装 IntelliJ IDEA 和 JDK 工具, 并进行相应的配置 1. 安装 JDK 2. 安装 IntelliJ IDEA 工具 3. 在 IntelliJ IDEA 中配置 JDK a. 打开 IntelliJ IDEA, 选择 Configure 文档版本 01 ( ) 228

239 7 Spark 应用开发 图 7-8 Quick Start b. 在 Configure 页面中选择的 Project Defaults 图 7-9 Configure c. 在 Project Defaults 页面中, 选择 Project Structure 文档版本 01 ( ) 229

240 7 Spark 应用开发 图 7-10 Project Defaults d. 在打开的 Project Structure 页面中, 选择 SDKs, 单击绿色加号添加 JDK 图 7-11 添加 JDK e. 在弹出的 Select Home Directoty for JDK 窗口, 选择对应的 JDK 目录, 然后单击 OK 文档版本 01 ( ) 230

241 7 Spark 应用开发 图 7-12 选择 JDK 目录 f. 完成 JDK 选择后, 单击 OK 完成配置 文档版本 01 ( ) 231

242 7 Spark 应用开发 图 7-13 完成 JDK 配置 ---- 结束 准备 Scala 开发环境 操作场景 操作步骤 Scala 开发环境可以搭建在 Windows 环境下, 而运行环境 ( 即客户端 ) 只能部署在 Linux 环境下 步骤 1 对于 Scala 开发环境, 推荐使用 IDEA 工具, 安装要求如 : JDK 使用 1.7 版本 ( 或 1.8 版本 ) IntelliJ IDEA( 版本 :13.1.6) Scala( 版本 :2.11.8) Scala 插件 ( 版本 : ) 文档版本 01 ( ) 232

243 7 Spark 应用开发 说明 Spark 不支持当客户端程序使用 IBM JDK 1.7 运行时, 使用 yarn-client 模式向服务端提交 Spark 任务 Oracle JDK 需进行安全加固, 具体操作如下 : 1. 到 Oracle 官方网站获取与 JDK 版本对应的 JCE(Java Cryptography Extension) 文件 JCE 文件解压后包含 local_policy.jar 和 US_export_policy.jar 拷贝 jar 包到如下路径 : Linux:JDK 安装目录 /jre/lib/security Windows:JDK 安装目录 \jre\lib\security 2. 将 客户端安装目录 /JDK/jdk/jre/lib/ext/ 目录下 SMS4JA.jar 拷贝到如下路径 : Linux:JDK 安装目录 /jre/lib/ext/ Windows:JDK 安装目录 \jre\lib\ext\ 步骤 2 安装 IntelliJ IDEA JDK 和 Scala 工具, 并进行相应的配置 1. 安装 JDK 2. 安装 IntelliJ IDEA 3. 安装 Scala 工具 4. 在 IntelliJ IDEA 中配置 JDK a. 打开 IntelliJ IDEA, 选择 Configure 图 7-14 Quick Start b. 在 Configure 页面中选择的 Project Defaults 文档版本 01 ( ) 233

244 7 Spark 应用开发 图 7-15 Configure c. 在 Project Defaults 页面中, 选择 Project Structure 图 7-16 Project Defaults d. 在打开的 Project Structure 页面中, 选择 SDKs, 单击绿色加号添加 JDK 文档版本 01 ( ) 234

245 7 Spark 应用开发 图 7-17 添加 JDK e. 在弹出的 Select Home Directoty for JDK 窗口, 选择对应的 JDK 目录, 然后单击 OK 文档版本 01 ( ) 235

246 7 Spark 应用开发 图 7-18 选择 JDK 目录 f. 完成 JDK 选择后, 单击 OK 完成配置 文档版本 01 ( ) 236

247 7 Spark 应用开发 图 7-19 完成 JDK 配置 5. 在 IntelliJ IDEA 中安装 Scala 插件 a. 在 Configure 页面, 选择 Plugins 图 7-20 Plugins b. 在 Plugins 页面, 选择 Install plugin from disk 文档版本 01 ( ) 237

248 7 Spark 应用开发 图 7-21 Install plugin from disk c. 在 Choose Plugin File 页面, 选择对应版本的 Scala 插件包, 单击 OK d. 在 Plugins 页面, 单击 Apply 安装 Scala 插件 e. 在弹出的 Plugins Changed 页面, 单击 Restart, 使配置生效 文档版本 01 ( ) 238

249 7 Spark 应用开发 图 7-22 Plugins Changed ---- 结束 准备 Python 开发环境 操作场景 操作步骤 Python 开发环境可以搭建在 Windows 环境下, 而运行环境 ( 即客户端 ) 只能部署在 Linux 环境下 步骤 1 步骤 2 对于 Python 开发环境, 直接使用 Notepad++ 编辑器 ( 或其他编写 Python 应用程序的 IDE) 即可 下载客户端样例配置程序到本地开发环境 使用 FTP 工具, 将运行调测环境的客户端包文件 MRS_Service_client 下载到本地, 并解压得到目录 MRS_Services_ClientConfig ---- 结束 准备 HiveODBC 开发环境 Windows 环境概要说明为了运行 MRS 产品 Spark 组件的 HiveODBC 接口样例代码, 需要完成下面的操作 操作步骤 步骤 1 确认客户端机器的时间与 MRS 集群的时间一致, 时间差要小于 5 分钟 MRS 集群的时间可通过登录主管理节点 (cluster 管理 IP 地址所在节点 ) 运行 date 命令查询 步骤 2 下载并安装 MIT Kerberos, 具体软件请到对应的官方网站获取 记录安装路径, 例如 : C:\Program Files\MIT\Kerberos 步骤 3 设置 Kerberos 的配置文件 向 MRS 集群管理员获取集群 Kerberos 的 krb5.conf 文件, 把相应的 krb5.conf 文件重命名为 krb5.ini, 并放到 C:\ProgramData\MIT\Kerberos5 目录中 文档版本 01 ( ) 239

250 7 Spark 应用开发 说明 C:\ProgramData 目录一般是隐藏的, 需要设置显示隐藏文件 步骤 4 在 Windows 上进行认证 使用 人机 用户登录 a. 从 MRS 集群管理员那里获取集群的用户名和密码, 用户名的格式为 : 域名 b. 打开 MIT Kerberos, 单击 get Ticket, 在弹出的 MIT Kerberos: Get Ticket 窗口中, Pricipal 输入用户名, Password 输入密码, 单击 OK 图 7-23 人机 用户登录界面 使用 机机 用户登录 a. 使用命令行进入到 MIT Kerberos 安装路径, 找到可执行文件 kinit.exe, 例如 : C:\Program Files\MIT\Kerberos\bin b. 执行如下命令 : kinit -k -t /path_to_userkeytab/user.keytab UserName 其中 path_to_userkeytab 为存放用户 keytab 文件的路径,user.keytab 为用户的 keytab 文件,UserName 为用户名 步骤 5 安装编译示例代码需要的软件 :Visual Studio 2012 步骤 6 下载客户端程序 1. 登录 MRS Manager 系统 在浏览器地址栏中输入访问地址, 地址格式为 Manager 系统的 WebService 浮动 IP 地址 :28443/web 2. 选择 服务管理 > Spark > 下载客户端, 下载客户端压缩文件到本地机器 说明 下载所有组件的客户端程序安装包, 可通过在界面选择 服务管理 > 下载客户端 进行下载 3. 解压缩该客户端压缩文件, 生成解压目录 4. 进入客户端解压后的目录 ( 以下简称解压目录 ) 文档版本 01 ( ) 240

251 7 Spark 应用开发 5. 将解压目录下的 Spark/DataSight_Spark_V100R002CXX_HiveODBC.zip 解压到指定的目录, 例如 : C:\DataSight_Spark_HiveODBC 步骤 7 安装 OpenSSL 1.x 版本 1. 下载并安装 ActivePerl( 版本 :5.24.0,64-bit,x64), 下载路径 : 到官网下载并安装 ActivePerl( 版本 :5.24.0,64-bit,x64) 3. 打开 Windows 操作系统 开始 菜单, 输入 cmd 命令, 打开一个命令行窗口, 进入 ${ActivePerl 安装目录 /eg, 执行以下命令验证 ActivePerl 是否安装成功 perl example.pl 运行结果显示如下信息说明安装成功 : Hello from ActivePerl! 4. 进入 C:\Program Files (x86)\microsoft Visual Studio 11.0\VC\bin 目录, 执行 vcvars32.bat 配置 VS2012 环境变量 5. 到官网下载并解压 openssl-1.x.tar.gz HiveODBC 驱动需要使用 OpenSSL 1.0 以上的版本 6. 在 Windows 系统的命令行窗口中, 进入 ${openssl 解压目录 执行以下命令 : perl Configure VC-WIN32 7. 进入 ${openssl 解压目录 执行以下命令 : ms\do_ms 8. 进入 ${openssl 解压目录 执行以下命令 : nmake -f ms\ntdll.mak 9. 打开 ${openssl 解压目录 /out32dll 目录, 获取该目录下的 libeay32.dll 和 ssleay32.dll 文件, 并拷贝至 HiveODBC 解压目录下的 Windows 目录 步骤 8 安装 Cyrus SASL( 版本 :2.1.23) 1. 到官网下载并解压 cyrus-sasl static-x86.zip 2. 直接运行解压后的 exe 文件进行安装 3. 进入 C:\CMU\bin 目录, 获取 libsasl.dll 并拷贝至 HiveODBC 解压目录下的 Windows 目录 步骤 9 注册驱动 1. 打开 Windows 操作系统 开始 菜单, 输入 cmd 命令, 打开一个命令行窗口, 进入步骤 6.5 目录下的 Windows 目录 2. 执行如下命令 : odbcreg /i hiveodbc hiveodbc.dll C:\DataSight_Spark_HiveODBC\WindowS 其中, C:\DataSight_Spark_HiveODBC\Windows 为 HiveODBC 解压目录下的 Windows 目录, 用户需根据实际目录做相应修改 出现提示后, 输入 y 运行结果如下 : C:\DataSight_Spark_HiveODBC\Windows>odbcreg /i hiveodbc hiveodbc.dll C: \DataSight_Spark_HiveODBC\Windows ODBCREG - to register or unregister ODBC driver Proceeding to register... driver: hiveodbc dll: hiveodbc.dll path: C:\DataSight_Spark_HiveODBC\Windows 文档版本 01 ( ) 241

252 7 Spark 应用开发 Confirm(y/n) hiveodbc installed/registered successfully 关于 odbcreg 工具的参数说明, 参考表 2 odbcreg 工具参数说明表 查看注册表 在注册表的 HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ODBC \ODBCINST.INI 下可以看到有名字为 hiveodbc 的驱动存在 说明 Windows 32 位操作系统的注册表路径为 HKEY_LOCAL_MACHINE\SOFTWARE\ODBC \ODBCINST.INI 步骤 10 配置连接 ZooKeeper 需要的认证信息和文件 从 MRS Manager 上下载已经创建好的 Kerberos 用户 ( 例如 sparkuser) 的 Kerberos 认证文件 krb5.conf 和 user.keytab, 将这两个文件拷贝至指定的目录, 例如 C: \DataSight_Spark_HiveODBC\odbcconf 目录, 在该目录下创建 jaas.conf 文件, 内容如下 : Client { com.sun.security.auth.module.krb5loginmodule required usekeytab=true keytab="c:\\datasight_spark_hiveodbc\\odbcconf\\user.keytab" principal="sparkuser@hadoop.com" useticketcache=false storekey=true debug=true; ; 其中的 keytab 为 user.keytab 存放的绝对路径, principal 为用户的 principal 步骤 11 配置数据源 配置文件数据源 a. 可将 HiveODBC 解压目录下的 Windows 目录中的 template.dsn 重命名为后缀是.dsn 的文件, 例如 hive.dsn b. 下面是配置非集群模式 hive.dsn 的内容示例 : [ODBC] DRIVER=hiveodbc MODE=0 HOST= PORT=23040 DATABASE=default PRINCIPAL=spark/hadoop.hadoop.com@HADOOP.COM FRAMED=0 NAMESPACE= KRB5PATH= JAASPATH= 说明 配置机器数据源 关于文件数据源的配置参数说明请参考表 7-6 a. 可将 HiveODBC 解压目录下的 Windows 目录中的 template.dsn 重命名为后缀是.ini 的文件, 例如 hive.ini b. 修改 hive.ini 文件中的内容, 必须将 [ODBC] 改成 [Hive] hive.ini 内容示例 : [Hive] DRIVER=hiveodbc MODE=0 HOST= PORT=23040 文档版本 01 ( ) 242

253 7 Spark 应用开发 步骤 12 安装 JDK1.8 DATABASE=default FRAMED=0 NAMESPACE= KRB5PATH= JAASPATH= 说明 关于机器数据源的配置参数说明请参考表 7-6 c. 打开 Windows 操作系统开始菜单, 输入 cmd 命令, 打开一个命令行窗口, 进入步骤 6.5 目录下的 Windows 目录 d. 执行如下命令 : odbcreg /d hiveodbcds C:\DataSight_Spark_HiveODBC\Windows\hive.ini 其中, C:\DataSight_Spark_HiveODBC\Windows 为 HiveODBC 解压目录下的 Windows 目录, 用户需根据实际目录做相应修改 e. 出现提示后, 确认打印的内容是否和 hive.ini 文件的内容一致, 确认一致后输入 y f. 运行结果如下 : ODBCREG - to register or unregister ODBC driver Attribute to be register are: DRIVER = hiveodbc DATABASE = default HOST = PORT = FRAMED = 0 MODE = 0 PRINCIPAL = spark/hadoop.hadoop.com@hadoop.com NAMESPACE = KRB5PATH = JAASPATH = Confirm(y/n) Add datasource. 说明 样例代码需使用机器数据源 在 Oracle 官网下载 32 位的 JDK1.8 进行安装 安装完成后, 配置系统环境变量 PATH, 将 JDK1.8 安装路径下的 jre\bin\client 目录加入 PATH 里面去 具体操作如下 : a. 点击 Windows 开始 菜单, 选择 计算机, 点击右键, 选择 属性 b. 在弹出界面中点击 高级系统设置, 在 系统属性 对话框中点击 环境变量 c. 在 系统变量 中找到 Path 变量, 点击 编辑, 在 变量值 中添加 ;$ {JAVA_HOME\ jre\bin\client, 点击 确定 步骤 13 配置 ODBCCLASSPATH 环境变量 如果是集群模式, 需要配置系统环境变量 ODBCCLASSPATH, 将连接 ZooKeeper 需要的 jar 路径加入到 ODBCCLASSPATH 里面,ODBCCLASSPATH 的值为 : C:/DataSight_Spark_HiveODBC/Windows/jars/zookeeper jar;C:/DataSight_Spark_HiveODBC/Windows/ jars/slf4j-api jar;c:/datasight_spark_hiveodbc/windows/jars/log4j jar;c:/ DataSight_Spark_HiveODBC/Windows/jars/slf4j-log4j jar;C:/DataSight_Spark_HiveODBC/Windows/ jars/zk-helper.jar;c:/datasight_spark_hiveodbc/windows/jars/log4j.properties;c:/ DataSight_Spark_HiveODBC/Windows/jars/commons-logging jar 文档版本 01 ( ) 243

254 7 Spark 应用开发 其中 C:/DataSight_Spark_HiveODBC 为 HiveODBC 驱动的实际安装路径 ---- 结束 Linux 环境 概要说明操作步骤 为了运行 MRS 产品 Spark 组件的 HiveODBC 接口样例代码, 需要完成下面的操作 步骤 1 步骤 2 确认客户端机器的时间与 MRS 集群的时间一致, 时间差要小于 5 分钟 下载客户端程序 1. 登录 MRS Manager 系统 在浏览器地址栏中输入访问地址, 地址格式为 Manager 系统的 WebService 浮动 IP 地址 :8080/web 2. 选择 服务管理 > Spark > 下载客户端, 下载客户端压缩文件到本地机器 说明 下载所有组件的客户端程序安装包, 可通过在界面 选择服务管理 > 下载客户端 进行下载 3. 解压缩该客户端压缩文件, 生成解压目录 步骤 3 安装 HiveODBC 驱动 1. 进入客户端解压后的目录 ( 以下简称解压目录 ) 2. 将解压目录下的 Spark/DataSight_Spark_V100R002CXX_HiveODBC.zip 解压到指定的目录, 例如 : /opt/datasight_spark_hiveodbc 3. 将 DataSight_Spark_HiveODBC/Linux 目录下的 RPM 安装包拷贝到需要安装驱动的节点服务器上, 执行 RPM 安装命令 Red Hat: rpm -ivh datasight-hiveodbc el6.x86_64.rpm --nodeps --force SUSE: rpm -ivh datasight-hiveodbc x86_64.rpm --nodeps --force HiveODBC 的安装路径是 /usr/local/hiveodbc, 动态库是安装在 /usr/lib64 路径下 4. 查询是否安装成功 rpm -q datasight-hiveodbc SUSE 环境上显示如下信息则说明安装成功 : datasight-hiveodbc Red Hat 环境上显示如下信息则说明安装成功 : datasight-hiveodbc el6.x86_64 再执行如下命令刷新动态库的缓存 ldconfig 说明 ldconfig 命令需要切换为 root 用户执行 文档版本 01 ( ) 244

255 7 Spark 应用开发 5. 检查 HiveODBC 所需的动态库是否已经可用 执行如下命令, 如果都有显示结果, 说明动态库已经安装好 : ldconfig -p grep libodbchive 结果 : 步骤 4 安装 JDK1.8 libodbchive.so.1 (libc6,x86-64) => /usr/lib64/libodbchive.so.1 libodbchive.so (libc6,x86-64) => /usr/lib64/libodbchive.so HiveODBC 驱动需要用户安装 JDK1.8, 请用户自行从 Oracle 官网下载, 或者使用 MRS 产品包自带的 JDK 安装完成之后需要确保 JDK 的动态链接库 libjvm.so 可用, 使用如下命令查询是否安装了 JDK 的动态链接库 libjvm.so: ldconfig -p grep libjvm.so 如果有如下显示结果, 说明动态库 libjvm.so 已经安装好 : libjvm.so (libc6,x86-64) => /usr/lib64/libjvm.so 如果查询没有数据, 说明没有安装成功, 需要手动设置, 使用如下命令 : ln -s $JDK_HOME/jre/lib/amd64/server/libjvm.so /usr/lib64/libjvm.so 其中 JDK_HOME 为 JDK 的安装目录 然后再执行如下命令, 刷新动态链接库的缓存 : ldconfig 再次使用如下命令, 查询是否已经包含 libjvm: ldconfig -p grep libjvm.so 如果查询没有数据, 说明没有安装成功 需要再次重复上面建立软连接的命令 步骤 5 安装 boost1.5.0 和 thrift 0.93( 包含 fb303) HiveODBC 驱动需要用户安装 boost1.5.0 和 thrift 0.93( 包含 fb303), 请用户自行从 boost 的官网和 thrift 的官网下载安装包, 并参照官网的说明完成安装 安装完成后, 需要确保以下四个动态库可用 : libboost_filesystem.so libboost_system.so libfb303.so libthrift so 使用如下命令可以查询 : ldconfig -p grep libboost_filesystem.so ldconfig -p grep libboost_system.so ldconfig -p grep libfb303.so ldconfig -p grep libthrift so 文档版本 01 ( ) 245

256 7 Spark 应用开发 如果查询到结果且动态库是在 /usr/lib64 路径下, 则说明安装成功, 动态库可用 以 libthrift so 为例, 显示如下结果表示安装成功 libthrift so (libc6,x86-64) => /usr/lib64/libthrift so 如果查询没有结果, 说明没有安装成功, 需要重新安装 如果查询到结果但是动态库不是在 /usr/lib64 路径下, 有两种解决方法 : 手动将这 4 个动态库从查询结果中显示的 ${ 动态库所在路径 拷贝至 /usr/lib64 目录下 使用如下命令手动设置 : ln -s ${ 动态库所在路径 /${ 动态库名称 /usr/lib64/${ 动态库名称 其中 ${ 动态库所在路径 为查询结果中显示的动态库所在路径,${ 动态库名称 分别为上述 4 个动态库的名称 然后再执行下面的命令, 刷新下动态链接库的缓存 : ldconfig 再次使用下面的命令, 查询 4 个动态库是否在 /usr/lib64 目录下 : ldconfig -p grep libboost_filesystem.so ldconfig -p grep libboost_system.so ldconfig -p grep libfb303.so ldconfig -p grep libthrift so 步骤 6 安装 OpenSSL 1.x 版本 HiveODBC 驱动需要使用 OpenSSL 1.0 以上的版本 1. 首先执行如下命令, 查询所装操作系统是否已经安装 OpenSSL1.0 以上版本 : openssl version -a 结果如下则符合要求 : OpenSSL fips 29 Mar 如果未安装 OpenSSL1.0 以上版本, 请用户从 OpenSSL 官网下载源码编译 或者找操作系统管理员申请一个 OpenSSL1.0 的安装包, 手动安装 步骤 7 设置 HiveODBC 驱动环境变量 1. HiveODBC 驱动需要连接 ZooKeeper 获取存储在上面的 Spark JDBCServer 的 IP 和端口, 所以需要先配置登录 ZooKeeper 的认证信息 配置连接 ZooKeeper 需要的 jar 包路径 : HiveODBC 驱动在集群模式下需要连接 ZooKeeper 实时获取 Spark JDBCServer 的 IP 地址, 所以需要用户配置环境变量 ODBCCLASSPATH, 指定 ZooKeeper 需要的 jar 包路径, 该路径为 HiveODBC 安装目录下的 jars 目录, 即 /usr/local/hiveodbc/jars 需要将该路径下的 jar 全部配置 命令如下 : export ODBCCLASSPATH=/usr/local/hiveodbc/jars/zookeeper jar:/usr/local/ hiveodbc/jars/slf4j-api jar:/usr/local/hiveodbc/jars/log4j jar:/usr/local/ hiveodbc/jars/slf4j-log4j jar:/usr/local/hiveodbc/jars/zk-helper.jar:/usr/ local/hiveodbc/jars/commons-logging jar 2. 配置连接 ZooKeeper 需要的认证信息和文件 需要准备三个文件 : krb5.conf user.keytab 和 jaas.conf 文档版本 01 ( ) 246

257 7 Spark 应用开发 附录 从 MRS Manager 上下载已经创建好的 Kerberos 用户 ( 例如 sparkuser) 的 Kerberos 认证文件 krb5.conf 和 user.keytab, 将这两个文件拷贝至指定的目录, 例如 opt/odbcconf 目录, 在该目录下创建 jaas.conf 文件, 内容如下 : Client { com.sun.security.auth.module.krb5loginmodule required usekeytab=true keytab="/opt/odbcconf/user.keytab" principal="sparkuser@hadoop.com" useticketcache=false storekey=true debug=true; ; 其中的 keytab 为 user.keytab 存放的绝对路径,principal 为用户的 principal 步骤 8 配置数据源文件 odbc.ini 在 /etc 目录下创建一个 odbc.ini 文件, 参考如下非集群模式示例, 配置好 odbc.ini 文件 [Hive] DRIVER=/usr/lib64/libodbchive.so MODE=0 HOST= PORT=23040 DATABASE=default PRINCIPAL=spark/hadoop.hadoop.com@HADOOP.COM FRAMED=0 NAMESPACE=thriftserver KRB5PATH= JAASPATH= 关于文件数据源的配置参数说明请参考表 7-6 然后执行下面的命令, 设置 ODBCINI 环境变量 : export ODBCINI=/etc/odbc.ini 说明 ---- 结束 HiveODBC 驱动介绍 HiveODBC 驱动包介绍 ODBCINI 可以指定 odbc.ini 文件的存储路径, 一般 odbc.ini 文件存放在 /etc 目录下 HiveODBC 驱动支持 ODBC 3.5 标准, 不兼容 ODBC1.x 和 ODBC 2.x 标准 Windows 下支持微软标准,Linux 下支持 unix ODBC 标准 HiveODBC 驱动包名称 : DataSight_Spark_V100R002CXX_HiveODBC.zip, 包含两个目录 :Linux 和 Windows, 目录结构如下 : DataSight_Spark_V100R002CXX_HiveODBC --Linux --datasight-hiveodbc el6.x86_64.rpm --datasight-hiveodbc x86_64.rpm --Windows --hiveodbc.dll --odbcreg.exe --template.dsn 文档版本 01 ( ) 247

258 7 Spark 应用开发 -- jars/ --Open_Source_Software_Notice_Spark_HiveODBC.doc 具体文件介绍见表 1 表 7-4 驱动包内文件介绍 文件 datasight-hiveodbc el6.x86_64.rpm datasight-hiveodbc x86_64.rpm hiveodbc.dll odbcreg.exe template.dsn 说明 Red Hat 版本的 HiveODBC 驱动安装包 SUSE 版本的 HiveODBC 驱动安装包 Windows 版本的 HiveODBC 驱动链接库 Windows 版本的 HiveODBC 驱动安装工具 Windows 版本的 HiveODBC 数据源配置示例文件 jars 访问 ZooKeeper 需要的 jar 包 Open_Source_Software_Notice_Spark_Hiv eodbc.doc DataSight Spark HiveODBC 开源声明 表 7-5 odbcreg 工具参数说明 参数取值范围描述 参数一 /i /u /d /r /i: 表示注册驱动 /u: 表示卸载驱动 /d: 表示注册数据源 /r: 表示卸载数据源 参数二 - 数据源名称 参数三 - HiveODBC 驱动的动态库或数据源文件名, 动态库文件后缀为.dll, 数据源文件后缀为.ini 参数四 - HiveODBC 驱动的动态库或数据源所在路径 参数一为 /r 或 /u 时不需要填写此项 说明 例如卸载数据源的命令为 :odbcreg /u drivername, 其中 drivername 为注册数据源时的数据源名称 文档版本 01 ( ) 248

259 7 Spark 应用开发 表 7-6 Hive ODBC 驱动数据源配置参数 参数取值范围必填描述 DRIVER - 是 使用 odbcreg 工具注册 HiveODBC 驱动的驱动名称 Windows 环境可自行命名, Linux 环境为 libodbchive.so 所在路径 MODE 0/1 是 0: 表示非集群模式 1: 表示集群模式 HOST 如果 MODE 取值为 0, 则该值为需要直连的 Spark JDBCServer 的业务 IP 如果 MODE 取值为 1, 则该值为需要连接的那个集群的 ZooKeeper 的业务 IP 和端口 示例 : :24002, :24002, :24002 是 集群的 ZooKeeper IP 和端口或者 Spark JDBCServer 的业务 IP PORT 如果 MODE 取值为 0, 则该值为需要直连的 Spark JDBCServer 的端口, 默认是 如果 MODE 取值为 1, 则该值为需要连接的那个集群的 ZooKeeper 的端口 是 集群的 ZooKeeper 端口或者 Spark JDBCServer 的端口 DATABAS E default 是 - PRINCIPA L 安全模式的格式为 :spark/ hadoop.realm@realm 否 Spark JDBCServer 的 principal, 例如 spark/ hadoop.hadoop.com@hadoop. COM FRAMED 0/1 否安全模式下, 该参数无效 NAMESP ACE KRB5PAT H JAASPAT H thriftserver 否 Spark JDBCServer 集群模式下,ZooKeeper 存储 Spark JDBCServer IP 和端口的 namespace 集群模式下必填 - 否 krb5.conf 的绝对路径 集群模 式下必填 - 否 jaas.conf 的绝对路径 集群模式下必填 文档版本 01 ( ) 249

260 7 Spark 应用开发 约束 不支持普通模式 支持的数据类型 : TINYINT,SMALLINT,INT,BIGINT,FLOAT,DOUBLE,DECIMAL,TIMESTAMP,DATE,STRING,VARCHAR,CHAR,BOOLEAN HiveODBC 只支持 ANSI 接口, 没有实现 Unicode 接口, 即不支持中文 准备运行环境 操作场景 准备运行调测环境 Spark 的运行环境 ( 即客户端 ) 只能部署在 Linux 环境下 您可以执行如下操作完成运行环境准备 步骤 1 步骤 2 步骤 3 在弹性云服务器管理控制台, 申请一个新的弹性云服务器, 用于应用开发运行调测 弹性云服务器的安全组需要和 MRS 集群 Master 节点的安全组相同 弹性云服务器的 VPC 需要与 MRS 集群在同一个 VPC 中 弹性云服务器的网卡需要与 MRS 集群在同一个网段中 申请弹性 IP, 绑定新申请的弹性云主机 IP, 并配置安全组出入规则 下载客户端程序 登录 Manager 系统 选择 服务管理 > 下载客户端 > 完整客户端, 下载客户端程序到本地机器 步骤 4 步骤 5 登陆客户端下载目标节点, 以 root 用户安装集群客户端 1. 执行以下命令解压客户端包 : tar -xvf /opt/mrs_services_client.tar 2. 执行以下命令校验安装文件包 : sha256sum -c /opt/mrs_services_clientconfig.tar.sha256 MRS_Services_ClientConfig.tar:OK 3. 执行以下命令解压安装文件包 : tar -xvf MRS_Services_ClientConfig.tar 4. 执行如下命令安装客户端到指定目录 ( 绝对路径 ), 例如 /opt/client 目录会自动创建 cd /opt/mrs_services_clientconfig sh install.sh /opt/client Components client installation is complete. 在主管理节点准备客户端, 并在 Master1 节点, 将集群配置文件从主管理节点复制到运行调测环境 步骤 6 修改 /etc/hosts 在文件中新添加弹性云服务器的主机名与弹性云服务器私有 IP 地址对应关系 在文件中新添加 OBS 域名与 IP 地址的对应关系 用户在中国华北区申请 MRS 集群, 需添加以下记录 : 文档版本 01 ( ) 250

261 7 Spark 应用开发 obs.huawei.com 步骤 7 执行以下命令, 更新客户端配置 : sh /opt/client/refreshconfig.sh 客户端安装目录客户端配置文件压缩包完整路径 例如, 执行命令 : sh /opt/client/refreshconfig.sh /opt/client /opt/mrs_services_client.tar ---- 结束 下载并导入样例工程 操作场景 Spark 针对多个场景提供样例工程, 包含 Java 样例工程和 Scala 样例工程等, 帮助客户快速学习 Spark 工程 针对 Java 和 Scala 不同语言的工程, 其导入方式相同 使用 Python 开发的样例工程不需要导入, 直接打开 Python 文件 (*.py) 即可 以下操作步骤以导入 Java 样例代码为例 操作流程如图 7-24 所示 图 7-24 导入样例工程流程 操作步骤 步骤 1 参照第二章 MRS 服务样例工程获取方式, 下载样例工程到本地 步骤 2 将 Java 样例工程导入到 IDEA 中 1. 打开 IntelliJ IDEA 在 Quick Start 页面选择 Import Project 或者, 针对已使用过的 IDEA 工具, 您可以从 IDEA 主界面直接添加 选择 File > Import project... 导入工程 文档版本 01 ( ) 251

262 7 Spark 应用开发 图 7-25 Import Project(Quick Start 页面 ) 2. 选择需导入的样例工程存放路径, 单击 OK 文档版本 01 ( ) 252

263 7 Spark 应用开发 图 7-26 Select File or Directory to Import 3. 确认导入路径和名称, 单击 Next 文档版本 01 ( ) 253

264 7 Spark 应用开发 图 7-27 Import Project from Maven 4. 选择需要导入的工程, 单击 Next 5. 确认工程所用 JDK, 单击 Next 图 7-28 Select project SDK 6. 确认工程名称和路径, 单击 Finish 完成导入 文档版本 01 ( ) 254

265 7 Spark 应用开发 图 7-29 Select project to import 7. 导入完成后,IDEA 主页显示导入的样例工程 图 7-30 已导入工程 步骤 3 ( 可选 ) 如果导入 Scala 语言开发的样例程序, 还需要为工程设置语言 1. 在 IDEA 主页, 选择 File > Project Structures... 进入 Project Structure 页面 2. 选择 Modules, 选中工程名称, 然后右键选择 Add > Scala 文档版本 01 ( ) 255

266 7 Spark 应用开发 图 7-31 选择 Scala 语言 3. 在设置界面, 选择编译的依赖 jar 包, 单击 Apply 图 7-32 选择编译依赖包 4. 单击 OK 保存设置 步骤 4 设置 IDEA 的文本文件编码格式, 解决乱码显示问题 1. 在 IDEA 首页, 选择 File > Settings... 文档版本 01 ( ) 256

267 7 Spark 应用开发 图 7-33 选择 Settings 2. 在 Settings 页面, 选择 File Encodings 然后在右侧的 IDE Encoding 的下拉框中, 选择 UTF-8 单击 Apply 3. 单击 OK 完成编码配置 ---- 结束 文档版本 01 ( ) 257

268 7 Spark 应用开发 新建工程 ( 可选 ) 操作场景 操作步骤 除了导入 Spark 样例工程, 您还可以使用 IDEA 新建一个 Spark 工程 如下步骤以创建一个 Scala 工程为例进行说明 步骤 1 打开 IDEA 工具, 选择 Create New Project 图 7-34 创建工程 步骤 2 在 New Project 页面, 选择 Scala 开发环境, 并选择 Scala Module, 然后单击 Next 如果您需要新建 Java 语言的工程, 选择对应参数即可 文档版本 01 ( ) 258

269 7 Spark 应用开发 图 7-35 选择开发环境 步骤 3 在工程信息页面, 填写工程名称和存放路径, 设置 JDK 版本, 并勾选 Config later ( 待工程创建完毕后引入 scala 的编译库文件 ), 然后单击 Finish 完成工程创建 文档版本 01 ( ) 259

270 7 Spark 应用开发 图 7-36 填写工程信息 ---- 结束 准备认证机制代码 前提条件 场景说明 MRS 服务集群开启了 Kerberos 认证 在安全集群环境下, 各个组件之间的相互通信不能够简单的互通, 而需要在通信之前进行相互认证, 以确保通信的安全性 用户在开发 Spark 应用程序时, 某些场景下, 需要 Spark 与 Hadoop HBase 等之间进行通信 那么 Spark 应用程序中需要写入安全认证代码, 确保 Spark 程序能够正常运行 安全认证有三种方式 : 命令认证 : 提交 Spark 应用程序运行前, 或者在使用 CLI 连接 SparkSQL 前, 在 Spark 客户端执行如下命令获得认证 文档版本 01 ( ) 260

271 7 Spark 应用开发 kinit 组件业务用户 配置认证 : 可以通过以下 3 种方式的任意一种指定安全认证信息 a. 在客户端的 spark-default.conf 配置文件中, 配置 spark.yarn.keytab 和 spark.yarn.principal 参数指定认证信息 b. 执行 bin/spark-submit 的命令中添加如下参数来指定认证信息 --conf spark.yarn.keytab=<keytab 文件路径 > --conf spark.yarn.principal=<principal 帐号 > c. 执行 bin/spark-submit 的命令中添加如下参数来指定认证信息 代码认证 : --keytab <keytab 文件路径 > --principal <Principal 帐号 > 通过获取客户端的 principal 和 keytab 文件在应用程序中进行认证 在安全集群环境下, 样例代码需要使用的认证方式如表 1 所示 : 表 7-7 安全认证方式 样例代码模式安全认证方式 spark-examples-normal yarn-client 命令认证 配置认证或 代码认证, 三种任选一 种 spark-examples-security ( 已包含安全认证代码 ) yarn-cluster yarn-client yarn-cluster 命令认证或者配置认证, 两种任选一种 代码认证 不支持 说明 如上表所示,yarn-cluster 模式中不支持在 Spark 工程代码中进行安全认证, 因为需要在应用启动前已完成认证 未提供 Python 样例工程的安全认证代码, 推荐在运行应用程序命令中设置安全认证参数 安全认证代码 (Java 版 ) 目前样例代码统一调用 LoginUtil 类进行安全认证 在 Spark 样例工程代码中, 不同的样例工程, 使用的认证代码不同, 基本安全认证或带 ZooKeeper 认证 样例工程中使用的示例认证参数如表 2 所示, 请根据实际情况修改对应参数值 表 7-8 参数描述 参数示例参数值描述 userprincipal sparkuser 用户用于认证的帐号 Principal, 您可以 联系管理员获取此帐号 文档版本 01 ( ) 261

272 7 Spark 应用开发 参数示例参数值描述 userkeytabpath krb5confpath ZKServerPrincipal /opt/ficlient/ user.keytab /opt/ficlient/ KrbClient/ kerberos/var/krb5kdc/ krb5.conf zookeeper/ hadoop.hadoop.com 用户用于认证的 Keytab 文件, 您可以联系管理员获取文件 krb5.conf 文件路径和文件名称 ZooKeeper 服务端 principal 请联系管理员获取对应帐号 基本安全认证 : Spark Core 和 Spark SQL 程序不需要访问 HBase 或 ZooKeeper, 所以使用基本的安全认证代码即可 请在程序中添加如下代码, 并根据实际情况设置安全认证相关参数 : String userprincipal = "sparkuser"; String userkeytabpath = "/opt/ficlient/user.keytab"; String krb5confpath = "/opt/ficlient/krbclient/kerberos/var/krb5kdc/krb5.conf"; Configuration hadoopconf = new Configuration(); LoginUtil.login(userPrincipal, userkeytabpath, krb5confpath, hadoopconf); 带 ZooKeeper 认证 : 安全认证代码 (Scala 版 ) 由于 Spark Streaming 通过 JDBC 访问 Spark SQL 和 Spark on HBase 样例程序, 不仅需要基础安全认证, 还需要添加 ZooKeeper 服务端 Principal 才能完成安全认证 请在程序中添加如下代码, 并根据实际情况设置安全认证相关参数 : String userprincipal = "sparkuser"; String userkeytabpath = "/opt/ficlient/user.keytab"; String krb5confpath = "/opt/ficlient/krbclient/kerberos/var/krb5kdc/krb5.conf"; String ZKServerPrincipal = "zookeeper/hadoop.hadoop.com"; String ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME = "Client"; String ZOOKEEPER_SERVER_PRINCIPAL_KEY = "zookeeper.server.principal"; Configuration hadoopconf = new Configuration(); LoginUtil.setJaasConf(ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME, userprincipal, userkeytabpath); LoginUtil.setZookeeperServerPrincipal(ZOOKEEPER_SERVER_PRINCIPAL_KEY, ZKServerPrincipal); LoginUtil.login(userPrincipal, userkeytabpath, krb5confpath, hadoopconf); 目前样例代码统一调用 LoginUtil 类进行安全认证 在 Spark 样例工程代码中, 不同的样例工程, 使用的认证代码不同, 基本安全认证或带 ZooKeeper 认证 样例工程中使用的示例认证参数如表 3 所示, 请根据实际情况修改对应参数值 表 7-9 参数描述 参数示例参数值描述 userprincipal sparkuser 用户用于认证的帐号 Principal, 您可以联系管理员获取此帐号 userkeytabpath /opt/ficlient/user.keytab 用户用于认证的 Keytab 文件, 您 可以联系管理员获取文件 文档版本 01 ( ) 262

273 7 Spark 应用开发 参数示例参数值描述 krb5confpath ZKServerPrincipal /opt/ficlient/krbclient/ kerberos/var/krb5kdc/ krb5.conf zookeeper/ hadoop.hadoop.com krb5.conf 文件路径和文件名称 ZooKeeper 服务端 principal 请联系管理员获取对应帐号 基本安全认证 : Spark Core 和 Spark SQL 程序不需要访问 HBase 或 ZooKeeper, 所以使用基本的安全认证代码即可 请在程序中添加如下代码, 并根据实际情况设置安全认证相关参数 : val userprincipal = "sparkuser" val userkeytabpath = "/opt/ficlient/user.keytab" val krb5confpath = "/opt/ficlient/krbclient/kerberos/var/krb5kdc/krb5.conf" val hadoopconf: Configuration = new Configuration() LoginUtil.login(userPrincipal, userkeytabpath, krb5confpath, hadoopconf); 带 ZooKeeper 认证 : 由于 Spark Streaming 通过 JDBC 访问 Spark SQL 和 Spark on HBase 样例程序, 不仅需要基础安全认证, 还需要添加 ZooKeeper 服务端 Principal 才能完成安全认证 请在程序中添加如下代码, 并根据实际情况设置安全认证相关参数 : val userprincipal = "sparkuser" val userkeytabpath = "/opt/ficlient/user.keytab" val krb5confpath = "/opt/ficlient/krbclient/kerberos/var/krb5kdc/krb5.conf" val ZKServerPrincipal = "zookeeper/hadoop.hadoop.com" val ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME: String = "Client" val ZOOKEEPER_SERVER_PRINCIPAL_KEY: String = "zookeeper.server.principal" val hadoopconf: Configuration = new Configuration(); LoginUtil.setJaasConf(ZOOKEEPER_DEFAULT_LOGIN_CONTEXT_NAME, userprincipal, userkeytabpath) LoginUtil.setZookeeperServerPrincipal(ZOOKEEPER_SERVER_PRINCIPAL_KEY, ZKServerPrincipal) LoginUtil.login(userPrincipal, userkeytabpath, krb5confpath, hadoopconf); 7.3 开发程序 Spark Core 程序 场景说明 场景说明 假定用户有某个周末网民网购停留时间的日志文本, 基于某些业务要求, 要求开发 Spark 应用程序实现如下功能 : 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 周末两天的日志文件第一列为姓名, 第二列为性别, 第三列为本次停留时间, 单位为分钟, 分隔符为, log1.txt: 周六网民停留日志 LiuYang,female,20 YuanJing,male,10 文档版本 01 ( ) 263

274 7 Spark 应用开发 GuoYijun,male,5 CaiXuyu,female,50 Liyuan,male,20 FangBo,female,50 LiuYang,female,20 YuanJing,male,10 GuoYijun,male,50 CaiXuyu,female,50 FangBo,female,60 log2.txt: 周日网民停留日志 LiuYang,female,20 YuanJing,male,10 CaiXuyu,female,50 FangBo,female,50 GuoYijun,male,5 CaiXuyu,female,50 Liyuan,male,20 CaiXuyu,female,50 FangBo,female,50 LiuYang,female,20 YuanJing,male,10 FangBo,female,50 GuoYijun,male,50 CaiXuyu,female,50 FangBo,female,60 数据规划 首先需要把原日志文件放置在 HDFS 系统里 1. 本地新建两个文本文件, 将 log1.txt 中的内容复制保存到 input_data1.txt, 将 log2.txt 中的内容复制保存到 input_data2.txt 2. 在 HDFS 上建立一个文件夹, /tmp/input, 并上传 input_data1.txt, input_data2.txt 到此目录, 命令如下 : a. 在 HDFS 客户端, 执行如下命令获取安全认证 cd /opt/hadoopclient kinit < 用于认证的业务用户 > b. 在 Linux 系统 HDFS 客户端使用命令 hadoop fs -mkdir /tmp/input(hdfs dfs 命令有同样的作用 ), 创建对应目录 c. 在 Linux 系统 HDFS 客户端使用命令 hadoop fs -put input_xxx.txt /tmp/input, 上传数据文件 开发思路 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 主要分为四个部分 : 读取原文件数据 筛选女性网民上网时间数据信息 汇总每个女性上网总时间 筛选出停留时间大于两个小时的女性网民信息 文档版本 01 ( ) 264

275 7 Spark 应用开发 Java 样例代码 功能简介 代码样例 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.femaleinfocollection 类 : // 创建一个配置类 SparkConf, 然后创建一个 SparkContext SparkConf conf = new SparkConf().setAppName("CollectFemaleInfo"); JavaSparkContext jsc = new JavaSparkContext(conf); // 读取原文件数据, 每一行记录转成 RDD 里面的一个元素 JavaRDD<String> data = jsc.textfile(args[0]); // 将每条记录的每列切割出来, 生成一个 Tuple JavaRDD<Tuple3<String,String,Integer>> person = data.map(new Function<String,Tuple3<String,String,Integer>>() { private static final long serialversionuid = public Tuple3<String, String, Integer> call(string s) throws Exception { // 按逗号分割一行数据 String[] tokens = s.split(","); // 将分割后的三个元素组成一个三元 Tuple Tuple3<String, String, Integer> person = new Tuple3<String, String, Integer>(tokens[0], tokens[1], Integer.parseInt(tokens[2])); return person; ); // 使用 filter 函数筛选出女性网民上网时间数据信息 JavaRDD<Tuple3<String,String,Integer>> female = person.filter(new Function<Tuple3<String,String,Integer>, Boolean>() { private static final long serialversionuid = public Boolean call(tuple3<string, String, Integer> person) throws Exception { // 根据第二列性别, 筛选出是 female 的记录 Boolean isfemale = person._2().equals("female"); return isfemale; ); // 汇总每个女性上网总时间 JavaPairRDD<String, Integer> females = female.maptopair(new PairFunction<Tuple3<String, String, Integer>, String, Integer>() { private static final long serialversionuid = public Tuple2<String, Integer> call(tuple3<string, String, Integer> female) throws Exception { // 取出姓名和停留时间两列, 用于后面按名字求逗留时间的总和 Tuple2<String, Integer> femaleandtime = new Tuple2<String, Integer>(female._1(), female._3()); return femaleandtime; 文档版本 01 ( ) 265

276 7 Spark 应用开发 ) JavaPairRDD<String, Integer> femaletime = females.reducebykey(new Function2<Integer, Integer, Integer>() { private static final long serialversionuid = public Integer call(integer integer, Integer integer2) throws Exception { // 将同一个女性的两次停留时间相加, 求和 return (integer + integer2); ); // 筛选出停留时间大于两个小时的女性网民信息 JavaPairRDD<String, Integer> rightfemales = females.filter(new Function<Tuple2<String, Integer>, Boolean>() { private static final long serialversionuid = public Boolean call(tuple2<string, Integer> s) throws Exception { // 取出女性用户的总停留时间, 并判断是否大于 2 小时 if(s._2() > (2 * 60)) { return true; return false; ); // 对符合的 female 信息进行打印显示 for(tuple2<string, Integer> d: rightfemales.collect()) { System.out.println(d._1() + "," + d._2()); Scala 样例代码 功能简介 代码样例 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.femaleinfocollection: // 配置 Spark 应用名称 val conf = new SparkConf().setAppName("CollectFemaleInfo") // 提交 Spark 作业 val sc = new SparkContext(conf) // 读取数据 其是传入参数 args(0) 指定数据路径 val text = sc.textfile(args(0)) // 筛选女性网民上网时间数据信息 val data = text.filter(_.contains("female")) // 汇总每个女性上网时间 val femaledata:rdd[(string,int)] = data.map{line => val t= line.split(',') (t(0),t(2).toint).reducebykey(_ + _) // 筛选出时间大于两个小时的女性网民信息, 并输出 val result = femaledata.filter(line => line._2 > 120) result.foreach(println) 文档版本 01 ( ) 266

277 7 Spark 应用开发 Python 样例代码 功能简介 代码样例 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 下面代码片段仅为演示, 具体代码参见 collectfemaleinfo.py: def contains(str, substr): if substr in str: return True return False if name == " main ": if len(sys.argv) < 2: print "Usage: CollectFemaleInfo <file>" exit(-1) # 创建 SparkContext, 设置 AppName sc = SparkContext(appName = "CollectFemaleInfo")? """ 以下程序主要实现以下几步功能 : 1. 读取数据 其是传入参数 argv[1] 指定数据路径 - textfile 2. 筛选女性网民上网时间数据信息 - filter 3. 汇总每个女性上网时间 - map/map/reducebykey 4. 筛选出时间大于两个小时的女性网民信息 - filter """ inputpath = sys.argv[1] result = sc.textfile(name = inputpath, use_unicode = False) \.filter(lambda line: contains(line, "female")) \.map(lambda line: line.split(',')) \.map(lambda dataarr: (dataarr[0], int(dataarr[2]))) \.reducebykey(lambda v1, v2: v1 + v2) \.filter(lambda tupleval: tupleval[1] > 120) \.collect() for (k, v) in result: print k + "," + str(v) # 停止 SparkContext sc.stop() Spark SQL 程序 场景说明 场景说明 假定用户有某个周末网民网购停留时间的日志文本, 基于某些业务要求, 要求开发 Spark 应用程序实现如下功能 : 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 周末两天的日志文件第一列为姓名, 第二列为性别, 第三列为本次停留时间, 单位为分钟, 分隔符为, log1.txt: 周六网民停留日志 LiuYang,female,20 YuanJing,male,10 GuoYijun,male,5 文档版本 01 ( ) 267

278 7 Spark 应用开发 CaiXuyu,female,50 Liyuan,male,20 FangBo,female,50 LiuYang,female,20 YuanJing,male,10 GuoYijun,male,50 CaiXuyu,female,50 FangBo,female,60 log2.txt: 周日网民停留日志 LiuYang,female,20 YuanJing,male,10 CaiXuyu,female,50 FangBo,female,50 GuoYijun,male,5 CaiXuyu,female,50 Liyuan,male,20 CaiXuyu,female,50 FangBo,female,50 LiuYang,female,20 YuanJing,male,10 FangBo,female,50 GuoYijun,male,50 CaiXuyu,female,50 FangBo,female,60 数据规划 首先需要把原日志文件放置在 HDFS 系统里 1. 本地新建两个文本文件, 将 log1.txt 中的内容复制保存到 input_data1.txt, 将 log2.txt 中的内容复制保存到 input_data2.txt 2. 在 HDFS 上建立一个文件夹, /tmp/input, 并上传 input_data1.txt, input_data2.txt 到此目录, 命令如下 : a. 在 HDFS 客户端, 执行如下命令获取安全认证 cd /opt/hadoopclient kinit < 用于认证的业务用户 > b. 在 Linux 系统 HDFS 客户端使用命令 hadoop fs -mkdir /tmp/input(hdfs dfs 命令有同样的作用 ), 创建对应目录 c. 在 Linux 系统 HDFS 客户端使用命令 hadoop fs -put input_xxx.txt /tmp/input, 上传数据文件 开发思路 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 主要分为四个部分 : 创建表, 将日志文件数据导入到表中 筛选女性网民, 提取上网时间数据信息 汇总每个女性上网总时间 筛选出停留时间大于两个小时的女性网民信息 文档版本 01 ( ) 268

279 7 Spark 应用开发 Java 样例代码 功能简介 代码样例 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.femaleinfocollection: SparkConf conf = new SparkConf().setAppName("CollectFemaleInfo"); JavaSparkContext jsc = new JavaSparkContext(conf); SQLContext sqlcontext = new org.apache.spark.sql.sqlcontext(jsc); // 通过隐式转换, 将 RDD 转换成 DataFrame JavaRDD<FemaleInfo> femaleinfojavardd = jsc.textfile(args[0]).map( new Function<String, FemaleInfo>() public FemaleInfo call(string line) throws Exception { String[] parts = line.split(","); ); FemaleInfo femaleinfo = new FemaleInfo(); femaleinfo.setname(parts[0]); femaleinfo.setgender(parts[1]); femaleinfo.setstaytime(integer.parseint(parts[2].trim())); return femaleinfo; Scala 样例代码 功能简介 代码样例 // 注册表 DataFrame schemafemaleinfo = sqlcontext.createdataframe(femaleinfojavardd,femaleinfo.class); schemafemaleinfo.registertemptable("femaleinfotable"); // 执行 SQL 查询 DataFrame femaletimeinfo = sqlcontext.sql("select * from " + "(select name,sum(staytime) as totalstaytime from FemaleInfoTable " + "where gender = 'female' group by name )" + " tmp where totalstaytime >120"); // 显示结果 List<String> result = femaletimeinfo.javardd().map(new Function<Row, String>() { public String call(row row) { return row.getstring(0) + "," + row.getlong(1); ).collect(); System.out.println(result); jsc.stop(); 上面是简单示例, 其它 sparksql 特性请参见如下链接 : latest/sql-programming-guide.html#running-sql-queries-programmatically 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.femaleinfocollection: 文档版本 01 ( ) 269

280 7 Spark 应用开发 object CollectFemaleInfo { // 表结构, 后面用来将文本数据映射为 df case class FemaleInfo(name: String, gender: String, staytime: Int) def main(args: Array[String]) { // 配置 Spark 应用名称 val sparkconf = new SparkConf().setAppName("FemaleInfo") val sc = new SparkContext(sparkConf) val sqlcontext = new org.apache.spark.sql.sqlcontext(sc) import sqlcontext.implicits._ // 通过隐式转换, 将 RDD 转换成 DataFrame, 然后注册表 sc.textfile(args(0)).map(_.split(",")).map(p => FemaleInfo(p(0), p(1), p(2).trim.toint)).todf.registertemptable("femaleinfotable") // 通过 sql 语句筛选女性上网时间数据, 对相同名字行进行聚合 val femaletimeinfo = sqlcontext.sql("select name,sum(staytime) as staytime from FemaleInfoTable where?gender = 'female' group by name") // 筛选出时间大于两个小时的女性网民信息, 并输出 val c = femaletimeinfo.filter("staytime >= 120").collect() c.foreach(println) sc.stop() 上面是简单示例, 其它 sparksql 特性请参见如下链接 : latest/sql-programming-guide.html#running-sql-queries-programmatically Spark Streaming 程序 场景说明 场景说明 假定用户有某个周末网民网购停留时间的日志文本, 基于某些业务要求, 要求开发 Spark 应用程序实现如下功能 : 实时统计连续网购时间超过半个小时的女性网民信息 周末两天的日志文件第一列为姓名, 第二列为性别, 第三列为本次停留时间, 单位为分钟, 分隔符为, log1.txt: 周六网民停留日志 LiuYang,female,20 YuanJing,male,10 GuoYijun,male,5 CaiXuyu,female,50 Liyuan,male,20 FangBo,female,50 LiuYang,female,20 YuanJing,male,10 GuoYijun,male,50 CaiXuyu,female,50 FangBo,female,60 log2.txt: 周日网民停留日志 LiuYang,female,20 YuanJing,male,10 CaiXuyu,female,50 FangBo,female,50 GuoYijun,male,5 CaiXuyu,female,50 Liyuan,male,20 CaiXuyu,female,50 FangBo,female,50 文档版本 01 ( ) 270

281 7 Spark 应用开发 LiuYang,female,20 YuanJing,male,10 FangBo,female,50 GuoYijun,male,50 CaiXuyu,female,50 FangBo,female,60 数据规划 Spark Streaming 样例工程的数据存储在 Kafka 组件中 ( 需要有 Kafka 权限用户 ) 1. 本地新建两个文本文件 input_data1.txt 和 input_data2.txt, 将 log1.txt 的内容复制保存到 input_data1.txt, 将 log2.txt 的内容复制保存到 input_data2.txt 2. 在客户端安装节点下创建文件目录 : /home/data 将上述两个文件上传到此 / home/data 目录下 3. 将 Kafka 的 Broker 配置参数 allow.everyone.if.no.acl.found 值设置为 true 4. 启动样例代码的 Producer, 向 Kafka 发送数据 java -cp {ClassPath com.mrs.example.spark.streamingexampleproducer {BrokerList {Topic 开发思路 Java 样例代码 功能介绍 统计日志文件中本周末网购停留总时间超过 2 个小时的女性网民信息 主要分为四个部分 : 接收 Kafka 中数据, 生成相应 DStream 筛选女性网民上网时间数据信息 汇总在一个时间窗口内每个女性上网时间 筛选连续上网时间超过阈值的用户, 并获取结果 实时统计连续网购时间超过半个小时的女性网民信息, 将统计结果直接打印或者输出写入到 Kafka 中 Spark Streaming Write To Print 代码样例 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.femaleinfocollectionprint: // 参数解析 : // <batchtime> 为 Streaming 分批的处理间隔 // <windowtime> 为统计数据的时间跨度, 时间单位都是秒 // <topics> 为 Kafka 中订阅的主题, 多以逗号分隔 // <brokers> 为获取元数据的 kafka 地址 public class FemaleInfoCollectionPrint { public static void main(string[] args) throws Exception { String batchtime = args[0]; final String windowtime = args[1]; String topics = args[2]; String brokers = args[3]; 文档版本 01 ( ) 271

282 7 Spark 应用开发 Duration batchduration = Durations.seconds(Integer.parseInt(batchTime)); Duration windowduration = Durations.seconds(Integer.parseInt(windowTime)); SparkConf conf = new SparkConf().setAppName("DataSightStreamingExample"); JavaStreamingContext jssc = new JavaStreamingContext(conf, batchduration); // 设置 Streaming 的 CheckPoint 目录, 由于窗口概念存在, 该参数必须设置 jssc.checkpoint("checkpoint"); // 组装 Kafka 的主题列表 HashSet<String> topicsset = new HashSet<String>(Arrays.asList(topics.split(","))); HashMap<String, String> kafkaparams = new HashMap<String, String>(); kafkaparams.put("metadata.broker.list", brokers); // 通过 brokers 和 topics 直接创建 kafka stream // 1. 接收 Kafka 中数据, 生成相应 DStream JavaDStream<String> lines = KafkaUtils.createDirectStream(jssc,String.class,String.class, StringDecoder.class, StringDecoder.class, kafkaparams, topicsset).map( new Function<Tuple2<String, String>, String>() public String call(tuple2<string, String> tuple2) { return tuple2._2(); ); // 2. 获取每一个行的字段属性 JavaDStream<Tuple3<String, String, Integer>> records = lines.map( new Function<String, Tuple3<String, String, Integer>>() public Tuple3<String, String, Integer> call(string line) throws Exception { String[] elems = line.split(","); return new Tuple3<String, String, Integer>(elems[0], elems[1], Integer.parseInt(elems[2])); ); // 3. 筛选女性网民上网时间数据信息 JavaDStream<Tuple2<String, Integer>> femalerecords = records.filter(new Function<Tuple3<String, String, Integer>, Boolean>() { public Boolean call(tuple3<string, String, Integer> line) throws Exception { if (line._2().equals("female")) { return true; else { return false; ).map(new Function<Tuple3<String, String, Integer>, Tuple2<String, Integer>>() { public Tuple2<String, Integer> call(tuple3<string, String, Integer> stringstringintegertuple3) throws Exception { return new Tuple2<String, Integer>(stringStringIntegerTuple3._1(), stringstringintegertuple3._3()); ); // 4. 汇总在一个时间窗口内每个女性上网时间 JavaPairDStream<String, Integer> aggregaterecords = JavaPairDStream.fromJavaDStream(femaleRecords).reduceByKeyAndWindow(new Function2<Integer, Integer, Integer>() { public Integer call(integer integer, Integer integer2) throws Exception { return integer + integer2;, new Function2<Integer, Integer, Integer>() { public Integer call(integer integer, Integer integer2) throws Exception { return integer - integer2;, windowduration, batchduration); 文档版本 01 ( ) 272

283 7 Spark 应用开发 JavaPairDStream<String, Integer> uptimeuser = aggregaterecords.filter(new Function<Tuple2<String, Integer>, Boolean>() { public Boolean call(tuple2<string, Integer> stringintegertuple2) throws Exception { if (stringintegertuple2._2() > 0.9 * Integer.parseInt(windowTime)) { return true; else { return false; ); // 5. 筛选连续上网时间超过阈值的用户, 并获取结果 uptimeuser.print(); // 6.Streaming 系统启动 jssc.start(); jssc.awaittermination(); Spark Streaming Write To Kafka 代码样例 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.javadstreamkafkawriter: 说明 Spark 版本升级后, 推荐使用新接口 createdirectstream, 老接口 createstream 仍然存在, 但是性能和稳定性差, 建议不要使用老接口开发应用程序 // 参数解析 : //<groupid> is the group ID for the consumer. //<brokers> is for bootstrapping and the producer will only use //<topic> is a kafka topic to consume from. public class JavaDstreamKafkaWriter { public static void main(string[] args) throws InterruptedException { if (args.length!= 3) { System.err.println("Usage: JavaDstreamKafkaWriter <groupid> <brokers> <topic>"); System.exit(1); final String groupid = args[0]; final String brokers = args[1]; final String topic = args[2]; SparkConf sparkconf = new SparkConf().setAppName("KafkaWriter"); // Populate Kafka properties Properties kafkaparams = new Properties(); kafkaparams.put("metadata.broker.list", brokers); kafkaparams.put("group.id", groupid); kafkaparams.put("auto.offset.reset", "smallest"); // Create Spark Java streaming context JavaStreamingContext ssc = new JavaStreamingContext(sparkConf, Durations.milliseconds(500)); // Populate data to write to kafka List<String> sentdata = new ArrayList(); sentdata.add("kafka_writer_test_msg_01"); sentdata.add("kafka_writer_test_msg_02"); sentdata.add("kafka_writer_test_msg_03"); // Create Java RDD queue Queue<JavaRDD<String>> sent = new LinkedList(); sent.add(ssc.sparkcontext().parallelize(sentdata)); 文档版本 01 ( ) 273

284 7 Spark 应用开发 // Create java Dstream with the data to be written JavaDStream wstream = ssc.queuestream(sent); // Write to kafka JavaDStreamKafkaWriterFactory.fromJavaDStream(wStream).writeToKafka(kafkaParams, new Function<String, KeyedMessage<String, byte[]>>() { public KeyedMessage<String, byte[]> call(string s) { return new KeyedMessage(topic, s.getbytes()); ); ssc.start(); ssc.awaittermination(); Scala 样例代码 功能介绍 实时统计连续网购时间超过半个小时的女性网民信息, 将统计结果直接打印或者输出写入到 Kafka 中 Spark Streaming Write To Print 代码样例 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.femaleinfocollectionprint: // 参数解析 : // <batchtime> 为 Streaming 分批的处理间隔 // <windowtime> 为统计数据的时间跨度, 时间单位都是秒 // <topics> 为 Kafka 中订阅的主题, 多以逗号分隔 // <brokers> 为获取元数据的 kafka 地址 val Array(batchTime, windowtime, topics, brokers) = args val batchduration = Seconds(batchTime.toInt) val windowduration = Seconds(windowTime.toInt) // 建立 Streaming 启动环境 val sparkconf = new SparkConf() sparkconf.setappname("datasightstreamingexample") val ssc = new StreamingContext(sparkConf, batchduration) // 设置 Streaming 的 CheckPoint 目录, 由于窗口概念存在, 该参数必须设置 ssc.checkpoint("checkpoint") // 组装 Kafka 的主题列表 val topicsset = topics.split(",").toset // 通过 brokers 和 topics 直接创建 kafka stream // 1. 接收 Kafka 中数据, 生成相应 DStream val kafkaparams = Map[String, String]("metadata.broker.list" -> brokers) val lines = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder]( ssc, kafkaparams, topicsset).map(_._2) // 2. 获取每一个行的字段属性 val records = lines.map(getrecord) // 3. 筛选女性网民上网时间数据信息 val femalerecords = records.filter(_._2 == "female").map(x => (x._1, x._3)) // 4. 汇总在一个时间窗口内每个女性上网时间 val aggregaterecords = femalerecords.reducebykeyandwindow(_ + _, _ - _, windowduration) 文档版本 01 ( ) 274

285 7 Spark 应用开发 // 5. 筛选连续上网时间超过阈值的用户, 并获取结果 aggregaterecords.filter(_._2 > 0.9 * windowtime.toint).print() // 6.Streaming 系统启动 ssc.start() ssc.awaittermination() 上述代码会引用以下函数 // 获取字段函数 def getrecord(line: String): (String, String, Int) = { val elems = line.split(",") val name = elems(0) val sexy = elems(1) val time = elems(2).toint (name, sexy, time) Spark Streaming Write To Kafka 代码样例 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.dstreamkafkawriter: 说明 Spark 版本升级后, 推荐使用新接口 createdirectstream, 老接口 createstream 仍然存在, 但是性能和稳定性差, 建议不要使用老接口开发应用程序 // 参数解析 : //<groupid> is the group ID for the consumer. //<brokers> is for bootstrapping and the producer will only use //<topic> is a kafka topic to consume from. if (args.length!= 3) { System.err.println("Usage: DstreamKafkaWriter <groupid> <brokers> <topic>") System.exit(1) val Array(groupId, brokers, topic) = args val sparkconf = new SparkConf().setAppName("KafkaWriter") // Populate Kafka properties val kafkaparams = new Properties() kafkaparams.put("metadata.broker.list", brokers) kafkaparams.put("group.id", groupid) kafkaparams.put("auto.offset.reset", "smallest") // Create Spark Java streaming context val ssc = new StreamingContext(sparkConf, Milliseconds(500)) // Populate data to write to kafka val sentdata = Seq("kafka_writer_test_msg_01", "kafka_writer_test_msg_02", "kafka_writer_test_msg_03") // Create RDD queue val sent = new mutable.queue[rdd[string]]() sent.enqueue(ssc.sparkcontext.makerdd(sentdata)) // Create Dstream with the data to be written val wstream = ssc.queuestream(sent) // Write to kafka wstream.writetokafka(kafkaparams, (x: String) => new KeyedMessage[String, Array[Byte]](topic, x.getbytes)) ssc.start() ssc.awaittermination() 文档版本 01 ( ) 275

286 7 Spark 应用开发 通过 JDBC 访问 Spark SQL 的程序 场景说明 场景说明 数据规划 用户自定义 JDBCServer 的客户端, 使用 JDBC 连接来进行数据表的创建 数据加载 查询和删除 步骤 1 步骤 2 步骤 3 确保以 HA 模式启动了 JDBCServer 服务, 并至少有一个实例对外服务 在主备 JDBCServer 节点上分别创建 /home/data 文件, 内容如下 : Miranda,32 Karlie,23 Candice,27 确保其对启动 JDBCServer 的用户有读写权限 确保客户端 classpath 下有 hive-site.xml 文件, 且根据实际集群情况配置所需要的参数 示例 : <?xml version="1.0" encoding="utf-8" standalone="no"?> <configuration> <property> <name>spark.thriftserver.ha.enabled</name> <value>true</value> </property> </configuration> ---- 结束 开发思路 1. 在 default 数据库下创建 child 表 2. 把 /home/data 的数据加载进 child 表中 3. 查询 child 表中的数据 4. 删除 child 表 Java 样例代码 功能简介 样例代码 使用自定义客户端的 JDBC 接口提交数据分析任务, 并返回结果 步骤 1 定义 SQL 语句 SQL 语句必须为单条语句, 注意其中不能包含 ; 示例 : ArrayList<String> sqllist = new ArrayList<String>(); sqllist.add("create TABLE CHILD (NAME STRING, AGE INT) ROW FORMAT DELIMITED FIELDS TERMINATED BY ','"); sqllist.add("load DATA LOCAL INPATH '/home/data' INTO TABLE CHILD"); sqllist.add("select * FROM child"); 文档版本 01 ( ) 276

287 7 Spark 应用开发 sqllist.add("drop TABLE child"); executesql(url, sqllist); 说明 样例工程中的 data 文件需要放到 JDBCServer 所在机器的 home 目录下 步骤 2 拼接 JDBC URL 说明 HA 模式下 url 的 host 和 port 必须为 ha-cluster String HA_CLUSTER_URL = "ha-cluster"; StringBuilder sb = new StringBuilder("jdbc:hive2://" + HA_CLUSTER_URL + "/default;"); String url = sb.tostring(); 步骤 3 步骤 4 加载 Hive JDBC 驱动 Class.forName("org.apache.hive.jdbc.HiveDriver").newInstance(); 获取 JDBC 连接, 执行 HQL, 输出查询的列名和结果到控制台, 关闭 JDBC 连接 连接字符串中的 zk.quorum 也可以使用配置文件中的配置项 spark.deploy.zookeeper.url 来代替 在网络拥塞的情况下, 您还可以设置客户端与 JDBCServer 连接的超时时间, 可以避免客户端由于无限等待服务端的返回而挂起 使用方式如下 : 在执行 DriverManager.getConnection 方法获取 JDBC 连接前, 添加 DriverManager.setLoginTimeout(n) 方法来设置超时时长, 其中 n 表示等待服务返回的超时时长, 单位为秒, 类型为 Int, 默认为 0 ( 表示永不超时 ) static void executesql(string url, ArrayList<String> sqls) throws ClassNotFoundException, SQLException { try { Class.forName("org.apache.hive.jdbc.HiveDriver").newInstance(); catch (Exception e) { e.printstacktrace(); Connection connection = null; PreparedStatement statement = null; try { connection = DriverManager.getConnection(url); for (int i = 0 ; i < sqls.size(); i++) { String sql = sqls.get(i); System.out.println("---- Begin executing sql: " + sql + " ----"); statement = connection.preparestatement(sql); ResultSet result = statement.executequery(); ResultSetMetaData resultmetadata = result.getmetadata(); Integer colnum = resultmetadata.getcolumncount(); for (int j = 1; j < colnum; j++) { System.out.println(resultMetaData.getColumnLabel(j) + "\t"); System.out.println(); while (result.next()) { for (int j = 1; j < colnum; j++){ System.out.println(result.getString(j) + "\t"); System.out.println(); System.out.println("---- Done executing sql: " + sql + " ----"); catch (Exception e) { e.printstacktrace(); finally { 文档版本 01 ( ) 277

288 7 Spark 应用开发 if (null!= statement) { statement.close(); if (null!= connection) { connection.close(); ---- 结束 Scala 样例代码 功能简介 样例代码 使用自定义客户端的 JDBC 接口提交数据分析任务, 并返回结果 步骤 1 定义 SQL 语句 SQL 语句必须为单条语句, 注意其中不能包含 ; 示例 : val sqllist = new ArrayBuffer[String] sqllist += "CREATE TABLE CHILD (NAME STRING, AGE INT) " + "ROW FORMAT DELIMITED FIELDS TERMINATED BY ','" sqllist += "LOAD DATA LOCAL INPATH '/home/data' INTO TABLE CHILD" sqllist += "SELECT * FROM child" sqllist += "DROP TABLE child" 说明 样例工程中的 data 文件需要放到 JDBCServer 所在机器的 home 目录下 步骤 2 拼接 JDBC URL 说明 HA 模式下 url 的 host 和 port 必须为 ha-cluster val HA_CLUSTER_URL = "ha-cluster" val sb = new StringBuilder(s"jdbc:hive2://$HA_CLUSTER_URL/default;") val url = sb.tostring() 步骤 3 步骤 4 加载 Hive JDBC 驱动 Class.forName("org.apache.hive.jdbc.HiveDriver").newInstance(); 获取 JDBC 连接, 执行 HQL, 输出查询的列名和结果到控制台, 关闭 JDBC 连接 连接字符串中的 zk.quorum 也可以使用配置文件中的配置项 spark.deploy.zookeeper.url 来代替 在网络拥塞的情况下, 您还可以设置客户端与 JDBCServer 连接的超时时间, 可以避免客户端由于无限等待服务端的返回而挂起 使用方式如下 : 在执行 DriverManager.getConnection 方法获取 JDBC 连接前, 添加 DriverManager.setLoginTimeout(n) 方法来设置超时时长, 其中 n 表示等待服务返回的超时时长, 单位为秒, 类型为 Int, 默认为 0 ( 表示永不超时 ) var connection: Connection = null var statement: PreparedStatement = null try { connection = DriverManager.getConnection(url) for (sql <- sqls) { println(s"---- Begin executing sql: $sql ----") statement = connection.preparestatement(sql) val result = statement.executequery() 文档版本 01 ( ) 278

289 7 Spark 应用开发 val resultmetadata = result.getmetadata val colnum = resultmetadata.getcolumncount for (i <- 1 to colnum) { print(resultmetadata.getcolumnlabel(i) + "\t") println() while (result.next()) { for (i <- 1 to colnum) { print(result.getstring(i) + "\t") println() println(s"---- Done executing sql: $sql ----") finally { if (null!= statement) { statement.close() if (null!= connection) { connection.close() ---- 结束 Spark on HBase 程序 场景说明 场景说明 数据规划 用户可以使用 Spark 调用 HBase 的接口来操作 HBase 表的功能 在 Spark 应用中, 用户可以自由使用 HBase 的接口来实现创建表 读取表 往表中插入数据等操作 首先需要把数据文件放置在 HDFS 系统里 1. 本地新建文本文件, 将 data 文件中的内容复制保存到 input_data1.txt 2. 在 HDFS 上建立一个文件夹, /tmp/input, 并上传 input_data1.txt 到此目录, 命令如下 : a. 在 HDFS 客户端, 执行如下命令获取安全认证 cd /opt/hadoopclient kinit < 用于认证的业务用户 > b. 在 Linux 系统 HDFS 客户端使用命令 hadoop fs -mkdir /tmp/input(hdfs dfs 命令有同样的作用 ), 创建对应目录 c. 在 Linux 系统 HDFS 客户端使用命令 hadoop fs -put input_xxx.txt /tmp/input, 上传数据文件 说明 如果开启了 kerberos 认证, 需要将客户端的配置文件 spark-default.conf 中的配置项 spark.yarn.security.credentials.hbase.enabled 置为 true 文档版本 01 ( ) 279

290 7 Spark 应用开发 开发思路 1. 创建 HBase 表 2. 往 HBase 表中插入数据 3. 通过 Spark Application 读取 HBase 表的数据 Java 样例代码 功能简介 代码样例 在 Spark 应用中, 通过使用 HBase 接口来实现创建表, 读取表, 往表中插入数据等操作 下面代码片段仅为演示, 具体代码参见 SparkOnHbaseJavaExample: 样例 : 创建 HBase 表 public class TableCreation { public static void main (String[] args) throws IOException { SparkConf conf = new SparkConf().setAppName("CollectFemaleInfo"); JavaSparkContext jsc = new JavaSparkContext(conf); Configuration hbconf = HBaseConfiguration.create(jsc.hadoopConfiguration()); // 创建和 hbase 的连接通道 Connection connection = ConnectionFactory.createConnection(hbConf); // 声明表的描述信息 TableName usertable = TableName.valueOf("shb1"); HTableDescriptor tabledescr = new HTableDescriptor(userTable); tabledescr.addfamily(new HColumnDescriptor("info".getBytes())); // 创建表 System.out.println("Creating table shb1. "); Admin admin = connection.getadmin(); if (admin.tableexists(usertable)) { admin.disabletable(usertable); admin.deletetable(usertable); admin.createtable(tabledescr); connection.close(); jsc.stop(); System.out.println("Done!"); 样例 : 在 HBase 表中插入数据 public class TableInputData { public static void main (String[] args) throws IOException { // 建立连接 hbase 的配置参数, 此时需要保证 hbase-site.xml 在 classpath 中 SparkConf conf = new SparkConf().setAppName("CollectFemaleInfo"); JavaSparkContext jsc = new JavaSparkContext(conf); Configuration hbconf = HBaseConfiguration.create(jsc.hadoopConfiguration()); // 声明表的信息 Table table = null; String tablename = "shb1"; byte[] familyname = Bytes.toBytes("info"); 文档版本 01 ( ) 280

291 7 Spark 应用开发 Connection connection = null; try { // 获取 hbase 连接 connection = ConnectionFactory.createConnection(hbConf); // 获取 table 对象 table = connection.gettable(tablename.valueof(tablename)); List<Tuple4<String, String, String, String>> data = jsc.textfile(args[0]).map( new Function<String, Tuple4<String, String, String, String>>() public Tuple4<String, String, String, String> call(string s) throws Exception { String[] tokens = s.split(","); return new Tuple4<String, String, String, String>(tokens[0], tokens[1], tokens[2], tokens[3]); ).collect(); Integer i = 0; for(tuple4<string, String, String, String> line: data) { Put put = new Put(Bytes.toBytes("row" + i)); put.addcolumn(familyname, Bytes.toBytes("c11"), Bytes.toBytes(line._1())); put.addcolumn(familyname, Bytes.toBytes("c12"), Bytes.toBytes(line._2())); put.addcolumn(familyname, Bytes.toBytes("c13"), Bytes.toBytes(line._3())); put.addcolumn(familyname, Bytes.toBytes("c14"), Bytes.toBytes(line._4())); i += 1; table.put(put); catch (IOException e) { e.printstacktrace(); finally { if (table!= null) { try { table.close(); catch (IOException e) { e.printstacktrace(); if (connection!= null) { try { // 关闭 hbase 连接. connection.close(); catch (IOException e) { e.printstacktrace(); jsc.stop(); 样例 : 读取 HBase 表数据 public class TableOutputData { public static void main(string[] args) throws IOException { System.setProperty("spark.serializer", "org.apache.spark.serializer.kryoserializer"); System.setProperty("spark.kryo.registrator", "com.huawei.bigdata.spark.examples.myregistrator"); // 建立连接 hbase 的配置参数, 此时需要保证 hbase-site.xml 在 classpath 中 SparkConf conf = new SparkConf().setAppName("CollectFemaleInfo"); JavaSparkContext jsc = new JavaSparkContext(conf); Configuration hbconf = HBaseConfiguration.create(jsc.hadoopConfiguration()); // 声明要查的表的信息 Scan scan = new org.apache.hadoop.hbase.client.scan(); scan.addfamily(bytes.tobytes("info")); 文档版本 01 ( ) 281

292 7 Spark 应用开发 org.apache.hadoop.hbase.protobuf.generated.clientprotos.scan proto = ProtobufUtil.toScan(scan); String scantostring = Base64.encodeBytes(proto.toByteArray()); hbconf.set(tableinputformat.input_table, "shb1"); hbconf.set(tableinputformat.scan, scantostring); // 通过 spark 接口获取表中的数据 JavaPairRDD rdd = jsc.newapihadooprdd(hbconf, TableInputFormat.class, ImmutableBytesWritable.class, Result.class); // 遍历 hbase 表中的每一行, 并打印结果 List<Tuple2<ImmutableBytesWritable, Result>> rddlist = rdd.collect(); for (int i = 0; i < rddlist.size(); i++) { Tuple2<ImmutableBytesWritable, Result> t2 = rddlist.get(i); ImmutableBytesWritable key = t2._1(); Iterator<Cell> it = t2._2().listcells().iterator(); while (it.hasnext()) { Cell c = it.next(); String family = Bytes.toString(CellUtil.cloneFamily(c)); String qualifier = Bytes.toString(CellUtil.cloneQualifier(c)); String value = Bytes.toString(CellUtil.cloneValue(c)); Long tm = c.gettimestamp(); System.out.println(" Family=" + family + " Qualifier=" + qualifier + " Value=" + value + " TimeStamp=" + tm); jsc.stop(); Scala 样例代码 功能简介 代码样例 在 Spark 应用中, 通过使用 HBase 接口来实现创建表, 读取表, 往表中插入数据等操作 下面代码片段仅为演示, 具体代码参见 SparkOnHbaseScalaExample: 样例 : 创建 HBase 表 // 建立连接 hbase 的配置参数, 此时需要保证 hbase-site.xml 在 classpath 中 val conf: SparkConf = new SparkConf val sc: SparkContext = new SparkContext(conf) val hbconf: Configuration = HBaseConfiguration.create(sc.hadoopConfiguration) // 创建和 hbase 的连接通道 val connection: Connection = ConnectionFactory.createConnection(hbConf) // 声明表的描述信息 val usertable = TableName.valueOf("shb1") val tabledescr = new HTableDescriptor(userTable) tabledescr.addfamily(new HColumnDescriptor("info".getBytes)) // 创建表 println("creating table shb1. ") val admin = connection.getadmin if (admin.tableexists(usertable)) { admin.disabletable(usertable) admin.deletetable(usertable) admin.createtable(tabledescr) connection.close() 文档版本 01 ( ) 282

293 7 Spark 应用开发 sc.stop() println("done!") 样例 : 在 HBase 表中插入数据 // 建立连接 hbase 的配置参数, 此时需要保证 hbase-site.xml 在 classpath 中 val conf = new SparkConf() val sc = new SparkContext(conf) val hbconf = HBaseConfiguration.create(sc.hadoopConfiguration) // 声明表的信息 val table: HTable = null val tablename = "shb1" val familyname = Bytes.toBytes("info"); var connection: Connection = null try { // 获取 hbase 连接 connection = ConnectionFactory.createConnection(hbConf); // 获取 table 对象 val table = connection.gettable(tablename.valueof(tablename)); val data = sc.textfile(args(0)).map { line => val value = line.split(",") (value(0), value(1), value(2), value(3)).collect() var i = 0 for (line <- data) { val put = new Put(Bytes.toBytes("row" + i)); put.addcolumn(familyname, Bytes.toBytes("c11"), Bytes.toBytes(line._1)) put.addcolumn(familyname, Bytes.toBytes("c12"), Bytes.toBytes(line._2)) put.addcolumn(familyname, Bytes.toBytes("c13"), Bytes.toBytes(line._3)) put.addcolumn(familyname, Bytes.toBytes("c14"), Bytes.toBytes(line._4)) i += 1 table.put(put) catch { case e: IOException => e.printstacktrace(); finally { if (table!= null) { try { // 关闭 HTable 对象 table.close(); catch { case e: IOException => e.printstacktrace(); if (connection!= null) { try { // 关闭 hbase 连接. connection.close(); catch { case e: IOException => e.printstacktrace(); sc.stop() 样例 : 读取 HBase 表数据 // 建立连接 hbase 的配置参数, 此时需要保证 hbase-site.xml 在 classpath 中 val conf = new SparkConf() val sc = new SparkContext(conf) val hbconf = HBaseConfiguration.create(sc.hadoopConfiguration) // 声明要查的表的信息 val scan = new Scan() scan.addfamily(bytes.tobytes("info")) 文档版本 01 ( ) 283

294 7 Spark 应用开发 val proto = ProtobufUtil.toScan(scan) val scantostring = Base64.encodeBytes(proto.toByteArray) hbconf.set(tableinputformat.input_table, "shb1") hbconf.set(tableinputformat.scan, scantostring) // 通过 spark 接口获取表中的数据 val rdd = sc.newapihadooprdd(hbconf, classof[tableinputformat], classof[immutablebyteswritable], classof[result]) // 遍历 hbase 表中的每一行, 并打印结果 rdd.collect().foreach(x => { val key = x._1.tostring val it = x._2.listcells().iterator() while (it.hasnext) { val c = it.next() val family = Bytes.toString(CellUtil.cloneFamily(c)) val qualifier = Bytes.toString(CellUtil.cloneQualifier(c)) val value = Bytes.toString(CellUtil.cloneValue(c)) val tm = c.gettimestamp println(" Family=" + family + " Qualifier=" + qualifier + " Value=" + value + " TimeStamp=" + tm) ) sc.stop() 从 HBase 读取数据再写入 HBase 场景说明 场景说明 数据规划 假定 HBase 的 table1 表存储用户当天消费的金额信息,table2 表存储用户历史消费的金额信息 现 table1 表有记录 key=1,cf:cid=100, 表示用户 1 在当天消费金额为 100 元 table2 表有记录 key=1,cf:cid=1000, 表示用户 1 的历史消息记录金额为 1000 元 基于某些业务要求, 要求开发 Spark 应用程序实现如下功能 : 根据用户名累计用户的历史消费金额, 即用户总消费金额 =100( 用户当天的消费金额 ) ( 用户历史消费金额 ) 上例所示, 运行结果 table2 表用户 key=1 的总消费金融为 cf:cid=1100 元 使用 Beeline 工具, 创建 HBase table1 和 table2, 并分别插入数据 步骤 1 确保 Thrift Server 已启动 然后在 Spark 客户端, 使用 Beeline 工具执行如下操作 步骤 2 执行如下命令创建 HBase table1 表 create table table1 ( key string, cid string ) 文档版本 01 ( ) 284

295 7 Spark 应用开发 using org.apache.spark.sql.hbase.hbasesource options( hbasetablename "table1", keycols "key", colsmapping "cid=cf.cid"); 步骤 3 通过 HBase 插入数据, 命令如下 : put 'table1', '1', 'cf:cid', '100' 步骤 4 执行如下命令创建 HBase table2 表 create table table2 ( key string, cid string ) using org.apache.spark.sql.hbase.hbasesource options( hbasetablename "table2", keycols "key", colsmapping "cid=cf.cid"); 步骤 5 通过 HBase 插入数据 put 'table2', '1', 'cf:cid', '1000' 说明 如果开启了 kerberos 认证, 需要将客户端的配置文件 spark-default.conf 和 sparkjdbc 服务端中的配置项 spark.yarn.security.credentials.hbase.enabled 置为 true ---- 结束 开发思路 1. 查询 table1 表的数据 2. 根据 table1 表数据的 key 值去 table2 表做查询 3. 把前两步相应的数据记录做连接操作 4. 把上一步骤的结果写到 table2 表 Java 样例代码 功能介绍 用户可以使用 Spark 调用 HBase 接口来操作 HBase table1 表, 然后把 table1 表的数据经过分析后写到 HBase table2 表中 文档版本 01 ( ) 285

296 7 Spark 应用开发 代码样例 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.sparkhbasetohbase /** * 从 table1 表读取数据, 根据 key 值去 table2 表获取相应记录, 把两者数据后, 更新到 table2 表 */ public class SparkHbasetoHbase { public static void main(string[] args) throws Exception { if (args.length < 1) { printusage(); SparkConf conf = new SparkConf().setAppName("SparkHbasetoHbase"); conf.set("spark.serializer", "org.apache.spark.serializer.kryoserializer"); conf.set("spark.kryo.registrator", "com.huawei.bigdata.spark.examples.myregistrator"); JavaSparkContext jsc = new JavaSparkContext(conf); // 建立连接 hbase 的配置参数, 此时需要保证 hbase-site.xml 在 classpath 中 Configuration hbconf = HBaseConfiguration.create(jsc.hadoopConfiguration()); // 声明表的信息 Scan scan = new org.apache.hadoop.hbase.client.scan(); scan.addfamily(bytes.tobytes("cf"));//colomn family org.apache.hadoop.hbase.protobuf.generated.clientprotos.scan proto = ProtobufUtil.toScan(scan); String scantostring = Base64.encodeBytes(proto.toByteArray()); hbconf.set(tableinputformat.input_table, "table1");//table name hbconf.set(tableinputformat.scan, scantostring); // 通过 spark 接口获取表中的数据 JavaPairRDD rdd = jsc.newapihadooprdd(hbconf, TableInputFormat.class, ImmutableBytesWritable.class, Result.class); // 遍历 hbase table1 表中的每一个 partition, 然后更新到 Hbase table2 表 // 如果数据条数较少, 也可以使用 rdd.foreach() 方法 final String zkquorum = args[0]; rdd.foreachpartition( new VoidFunction<Iterator<Tuple2<ImmutableBytesWritable, Result>>>() { public void call(iterator<tuple2<immutablebyteswritable, Result>> iterator) throws Exception { hbasewriter(iterator, zkquorum); ); jsc.stop(); /** * 在 executor 端更新 table2 表记录 * iterator table1 表的 partition 数据 */ private static void hbasewriter(iterator<tuple2<immutablebyteswritable, Result>> iterator, String zkquorum) throws IOException { // 准备读取 hbase String tablename = "table2"; String columnfamily = "cf"; String qualifier = "cid"; Configuration conf = HBaseConfiguration.create(); conf.set("hbase.zookeeper.property.clientport", "24002"); conf.set("hbase.zookeeper.quorum", zkquorum); Connection connection = null; Table table = null; try { connection = ConnectionFactory.createConnection(conf); table = connection.gettable(tablename.valueof(tablename)); List<Get> rowlist = new ArrayList<Get>(); 文档版本 01 ( ) 286

297 7 Spark 应用开发 List<Tuple2<ImmutableBytesWritable, Result>> table1list = new ArrayList<Tuple2<ImmutableBytesWritable, Result>>(); while (iterator.hasnext()) { Tuple2<ImmutableBytesWritable, Result> item = iterator.next(); Get get = new Get(item._2().getRow()); table1list.add(item); rowlist.add(get); // 获取 table2 表记录 Result[] resultdatabuffer = table.get(rowlist); // 修改 table2 表记录 List<Put> putlist = new ArrayList<Put>(); for (int i = 0; i < resultdatabuffer.length; i++) { Result resultdata = resultdatabuffer[i];//hbase2 row if (!resultdata.isempty()) { // 查询 hbase1value String hbase1value = ""; Iterator<Cell> it = table1list.get(i)._2().listcells().iterator(); while (it.hasnext()) { Cell c = it.next(); // 判断 cf 和 qualifile 是否相同 if (columnfamily.equals(bytes.tostring(cellutil.clonefamily(c))) && qualifier.equals(bytes.tostring(cellutil.clonequalifier(c)))) { hbase1value = Bytes.toString(CellUtil.cloneValue(c)); String hbase2value = Bytes.toString(resultData.getValue(columnFamily.getBytes(), qualifier.getbytes())); Put put = new Put(table1List.get(i)._2().getRow()); // 计算结果 int resultvalue = Integer.parseInt(hbase1Value) + Integer.parseInt(hbase2Value); // 设置结果到 put 对象 put.addcolumn(bytes.tobytes(columnfamily), Bytes.toBytes(qualifier), Bytes.toBytes(String.valueOf(resultValue))); putlist.add(put); if (putlist.size() > 0) { table.put(putlist); catch (IOException e) { e.printstacktrace(); finally { if (table!= null) { try { table.close(); catch (IOException e) { e.printstacktrace(); if (connection!= null) { try { // 关闭 Hbase 连接 connection.close(); catch (IOException e) { e.printstacktrace(); private static void printusage() { System.out.println("Usage: {zkquorum"); System.exit(1); 文档版本 01 ( ) 287

298 7 Spark 应用开发 Scala 样例代码 功能介绍 代码样例 用户可以使用 Spark 调用 HBase 接口来操作 HBase table1 表, 然后把 table1 表的数据经过分析后写到 HBase table2 表中 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.sparkhbasetohbase /** * 从 table1 表读取数据, 根据 key 值去 table2 表获取相应记录, 把两者数据后, 更新到 table2 表 */ object SparkHbasetoHbase { case class FemaleInfo(name: String, gender: String, staytime: Int) def main(args: Array[String]) { if (args.length < 1) { printusage val conf = new SparkConf().setAppName("SparkHbasetoHbase") conf.set("spark.serializer", "org.apache.spark.serializer.kryoserializer") conf.set("spark.kryo.registrator", "com.huawei.bigdata.spark.examples.myregistrator") val sc = new SparkContext(conf) // 建立连接 hbase 的配置参数, 此时需要保证 hbase-site.xml 在 classpath 中 val hbconf = HBaseConfiguration.create(sc.hadoopConfiguration) // 声明表的信息 val scan = new Scan() scan.addfamily(bytes.tobytes("cf"))//colomn family val proto = ProtobufUtil.toScan(scan) val scantostring = Base64.encodeBytes(proto.toByteArray) hbconf.set(tableinputformat.input_table, "table1")//table name hbconf.set(tableinputformat.scan, scantostring) // 通过 spark 接口获取表中的数据 val rdd = sc.newapihadooprdd(hbconf, classof[tableinputformat], classof[immutablebyteswritable], classof[result]) // 遍历 hbase table1 表中的每一个 partition, 然后更新到 Hbase table2 表 // 如果数据条数较少, 也可以使用 rdd.foreach() 方法 rdd.foreachpartition(x => hbasewriter(x, args(0))) sc.stop() /** * 在 executor 端更新 table2 表记录 * iterator table1 表的 partition 数据 */ def hbasewriter(iterator: Iterator[(ImmutableBytesWritable, Result)], zkquorum: String): Unit = { // 准备读取 hbase val tablename = "table2" val columnfamily = "cf" val qualifier = "cid" val conf = HBaseConfiguration.create() conf.set("hbase.zookeeper.property.clientport", "24002") conf.set("hbase.zookeeper.quorum", zkquorum) var table: Table = null var connection: Connection = null try { connection = ConnectionFactory.createConnection(conf) 文档版本 01 ( ) 288

299 7 Spark 应用开发 table = connection.gettable(tablename.valueof(tablename)) val iteratorarray = iterator.toarray val rowlist = new util.arraylist[get]() for (row <- iteratorarray) { val get = new Get(row._2.getRow) rowlist.add(get) // 获取 table2 表记录 val resultdatabuffer = table.get(rowlist) // 修改 table2 表记录 val putlist = new util.arraylist[put]() for (i <- 0 until iteratorarray.size) { val resultdata = resultdatabuffer(i) //hbase2 row if (!resultdata.isempty) { // 查询 hbase1value var hbase1value = "" val it = iteratorarray(i)._2.listcells().iterator() while (it.hasnext) { val c = it.next() // 判断 cf 和 qualifile 是否相同 if (columnfamily.equals(bytes.tostring(cellutil.clonefamily(c))) && qualifier.equals(bytes.tostring(cellutil.clonequalifier(c)))) { hbase1value = Bytes.toString(CellUtil.cloneValue(c)) val hbase2value = Bytes.toString(resultData.getValue(columnFamily.getBytes, qualifier.getbytes)) val put = new Put(iteratorArray(i)._2.getRow) // 计算结果 val resultvalue = hbase1value.toint + hbase2value.toint // 设置结果到 put 对象 put.addcolumn(bytes.tobytes(columnfamily), Bytes.toBytes(qualifier), Bytes.toBytes(resultValue.toString)) putlist.add(put) if (putlist.size() > 0) { table.put(putlist) catch { case e: IOException => e.printstacktrace(); finally { if (table!= null) { try { table.close() catch { case e: IOException => e.printstacktrace(); if (connection!= null) { try { // 关闭 Hbase 连接 connection.close() catch { case e: IOException => e.printstacktrace() private def printusage { System.out.println("Usage: {zkquorum") System.exit(1) 文档版本 01 ( ) 289

300 7 Spark 应用开发 /** * 序列化辅助类 */ class MyRegistrator extends KryoRegistrator { override def registerclasses(kryo: Kryo) { kryo.register(classof[org.apache.hadoop.hbase.io.immutablebyteswritable]) kryo.register(classof[org.apache.hadoop.hbase.client.result]) kryo.register(classof[array[(any, Any)]]) kryo.register(classof[array[org.apache.hadoop.hbase.cell]]) kryo.register(classof[org.apache.hadoop.hbase.notagskeyvalue]) kryo.register(classof[org.apache.hadoop.hbase.protobuf.generated.clientprotos.regionloadstats]) 从 Hive 读取数据再写入 HBase 场景说明 场景说明 数据规划 假定 Hive 的 person 表存储用户当天消费的金额信息,HBase 的 table2 表存储用户历史消费的金额信息 现 person 表有记录 name=1,account=100, 表示用户 1 在当天消费金额为 100 元 table2 表有记录 key=1,cf:cid=1000, 表示用户 1 的历史消息记录金额为 1000 元 基于某些业务要求, 要求开发 Spark 应用程序实现如下功能 : 根据用户名累计用户的历史消费金额, 即用户总消费金额 =100( 用户当天的消费金额 ) ( 用户历史消费金额 ) 上例所示, 运行结果 table2 表用户 key=1 的总消费金融为 cf:cid=1100 元 在开始开发应用前, 需要创建 Hive 表, 命名为 person, 并插入数据 同时, 创建 HBase table2 表, 用于将分析后的数据写入 步骤 1 步骤 2 将原日志文件放置到 HDFS 系统中 1. 在本地新建一个空白的 log1.txt 文件, 并在文件内写入如下内容 : 1, 在 HDFS 中新建一个目录 /tmp/input, 并将 log1.txt 文件上传至此目录 a. 在 HDFS 客户端, 执行如下命令获取安全认证 cd /opt/hadoopclient kinit < 用于认证的业务用户 > b. 在 Linux 系统 HDFS 客户端使用命令 hadoop fs -mkdir /tmp/input(hdfs dfs 命令有同样的作用 ), 创建对应目录 c. 在 Linux 系统 HDFS 客户端使用命令 hadoop fs -put log1.txt /tmp/input, 上传数据文件 将导入的数据放置在 Hive 表里 首先, 确保 Thrift Server 已启动 然后使用 Beeline 工具, 创建 Hive 表, 并插入数据 文档版本 01 ( ) 290

301 7 Spark 应用开发 1. 执行如下命令, 创建命名为 person 的 Hive 表 create table person ( name STRING, account INT )ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' ESCAPED BY '\\' STORED AS TEXTFILE; 2. 执行如下命令插入数据 步骤 3 创建 HBase 表 load data inpath '/tmp/input/log1.txt' into table person; 确保 Thrift Server 已启动, 然后使用 Beeline 工具, 创建 HBase 表, 并插入数据 1. 执行如下命令, 创建命名为 table2 的 HBase 表 create table table2 ( key string, cid string ) using org.apache.spark.sql.hbase.hbasesource options( hbasetablename "table2", keycols "key", colsmapping "cid=cf.cid" ); 2. 通过 HBase 插入数据, 执行如下命令 put 'table2', '1', 'cf:cid', '1000' 说明 如果开启了 kerberos 认证, 需要将客户端的配置文件 spark-default.conf 和 sparkjdbc 服务端中的配置项 spark.yarn.security.credentials.hbase.enabled 置为 true ---- 结束 开发思路 1. 查询 Hive person 表的数据 2. 根据 person 表数据的 key 值去 table2 表做查询 3. 把前两步相应的数据记录做相加操作 4. 把上一步骤的结果写到 table2 表 文档版本 01 ( ) 291

302 7 Spark 应用开发 Java 样例代码 功能介绍 代码样例 在 Spark 应用中, 通过使用 Spark 调用 Hive 接口来操作 hive 表, 然后把 Hive 表的数据经过分析后写到 HBase 表 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.sparkhivetohbase /** * 从 hive 表读取数据, 根据 key 值去 hbase 表获取相应记录, 把两者数据做操作后, 更新到 hbase 表 */ public class SparkHivetoHbase { public static void main(string[] args) throws Exception { if (args.length < 1) { printusage(); // 通过 spark 接口获取表中的数据 SparkConf conf = new SparkConf().setAppName("SparkHivetoHbase"); JavaSparkContext jsc = new JavaSparkContext(conf); HiveContext sqlcontext = new org.apache.spark.sql.hive.hivecontext(jsc); DataFrame dataframe = sqlcontext.sql("select name, account from person"); // 遍历 hive 表中的每一个 partition, 然后更新到 hbase 表 // 如果数据条数较少, 也可以使用 foreach() 方法 final String zkquorum = args[0]; dataframe.tojavardd().foreachpartition( new VoidFunction<Iterator<Row>>() { public void call(iterator<row> iterator) throws Exception { hbasewriter(iterator,zkquorum); ); jsc.stop(); /** * 在 executor 端更新 hbase 表记录 * iterator hive 表的 partition 数据 */ private static void hbasewriter(iterator<row> iterator, String zkquorum) throws IOException { // 读取 hbase String tablename = "table2"; String columnfamily = "cf"; Configuration conf = HBaseConfiguration.create(); conf.set("hbase.zookeeper.property.clientport", "24002"); conf.set("hbase.zookeeper.quorum", zkquorum); Connection connection = null; Table table = null; try { connection = ConnectionFactory.createConnection(conf); table = connection.gettable(tablename.valueof(tablename)); List<Row> table1list = new ArrayList<Row>(); List<Get> rowlist = new ArrayList<Get>(); while (iterator.hasnext()) { Row item = iterator.next(); Get get = new Get(item.getString(0).getBytes()); table1list.add(item); rowlist.add(get); 文档版本 01 ( ) 292

303 7 Spark 应用开发 // 获取 hbase 表记录 Result[] resultdatabuffer = table.get(rowlist); // 修改 hbase 表记录 List<Put> putlist = new ArrayList<Put>(); for (int i = 0; i < resultdatabuffer.length; i++) { // hive 表值 Result resultdata = resultdatabuffer[i]; if (!resultdata.isempty()) { // get hivevalue int hivevalue = table1list.get(i).getint(1); // 根据列簇和列, 获取 hbase 值 String hbasevalue = Bytes.toString(resultData.getValue(columnFamily.getBytes(), "cid".getbytes())); Put put = new Put(table1List.get(i).getString(0).getBytes()); // 计算结果 int resultvalue = hivevalue + Integer.valueOf(hbaseValue); // 设置结果到 put 对象 put.addcolumn(bytes.tobytes(columnfamily), Bytes.toBytes("cid"), Bytes.toBytes(String.valueOf(resultValue))); putlist.add(put); if (putlist.size() > 0) { table.put(putlist); catch (IOException e) { e.printstacktrace(); finally { if (table!= null) { try { table.close(); catch (IOException e) { e.printstacktrace(); if (connection!= null) { try { // 关闭 Hbase 连接. connection.close(); catch (IOException e) { e.printstacktrace(); private static void printusage() { System.out.println("Usage: {zkquorum"); System.exit(1); Scala 样例代码 功能介绍 代码样例 在 Spark 应用中, 通过使用 Spark 调用 Hive 接口来操作 hive 表, 然后把 Hive 表的数据经过分析后写到 HBase 表 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.sparkhivetohbase 文档版本 01 ( ) 293

304 7 Spark 应用开发 /** * 从 hive 表读取数据, 根据 key 值去 hbase 表获取相应记录, 把两者数据做操作后, 更新到 hbase 表 */ object SparkHivetoHbase { case class FemaleInfo(name: String, gender: String, staytime: Int) def main(args: Array[String]) { if (args.length < 1) { printusage // 通过 spark 接口获取表中的数据 val sparkconf = new SparkConf().setAppName("SparkHivetoHbase") val sc = new SparkContext(sparkConf) val sqlcontext = new org.apache.spark.sql.hive.hivecontext(sc) import sqlcontext.implicits._ val dataframe = sqlcontext.sql("select name, account from person") // 遍历 hive 表中的每一个 partition, 然后更新到 hbase 表 // 如果数据条数较少, 也可以使用 foreach() 方法 dataframe.rdd.foreachpartition(x => hbasewriter(x, args(0))) sc.stop() /** * 在 executor 端更新 hbase 表记录 * iterator hive 表的 partition 数据 */ def hbasewriter(iterator: Iterator[Row], zkquorum: String): Unit = { // 读取 hbase val tablename = "table2" val columnfamily = "cf" val conf = HBaseConfiguration.create() conf.set("hbase.zookeeper.property.clientport", "24002") conf.set("hbase.zookeeper.quorum", zkquorum) var table: Table = null var connection: Connection = null try { connection = ConnectionFactory.createConnection(conf) table = connection.gettable(tablename.valueof(tablename)) val iteratorarray = iterator.toarray val rowlist = new util.arraylist[get]() for (row <- iteratorarray) { val get = new Get(row.getString(0).getBytes) rowlist.add(get) // 获取 hbase 表记录 val resultdatabuffer = table.get(rowlist) // 修改 hbase 表记录 val putlist = new util.arraylist[put]() for (i <- 0 until iteratorarray.size) { // hbase row val resultdata = resultdatabuffer(i) if (!resultdata.isempty) { // hive 表值 var hivevalue = iteratorarray(i).getint(1) // 根据列簇和列, 获取 hbase 值 val hbasevalue = Bytes.toString(resultData.getValue(columnFamily.getBytes, "cid".getbytes)) val put = new Put(iteratorArray(i).getString(0).getBytes) // 计算结果 val resultvalue = hivevalue + hbasevalue.toint // 设置结果到 put 对象 put.addcolumn(bytes.tobytes(columnfamily), Bytes.toBytes("cid"), Bytes.toBytes(resultValue.toString)) putlist.add(put) if (putlist.size() > 0) { table.put(putlist) catch { 文档版本 01 ( ) 294

305 7 Spark 应用开发 case e: IOException => e.printstacktrace(); finally { if (table!= null) { try { table.close() catch { case e: IOException => e.printstacktrace(); if (connection!= null) { try { // 关闭 Hbase 连接. connection.close() catch { case e: IOException => e.printstacktrace() private def printusage { System.out.println("Usage: {zkquorum") System.exit(1) Streaming 从 Kafka 读取数据再写入 HBase 场景说明 场景说明 数据规划 假定某个业务 Kafka 每 30 秒就会收到 5 个用户的消费记录 Hbase 的 table1 表存储用户历史消费的金额信息 现 table1 表有 10 条记录, 表示有用户名分别为 1-10 的用户, 他们的历史消费金额初始化都是 0 元 基于某些业务要求, 开发的 Spark 应用程序实现如下功能 : 实时累加计算用户的消费金额信息 : 即用户总消费金额 = 用户的消费金额 (kafka 数据 ) + 用户历史消费金额 (table1 表的值 ), 更新到 table1 表 步骤 1 创建 HBase 表, 并插入数据 确保 Thrift Server 已启动, 在 Spark 客户端使用 Beeline 工具, 创建 table1 表 1. 执行如下命令, 创建命名为 table1 的 HBase 表 create table table1 ( key string, cid string ) using org.apache.spark.sql.hbase.hbasesource 文档版本 01 ( ) 295

306 7 Spark 应用开发 options( hbasetablename "table1", keycols "key", colsmapping "cid=cf.cid" ); 2. 通过 HBase 执行如下命令, 将数据插入 table1 表中 put 'table1', '1', 'cf:cid', '0' put 'table1', '2', 'cf:cid', '0' put 'table1', '3', 'cf:cid', '0' put 'table1', '4', 'cf:cid', '0' put 'table1', '5', 'cf:cid', '0' put 'table1', '6', 'cf:cid', '0' put 'table1', '7', 'cf:cid', '0' put 'table1', '8', 'cf:cid', '0' put 'table1', '9', 'cf:cid', '0' put 'table1', '10', 'cf:cid', '0' 步骤 2 Spark Streaming 样例工程的数据存储在 Kafka 中 1. 确保集群安装完成, 包括 HDFS Yarn Spark 2. 将 kafka 的 Broker 配置参数 allow.everyone.if.no.acl.found 的值修改为 true 3. 创建 Topic {zkquorum 表示 ZooKeeper 集群信息, 格式为 IP:port $KAFKA_HOME/bin/kafka-topics.sh --create --zookeeper {zkquorum/kafka -- replication-factor 1 --partitions 3 --topic {Topic 4. 启动样例代码的 Producer, 向 Kafka 发送数据 {ClassPath 表示工程 jar 包的存放路径, 详细路径由用户指定, 可参考编包并运行程序章节中导出 jar 包的操作步骤 java -cp $SPARK_HOME/lib/*:$SPARK_HOME/lib/streamingClient/*:{ClassPath com.huawei.bigdata.spark.examples.streaming.streamingexampleproducer {BrokerList {Topic 说明 如果开启了 kerberos 认证, 需要将客户端的配置文件 spark-default.conf 和 sparkjdbc 服务端中的配置项 spark.yarn.security.credentials.hbase.enabled 置为 true ---- 结束 开发思路 1. 接收 Kafka 中数据, 生成相应 DStream 2. 筛选数据信息并分析 3. 找到对应的 HBase 表记录 4. 计算结果, 写到 HBase 表 Java 样例代码 功能介绍 在 Spark 应用中, 通过使用 Streaming 调用 kafka 接口来获取数据, 然后把数据经过分析后, 找到对应的 HBase 表记录, 再写到 HBase 表 文档版本 01 ( ) 296

307 7 Spark 应用开发 代码样例 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.sparkonstreamingtohbase /** * 运行 streaming 任务, 根据 value 值从 hbase table1 表读取数据, 把两者数据做操作后, 更新到 hbase table1 表 */ public class SparkOnStreamingToHbase { public static void main(string[] args) throws Exception { if (args.length < 4) { printusage(); String checkpointdir = args[0]; String topics = args[1]; final String brokers = args[2]; final String zkquorum = args[3]; Duration batchduration = Durations.seconds(5); SparkConf sparkconf = new SparkConf().setAppName("SparkOnStreamingToHbase"); JavaStreamingContext jssc = new JavaStreamingContext(sparkConf, batchduration); // 设置 Streaming 的 CheckPoint 目录 if (!"nocp".equals(checkpointdir)) { jssc.checkpoint(checkpointdir); final String columnfamily = "cf"; final String zkclientport = "24002"; HashMap<String, String> kafkaparams = new HashMap<String, String>(); kafkaparams.put("metadata.broker.list", brokers); String[] topicarr = topics.split(","); Set<String> topicset = new HashSet<String>(Arrays.asList(topicArr)); // 通过 brokers 和 topics 直接创建 kafka stream // 接收 Kafka 中数据, 生成相应 DStream JavaDStream<String> lines = KafkaUtils.createDirectStream(jssc, String.class, String.class, StringDecoder.class, StringDecoder.class, kafkaparams, topicset).map( new Function<Tuple2<String, String>, String>() { public String call(tuple2<string, String> tuple2) { // map(_._1) 是消息的 key, map(_._2) 是消息的 value return tuple2._2(); ); lines.foreachrdd( new Function<JavaRDD<String>, Void>() { public Void call(javardd<string> rdd) throws Exception { rdd.foreachpartition( new VoidFunction<Iterator<String>>() { public void call(iterator<string> iterator) throws Exception { hbasewriter(iterator, zkclientport, zkquorum, columnfamily); ); return null; ); jssc.start(); jssc.awaittermination(); /** * 在 executor 端写入数据 文档版本 01 ( ) 297

308 7 Spark 应用开发 iterator 消息 zkclientport zkquorum columnfamily */ private static void hbasewriter(iterator<string> iterator, String zkclientport, String zkquorum, String columnfamily) throws IOException { Configuration conf = HBaseConfiguration.create(); conf.set("hbase.zookeeper.property.clientport", zkclientport); conf.set("hbase.zookeeper.quorum", zkquorum); Connection connection = null; Table table = null; try { connection = ConnectionFactory.createConnection(conf); table = connection.gettable(tablename.valueof("table1")); List<Get> rowlist = new ArrayList<Get>(); while (iterator.hasnext()) { Get get = new Get(iterator.next().getBytes()); rowlist.add(get); // 获取 table1 的数据 Result[] resultdatabuffer = table.get(rowlist); // 设置 table1 的数据 List<Put> putlist = new ArrayList<Put>(); for (int i = 0; i < resultdatabuffer.length; i++) { String row = new String(rowList.get(i).getRow()); Result resultdata = resultdatabuffer[i]; if (!resultdata.isempty()) { // 根据列簇和列, 获取旧值 String acid = Bytes.toString(resultData.getValue(columnFamily.getBytes(), "cid".getbytes())); Put put = new Put(Bytes.toBytes(row)); // 计算结果 int resultvalue = Integer.valueOf(row) + Integer.valueOf(aCid); put.addcolumn(bytes.tobytes(columnfamily), Bytes.toBytes("cid"), Bytes.toBytes(String.valueOf(resultValue))); putlist.add(put); if (putlist.size() > 0) { table.put(putlist); catch (IOException e) { e.printstacktrace(); finally { if (table!= null) { try { table.close(); catch (IOException e) { e.printstacktrace(); if (connection!= null) { try { // 关闭 Hbase 连接. connection.close(); catch (IOException e) { e.printstacktrace(); private static void printusage() { System.out.println("Usage: {checkpointdir {topic {brokerlist {zkquorum"); System.exit(1); 文档版本 01 ( ) 298

309 7 Spark 应用开发 Scala 样例代码 功能介绍 代码样例 在 Spark 应用中, 通过使用 Streaming 调用 kafka 接口来获取数据, 然后把数据经过分析后, 找到对应的 HBase 表记录, 再写到 HBase 表 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.sparkonstreamingtohbase /** * 运行 streaming 任务, 根据 value 值从 hbase table1 表读取数据, 把两者数据做操作后, 更新到 hbase table1 表 */ object SparkOnStreamingToHbase { def main(args: Array[String]) { if (args.length < 4) { printusage val Array(checkPointDir, topics, brokers, zkquorum) = args val sparkconf = new SparkConf().setAppName("DirectStreamToHbase") val ssc = new StreamingContext(sparkConf, Seconds(5)) // 设置 Streaming 的 CheckPoint 目录 if (!"nocp".equals(checkpointdir)) { ssc.checkpoint(checkpointdir) val columnfamily = "cf" val zkclientport = "24002" val kafkaparams = Map[String, String]( "metadata.broker.list" -> brokers ) val topicarr = topics.split(",") val topicset = topicarr.toset // map(_._1) 是消息的 key, map(_._2) 是消息的 value val lines = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaparams, topicset).map(_._2) lines.foreachrdd(rdd => { //partition 运行在 executor 上 rdd.foreachpartition(iterator => hbasewriter(iterator, zkclientport, zkquorum, columnfamily)) ) ssc.start() ssc.awaittermination() /** * 在 executor 端写入数据 iterator 消息 zkclientport zkquorum columnfamily */ def hbasewriter(iterator: Iterator[String], zkclientport: String, zkquorum: String, columnfamily: String): Unit = { val conf = HBaseConfiguration.create() conf.set("hbase.zookeeper.property.clientport", zkclientport) conf.set("hbase.zookeeper.quorum", zkquorum) var table: Table = null var connection: Connection = null 文档版本 01 ( ) 299

310 7 Spark 应用开发 try { connection = ConnectionFactory.createConnection(conf) table = connection.gettable(tablename.valueof("table1")) val iteratorarray = iterator.toarray val rowlist = new util.arraylist[get]() for (row <- iteratorarray) { val get = new Get(row.getBytes) rowlist.add(get) // 获取 table1 的数据 val resultdatabuffer = table.get(rowlist) // 设置 table1 的数据 val putlist = new util.arraylist[put]() for (i <- 0 until iteratorarray.size) { val row = iteratorarray(i) val resultdata = resultdatabuffer(i) if (!resultdata.isempty) { // 根据列簇和列, 获取旧值 val acid = Bytes.toString(resultData.getValue(columnFamily.getBytes, "cid".getbytes)) val put = new Put(Bytes.toBytes(row)) // 计算结果 val resultvalue = row.toint + acid.toint put.addcolumn(bytes.tobytes(columnfamily), Bytes.toBytes("cid"), Bytes.toBytes(resultValue.toString)) putlist.add(put) if (putlist.size() > 0) { table.put(putlist) catch { case e: IOException => e.printstacktrace(); finally { if (table!= null) { try { table.close() catch { case e: IOException => e.printstacktrace(); if (connection!= null) { try { // 关闭 Hbase 连接. connection.close() catch { case e: IOException => e.printstacktrace() private def printusage { System.out.println("Usage: {checkpointdir {topic {brokerlist {zkquorum") System.exit(1) Spark Streaming 对接 kafka0-10 程序 文档版本 01 ( ) 300

311 7 Spark 应用开发 场景说明 场景说明 假定某个业务 Kafka 每 1 秒就会收到 1 个单词记录 基于某些业务要求, 开发的 Spark 应用程序实现如下功能 : 实时累加计算每个单词的记录总数 log1.txt 示例文件 : LiuYang YuanJing GuoYijun CaiXuyu Liyuan FangBo LiuYang YuanJing GuoYijun CaiXuyu FangBo 数据规划 Spark Streaming 样例工程的数据存储在 Kafka 组件中 向 Kafka 组件发送数据 ( 需要有 kafka 权限用户 ) 1. 确保集群安装完成, 包括 HDFS Yarn Spark 和 Kafka 2. 本地新建文件 input_data1.txt, 将 log1.txt 的内容复制保存到 input_data1.txt 在客户端安装节点下创建文件目录 : /home/data 将上述文件上传到此 / home/data 目录下 3. 将 kafka 的 Broker 配置参数 allow.everyone.if.no.acl.found 的值修改为 true 4. 创建 Topic {zkquorum 表示 ZooKeeper 集群信息, 格式为 IP:port $KAFKA_HOME/bin/kafka-topics.sh --create --zookeeper {zkquorum/kafka -- replication-factor 1 --partitions 3 --topic {Topic 5. 启动 Kafka 的 Producer, 向 Kafka 发送数据 java -cp {ClassPath com.huawei.bigdata.spark.examples.streamingexampleproducer {BrokerList {Topic 文档版本 01 ( ) 301

312 7 Spark 应用开发 说明 若用户需要对接安全 Kafka, 则还需要在 spark 客户端的 conf 目录下的 jaas.conf 文件中增加 KafkaClient 的配置信息, 示例如下 : KafkaClient { com.sun.security.auth.module.krb5loginmodule required usekeytab=true keytab = "./user.keytab" principal="leob@hadoop.com" useticketcache=false storekey=true debug=true; ; 在 Spark on YARN 模式下,jaas.conf 和 user.keytab 通过 YARN 分发到 Spark on YARN 的 container 目录下, 因此 KafkaClient 中对于 keytab 的配置路径必须为相对 jaas.conf 的所在路径, 例如./user.keytab 开发思路 1. 接收 Kafka 中数据, 生成相应 DStream 2. 对单词记录进行分类统计 3. 计算结果, 并进行打印 Java 样例代码 功能介绍 在 Spark 应用中, 通过使用 Streaming 调用 kafka 接口来获取单词记录, 然后把单词记录分类统计, 得到每个单词记录数, 或将数据写入 Kafka0-10 Streaming 读取 Kafka0-10 代码样例 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.securitykafkawordcount /** * 从 Kafka 的一个或多个主题消息 * <checkpointdir> 是 Spark Streaming 检查点目录 * <brokers> 是用于自举, 制作人只会使用它来获取元数据 * <topics> 是要消费的一个或多个 kafka 主题的列表 * <batchtime> 是 Spark Streaming 批次持续时间 ( 以秒为单位 ) */ public class SecurityKafkaWordCount { public static void main(string[] args) throws Exception { JavaStreamingContext ssc = createcontext(args); // 启动 Streaming 系统 ssc.start(); try { ssc.awaittermination(); catch (InterruptedException e) { private static JavaStreamingContext createcontext(string[] args) throws Exception { String checkpointdir = args[0]; String brokers = args[1]; String topics = args[2]; String batchsize = args[3]; // 新建一个 Streaming 启动环境. 文档版本 01 ( ) 302

313 7 Spark 应用开发 SparkConf sparkconf = new SparkConf().setAppName("KafkaWordCount"); JavaStreamingContext ssc = new JavaStreamingContext(sparkConf, new Duration(Long.parseLong(batchSize) * 1000)); // 配置 Streaming 的 CheckPoint 目录 // 由于窗口概念的存在, 此参数是必需的 ssc.checkpoint(checkpointdir); // 获取获取 kafka 使用的 topic 列表 String[] topicarr = topics.split(","); Set<String> topicset = new HashSet<String>(Arrays.asList(topicArr)); Map<String, Object> kafkaparams = new HashMap(); kafkaparams.put("bootstrap.servers", brokers); kafkaparams.put("value.deserializer", "org.apache.kafka.common.serialization.stringdeserializer"); kafkaparams.put("key.deserializer", "org.apache.kafka.common.serialization.stringdeserializer"); kafkaparams.put("group.id", "DemoConsumer"); kafkaparams.put("security.protocol", "SASL_PLAINTEXT"); kafkaparams.put("sasl.kerberos.service.name", "kafka"); kafkaparams.put("kerberos.domain.name", "hadoop.hadoop.com"); LocationStrategy locationstrategy = LocationStrategies.PreferConsistent(); ConsumerStrategy consumerstrategy = ConsumerStrategies.Subscribe(topicSet, kafkaparams); // 用 brokers and topics 新建 direct kafka stream // 从 Kafka 接收数据并生成相应的 DStream JavaInputDStream<ConsumerRecord<String, String>> messages = KafkaUtils.createDirectStream(ssc, locationstrategy, consumerstrategy); // 获取每行中的字段属性 JavaDStream<String> lines = messages.map(new Function<ConsumerRecord<String, String>, String>() public String call(consumerrecord<string, String> tuple2) throws Exception { return tuple2.value(); ); // 汇总计算字数的总时间 JavaPairDStream<String, Integer> wordcounts = lines.maptopair( new PairFunction<String, String, Integer>() public Tuple2<String, Integer> call(string s) { return new Tuple2<String, Integer>(s, 1); ).reducebykey(new Function2<Integer, Integer, Integer>() public Integer call(integer i1, Integer i2) { return i1 + i2; ).updatestatebykey( new Function2<List<Integer>, Optional<Integer>, Optional<Integer>>() public Optional<Integer> call(list<integer> values, Optional<Integer> state) { int out = 0; if (state.ispresent()) { out += state.get(); for (Integer v : values) { out += v; return Optional.of(out); ); // 打印结果 wordcounts.print(); return ssc; 文档版本 01 ( ) 303

314 7 Spark 应用开发 Streaming Write To Kafka 0-10 样例代码 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.dstreamkafkawriter 说明 建议使用新的 API createdirectstream 代替旧的 API createstream 进行应用程序开发 旧的 API 仍然可以使用, 但新的 API 性能和稳定性更好 /** * 参数解析 : * <checkpointdir> 为 checkpoint 目录 * <topics> 为 Kafka 中订阅的主题, 多以逗号分隔 * <brokers> 为获取元数据的 Kafka 地址 */ public class JavaDstreamKafkaWriter { public static void main(string[] args) throws InterruptedException { if (args.length!= 4) { System.err.println("Usage: DstreamKafkaWriter <checkpointdir> <brokers> <topic>"); System.exit(1); String checkpointdir = args[0]; String brokers = args[1]; String topic = args[2]; SparkConf sparkconf = new SparkConf().setAppName("KafkaWriter"); // 填写 Kafka 的 properties Map kafkaparams = new HashMap<String, Object>(); kafkaparams.put("zookeeper.connect", brokers); kafkaparams.put("metadata.broker.list", brokers); kafkaparams.put("group.id", "dstreamkafkawriterft08"); kafkaparams.put("auto.offset.reset", "smallest"); // 创建 Java Spark Streaming 的 Context JavaStreamingContext ssc = new JavaStreamingContext(sparkConf, Durations.milliseconds(500)); // 填写写入 Kafka 的数据 List<String> sentdata = new ArrayList<String>(); sentdata.add("kafka_writer_test_msg_01"); sentdata.add("kafka_writer_test_msg_02"); sentdata.add("kafka_writer_test_msg_03"); // 创建 Java RDD 队列 Queue<JavaRDD<String>> sent = new LinkedList(); sent.add(ssc.sparkcontext().parallelize(sentdata)); // 创建写数据的 Java DStream JavaDStream wstream = ssc.queuestream(sent); // 写入 Kafka JavaDStreamKafkaWriterFactory.fromJavaDStream(wStream).writeToKafka(JavaConverters.mapAsScalaMapCon verter(kafkaparams), new Function<String, ProducerRecord<String, byte[]>>() { public ProducerRecord<String, byte[]> call(string s) { return new ProducerRecord(topic, s.getbytes()); ); ssc.start(); ssc.awaittermination(); 文档版本 01 ( ) 304

315 7 Spark 应用开发 Scala 样例代码 功能介绍 在 Spark 应用中, 通过使用 Streaming 调用 kafka 接口来获取单词记录, 然后把单词记录分类统计, 得到每个单词记录数, 或将数据写入 Kafka0-10 Streaming 读取 Kafka0-10 代码样例 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.securitykafkawordcount /** * 从 Kafka 的一个或多个主题消息 * <checkpointdir> 是 Spark Streaming 检查点目录 * <brokers> 是用于自举, 制作人只会使用它来获取元数据 * <topics> 是要消费的一个或多个 kafka 主题的列表 * <batchtime> 是 Spark Streaming 批次持续时间 ( 以秒为单位 ) */ object SecurityKafkaWordCount { def main(args: Array[String]) { val ssc = createcontext(args) // 启动 Streaming 系统 ssc.start() ssc.awaittermination() def createcontext(args : Array[String]) : StreamingContext = { val Array(checkPointDir, brokers, topics, batchsize) = args // 新建一个 Streaming 启动环境 val sparkconf = new SparkConf().setAppName("KafkaWordCount") val ssc = new StreamingContext(sparkConf, Seconds(batchSize.toLong)) // 配置 Streaming 的 CheckPoint 目录 // 由于窗口概念的存在, 此参数是必需的 ssc.checkpoint(checkpointdir) // 获取获取 kafka 使用的 topic 列表 val topicarr = topics.split(",") val topicset = topicarr.toset val kafkaparams = Map[String, String]( "bootstrap.servers" -> brokers, "value.deserializer" -> "org.apache.kafka.common.serialization.stringdeserializer", "key.deserializer" -> "org.apache.kafka.common.serialization.stringdeserializer", "group.id" -> "DemoConsumer", "security.protocol" -> "SASL_PLAINTEXT", "sasl.kerberos.service.name" -> "kafka", "kerberos.domain.name" -> "hadoop.hadoop.com" ); val locationstrategy = LocationStrategies.PreferConsistent val consumerstrategy = ConsumerStrategies.Subscribe[String, String](topicSet, kafkaparams) // 用 brokers and topics 新建 direct kafka stream // 从 Kafka 接收数据并生成相应的 DStream val stream = KafkaUtils.createDirectStream[String, String](ssc, locationstrategy, consumerstrategy) // 获取每行中的字段属性 val tf = stream.transform ( rdd => 文档版本 01 ( ) 305

316 7 Spark 应用开发 rdd.map(r => (r.value, 1L)) ) // 汇总计算字数的总时间 val wordcounts = tf.reducebykey(_ + _) val totalcounts = wordcounts.updatestatebykey(updatafunc) totalcounts.print() ssc def updatafunc(values : Seq[Long], state : Option[Long]) : Option[Long] = Some(values.sum + state.getorelse(0l)) Streaming Write To Kafka 0-10 样例代码 下面代码片段仅为演示, 具体代码参见 com.huawei.bigdata.spark.examples.dstreamkafkawriter 说明 建议使用新的 API createdirectstream 代替原有 API createstream 进行应用程序开发 原有 API 仍然可以使用, 但新的 API 性能和稳定性更好 /** * 参数解析 : * <checkpointdir> 为 checkpoint 目录 * <topics> 为 Kafka 中订阅的主题, 多以逗号分隔 * <brokers> 为获取元数据的 Kafka 地址 */ object DstreamKafkaWriterTest1 { def main(args: Array[String]) { if (args.length!= 4) { System.err.println("Usage: DstreamKafkaWriterTest <checkpointdir> <brokers> <topic>") System.exit(1) val Array(checkPointDir, brokers, topic) = args val sparkconf = new SparkConf().setAppName("KafkaWriter") // 填写 Kafka 的 properties val kafkaparams = Map[String, String]( "bootstrap.servers" -> brokers, "value.deserializer" -> "org.apache.kafka.common.serialization.stringdeserializer", "key.deserializer" -> "org.apache.kafka.common.serialization.stringdeserializer", "value.serializer" -> "org.apache.kafka.common.serialization.bytearrayserializer", "key.serializer" -> "org.apache.kafka.common.serialization.stringserializer", "group.id" -> "dstreamkafkawriterft", "auto.offset.reset" -> "latest" ) // 创建 Streaming 的 context val ssc = new StreamingContext(sparkConf, Milliseconds(500)); val sentdata = Seq("kafka_writer_test_msg_01", "kafka_writer_test_msg_02", "kafka_writer_test_msg_03") // 创建 RDD 队列 val sent = new mutable.queue[rdd[string]]() sent.enqueue(ssc.sparkcontext.makerdd(sentdata)) // 创建写数据的 DStream val wstream = ssc.queuestream(sent) // 使用 writetokafka API 把数据写入 Kafka wstream.writetokafka(kafkaparams, (x: String) => new ProducerRecord[String, Array[Byte]](topic, x.getbytes)) // 启动 streaming 的 context 文档版本 01 ( ) 306

317 7 Spark 应用开发 ssc.start() ssc.awaittermination() Structured Streaming 程序 场景说明 场景说明 数据规划 在 Spark 应用中, 通过使用 StructuredStreaming 调用 kafka 接口来获取单词记录, 然后把单词记录分类统计, 得到每个单词记录数 StructuredStreaming 样例工程的数据存储在 Kafka 组件中 向 Kafka 组件发送数据 ( 需要有 kafka 权限用户 ) 1. 确保集群安装完成, 包括 HDFS Yarn Spark 和 Kafka 2. 将 kafka 的 Broker 配置参数 allow.everyone.if.no.acl.found 的值修改为 true 3. 创建 Topic {zkquorum 表示 ZooKeeper 集群信息, 格式为 IP:port $KAFKA_HOME/bin/kafka-topics.sh --create --zookeeper {zkquorum/kafka -- replication-factor 1 --partitions 1 --topic {Topic 4. 启动 Kafka 的 Producer, 向 Kafka 发送数据 {ClassPath 表示工程 jar 包的存放路径, 详细路径由用户指定, 可参考编包并运行程序 java -cp $SPARK_HOME/jars/*:$SPARK_HOME/jars/streamingClient010/*: {ClassPath com.huawei.bigdata.spark.examples.kafkawordcountproducer {BrokerList {Topic {messagespersec {wordspermessage 说明 若用户需要对接安全 Kafka, 则还需要在 spark 客户端的 conf 目录下的 jaas.conf 文件中增加 KafkaClient 的配置信息, 示例如下 : KafkaClient { com.sun.security.auth.module.krb5loginmodule required usekeytab=true keytab = "./user.keytab" principal="leob@hadoop.com" useticketcache=false storekey=true debug=true; ; 在 Spark on YARN 模式下,jaas.conf 和 user.keytab 通过 YARN 分发到 Spark on YARN 的 container 目录下, 因此 KafkaClient 中对于 keytab 的配置路径必须为相对 jaas.conf 的所在路径, 例如./user.keytab 开发思路 1. 接收 Kafka 中数据, 生成相应 DataStreamReader 2. 对单词记录进行分类统计 文档版本 01 ( ) 307

318 7 Spark 应用开发 3. 计算结果, 并进行打印 Java 样例代码 功能介绍 代码样例 在 Spark 应用中, 通过使用 StructuredStreaming 调用 kafka 接口来获取单词记录, 然后把单词记录分类统计, 得到每个单词记录数 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.securitykafkawordcount 文档版本 01 ( ) 308

319 7 Spark 应用开发 说明 当 Streaming DataFrame/Dataset 中有新的可用数据时,outputMode 用于配置写入 Streaming 接收器的数据 其默认值为 append public class SecurityKafkaWordCount { public static void main(string[] args) throws Exception { if (args.length < 6) { System.err.println("Usage: SecurityKafkaWordCount <bootstrap-servers> " + "<subscribe-type> <topics> <protocol> <service> <domain>"); System.exit(1); String bootstrapservers = args[0]; String subscribetype = args[1]; String topics = args[2]; String protocol = args[3]; String service = args[4]; String domain = args[5]; SparkSession spark = SparkSession.builder().appName("SecurityKafkaWordCount").getOrCreate(); // 创建表示来自 kafka 的输入行流的 DataSet Dataset<String> lines = spark.readstream().format("kafka").option("kafka.bootstrap.servers", bootstrapservers).option(subscribetype, topics).option("kafka.security.protocol", protocol).option("kafka.sasl.kerberos.service.name", service).option("kafka.kerberos.domain.name", domain).load().selectexpr("cast(value AS STRING)").as(Encoders.STRING()); // 生成运行字数 Dataset<Row> wordcounts = lines.flatmap(new FlatMapFunction<String, String>() public Iterator<String> call(string x) { return Arrays.asList(x.split(" ")).iterator();, Encoders.STRING()).groupBy("value").count(); // 开始运行将运行计数打印到控制台的查询 StreamingQuery query = wordcounts.writestream().outputmode("complete").format("console").start(); query.awaittermination(); Scala 样例代码 功能介绍 在 Spark 应用中, 通过使用 StructuredStreaming 调用 kafka 接口来获取单词记录, 然后把单词记录分类统计, 得到每个单词记录数 文档版本 01 ( ) 309

320 7 Spark 应用开发 代码样例 下面代码片段仅为演示, 具体代码参见 : com.huawei.bigdata.spark.examples.securitykafkawordcount 说明 当 Streaming DataFrame/Dataset 中有新的可用数据时,outputMode 用于配置写入 Streaming 接收器的数据 其默认值为 append, 若需要更换输出方式, 参见 DataSight Spark V100R002CXX Spark2.1 API Reference 中 outputmode 的描述 object SecurityKafkaWordCount { def main(args: Array[String]): Unit = { if (args.length < 6) { System.err.println("Usage: SecurityKafkaWordCount <bootstrap-servers> " + "<subscribe-type> <topics> <protocol> <service> <domain>") System.exit(1) val Array(bootstrapServers, subscribetype, topics, protocol, service, domain) = args val spark = SparkSession.builder.appName("SecurityKafkaWordCount").getOrCreate() import spark.implicits._ // 创建表示来自 kafka 的输入行流的 DataSet val lines = spark.readstream.format("kafka").option("kafka.bootstrap.servers", bootstrapservers).option(subscribetype, topics).option("kafka.security.protocol", protocol).option("kafka.sasl.kerberos.service.name", service).option("kafka.kerberos.domain.name", domain).load().selectexpr("cast(value AS STRING)").as[String] // 生成运行字数 val wordcounts = lines.flatmap(_.split(" ")).groupby("value").count() // 开始运行将运行计数打印到控制台的查询 val query = wordcounts.writestream.outputmode("complete").format("console").start() query.awaittermination() Spark 访问 OBS 功能简介 1. 通过 spark 应用访问 OBS 访问之前需要在 spark 客户端目录 spark/conf/core-site.xml 中添加 fs.s3a.access.key 和 fs.s3a.secret.key <property> <name>fs.s3a.access.key</name> <value>xxxxxxxxxxxxxxxx</value> </property> <property> 文档版本 01 ( ) 310

321 7 Spark 应用开发 <name>fs.s3a.secret.key</name> <value>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</value> </property> 说明 请联系 MRS 管理员获取 fs.s3a.access.key 和 fs.s3a.secret.key 访问 OBS 的 url 如下 : s3a://obs-lihui3/input/news.txt 说明 s3a 为前缀 obs-lihui3 为桶名 2. 在 spark-sql 和 spark-beeline 中访问 OBS 如果需要在 spark-sql 和 spark-beeline 中访问 OBS, 需要手动执行如下命令设置 AK, SK set fs.s3a.access.key=xxxxxxxxxxxxxxxx; set fs.s3a.secret.key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; 说明 HiveODBC 程序 请联系 MRS 管理员获取 fs.s3a.access.key 和 fs.s3a.secret.key C 样例代码 功能介绍 代码样例 使用 ODBC 连接至 Spark ThriftServerJDBCServer, 并执行 show databases 语句 下面代码片段仅为演示, 具体代码参见 spark_hiveodbc_example.c int main() { SQLHENV henv; SQLHDBC hdbc; SQLHSTMT hstmt; SQLRETURN retcode; SQLCHAR OutConnStr[255]; SQLSMALLINT OutConnStrLen; // 1. 指定数据源名称 SQLCHAR* ConnStrIn = (SQLCHAR *)"DSN=hiveodbcds"; int mustfreeconnect = 0; int mustdisconnect = 0; // 2. 创建 SQL_HANDLE_ENV 的句柄 retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); CHECK_STATUS_ENV(retcode,(SQLCHAR *)"SQLAllocEnv", henv); // 3. 设置 ODBC 版本的环境属性 if (retcode == SQL_SUCCESS retcode == SQL_SUCCESS_WITH_INFO) { retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER*)3, 0); CHECK_STATUS_ENV(retcode,(SQLCHAR *)"SQLSetEnvAttr", henv); // 4. 创建一个连接句柄 if (retcode == SQL_SUCCESS retcode == SQL_SUCCESS_WITH_INFO) { retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); if (retcode == SQL_SUCCESS retcode == SQL_SUCCESS_WITH_INFO) { retcode = SQLAllocConnect(henv, &hdbc); mustfreeconnect = CHECK_STATUS_DBC(retcode, (SQLCHAR *)"SQLAllocConnect", henv); retcode = SQLDriverConnect( hdbc, 文档版本 01 ( ) 311

322 7 Spark 应用开发 NULL, (SQLCHAR *)ConnStrIn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT); mustdisconnect = CHECK_STATUS_DBC(retcode, (SQLCHAR *)"SQLConnect", hdbc); // 5. 创建一个 statement 句柄 if (retcode == SQL_SUCCESS retcode == SQL_SUCCESS_WITH_INFO) { retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); // 6. 查询数据 SQLCHAR * sql = (SQLCHAR *)"show databases;"; viewfetchresults(hdbc,sql); printf("ok!\n"); if (retcode == SQL_SUCCESS retcode == SQL_SUCCESS_WITH_INFO) { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); SQLDisconnect(hdbc); SQLFreeHandle(SQL_HANDLE_DBC, hdbc); SQLFreeHandle(SQL_HANDLE_ENV, henv); return 1; 7.4 调测程序 编包并运行程序 操作场景 操作步骤 在程序代码完成开发后, 您可以将打包好的 jar 包上传至 Linux 客户端环境中运行应用 使用 Scala 或 Java 语言开发的应用程序在 Spark 客户端的运行步骤是一样的 说明 Spark 应用程序只支持在 Linux 环境下运行, 不支持在 Windows 环境下运行 使用 Python 开发的 Spark 应用程序无需打包成 jar, 只需将样例工程拷贝到编译机器上即可 步骤 1 步骤 2 在工程目录下执行 mvn package 命令打出 jar 包, 在工程目录 target 目录下获取, 比如 :FemaleInfoCollection.jar 将步骤 1 中生成的 Jar 包 ( 如 CollectFemaleInfo.jar) 拷贝到 Spark 运行环境下 ( 即 Spark 客户端 ), 如 /opt/female 运行 Spark 应用程序 注意 在 Spark 任务运行过程中禁止重启 HDFS 服务或者重启所有 DataNode 实例, 否则可能会导致任务失败, 并可能导致 JobHistory 部分数据丢失 运行 Spark Core(Scala 和 Java) 样例程序 文档版本 01 ( ) 312

323 7 Spark 应用开发 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 其中,<inputPath> 指 HDFS 文件系统中 input 的路径 bin/spark-submit --class com.huawei.bigdata.spark.examples.femaleinfocollection -- master yarn-client /opt/female/femaleinfocollection.jar <inputpath> 运行 Spark SQL 样例程序 (Scala 和 Java 语言 ) 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 其中,<inputPath> 指 HDFS 文件系统中 input 的路径 bin/spark-submit --class com.huawei.bigdata.spark.examples.femaleinfocollection -- master yarn-client /opt/female/femaleinfocollection.jar <inputpath> 运行 Spark Streaming 样例程序 (Scala 和 Java 语言 ) 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 说明 由于 Spark Streaming Kafka 的依赖包在客户端的存放路径与其他依赖包不同, 如其他依赖包路径为 $SPARK_HOME/lib, 而 Spark Streaming Kafka 依赖包路径为 $SPARK_HOME/lib/streamingClient 所以在运行应用程序时, 需要在 spark-submit 命令中添加配置项, 指定 Spark Streaming Kafka 的依赖包路径, 如 --jars $SPARK_HOME/lib/ streamingclient/kafka-clients jar,$spark_home/lib/streamingclient/ kafka_ jar,$spark_home/lib/streamingclient/spark-streamingkafka-0-8_ jar Spark Streaming Write To Print 代码样例 bin/spark-submit --master yarn-client --jars $SPARK_HOME/jars/streamingClient/ kafka-clients jar,$spark_home/jars/streamingclient/kafka_ jar, $SPARK_HOME/jars/streamingClient/spark-streaming-kafka-0-8_ jar --class com.huawei.bigdata.spark.examples.femaleinfocollectionprint /opt/female/ FemaleInfoCollectionPrint.jar <batchtime> <windowtime> <topics> <brokers> Spark Streaming Write To Kafka 代码样例 bin/spark-submit --master yarn-client --jars $SPARK_HOME/jars/streamingClient/ kafka-clients jar,$spark_home/jars/streamingclient/kafka_ jar, $SPARK_HOME/jars/streamingClient/spark-streaming-kafka-0-8_ jar --class com.huawei.bigdata.spark.examples.femaleinfocollectionkafka /opt/female/ FemaleInfoCollectionKafka.jar <batchtime> <windowtime> <topics> <brokers> 运行 通过 JDBC 访问 Spark SQL 样例程序 (Scala 和 Java 语言 ) 进入 Spark 客户端目录, 使用 java -cp 命令运行代码 java -cp <spark-home>/lib/*:<spark-home>/conf:/opt/female/ ThriftServerQueriesTest.jar com.huawei.bigdata.spark.examples.thriftserverqueriestest 说明 上面的命令行中, 您可以根据不同样例工程, 最小化选择其对应的运行依赖包 样例工程对应的运行依赖包详情, 请参见步骤 1 运行 Spark on HBase 样例程序 (Scala 和 Java 语言 ) 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 运行样例程序时, 程序运行顺序为 :TableCreation TableInputData TableOutputData 其中, 在运行 TableInputData 样例程序时需要指定 <inputpath>,<inputpath> 指 HDFS 文件系统中 input 的路径 bin/spark-submit --class com.huawei.bigdata.spark.examples.tableinputdata --master yarn-client /opt/female/tableinputdata.jar <inputpath> 运行 Spark HBase to HBase 样例程序 (Scala 和 Java 语言 ) 文档版本 01 ( ) 313

324 7 Spark 应用开发 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 其中, 在运行样例程序时需要指定 <zkquorum>,<zkquorum> 指 ZooKeeper 的 IP 地址 bin/spark-submit --class com.huawei.bigdata.spark.examples.sparkhbasetohbase -- master yarn-client /opt/female/femaleinfocollection.jar <zkquorum> 运行 Spark Hive to HBase 样例程序 (Scala 和 Java 语言 ) 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 在运行样例程序时需要指定 <zkquorum>,<zkquorum> 指 ZooKeeper 服务器 ip 地址 bin/spark-submit --class com.huawei.bigdata.spark.examples.sparkhivetohbase -- master yarn-client /opt/female/femaleinfocollection.jar <zkquorum> 运行 Spark Streaming Kafka to HBase 样例程序 (Scala 和 Java 语言 ) 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 在运行样例程序时需要指定 <checkpointdir><topic><brokerlist>, 其中 <checkpointdir> 指应用程序结果备份到 HDFS 的路径,<topic> 指读取 kafka 上的 topic 名称,<brokerList> 指 Kafka 服务器 IP 地址 说明 由于 Spark Streaming Kafka 的依赖包在客户端的存放路径与其他依赖包不同, 如其他依赖包路径为 $SPARK_HOME/lib, 而 Spark Streaming Kafka 依赖包路径为 $SPARK_HOME/lib/streamingClient 所以在运行应用程序时, 需要在 spark-submit 命令中添加配置项, 指定 Spark Streaming Kafka 的依赖包路径, 如 --jars $SPARK_HOME/jars/ streamingclient/kafka-clients jar,$spark_home/jars/streamingclient/ kafka_ jar,$spark_home/jars/streamingclient/spark-streamingkafka-0-8_ jar Spark Streaming To HBase 代码样例 bin/spark-submit --master yarn-client --jars --jars $SPARK_HOME/jars/ streamingclient/kafka-clients jar,$spark_home/jars/streamingclient/ kafka_ jar,$spark_home/jars/streamingclient/spark-streamingkafka-0-8_ jar --class com.huawei.bigdata.spark.examples.streaming.sparkonstreamingtohbase /opt/female/ FemaleInfoCollectionPrint.jar <checkpointdir> <topic> <brokerlist> 运行 Spark Streaming 对接 Kafka0-10 样例程序 (Scala 和 Java 语言 ) 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 在运行样例程序时需要指定 <checkpointdir> <brokers> <topic> <batchtime>, 其中 <checkpointdir> 指应用程序结果备份到 HDFS 的路径,<brokers> 指获取元数据的 Kafka 地址,<topic> 指读取 Kafka 上的 topic 名称,<batchTime> 指 Streaming 分批的处理间隔 Spark Streaming 读取 Kafka 0-10 代码样例 : bin/spark-submit --master yarn-client --files./jaas.conf,./user.keytab --driver-javaoptions "-Djava.security.auth.login.config=./jaas.conf" --conf "spark.executor.extrajavaoptions=-djava.security.auth.login.config=./jaas.conf" --jars $SPARK_HOME/jars/streamingClient010/kafka-clients jar,$SPARK_HOME/ jars/streamingclient010/kafka_ jar,$spark_home/jars/ streamingclient010/spark-streaming-kafka-0-10_ jar --class com.huawei.bigdata.spark.examples.securitykafkawordcount /opt/ SparkStreamingKafka010JavaExample-1.0.jar <checkpointdir> <brokers> <topic> <batchtime> 其中配置示例如下 : 文档版本 01 ( ) 314

325 7 Spark 应用开发 --files./jaas.conf,./user.keytab // 使用 --files 指定 jaas.conf 和 keytab 文件 --driver-java-options "-Djava.security.auth.login.config=./jaas.conf" // 指定 driver 侧 jaas.conf 文件路径,yarn-client 模式下使用 --driver-java-options "-Djava.security.auth.login.config 指定 ;yarn-cluster 模式下使用 --conf "spark.yarn.cluster.driver.extrajavaoptions 指定 --conf "spark.executor.extrajavaoptions=-djava.security.auth.login.config=./ jaas.conf"// 指定 executor 侧 jaas.conf 文件路径 Spark Streaming Write To Kafka 0-10 代码样例 : bin/spark-submit --master yarn-client --jars $SPARK_HOME/jars/streamingClient010/ kafka-clients jar,$spark_home/jars/streamingclient010/ kafka_ jar,$spark_home/jars/streamingclient010/spark-streamingkafka-0-10_ jar --class com.huawei.bigdata.spark.examples.javadstreamkafkawriter /opt/ JavaDstreamKafkaWriter.jar <checkpointdir> <brokers> <topics> 运行 Spark Structured Streaming 样例程序 (Scala 和 Java 语言 ) 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 在运行样例程序时需要指定 <brokers> <subscribe-type> <topic> <protocol> <service> <domain>, 其中 <brokers> 指获取元数据的 Kafka 地址,<subscribe-type> 指 Kafka 订阅类型,<topic> 指读取 Kafka 上的 topic 名称,<protocol> 指安全访问协议, <service> 指 kerberos 服务名称,<domain> 指 kerberos 域名 说明 由于 Spark Structured Streaming Kafka 的依赖包在客户端的存放路径与其他依赖包不同, 如其他依赖包路径为 $SPARK_HOME/jars, 而 Spark Structured Streaming Kafka 依赖包路径为 $SPARK_HOME/jars/streamingClient010 所以在运行应用程序时, 需要在 sparksubmit 命令中添加配置项, 指定 Spark Streaming Kafka 的依赖包路径, 如 --jars $SPARK_HOME/jars/streamingClient010/kafka-clients jar,$SPARK_HOME/jars/ streamingclient010/kafka_ jar,$spark_home/jars/streamingclient010/spark-sqlkafka-0-10_ jar Spark Structured Streaming 对接 Kafka 代码样例 bin/spark-submit --master yarn-client --files <local Path>/jaas.conf,<local path>/ user.keytab --driver-java-options "-Djava.security.auth.login.config=<local path>/ jaas.conf" --conf "spark.executor.extrajavaoptions=-djava.security.auth.login.config=./ jaas.conf" --jars $SPARK_HOME/jars/streamingClient010/kafka-clients jar, $SPARK_HOME/jars/streamingClient010/kafka_ jar,$SPARK_HOME/ jars/streamingclient010/spark-sql-kafka-0-10_ jar --class com.huawei.bigdata.spark.examples.securitykafkawordcount /opt/ SparkStructuredStreamingScalaExample-1.0.jar <brokers> <subscribe-type> <topic> <protocol> <service> <domain> 其中配置示例如下 : --files <local Path>/jaas.conf,<local Path>/user.keytab // 使用 --files 指定 jaas.conf 和 keytab 文件 --driver-java-options "-Djava.security.auth.login.config=<local Path>/jaas.conf" // 指定 driver 侧 jaas.conf 文件路径,yarn-client 模式下使用 --driver-java-options "- Djava.security.auth.login.config 指定 ;yarn-cluster 模式下使用 --conf "spark.yarn.cluster.driver.extrajavaoptions 指定 如果报没有权限读写本地目录的错误, 需要指定 spark.sql.streaming.checkpointlocation 参数, 且用户必须具有该参数指定的目录的读 写权限 --conf "spark.executor.extrajavaoptions=-djava.security.auth.login.config=./jaas.conf" // 指定 executor 侧 jaas.conf 文件路径 提交 Python 语言开发的应用程序 进入 Spark 客户端目录, 调用 bin/spark-submit 脚本运行代码 其中,<inputPath> 指 HDFS 文件系统中 input 的路径 说明 由于样例代码中未给出认证信息, 请在执行应用程序时通过配置项 spark.yarn.keytab 和 spark.yarn.principal 指定认证信息 文档版本 01 ( ) 315

326 7 Spark 应用开发 ---- 结束 bin/spark-submit --master yarn-client --conf spark.yarn.keytab=/opt/ficlient/ user.keytab --conf spark.yarn.principal=sparkuser /opt/female/sparkpythonexample/ collectfemaleinfo.py <inputpath> 参考信息 通过 JDBC 访问 Spark SQL 样例程序 (Scala 和 Java 语言 ), 其对应的运行依赖包如下 : 通过 JDBC 访问 Spark SQL 样例工程 (Scala) commons-collections-<version>.jar commons-configuration-<version>.jar commons-io-<version>.jar commons-lang-<version>.jar commons-logging-<version>.jar guava-<version>.jar hadoop-auth-<version>.jar hadoop-common-<version>.jar hadoop-mapreduce-client-core-<version>.jar hive-exec-<version>.spark2.jar hive-jdbc-<version>.spark2.jar hive-metastore-<version>.spark2.jar hive-service-<version>.spark2.jar httpclient-<version>.jar httpcore-<version>.jar libthrift-<version>.jar log4j-<version>.jar slf4j-api-<version>.jar zookeeper-<version>.jar scala-library-<version>.jar 通过 JDBC 访问 Spark SQL 样例工程 (Java) commons-collections-<version>.jar commons-configuration-<version>.jar commons-io-<version>.jar commons-lang-<version>.jar commons-logging-<version>.jar guava-<version>.jar hadoop-auth-<version>.jar hadoop-common-<version>.jar hadoop-mapreduce-client-core-<version>.jar hive-exec-<version>.spark2.jar 文档版本 01 ( ) 316

327 7 Spark 应用开发 查看调测结果 操作场景 hive-jdbc-<version>.spark2.jar hive-metastore-<version>.spark2.jar hive-service-<version>.spark2.jar httpclient-<version>.jar httpcore-<version>.jar libthrift-<version>.jar log4j-<version>.jar slf4j-api-<version>.jar zookeeper-<version>.jar Spark 应用程序运行完成后, 您可以查看运行结果数据, 也可以通过 Spark WebUI 查看应用程序运行情况 操作步骤 查看 Spark 应用运行结果数据 结果数据存储路径和格式已经与 Spark 应用程序指定, 您可以通过指定文件中获取到运行结果数据 查看 Spark 应用程序运行情况 Spark 主要有两个 Web 页面 Spark UI 页面, 用于展示正在执行的应用的运行情况 页面主要包括了 Jobs Stages Storage Environment 和 Executors 五个部分 Streaming 应用会多一个 Streaming 标签页 页面入口 : 在公有云管理控制台, 选择 基本信息 > Yarn 资源管理页面 进入 Web 界面, 查找到对应的 Spark 应用程序 单击应用信息的最后一列 ApplicationMaster, 即可进入 SparkUI 页面 History Server 页面, 用于展示已经完成的和未完成的 Spark 应用的运行情况 页面包括了应用 ID 应用名称 开始时间 结束时间 执行时间 所属用户等信息 单击应用 ID, 页面将跳转到该应用的 SparkUI 页面 查看 Spark 日志获取应用运行情况 您可以查看 Spark 日志了解应用运行情况, 并根据日志信息调整应用程序 HiveODBC 样例运行及结果查看 Windows 环境 HiveODBC 示例工程的运行 1. 获取 Spark 客户端解压目录下 Spark\sampleCode\spark-examples-security 中的样例工程 SparkHiveODBCExample 2. 进入 SparkHiveODBCExample 目录后, 双击 SparkHiveODBCExample.sln, 在 Visual Studio 2012 中打开 SparkHiveODBCExample 工程 文档版本 01 ( ) 317

328 7 Spark 应用开发 Linux 环境 3. 在左边的导航栏中选中 SparkHiveODBCExample 工程, 按 F5 键, 编译并运行示例代码 4. 执行样例代码后弹出的界面窗口会显示所连的 Hive 中的所有数据库名称 说明 使用 unixodbc 自带的工具 isql 测试 由于运行比较快, 可以在 hiveodbcexample.c 中的 main 函数加断点, 再运行查看结果 在 Linux 环境上, 用户可以安装 unixodbc( 版本 :2.3.2), 并使用其自带的 isql 工具来验证 HiveODBC 驱动的功能是否正常 unixodbc 的安装配置请参见官网 : HiveODBC 驱动内部没有进行认证, 需要用户手动使用 kinit 命令登录 1. 使用某个属于 Spark 组的用户 ( 如 sparkuser ) 登录 : kinit sparkuser 2. 执行 isql 命令 : isql -v Hive 3. 出现如下结果, 说明登录成功 : Connected! sql-statement help [tablename] quit SQL> 4. 可使用该工具进行 Hive 操作, 例如 show databases, 运行结果如下 : SQL> show databases; result default 使用 HiveODBC 对接 Tableau 操作步骤 1. 确认 Tableau 已经安装, 并正常运行 说明 由于使用界面图标启动 Tableau 会关闭 ODBC 的日志, 所以建议启动时, 从命令行启动 2. Tableau 启动后会进入连接选择界面, 单击 更多服务器..., 再单击 其他数据库 (ODBC) 3. 在 DSN 的下拉框选择已配置的 HiveODBC 数据源, 然后单击 连接 按钮, 再单击 确定 4. 进入 Tableau 工作簿界面后, 在界面左侧 选择数据库 的下拉框中选择数据库后会出现表的搜索框 单击搜索框内的放大镜图标即可列出当前数据库下的所有表 文档版本 01 ( ) 318

329 7 Spark 应用开发 5. 从左侧栏中选择一张表往右侧工作区拖拽, 即可查看该表的数据 约束 只支持 Tableau 9.0(64 位 ) 版本 要求配置的数据库下必须有表 支持的数据类型 : TINYINT,SMALLINT,INT,BIGINT,FLOAT,DOUBLE,DECIMAL,TIMESTAMP,DATE,STRING,VARCHAR,CHAR,BOOLEAN 说明 使用 HiveODBC 与 Tableau 对接, 不支持对 date timestamp 类型的数据进行聚合操作, 即在工作表中无法展示 date timestamp 类型的数据 连接的数据库, 只支持选择数据源中配置的数据库, 即不支持切换数据库 支持操作 使用 HiveODBC 从 Tableau 连接 Spark ThriftServer 数据源页面选择表, 预览数据 数据源页面选择多张表做 Join, 预览数据 数据源页面自定义 SQL, 预览数据 工作表页面绘制图表, 查询成功, 并展示图表 7.5 调优程序 Spark Core 调优 数据序列化 操作场景 Spark 支持两种方式的序列化 : Java 原生序列化 JavaSerializer Kryo 序列化 KryoSerializer 序列化对于 Spark 应用的性能来说, 具有很大的影响 在特定的数据格式的情况下, KryoSerializer 的性能可以达到 JavaSerializer 的 10 倍以上, 而对于一些 Int 之类的基本类型数据, 性能的提升就几乎可以忽略 KryoSerializer 依赖 Twitter 的 Chill 库来实现, 相对于 JavaSerializer, 主要的问题在于不是所有的 Java Serializable 对象都能支持, 兼容性不好, 所以需要手动注册类 序列化功能用在两个地方 : 序列化任务和序列化数据 Spark 任务序列化只支持 JavaSerializer, 数据序列化支持 JavaSerializer 和 KryoSerializer 文档版本 01 ( ) 319

330 7 Spark 应用开发 操作步骤 配置内存 操作场景 Spark 程序运行时, 在 shuffle 和 RDD Cache 等过程中, 会有大量的数据需要序列化, 默认使用 JavaSerializer, 通过配置让 KryoSerializer 作为数据序列化器来提升序列化性能 在开发应用程序时, 添加如下代码来使用 KryoSerializer 作为数据序列化器 实现类注册器并手动注册类 package com.etl.common; import com.esotericsoftware.kryo.kryo; import org.apache.spark.serializer.kryoregistrator; public class DemoRegistrator implements KryoRegistrator public void registerclasses(kryo kryo) { // 以下为示例类, 请注册自定义的类 kryo.register(aggratekey.class); kryo.register(aggratevalue.class); 您可以在 Spark 客户端对 spark.kryo.registrationrequired 参数进行配置, 设置是否需要 Kryo 注册序列化 当参数设置为 true 时, 如果工程中存在未被序列化的类, 则会抛出异常 如果设置为 false( 默认值 ),Kryo 会自动将未注册的类名写到对应的对象中 此操作会对系统性能造成影响 设置为 true 时, 用户需手动注册类, 针对未序列化的类, 系统不会自动写入类名, 而是抛出异常, 相对比 false, 其性能较好 配置 KryoSerializer 作为数据序列化器和类注册器 val conf = new SparkConf() conf.set("spark.serializer", "org.apache.spark.serializer.kryoserializer").set("spark.kryo.registrator", "com.etl.common.demoregistrator") Spark 是内存计算框架, 计算过程中内存不够对 Spark 的执行效率影响很大 可以通过监控 GC(Garbage Collection), 评估内存中 RDD 的大小来判断内存是否变成性能瓶颈, 并根据情况优化 监控节点进程的 GC 情况 ( 在客户端的 conf/spark-defaults.conf 配置文件中, 在 spark.driver.extrajavaoptions 和 spark.executor.extrajavaoptions 配置项中添加参数 :"- verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps" ), 如果频繁出现 Full GC, 需要优化 GC 把 RDD 做 Cache 操作, 通过日志查看 RDD 在内存中的大小, 如果数据太大, 需要改变 RDD 的存储级别来优化 操作步骤 优化 GC, 调整老年代和新生代的大小和比例 在客户端的 conf/spark-defaults.conf 配置文件中, 在 spark.driver.extrajavaoptions 和 spark.executor.extrajavaoptions 配置项中添加参数 :-XX:NewRatio 如," -XX:NewRatio=2", 则新生代占整个堆空间的 1/3, 老年代占 2/3 开发 Spark 应用程序时, 优化 RDD 的数据结构 使用原始类型数组替代集合类, 如可使用 fastutil 库 文档版本 01 ( ) 320

331 7 Spark 应用开发 设置并行度 操作场景 操作步骤 使用广播变量 操作场景 操作步骤 避免嵌套结构 Key 尽量不要使用 String 开发 Spark 应用程序时, 建议序列化 RDD RDD 做 cache 时默认是不序列化数据的, 可以通过设置存储级别来序列化 RDD 减小内存 例如 : testrdd.persist(storagelevel.memory_only_ser) 并行度控制任务的数量, 影响 shuffle 操作后数据被切分成的块数 调整并行度让任务的数量和每个任务处理的数据与机器的处理能力达到最优 查看 CPU 使用情况和内存占用情况, 当任务和数据不是平均分布在各节点, 而是集中在个别节点时, 可以增大并行度使任务和数据更均匀的分布在各个节点 增加任务的并行度, 充分利用集群机器的计算能力, 一般并行度设置为集群 CPU 总和的 2-3 倍 并行度可以通过如下三种方式来设置, 用户可以根据实际的内存 CPU 数据以及应用程序逻辑的情况调整并行度参数 在会产生 shuffle 的操作函数内设置并行度参数, 优先级最高 testrdd.groupbykey(24) 在代码中配置 spark.default.parallelism 设置并行度, 优先级次之 val conf = new SparkConf() conf.set("spark.default.parallelism", 24) 在 $SPARK_HOME/conf/spark-defaults.conf 文件中配置 spark.default.parallelism 的值, 优先级最低 spark.default.parallelism 24 Broadcast( 广播 ) 可以把数据集合分发到每一个节点上,Spark 任务在执行过程中要使用这个数据集合时, 就会在本地查找 Broadcast 过来的数据集合 如果不使用 Broadcast, 每次任务需要数据集合时, 都会把数据序列化到任务里面, 不但耗时, 还使任务变得很大 1. 每个任务分片在执行中都需要同一份数据集合时, 就可以把公共数据集 Broadcast 到每个节点, 让每个节点在本地都保存一份 2. 大表和小表做 join 操作时可以把小表 Broadcast 到各个节点, 从而就可以把 join 操作转变成普通的操作, 减少了 shuffle 操作 在开发应用程序时, 添加如下代码, 将 testarr 数据广播到各个节点 def main(args: Array[String]) {... val testarr: Array[Long] = new Array[Long](200) 文档版本 01 ( ) 321

332 7 Spark 应用开发 val testbroadcast: Broadcast[Array[Long]] = sc.broadcast(testarr) val resultrdd: RDD[Long] = inpputrdd.map(input => handledata(testbroadcast, input))... def handledata(broadcast: Broadcast[Array[Long]], input: String) { val value = broadcast.value 使用 External Shuffle Service 提升性能 操作场景 Spark 系统在运行含 shuffle 过程的应用时,Executor 进程除了运行 task, 还要负责写 shuffle 数据, 给其他 Executor 提供 shuffle 数据 当 Executor 进程任务过重, 导致 GC 而不能为其他 Executor 提供 shuffle 数据时, 会影响任务运行 External shuffle Service 是长期存在于 NodeManager 进程中的一个辅助服务 通过该服务来抓取 shuffle 数据, 减少了 Executor 的压力, 在 Executor GC 的时候也不会影响其他 Executor 的任务运行 操作步骤 1. 在 NodeManager 中启动 External shuffle Service a. 在 yarn-site.xml 中添加如下配置项 : <property> <name>yarn.nodemanager.aux-services</name> <value>spark_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.spark_shuffle.class</name> <value>org.apache.spark.network.yarn.yarnshuffleservice</value> </property> <property> <name>spark.shuffle.service.port</name> <value>7337</value> </property> 配置参数 yarn.nodemanag er.aux-services yarn.nodemanag er.auxservices.spark_s huffle.class spark.shuffle.ser vice.port 描述 NodeManager 中一个长期运行的辅助服务, 用于提升 Shuffle 计算性能 NodeManager 中辅助服务对应的类 Shuffle 服务监听数据获取请求的端口 可选配置, 默认值为 7337 b. 添加依赖的 jar 包 拷贝 ${SPARK_HOME/lib/spark yarn-shuffle.jar 到 $ {HADOOP_HOME/share/hadoop/yarn/lib/ 目录下 c. 重启 NodeManager 进程, 也就启动了 External shuffle Service 2. Spark 应用使用 External shuffle Service 文档版本 01 ( ) 322

333 7 Spark 应用开发 在 spark-defaults.conf 中必须添加如下配置项 : spark.shuffle.service.enabled true spark.shuffle.service.port 7337 配置参数 spark.shuffle.ser vice.enabled spark.shuffle.ser vice.port 描述 NodeManager 中一个长期运行的辅助服务, 用于提升 Shuffle 计算性能 默认为 false, 表示不启用该功能 Shuffle 服务监听数据获取请求的端口 可选配置, 默认值为 7337 说明 1. 如果 yarn.nodemanager.aux-services 配置项已存在, 则在 value 中添加 spark_shuffle, 且用逗号和其他值分开 2. spark.shuffle.service.port 的值需要和上面 yarn-site.xml 中的值一样 Yarn 模式下动态资源调度 操作场景 对于 Spark 应用来说, 资源是影响 Spark 应用执行效率的一个重要因素 当一个长期运行的服务 ( 比如 JDBCServer), 若分配给它多个 Executor, 可是却没有任何任务分配给它, 而此时有其他的应用却资源紧张, 这就造成了很大的资源浪费和资源不合理的调度 动态资源调度就是为了解决这种场景, 根据当前应用任务的负载情况, 实时的增减 Executor 个数, 从而实现动态分配资源, 使整个 Spark 系统更加健康 操作步骤 1. 需要先配置 External shuffle service 2. 在 spark-defaults.conf 中必须添加配置项 spark.dynamicallocation.enabled, 并将该参数的值设置为 true, 表示开启动态资源调度功能 默认情况下关闭此功能 3. 下面是一些可选配置, 如表 7-10 所示 表 7-10 动态资源调度参数 配置项说明默认值 spark.dynamicallocatio n.minexecutors 最小 Executor 个数 0 spark.dynamicallocatio n.initialexecutors spark.dynamicallocatio n.maxexecutors 初始 Executor 个数 最大 executor 个数 spark.dynamicallocati on.minexecutors Integer.MAX_VALUE 文档版本 01 ( ) 323

334 7 Spark 应用开发 配置项说明默认值 spark.dynamicallocatio n.schedulerbacklogtim eout spark.dynamicallocatio n.sustainedschedulerba cklogtimeout spark.dynamicallocatio n.executoridletimeout spark.dynamicallocatio n.cachedexecutoridleti meout 调度第一次超时时间 调度第二次及之后超时时间 普通 Executor 空闲超时时间 含有 cached blocks 的 Executor 空闲超时时间 1(s) spark.dynamicallocati on.schedulerbacklogt imeout 60(s) Integer.MAX_VALUE 说明 使用动态资源调度功能, 必须配置 External Shuffle Service 如果没有使用 External Shuffle Service,Executor 被杀时会丢失 shuffle 文件 如果通过 spark.executor.instances 或者 --num-executors 指定了 Executor 的个数, 即使配置了动态资源调度功能, 动态资源调度功能也不会生效 当前动态资源分配功能开启后, 不能完全避免 task 被分配到即将要移除的 executor, 但是一般情况下只会导致该 task 失败, 只有同一个 task 失败 4 次 ( 可通过 spark.task.maxfailures 配置 ) 才会导致 job 失败, 所以正常情况下基本不会因为 task 被分配到即将要移除的 executor 导致 job 失败, 并且可以通过调大 spark.task.maxfailures 来减小问题发生的概率 配置进程参数 操作场景 操作步骤 Spark on YARN 模式下, 有 Driver ApplicationMaster Executor 三种进程 在任务调度和运行的过程中,Driver 和 Executor 承担了很大的责任, 而 ApplicationMaster 主要负责 container 的启停 因而 Driver 和 Executor 的参数配置对 spark 应用的执行有着很大的影响意义 用户可通过如下操作对 Spark 集群性能做优化 步骤 1 配置 Driver 内存 Driver 负责任务的调度, 和 Executor AM 之间的消息通信 当任务数变多, 任务平行度增大时,Driver 内存都需要相应增大 您可以根据实际任务数量的多少, 为 Driver 设置一个合适的内存 将 spark-defaults.conf 中的 spark.driver.memory 配置项或者 spark-env.sh 中的 SPARK_DRIVER_MEMORY 配置项设置为合适大小 在使用 spark-submit 命令时, 添加 --driver-memory MEM 参数设置内存 步骤 2 配置 Executor 个数 文档版本 01 ( ) 324

335 7 Spark 应用开发 每个 Executor 每个核同时能跑一个 task, 所以增加了 Executor 的个数相当于增大了任务的并发度 在资源充足的情况下, 可以相应增加 Executor 的个数, 以提高运行效率 将 spark-defaults.conf 中的 spark.executor.instance 配置项或者 sparkenv.sh 中的 SPARK_EXECUTOR_INSTANCES 配置项设置为合适大小 您还可以设置动态资源调度功能进行优化 在使用 spark-submit 命令时, 添加 --num-executors NUM 参数设置 Executor 个数 步骤 3 配置 Executor 核数 每个 Executor 多个核同时能跑多个 task, 相当于增大了任务的并发度 但是由于所有核共用 Executor 的内存, 所以要在内存和核数之间做好平衡 将 spark-defaults.conf 中的 spark.executor.cores 配置项或者 spark-env.sh 中的 SPARK_EXECUTOR_CORES 配置项设置为合适大小 在使用 spark-submit 命令时, 添加 --executor-cores NUM 参数设置核数 步骤 4 配置 Executor 内存 Executor 的内存主要用于任务执行 通信等 当一个任务很大的时候, 可能需要较多资源, 因而内存也可以做相应的增加 ; 当一个任务较小运行较快时, 就可以增大并发度减少内存 将 spark-defaults.conf 中的 spark.executor.memory 配置项或者 sparkenv.sh 中的 SPARK_EXECUTOR_MEMORY 配置项设置为合适大小 在使用 spark-submit 命令时, 添加 --executor-memory MEM 参数设置内存 ---- 结束 示例 在执行 spark wordcount 计算中 1.6T 数据,250 个 executor 在默认参数下执行失败, 出现 Futures timed out 和 OOM 错误 因为数据量大,task 数多, 而 wordcount 每个 task 都比较小, 完成速度快 当 task 数多时 driver 端相应的一些对象就变大了, 而且每个 task 完成时 executor 和 driver 都要通信, 这就会导致由于内存不足, 进程之间通信断连等问题 当把 Driver 的内存设置到 4g 时, 应用成功跑完 使用 ThriftServer 执行 TPC-DS 测试套, 默认参数配置下也报了很多错误 :Executor Lost 等 而当配置 Driver 内存为 30g,executor 核数为 2,executor 个数为 125, executor 内存为 6g 时, 所有任务才执行成功 设计 DAG 操作场景 操作步骤 合理的设计程序结构, 可以优化执行效率 在程序编写过程中要尽量减少 shuffle 操作, 合并窄依赖操作 以 同行车判断 例子讲解 DAG 设计的思路 文档版本 01 ( ) 325

336 7 Spark 应用开发 数据格式 : 通过收费站时间 车牌号 收费站编号... 逻辑 : 以下两种情况下判定这两辆车是同行车 : 如果两辆车都通过相同序列的收费站, 通过同一收费站之间的时间差小于一个特定的值 该例子有两种实现模式, 其中实现 1 的逻辑如图 7-37 所示, 实现 2 的逻辑如图 7-38 所示 图 7-37 实现 1 逻辑 实现 1 的逻辑说明 : 1. 根据车牌号聚合该车通过的所有收费站并排序, 处理后数据如下 : 车牌号 1,[( 通过时间, 收费站 3),( 通过时间, 收费站 2),( 通过时间, 收费站 4),( 通过时间, 收费站 5)] 2. 标识该收费站是这辆车通过的第几个收费站 ( 收费站 3,( 车牌号 1, 通过时间, 通过的第 1 个收费站 )) ( 收费站 2,( 车牌号 1, 通过时间, 通过的第 2 个收费站 )) ( 收费站 4,( 车牌号 1, 通过时间, 通过的第 3 个收费站 )) ( 收费站 5,( 车牌号 1, 通过时间, 通过的第 4 个收费站 )) 3. 根据收费站聚合数据 收费站 1,[( 车牌号 1, 通过时间, 通过的第 1 个收费站 ),( 车牌号 2, 通过时间, 通过的第 5 个收费站 ),( 车牌号 3, 通过时间, 通过的第 2 个收费站 )] 4. 判断两辆车通过该收费站的时间差是否满足同行车的要求, 如果满足则取出这两辆车 ( 车牌号 1, 车牌号 2),( 通过的第 1 个收费站, 通过的第 5 个收费站 ) ( 车牌号 1, 车牌号 3),( 通过的第 1 个收费站, 通过的第 2 个收费站 ) 5. 根据通过相同收费站的两辆车的车牌号聚合数据, 如下 : ( 车牌号 1, 车牌号 2),[( 通过的第 1 个收费站, 通过的第 5 个收费站 ),( 通过的第 2 个收费站, 通过的第 6 个收费站 ),( 通过的第 1 个收费站, 通过的第 7 个收费站 ),( 通过的第 3 个收费站, 通过的第 8 个收费站 )] 文档版本 01 ( ) 326

337 7 Spark 应用开发 6. 如果车牌号 1 和车牌号 2 通过相同收费站是顺序排列的 ( 比如收费站 是车牌 1 通过的第 个收费站, 是车牌 2 通过的第 个收费站 ) 且数量大于同行车要求的数量则这两辆车是同行车 实现 1 逻辑的缺点 : 逻辑复杂 实现过程中 shuffle 操作过多, 对性能影响较大 图 7-38 实现 2 逻辑 实现 2 的逻辑说明 : 1. 根据车牌号聚合该车通过的所有收费站并排序, 处理后数据如下 : 车牌号 1,[( 通过时间, 收费站 3),( 通过时间, 收费站 2),( 通过时间, 收费站 4),( 通过时间, 收费站 5)] 2. 根据同行车要通过的收费站数量 ( 例子里为 3) 分段该车通过的收费站序列, 如上面的数据被分解成 : 收费站 3-> 收费站 2-> 收费站 4, ( 车牌号 1,[ 收费站 3 时间, 收费站 2 时间, 收费站 4 时间 ]) 收费站 2-> 收费站 4-> 收费站 5, ( 车牌号 1,[ 收费站 2 时间, 收费站 4 时间, 收费站 5 时间 ]) 3. 把通过相同收费站序列的车辆聚合, 如下 : 收费站 3-> 收费站 2-> 收费站 4,[( 车牌号 1,[ 收费站 3 时间, 收费站 2 时间, 收费站 4 时间 ]),( 车牌号 2,[ 收费站 3 时间, 收费站 2 时间, 收费站 4 时间 ]),( 车牌号 3,[ 收费站 3 时间, 收费站 2 时间, 收费站 4 时间 ])] 4. 判断通过相同序列收费站的车辆通过相同收费站的时间差是不是满足同行车的要求, 如果满足则说明是同行车 实现 2 的优点如下 : 简化了实现逻辑 减少了一个 groupbykey, 也就减少了一次 shuffle 操作, 提升了性能 文档版本 01 ( ) 327

338 7 Spark 应用开发 经验总结 使用 mappartitions, 按每个分区计算结果 如果每条记录的开销太大, 例 : rdd.map{x=>conn=getdbconn;conn.write(x.tostring);conn.close 则可以使用 MapPartitions, 按每个分区计算结果, 如 rdd.mappartitions(records => conn.getdbconn;for(item <- records) write(item.tostring); conn.close) 使用 mappartitions 可以更灵活地操作数据, 例如对一个很大的数据求 TopN, 当 N 不是很大时, 可以先使用 mappartitions 对每个 partition 求 TopN,collect 结果到本地之后再做排序取 TopN 这样相比直接对全量数据做排序取 TopN 效率要高很多 使用 coalesce 调整分片的数量 localdir 配置 Collect 小数据 使用 reducebykey coalesce 可以调整分片的数量 coalesce 函数有两个参数 : coalesce(numpartitions: Int, shuffle: Boolean = false) 当 shuffle 为 true 的时候, 函数作用与 repartition(numpartitions: Int) 相同, 会将数据通过 Shuffle 的方式重新分区 ; 当 shuffle 为 false 的时候, 则只是简单的将父 RDD 的多个 partition 合并到同一个 task 进行计算,shuffle 为 false 时, 如果 numpartitions 大于父 RDD 的切片数, 那么分区不会重新调整 遇到下列场景, 可选择使用 coalesce 算子 : 当之前的操作有很多 filter 时, 使用 coalesce 减少空运行的任务数量 此时使用 coalesce(numpartitions, false),numpartitions 小于父 RDD 切片数 当输入切片个数太大, 导致程序无法正常运行时使用 当任务数过大时候 Shuffle 压力太大导致程序挂住不动, 或者出现 linux 资源受限的问题 此时需要对数据重新进行分区, 使用 coalesce(numpartitions, true) Spark 的 Shuffle 过程需要写本地磁盘,Shuffle 是 Spark 性能的瓶颈,I/O 是 Shuffle 的瓶颈 配置多个磁盘则可以并行的把数据写入磁盘 如果节点中挂载多个磁盘, 则在每个磁盘配置一个 Spark 的 localdir, 这将有效分散 Shuffle 文件的存放, 提高磁盘 I/O 的效率 如果只有一个磁盘, 配置了多个目录, 性能提升效果不明显 大数据量不适用 collect 操作 collect 操作会将 Executor 的数据发送到 Driver 端, 因此使用 collect 前需要确保 Driver 端内存足够, 以免 Driver 进程发生 OutOfMemory 异常 当不确定数据量大小时, 可使用 saveastextfile 等操作把数据写入 HDFS 中 只有在能够大致确定数据大小且 driver 内存充足的时候, 才能使用 collect reducebykey 会在 Map 端做本地聚合, 使得 Shuffle 过程更加平缓, 而 groupbykey 等 Shuffle 操作不会在 Map 端做聚合 因此能使用 reducebykey 的地方尽量使用该算子, 避免出现 groupbykey().map(x=>(x._1,x._2.size)) 这类实现方式 文档版本 01 ( ) 328

339 7 Spark 应用开发 广播 map 代替数组 当每条记录需要查表, 如果是 Driver 端用广播方式传递的数据, 数据结构优先采用 set/map 而不是 Iterator, 因为 Set/Map 的查询速率接近 O(1), 而 Iterator 是 O(n) 数据倾斜 当数据发生倾斜 ( 某一部分数据量特别大 ), 虽然没有 GC(Garbage Collection, 垃圾回收 ), 但是 task 执行时间严重不一致 需要重新设计 key, 以更小粒度的 key 使得 task 大小合理化 修改并行度 优化数据结构 把数据按列存放, 读取数据时就可以只扫描需要的列 使用 Hash Shuffle 时, 通过设置 spark.shuffle.consolidatefiles 为 true, 来合并 shuffle 中间文件, 减少 shuffle 文件的数量, 减少文件 IO 操作以提升性能 最终文件数为 reduce tasks 数目 SQL 和 DataFrame 调优 Spark SQL join 优化 操作场景 Spark SQL 中, 当对两个表进行 join 操作时, 利用 Broadcast 特性 ( 请参见使用广播变量 ), 将小表 BroadCast 到各个节点上, 从而转变成非 shuffle 操作, 提高任务执行性能 说明 这里 join 操作, 只指 inner join 操作步骤 在 Spark SQL 中进行 Join 操作时, 可以按照以下步骤进行优化 为了方便说明, 设表 A 和表 B, 且 A B 表都有个名为 name 的列 对 A B 表进行 join 操作 1. 估计表的大小 根据每次加载数据的大小, 来估计表大小 也可以在 Hive 的数据库存储路径下直接查看表的大小 首先在 Spark 的配置文件 hive-site.xml 中, 查看 Hive 的数据库路径的配置, 默认为 /user/sparkhive/ warehouse <property> <name>hive.metastore.warehouse.dir</name> <value>/user/sparkhive/warehouse</value> </property> 然后通过 hadoop 命令查看对应表的大小 如查看表 A 的大小命令为 : hadoop fs -du -s -h ${test.warehouse.dir/a 文档版本 01 ( ) 329

340 7 Spark 应用开发 说明 进行广播操作, 对表有要求 : 1. 至少有一个表不是空表 ; 2. 表不能是 external table ; 3. 表的储存方式需为 textfile( 默认是 textfile 文件格式 ), 如 create table A( name string ) stored as textfile; 或 : create table A( name string ); 2. 配置自动广播的阈值 Spark 中, 判断表是否广播的阈值为 ( 即 10M) 如果两个表的大小至少有一个小于 10M 时, 可以跳过该步骤 自动广播阈值的配置参数介绍, 见表 7-11 表 7-11 参数介绍 参数默认值描述 spark.sql.autobroadcastjoi nthreshold 当进行 join 操作时, 配置广播的最大值 ; 当表的字节数小于该值时便进行广播 当配置为 -1 时, 将不进行广播 参见 sql-programming-guide.html 配置自动广播阈值的方法 : 在 Spark 的配置文件 spark-defaults.conf 中, 设置 spark.sql.autobroadcastjointhreshold 的值 其中,<size> 根据场景而定, 但要求该值至少比其中一个表大 spark.sql.autobroadcastjointhreshold = <size> 利用 Hive CLI 命令, 设置阈值 在运行 Join 操作时, 提前运行下面语句 SET spark.sql.autobroadcastjointhreshold=<size> 其中,<size> 根据场景而定, 但要求该值至少比其中一个表大 3. 进行 join 操作 这时 join 的两个 table, 至少有个表是小于阈值的 如果 A 表和 B 表都小于阈值, 且 A 表的字节数小于 B 表时, 则运行 B join A, 如 SELECT A.name FROM B JOIN A ON A.name = B.name; 否则运行 A join B SELECT A.name FROM A JOIN B ON A.name = B.name; 4. 使用 Executor 广播减少 Driver 内存压力 默认的 BroadCastJoin 会将小表的内容, 全部收集到 Driver 中, 因此需要适当的调大 Driver 的内存 内存增加的计算公式为 : spark.sql.autobroadcastjointhreshold x the number of broadcast table x 2 当广播任务比较频繁的时候,Driver 有可能因为 OOM 而异常退出 此时, 可以开启 Executor 广播, 配置 Executor 广播参数 spark.sql.bigdata.useexecutorbroadcast 为 true, 减少 Driver 内存压力 文档版本 01 ( ) 330

341 7 Spark 应用开发 表 7-12 参数介绍 参数 描述 默认 值 spark.sql.bigdata.useexecu torbroadcast 设置为 true 时, 使用 Executor 广播, 将表数据缓存在 Executor 中, 而不是放在 Driver 之中, 减少 Spark Driver 内存的压力 true 参考信息 小表执行超时, 导致任务结束 默认情况下,BroadCastJoin 只允许小表计算 5 分钟, 超过 5 分钟该任务会出现超时异常, 而这个时候小表的 broadcast 任务依然在执行, 造成资源浪费 这种情况下, 有两种方式处理 : 调整 spark.sql.broadcasttimeout 的数值, 加大超时的时间限制 降低 spark.sql.autobroadcastjointhreshold 的数值, 不使用 BroadCastJoin 的优化 优化数据倾斜场景下的 SparkSQL 性能 配置场景 在 Spark SQL 多表 Join 的场景下, 会存在关联键严重倾斜的情况, 导致 Hash 分桶后, 部分桶中的数据远远高于其它分桶 最终导致部分 Task 过重, 跑得很慢 ; 其它 Task 过轻, 跑得很快 一方面, 数据量大 Task 运行慢, 使得计算性能低 ; 另一方面, 数据量少的 Task 在运行完成后, 导致很多 CPU 空闲, 造成 CPU 资源浪费 通过如下配置项可将部分数据采用 Broadcast 方式分发, 以便均衡 Task, 提高 CPU 资源的利用率, 从而提高性能 配置描述 说明 未产生倾斜的数据, 将采用原有方式进行分桶并运行 使用约束 : 1. 两表中倾斜的关联键不同 ( 例如 :A join B on A.name = B.name, 如果 A 中 name = "zhangsan" 倾斜, 那么 B 中 name = "zhangsan" 不能倾斜, 其他的如 name = "lisi" 可以倾斜 ) 2. 只支持两表间的 Join 3. TS(TableScan) 到 Join 之间的操作只支持 TS(TableScan) FIL(Filter) SEL (Select) 在客户端的 spark-defaults.conf 配置文件中调整如下参数 文档版本 01 ( ) 331

342 7 Spark 应用开发 表 7-13 参数说明 参数 描述 默认 值 spark.sql.planner.sk ewjoin spark.sql.planner.sk ewjoin.threshold 设置是否开启数据倾斜优化 true 表示开启 开启后, 会基于 spark.sql.planner.skewjoin.threshold 参数识别出倾斜关键, 系统会将这部分数据采用 Broadcast 方式, 可以避免大任务, 提升 CPU 利用率, 从而提升性能 用于判断是否存在数据倾斜的阈值 当存在关联键的个数大于该阈值, 则存在数据倾斜, 该关联键为倾斜关联键 false 优化小文件场景下的 SparkSQL 性能 配置场景 Spark SQL 的表中, 经常会存在很多小文件 ( 大小远小于 HDFS 块大小 ), 每个小文件默认对应 Spark 中的一个 Partition, 也就是一个 Task 在很多小文件场景下,Spark 会起很多 Task 当 SQL 逻辑中存在 Shuffle 操作时, 会大大增加 hash 分桶数, 严重影响性能 在小文件场景下, 您可以通过如下配置手动指定每个 Task 的数据量 (Split Size), 确保不会产生过多的 Task, 提高性能 说明 当 SQL 逻辑中不包含 Shuffle 操作时, 设置此配置项, 不会有明显的性能提升 配置描述 在客户端的 spark-defaults.conf 配置文件中调整如下参数 表 7-14 参数说明 参数描述默认值 spark.sql.small.fi le.combine spark.sql.small.fi le.split.size 用于设置是否开启小文件优化 true 表示开启 开启后, 可以避免过多的小 Task 合并小文件后, 用于指定单个 Task 期望的数据量 单位 :Byte false INSERT...SELECT 操作调优操作场景 在以下几种情况下, 执行 INSERT...SELECT 操作可以进行一定的调优操作 查询的数据是大量的小文件 文档版本 01 ( ) 332

343 7 Spark 应用开发 查询的数据是较多的大文件 在 beeline/thriftserver 模式下使用非 spark 用户操作 操作步骤 可对 INSERT...SELECT 操作做如下的调优操作 如果建的是 Hive 表, 将存储类型设为 Parquet, 从而减少执行 INSERT...SELECT 语句的时间 建议使用 spark-sql 或者在 beeline/thriftserver 模式下使用 spark 用户来执行 INSERT...SELECT 操作, 避免执行更改文件 owner 的操作, 从而减少执行 INSERT...SELECT 语句的时间 说明 在 beeline/thriftserver 模式下,executor 的用户跟 driver 是一致的,driver 是 thriftserver 服务的一部分, 是由 spark 用户启动的, 因此其用户也是 spark 用户, 且当前无法实现在运行时将 beeline 端的用户透传到 executor, 因此使用非 spark 用户时需要对文件进行更改 owner 为 beeline 端的用户, 即实际用户 如果查询的数据是大量的小文件将会产生大量 map 操作, 从而导致输出大量的小文件 在执行重命名文件操作时将会耗费较多时间, 此时可以通过设置 spark.sql.small.file.combine = true 来开启小文件合并功能来减少输出文件数, 减少执行重命名文件操作的时间, 从而减少执行 INSERT...SELECT 语句的时间 如果查询的数据是较多的大文件, 那么执行该操作的输出文件也会很多 当这个量级达到数十万时, 此时可以通过设置 spark.sql.small.file.combine = true 来开启小文件合并功能, 同时设置 spark.sql.small.file.split.size 为一个较合理的值, 控制输出文件大小, 减小输出文件个数 ( 原则是确保 reduce 任务能够充分利用集群资源, 否则会增加写文件的时间 ), 减少执行重命名文件的时间, 从而减少执行 INSERT...SELECT 语句的时间 说明 Spark Streaming 调优 操作场景 上述优化操作并不能解决全部的性能问题, 对于以下两种场景仍然需要较多时间 : 对于动态分区表, 如果其分区数非常多, 那么也需要执行较长的时间 如果查询的数据为大量的大文件, 那么即使开启小文件合并功能, 其输出文件也依旧很多, 那么也需要较长的时间 Streaming 作为一种 mini-batch 方式的流式处理框架, 它主要的特点是 : 秒级时延和高吞吐量 因此 Streaming 调优的目标 : 在秒级延迟的情景下, 提高 Streaming 的吞吐能力, 在单位时间处理尽可能多的数据 说明 本章节适用于输入数据源为 Kafka 的使用场景 操作步骤 一个简单的流处理系统由以下三部分组件组成 : 数据源 + 接收器 + 处理器 数据源为 Kafka, 接受器为 Streaming 中的 Kafka 数据源接收器, 处理器为 Streaming 对 Streaming 调优, 就必须使三个部件的性能都最优化 文档版本 01 ( ) 333

344 7 Spark 应用开发 数据源调优 在实际的应用场景中, 数据源为了保证数据的容错性, 会将数据保存在本地磁盘中, 而 Streaming 的计算结果往往全部在内存中完成, 数据源很有可能成为流式系统的最大瓶颈点 对 Kafka 的性能调优, 有以下几个点 : 使用 Kafka 以后版本, 可以使用异步模式的新 Producer 接口 配置多个 Broker 的目录, 设置多个 IO 线程, 配置 Topic 合理的 Partition 个数 详情请参见 Kafka 开源文档中的 性能调优 部分 : documentation.html 接收器调优 Streaming 中已有多种数据源的接收器, 例如 Kafka Flume MQTT ZeroMQ 等, 其中 Kafka 的接收器类型最多, 也是最成熟一套接收器 Kafka 包括三种模式的接收器 API: KafkaReceiver: 直接接收 Kafka 数据, 进程异常后, 可能出现数据丢失 ReliableKafkaReceiver: 通过 ZooKeeper 记录接收数据位移 DirectKafka: 直接通过 RDD 读取 Kafka 每个 Partition 中的数据, 数据高可靠 从实现上来看,DirectKafka 的性能会是最好的, 实际测试上来看,DirectKafka 也确实比其他两个 API 性能好 因此推荐使用 DirectKafka 的 API 实现接收器 数据接收器作为一个 Kafka 的消费者, 对于它的配置优化, 请参见 Kafka 开源文档 : 处理器调优 Streaming 的底层由 Spark 执行, 因此大部分对于 Spark 的调优措施, 都可以应用在 Streaming 之中, 例如 : 数据序列化 配置内存 设置并行度 使用 External Shuffle Service 提升性能 说明 Spark CBO 调优 操作场景 在做 Spark Streaming 的性能优化时需注意一点, 越追求性能上的优化,Streaming 整体的可靠性会越差 例如 : spark.streaming.receiver.writeaheadlog.enable 配置为 false 的时候, 会明显减少磁盘的操作, 提高性能, 但由于缺少 WAL 机制, 会出现异常恢复时, 数据丢失 因此, 在调优 Streaming 的时候, 这些保证数据可靠性的配置项, 在生产环境中是不能关闭的 SQL 语句转化为具体执行计划是由 SQL 查询编译器决定的, 同一个 SQL 语句可以转化成多种物理执行计划, 如何指导编译器选择效率最高的执行计划, 这就是优化器的主要作用 传统数据库 ( 例如 Oracle) 的优化器有两种 : 基于规则的优化器 (Rule-Based Optimization,RBO) 和基于代价的优化器 (Cost-Based Optimization,CBO) RBO RBO 使用的规则是根据经验形成的, 只要按照这个规则去写 SQL 语句, 无论数据表中的内容怎样 数据分布如何, 都不会影响到执行计划 文档版本 01 ( ) 334

345 7 Spark 应用开发 CBO CBO 是根据实际数据分布和组织情况, 评估每个计划的执行代价, 从而选择代价最小的执行计划 目前 Spark 的优化器都是基于 RBO 的, 已经有数十条优化规则, 例如谓词下推 常量折叠 投影裁剪等, 这些规则是有效的, 但是它对数据是不敏感的 导致的问题是数据表中数据分布发生变化时,RBO 是不感知的, 基于 RBO 生成的执行计划不能确保是最优的 而 CBO 的重要作用就是能够根据实际数据分布估算出 SQL 语句, 生成一组可能被使用的执行计划中代价最小的执行计划, 从而提升性能 目前 CBO 主要的优化点是 Join 算法选择 举个简单例子, 当两个表做 Join 操作, 如果其中一张原本很大的表经过 Filter 操作之后结果集小于 BroadCast 的阈值, 在没有 CBO 情况下是无法感知大表过滤后变小的情况, 采用的是 SortMergeJoin 算法, 涉及到大量 Shuffle 操作, 很耗费性能 ; 在有 CBO 的情况下是可以感知到结果集的变化, 采用的是 BroadcastHashJoin 算法, 会将过滤后的小表 BroadCast 到每个节点, 转变为非 Shuffle 操作, 从而大大提高性能 操作步骤 Spark CBO 的设计思路是, 基于表和列的统计信息, 对各个操作算子 (Operator) 产生的中间结果集大小进行估算, 最后根据估算的结果来选择最优的执行计划 1. 设置配置项 在 spark-defaults.conf 配置文件中增加配置项 spark.sql.cbo, 将其设置为 true, 默认为 false 在客户端执行 SQL 语句 set spark.sql.cbo=true 进行配置 2. 执行统计信息生成命令, 得到统计信息 说明 此步骤只需在运行所有 SQL 前执行一次 如果数据集发生了变化 ( 插入 更新或删除 ), 为保证 CBO 的优化效果, 需要对有变化的表或者列再次执行统计信息生成命令重新生成统计信息, 以得到最新的数据分布情况 表 : 执行 COMPUTE STATS FOR TABLE src 命令计算表的统计信息, 统计信息包括记录条数 文件数和物理存储总大小 列 : 3. CBO 调优 执行 COMPUTE STATS FOR TABLE src ON COLUMNS 命令计算所有列的统计信息 执行 COMPUTE STATS FOR TABLE src ON COLUMNS name,age 命令计算表中 name 和 age 两个字段的统计信息 当前列的统计信息支持四种类型 : 数值类型 日期类型 时间类型和字符串类型 对于数值类型 日期类型和时间类型, 统计信息包括 : Max Min 不同值个数 (Number of Distinct Value,NDV) 空值个数 (Number of Null) 和 Histogram( 支持等宽 等高直方图 ); 对于字符串类型, 统计信息包括 :Max Min Max Length Average Length 不同值个数 (Number of Distinct Value,NDV) 空值个数 (Number of Null) 和 Histogram( 支持等宽直方图 ) 自动优化 : 用户根据自己的业务场景, 输入 SQL 语句查询, 程序会自动去判断输入的 SQL 语句是否符合优化的场景, 从而自动选择 Join 优化算法 手动优化 : 用户可以通过 DESC FORMATTED src 命令查看统计信息, 根据统计信息的分布, 人工优化 SQL 语句 文档版本 01 ( ) 335

346 7 Spark 应用开发 7.6 Spark 接口 Java 由于 Spark 开源版本升级, 为避免出现 API 兼容性或可靠性问题, 建议用户使用配套版本的开源 API Spark Core 常用接口 Spark 主要使用到如下这几个类 : JavaSparkContext: 是 Spark 的对外接口, 负责向调用该类的 Java 应用提供 Spark 的各种功能, 如连接 Spark 集群, 创建 RDD, 累积量和广播量等 它的作用相当于一个容器 SparkConf:Spark 应用配置类, 如设置应用名称, 执行模式,executor 内存等 JavaRDD: 用于在 java 应用中定义 JavaRDD 的类, 功能类似于 scala 中的 RDD(Resilient Distributed Dataset) 类 JavaPairRDD: 表示 key-value 形式的 JavaRDD 类 提供的方法有 groupbykey, reducebykey 等 Broadcast: 广播变量类 广播变量允许保留一个只读的变量, 缓存在每一台机器上, 而非每个任务保存一份拷贝 StorageLevel: 数据存储级别 有内存 (MEMORY_ONLY), 磁盘 (DISK_ONLY), 内存 + 磁盘 (MEMORY_AND_DISK) 等 JavaRDD 支持两种类型的操作 :Transformation 和 Action, 这两种类型的常用方法如表 7-15 和表 7-16 表 7-15 Transformation 方法 <R> JavaRDD<R> map(function<t,r> f) JavaRDD<T> filter(function<t,boolea n> f) <U> JavaRDD<U> flatmap(flatmapfunctio n<t,u> f) JavaRDD<T> sample(boolean withreplacement, double fraction, long seed) JavaRDD<T> distinct(int numpartitions) 说明对 RDD 中的每个 element 使用 Function 对 RDD 中所有元素调用 Function, 返回为 true 的元素 先对 RDD 所有元素调用 Function, 然后将结果扁平化 抽样 去除重复元素 文档版本 01 ( ) 336

347 7 Spark 应用开发 方法 JavaPairRDD<K,Iterable <V>> groupbykey(int numpartitions) JavaPairRDD<K,V> reducebykey(function2 <V,V,V> func, int numpartitions) JavaPairRDD<K,V> sortbykey(boolean ascending, int numpartitions) JavaPairRDD<K,scala.T uple2<v,w>> join(javapairrdd<k,w > other) JavaPairRDD<K,scala.T uple2<iterable<v>,iterab le<w>>> cogroup(javapairrdd< K,W> other, int numpartitions) JavaPairRDD<T,U> cartesian(javarddlike< U,?> other) 说明 返回 (K,Seq[V]), 将 key 相同的 value 组成一个集合 对 key 相同的 value 调用 Function 按照 key 来进行排序,ascending 为 true 时是升序否则为降序 当有两个 KV 的 dataset(k,v) 和 (K,W), 返回的是 (K,(V,W)) 的 dataset,numtasks 为并发的任务数 当有两个 KV 的 dataset(k,v) 和 (K,W), 返回的是 <K,scala.Tuple2<Iterable<V>,Iterable<W>>> 的 dataset, numtasks 为并发的任务数 返回该 RDD 与其它 RDD 的笛卡尔积 表 7-16 Action 方法 T reduce(function2<t,t,t > f) java.util.list<t> collect() long count() T first() java.util.list<t> take(int num) java.util.list<t> takesample(boolean withreplacement, int num, long seed) 说明 对 RDD 中的元素调用 Function2 返回包含 RDD 中所有元素的一个数组 返回的是 dataset 中的 element 的个数 返回的是 dataset 中的第一个元素 返回前 n 个 elements 对 dataset 随机抽样, 返回有 num 个元素组成的数组 withreplacement 表示是否使用 replacement 文档版本 01 ( ) 337

348 7 Spark 应用开发 方法 void saveastextfile(string path, Class<? extends org.apache.hadoop.io.co mpress.compressioncod ec> codec) java.util.map<k,object> countbykey() void foreach(voidfunction<t > f) java.util.map<t,long> countbyvalue() 说明 把 dataset 写到一个 text file hdfs 或者 hdfs 支持的文件系统中,spark 把每条记录都转换为一行记录, 然后写到 file 中 对每个 key 出现的次数做统计 在数据集的每一个元素上, 运行函数 func 对 RDD 中每个元素出现的次数进行统计 Spark Streaming 常用接口 Spark Streaming 中常见的类有 : JavaStreamingContext: 是 Spark Streaming 功能的主入口, 负责提供创建 DStreams 的方法, 入参中需要设置批次的时间间隔 JavaDStream: 是一种代表 RDDs 连续序列的数据类型, 代表连续数据流 JavaPairDStream:KV DStream 的接口, 提供 reducebykey 和 join 等操作 JavaReceiverInputDStream<T>: 定义任何从网络接受数据的输入流 Spark Streaming 的常见方法与 Spark Core 类似, 下表罗列了 Spark Streaming 特有的一些方法 表 7-17 Spark Streaming 方法 方法 JavaReceiverInputDStrea m<java.lang.string> socketstream(java.lang.s tring hostname,int port) JavaDStream<java.lang. String> textfilestream(java.lang. String directory) void start() 说明 创建一个输入流, 通过 TCP socket 从对应的 hostname 和端口接受数据 接受的字节被解析为 UTF8 格式 默认的存储级别为 Memory+Disk 入参 directory 为 HDFS 目录, 该方法创建一个输入流检测可兼容 Hadoop 文件系统的新文件, 并且读取为文本文件 启动 Streaming 计算 void awaittermination() 当前进程等待终止, 如 Ctrl+C 等 void stop() 终止 Streaming 计算 文档版本 01 ( ) 338

349 7 Spark 应用开发 方法 <T> JavaDStream<T> transform(java.util.list<j avadstream<?>> dstreams,function2<java.util.list<javardd<? >>,Time,JavaRDD<T>> transformfunc) <T> JavaDStream<T> union(javadstream<t> first,java.util.list<javad Stream<T>> rest) 说明 对每个 RDD 进行 function 操作, 得到一个新的 DStream 这个函数中 JavaRDDs 的顺序和 list 中对应的 DStreams 保持一致 从多个具备相同类型和滑动时间的 DStream 中创建统一的 DStream 表 7-18 Streaming 增强特性接口 方法 JAVADStreamKafkaWriter.writeToKafka() JAVADStreamKafkaWriter.writeToKafkaBy Single() 说明 支持将 DStream 中的数据批量写入到 Kafka 支持将 DStream 中的数据逐条写入到 Kafka Spark SQL 常用接口 Spark SQL 中重要的类有 : SQLContext: 是 Spark SQL 功能和 DataFrame 的主入口 DataFrame: 是一个以命名列方式组织的分布式数据集 DataFrameReader: 从外部存储系统加载 DataFrame 的接口 DataFrameStatFunctions: 实现 DataFrame 的统计功能 UserDefinedFunction: 用户自定义的函数 常见的 Actions 方法有 : 表 7-19 Spark SQL 方法介绍 方法 Row[] collect() long count() DataFrame describe(java.lang.string... cols) Row first() 说明 返回一个数组, 包含 DataFrame 的所有列 返回 DataFrame 的行数 计算统计信息, 包含计数, 平均值, 标准差, 最小值和最大值 返回第一行 文档版本 01 ( ) 339

350 7 Spark 应用开发 方法 说明 Row[] head(int n) 返回前 n 行 void show() 用表格形式显示 DataFrame 的前 20 行 Row[] take(int n) 返回 DataFrame 中的前 n 行 表 7-20 基本的 DataFrame Functions 介绍 方法 void explain(boolean extended) void printschema() registertemptable DataFrame todf(java.lang.string... colnames) DataFrame sort(java.lang.string sortcol,java.lang.string... sortcols) GroupedData rollup(column... cols) 说明 打印出 SQL 语句的逻辑计划和物理计划 打印 schema 信息到控制台 将 DataFrame 注册为一张临时表, 其周期和 SQLContext 绑定在一起 返回一个列重命名的 DataFrame 根据不同的列, 按照升序或者降序排序 对当前的 DataFrame 特定列进行多维度的回滚操作 Scala 由于 Spark 开源版本升级, 为避免出现 API 兼容性或可靠性问题, 建议用户使用配套版本的开源 API Spark Core 常用接口 Spark 主要使用到如下这几个类 : SparkContext: 是 Spark 的对外接口, 负责向调用该类的 scala 应用提供 Spark 的各种功能, 如连接 Spark 集群, 创建 RDD 等 SparkConf:Spark 应用配置类, 如设置应用名称, 执行模式,executor 内存等 RDD(Resilient Distributed Dataset): 用于在 Spark 应用程序中定义 RDD 的类, 该类提供数据集的操作方法, 如 map,filter PairRDDFunctions: 为 key-value 对的 RDD 数据提供运算操作, 如 groupbykey Broadcast: 广播变量类 广播变量允许保留一个只读的变量, 缓存在每一台机器上, 而非每个任务保存一份拷贝 文档版本 01 ( ) 340

351 7 Spark 应用开发 StorageLevel: 数据存储级别 有内存 (MEMORY_ONLY), 磁盘 (DISK_ONLY), 内存 + 磁盘 (MEMORY_AND_DISK) 等 RDD 上支持两种类型的操作 :Transformation 和 Action, 这两种类型的常用方法如表 7-21 和表 7-22 所示 表 7-21 Transformation 方法 map[u](f: (T) => U): RDD[U] filter(f: (T) => Boolean): RDD[T] flatmap[u](f: (T) => TraversableOnce[U]) (implicit arg0: ClassTag[U]): RDD[U] sample(withreplacement : Boolean, fraction: Double, seed: Long = Utils.random.nextLong): RDD[T] union(other: RDD[T]): RDD[T] distinct([numpartitions: Int]): RDD[T] groupbykey(): RDD[(K, Iterable[V])] reducebykey(func: (V, V) => V[, numpartitions: Int]): RDD[(K, V)] sortbykey(ascending: Boolean = true, numpartitions: Int = self.partitions.length): RDD[(K, V)] join[w](other: RDD[(K, W)][, numpartitions: Int]): RDD[(K, (V, W))] cogroup[w](other: RDD[(K, W)], numpartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))] 说明 对调用 map 的 RDD 数据集中的每个 element 都使用 f 方法, 生成新的 RDD 对 RDD 中所有元素调用 f 方法, 生成将满足条件数据集以 RDD 形式返回 先对 RDD 所有元素调用 f 方法, 然后将结果扁平化, 生成新的 RDD 抽样, 返回 RDD 一个子集 返回一个新的 RDD, 包含源 RDD 和给定 RDD 的元素的集合 去除重复元素, 生成新的 RDD 返回 (K,Iterable[V]), 将 key 相同的 value 组成一个集合 对 key 相同的 value 调用 func 按照 key 来进行排序, 是升序还是降序,ascending 是 boolean 类型 当有两个 KV 的 dataset(k,v) 和 (K,W), 返回的是 (K,(V,W)) 的 dataset,numpartitions 为并发的任务数 将当有两个 key-value 对的 dataset(k,v) 和 (K,W), 返回的是 (K, (Iterable[V], Iterable[W])) 的 dataset,numpartitions 为并发的任务数 文档版本 01 ( ) 341

352 7 Spark 应用开发 方法 cartesian[u](other: RDD[U])(implicit arg0: ClassTag[U]): RDD[(T, U)] 说明 返回该 RDD 与其它 RDD 的笛卡尔积 表 7-22 Action 方法 说明 reduce(f: (T, T) => T): 对 RDD 中的元素调用 f collect(): Array[T] count(): Long first(): T 返回包含 RDD 中所有元素的一个数组 返回的是 dataset 中的 element 的个数 返回的是 dataset 中的第一个元素 take(num: Int): Array[T] 返回前 n 个 elements takesample(withreplace ment: Boolean, num: Int, seed: Long = Utils.random.nextLong): Array[T] saveastextfile(path: String): Unit saveassequencefile(pat h: String, codec: Option[Class[_ <: CompressionCodec]] = None): Unit countbykey(): Map[K, Long] foreach(func: (T) => Unit): Unit countbyvalue()(implicit ord: Ordering[T] = null): Map[T, Long] takesample(withreplacement,num,seed) 对 dataset 随机抽样, 返回有 num 个元素组成的数组 withreplacement 表示是否使用 replacement 把 dataset 写到一个 text file HDFS 或者 HDFS 支持的文件系统中,spark 把每条记录都转换为一行记录, 然后写到 file 中 只能用在 key-value 对上, 然后生成 SequenceFile 写到本地或者 hadoop 文件系统 对每个 key 出现的次数做统计 在数据集的每一个元素上, 运行函数 func 对 RDD 中每个元素出现的次数进行统计 Spark Streaming 常用接口 Spark Streaming 中常见的类有 : StreamingContext: 是 Spark Streaming 功能的主入口, 负责提供创建 DStreams 的方法, 入参中需要设置批次的时间间隔 文档版本 01 ( ) 342

353 7 Spark 应用开发 dstream.dstream: 是一种代表 RDDs 连续序列的数据类型, 代表连续数据流 dstream.paridstreamfunctions: 键值对的 DStream, 常见的操作如 groupbykey 和 reducebykey 对应的 Spark Streaming 的 JAVA API 是 JavaSteamingContext,JavaDStream 和 JavaPairDStream Spark Streaming 的常见方法与 Spark Core 类似, 下表罗列了 Spark Streaming 特有的一些方法 表 7-23 Spark Streaming 方法介绍 方法 sockettextstream(hostna me: String, port: Int, storagelevel: StorageLevel = StorageLevel.MEMORY _AND_DISK_SER_2): ReceiverInputDStream[S tring] start():unit awaittermination(timeou t: long):unit stop(stopsparkcontext: Boolean, stopgracefully: Boolean): Unit transform[t](dstreams: Seq[DStream[_]], transformfunc: (Seq[RDD[_]], Time)? RDD[T])(implicit arg0: ClassTag[T]): DStream[T] UpdateStateByKey(func) window(windowlength, slideinterval) countbywindow(windo wlength, slideinterval) reducebywindow(func, windowlength, slideinterval) join(otherstream, [numtasks]) 说明 用 TCP 协议 ( 源主机 : 端口 ) 创建一个输入流 启动 Streaming 计算 当前进程等待终止, 如 Ctrl+C 等 终止 Streaming 计算 对每一个 RDD 应用 function 操作得到一个新的 DStream 更新 DStream 的状态 使用此方法, 需要定义状态和状态更新函数 根据源 DStream 的窗口批次计算得到一个新的 DStream 返回流中滑动窗口元素的个数 当调用在 DStream 的 KV 对上, 返回一个新的 DStream 的 KV 对, 其中每个 Key 的 Value 根据滑动窗口中批次的 reduce 函数聚合得到 实现不同的 Spark Streaming 之间做合并操作 文档版本 01 ( ) 343

354 7 Spark 应用开发 方法 DStreamKafkaWriter.wri tetokafka() DStreamKafkaWriter.wri tetokafkabysingle() 说明 支持将 DStream 中的数据批量写入到 Kafka 支持将 DStream 中的数据逐条写入到 Kafka 表 7-24 Streaming 增强特性接口 方法 DStreamKafkaWriter.wri tetokafka() DStreamKafkaWriter.wri tetokafkabysingle() 说明 支持将 DStream 中的数据批量写入到 Kafka 支持将 DStream 中的数据逐条写入到 Kafka SparkSQL 常用接口 Spark SQL 中常用的类有 : SQLContext: 是 Spark SQL 功能和 DataFrame 的主入口 DataFrame: 是一个以命名列方式组织的分布式数据集 HiveContext: 获取存储在 Hive 中数据的主入口 表 7-25 常用的 Actions 方法 方法 collect(): Array[Row] count(): Long describe(cols: String*): DataFrame first(): Row 说明 返回一个数组, 包含 DataFrame 的所有列 返回 DataFrame 中的行数 计算统计信息, 包含计数, 平均值, 标准差, 最小值和最大值 返回第一行 Head(n:Int): Row 返回前 n 行 show(numrows: Int, truncate: Boolean): Unit 用表格形式显示 DataFrame take(n:int): Array[Row] 返回 DataFrame 中的前 n 行 文档版本 01 ( ) 344

355 7 Spark 应用开发 表 7-26 基本的 DataFrame Functions 方法 explain(): Unit printschema(): Unit registertemptable(table Name: String): Unit todf(colnames: String*): DataFrame 说明 打印出 SQL 语句的逻辑计划和物理计划 打印 schema 信息到控制台 将 DataFrame 注册为一张临时表, 其周期和 SQLContext 绑定在一起 返回一个列重命名的 DataFrame Python Spark Core 常用接口 由于 Spark 开源版本升级, 为避免出现 API 兼容性或可靠性问题, 建议用户使用配套版本的开源 API Spark 主要使用到如下这几个类 : pyspark.sparkcontext: 是 Spark 的对外接口 负责向调用该类的 python 应用提供 Spark 的各种功能, 如连接 Spark 集群 创建 RDD 广播变量等 pyspark.sparkconf:spark 应用配置类 如设置应用名称, 执行模式,executor 内存等 pyspark.rdd(resilient Distributed Dataset): 用于在 Spark 应用程序中定义 RDD 的类, 该类提供数据集的操作方法, 如 map,filter pyspark.broadcast: 广播变量类 广播变量允许保留一个只读的变量, 缓存在每一台机器上, 而非每个任务保存一份拷贝 pyspark.storagelevel: 数据存储级别 有内存 (MEMORY_ONLY), 磁盘 (DISK_ONLY), 内存 + 磁盘 (MEMORY_AND_DISK) 等 pyspark.sql.sqlcontext: 是 SparkSQL 功能的主入口 可用于创建 DataFrame, 注册 DataFrame 为一张表, 表上执行 SQL 等 pyspark.sql.dataframe: 分布式数据集 DataFrame 等效于 SparkSQL 中的关系表, 可被 SQLContext 中的方法创建 pyspark.sql.dataframenafunctions:dataframe 中处理数据缺失的函数 pyspark.sql.dataframestatfunctions:dataframe 中统计功能的函数, 可以计算列之间的方差, 样本协方差等 RDD 上支持两种类型的操作 :transformation 和 action, 这两种类型的常用方法如表 7-27 和表 7-28 文档版本 01 ( ) 345

356 7 Spark 应用开发 表 7-27 Transformation 方法 map(f, preservespartitioning=fa lse) filter(f) flatmap(f, preservespartitioning=fa lse) sample(withreplacement, fraction, seed=none) union(rdds) distinct([numpartitions: Int]): RDD[T] groupbykey(): RDD[(K, Iterable[V])] reducebykey(func, numpartitions=none) sortbykey(ascending=tr ue, numpartitions=none, keyfunc=function <lambda>) join(other, numpartitions) cogroup(other, numpartitions) cartesian(other) 说明 对调用 map 的 RDD 数据集中的每个 element 都使用 Func, 生成新的 RDD 对 RDD 中所有元素调用 Func, 生成将满足条件数据集以 RDD 形式返回 先对 RDD 所有元素调用 Func, 然后将结果扁平化, 生成新的 RDD 抽样, 返回 RDD 一个子集 返回一个新的 RDD, 包含源 RDD 和给定 RDD 的元素的集合 去除重复元素, 生成新的 RDD 返回 (K,Iterable[V]), 将 key 相同的 value 组成一个集合 对 key 相同的 value 调用 Func 按照 key 来进行排序, 是升序还是降序,ascending 是 boolean 类型 当有两个 KV 的 dataset(k,v) 和 (K,W), 返回的是 (K,(V,W)) 的 dataset,numpartitions 为并发的任务数 将当有两个 key-value 对的 dataset(k,v) 和 (K,W), 返回的是 (K, (Iterable[V], Iterable[W])) 的 dataset,numpartitions 为并发的任务数 返回该 RDD 与其它 RDD 的笛卡尔积 表 7-28 Action 方法 说明 reduce(f) 对 RDD 中的元素调用 Func collect() count() first() 返回包含 RDD 中所有元素的一个数组 返回的是 dataset 中的 element 的个数 返回的是 dataset 中的第一个元素 文档版本 01 ( ) 346

357 7 Spark 应用开发 方法 说明 take(num) 返回前 num 个 elements takesample(withreplace ment, num, seed) saveastextfile(path, compressioncodecclass) saveassequencefile(pat h, compressioncodecclass =None) countbykey() foreach(func) countbyvalue() takesample(withreplacement,num,seed) 对 dataset 随机抽样, 返回有 num 个元素组成的数组 withreplacement 表示是否使用 replacement 把 dataset 写到一个 text file HDFS 或者 HDFS 支持的文件系统中,spark 把每条记录都转换为一行记录, 然后写到 file 中 只能用在 key-value 对上, 然后生成 SequenceFile 写到本地或者 hadoop 文件系统 对每个 key 出现的次数做统计 在数据集的每一个元素上, 运行函数 对 RDD 中每个不同 value 出现的次数进行统计 Spark Streaming 常用接口 Spark Streaming 中常见的类有 : pyspark.streaming.streamingcontext: 是 Spark Streaming 功能的主入口, 负责提供创建 DStreams 的方法, 入参中需要设置批次的时间间隔 pyspark.streaming.dstream: 是一种代表 RDDs 连续序列的数据类型, 代表连续数据流 dstream.paridstreamfunctions: 键值对的 DStream, 常见的操作如 groupbykey 和 reducebykey 对应的 Spark Streaming 的 JAVA API 是 JavaSteamingContext,JavaDStream 和 JavaPairDStream Spark Streaming 的常见方法与 Spark Core 类似, 下表罗列了 Spark Streaming 特有的一些方法 表 7-29 Spark Streaming 常用接口介绍 方法 sockettextstream(hostna me, port, storagelevel) start() awaittermination(timeou t) 说明从 TCP 源主机 : 端口创建一个输入流 启动 Streaming 计算 当前进程等待终止, 如 Ctrl+C 等 文档版本 01 ( ) 347

358 7 Spark 应用开发 方法 stop(stopsparkcontext, stopgracefully) UpdateStateByKey(func) window(windowlength, slideinterval) countbywindow(windo wlength, slideinterval) reducebywindow(func, windowlength, slideinterval) join(other,numpartitions) 说明 终止 Streaming 计算,stopSparkContext 用于判断是否需要终止相关的 SparkContext,StopGracefully 用于判断是否需要等待所有接受到的数据处理完成 更新 DStream 的状态 使用此方法, 需要定义 State 和状态更新函数 根据源 DStream 的窗口批次计算得到一个新的 DStream 返回流中滑动窗口元素的个数 当调用在 DStream 的 KV 对上, 返回一个新的 DStream 的 KV 对, 其中每个 Key 的 Value 根据滑动窗口中批次的 reduce 函数聚合得到 实现不同的 Spark Streaming 之间做合并操作 SparkSQL 常用接口 Spark SQL 中在 Python 中重要的类有 : pyspark.sql.sqlcontext: 是 Spark SQL 功能和 DataFrame 的主入口 pyspark.sql.dataframe: 是一个以命名列方式组织的分布式数据集 pyspark.sql.hivecontext: 获取存储在 Hive 中数据的主入口 pyspark.sql.dataframestatfunctions: 统计功能中一些函数 pyspark.sql.functions:dataframe 中内嵌的函数 pyspark.sql.window:sql 中提供窗口功能 表 7-30 Spark SQL 常用的 Action 方法 collect() count() describe() first() 说明 返回一个数组, 包含 DataFrame 的所有列 返回 DataFrame 中的行数 计算统计信息, 包含计数, 平均值, 标准差, 最小值和最大值 返回第一行 head(n) 返回前 n 行 show() 用表格形式显示 DataFrame take(num) 返回 DataFrame 中的前 num 行 文档版本 01 ( ) 348

359 7 Spark 应用开发 表 7-31 基本的 DataFrame Functions 方法 explain() printschema() registertemptable(name ) 说明 打印出 SQL 语句的逻辑计划和物理计划 打印 schema 信息到控制台 将 DataFrame 注册为一张临时表, 命名为 name, 其周期和 SQLContext 绑定在一起 todf() 返回一个列重命名的 DataFrame REST API 功能简介 准备运行环境 REST 接口 Spark 的 REST API 以 JSON 格式展现 Web UI 的一些指标, 提供用户一种更简单的方法去创建新的展示和监控的工具, 并且支持查询正在运行的 app 和已经结束的 app 的相关信息 开源的 Spark REST 接口支持对 Jobs Stages Storage Environment 和 Executors 的信息进行查询,MRS 版本中添加了查询 SQL JDBC/ODBC Server 和 Streaming 的信息的 REST 接口 开源 REST 接口完整和详细的描述请参考官网上的文档以了解其使用方法 : 安装客户端 在节点上安装客户端, 如安装到 /opt/client 目录 1. 确认服务端 Spark 组件已经安装, 并正常运行 2. 客户端运行环境已安装 1.7 或 1.8 版本的 JDK 3. 获取并解压缩安装包 MRS_Spark_Client.tar 执行如下命令解压 tar -xvf MRS_Spark_Client.tar tar -xvf MRS_Spark_ClientConfig.tar 说明 由于不兼容老版本客户端, 建议用户获取与服务端集群相同版本的客户端安装包进行安装部署 4. 进入解压文件夹, 即 MRS_Spark_ClientConfig, 执行下列命令安装客户端 sh install.sh /opt/client 其中 /opt/client 为用户自定义路径, 此处仅为举例 5. 进入客户端安装目录 /opt/client, 执行下列命令初始化环境变量 source bigdata_env 通过以下命令可跳过 REST 接口过滤器获取相应的应用信息 获取 JobHistory 中所有应用信息 : 命令 : curl --insecure 文档版本 01 ( ) 349

360 7 Spark 应用开发 其中 为 JobHistory 节点的业务 IP,22500 为 JobHistory 的端口号 结果 : [ { "id" : "application_ _0042", "name" : "Spark-JDBCServer", "attempts" : [ { "starttime" : " T16:57:15.237CST", "endtime" : " T17:01:22.573CST", "lastupdated" : " T17:01:22.614CST", "duration" : , "sparkuser" : "spark", "completed" : true ], { "id" : "application_ _0047-part1", "name" : "SparkSQL:: ", "attempts" : [ { "starttime" : " T11:57:36.626CST", "endtime" : " T07:59:59.999CST", "lastupdated" : " T11:57:48.613CST", "duration" : 0, "sparkuser" : "admin", "completed" : false ] ] 结果分析 : 通过这个命令, 可以查询当前集群中所有的 Spark 应用 ( 包括正在运行的应用和已经完成的应用 ), 每个应用的信息如下表 1 表 7-32 应用常用信息 参数 id name attempts 描述 应用的 ID 应用的 Name 应用的尝试, 包含了开始时间 结束时间 执行用户 是否完成等信息 获取 JobHistory 中某个应用的信息 : 命令 : curl mode=monitoring --insecure 其中 为 JobHistory 节点的业务 IP,22500 为 JobHistory 的端口号, application_ _0042 为应用的 id 结果 : { "id" : "application_ _0042", "name" : "Spark-JDBCServer", "attempts" : [ { "starttime" : " T16:57:15.237CST", "endtime" : " T17:01:22.573CST", "lastupdated" : " T17:01:22.614CST", "duration" : , "sparkuser" : "spark", "completed" : true ] 文档版本 01 ( ) 350

361 7 Spark 应用开发 结果分析 : 通过这个命令, 可以查询某个 Spark 应用的信息, 显示的信息如表 1 所示 获取正在执行的某个应用的 Executor 信息 : 针对 alive executor 命令 : curl applications/application_ _0046/executors?mode=monitoring --insecure 针对全部 executor(alive&dead) 命令 : curl applications/application_ _0046/allexecutors?mode=monitoring --insecure 其中 为 ResourceManager 主节点的业务 IP,26001 为 ResourceManager 的端口号,application_ _0046 为在 YARN 中的应用 ID 结果 : [{ "id" : "driver", "hostport" : " :23886", "isactive" : true, "rddblocks" : 0, "memoryused" : 0, "diskused" : 0, "activetasks" : 0, "failedtasks" : 0, "completedtasks" : 0, "totaltasks" : 0, "totalduration" : 0, "totalinputbytes" : 0, "totalshuffleread" : 0, "totalshufflewrite" : 0, "maxmemory" : , "executorlogs" : {, { "id" : "1", "hostport" : " :23902", "isactive" : true, "rddblocks" : 0, "memoryused" : 0, "diskused" : 0, "activetasks" : 0, "failedtasks" : 0, "completedtasks" : 0, "totaltasks" : 0, "totalduration" : 0, "totalinputbytes" : 0, "totalshuffleread" : 0, "totalshufflewrite" : 0, "maxmemory" : , "executorlogs" : { "stdout" : " container_ _0049_01_000002/admin/stdout?start=-4096", "stderr" : " container_ _0049_01_000002/admin/stderr?start=-4096" ] 结果分析 : 通过这个命令, 可以查询当前应用的所有 Executor 信息 ( 包括 Driver), 每个 Executor 的信息包含如下表 2 所示的常用信息 文档版本 01 ( ) 351

362 7 Spark 应用开发 表 7-33 Executor 常用信息 参数 id hostport executorlogs 描述 Executor 的 ID Executor 所在节点的 ip: 端口 Executor 的日志查看路径 REST API 增强 SQL 相关的命令 : 获取所有 SQL 语句和执行时间最长的 SQL 语句 SparkUI 命令 : curl applications/spark-jdbcserverapplication_ _0053/sql?mode=monitoring -- insecure 其中 为 ResourceManager 主节点的业务 IP,26001 为 ResourceManager 的端口号,application_ _0053 为在 YARN 中的应用 ID,Spark-JDBCServer 是 Spark 应用的 name JobHistory 命令 : curl part1/sql?mode=monitoring --insecure 其中 为 JobHistory 节点的业务 IP,22500 为 JobHistory 的端口号, application_ _0004-part1 为应用 ID 结果 : SparkUI 命令和 JobHistory 命令的查询结果均为 : { "longestdurationofcompletedsql" : [ { "id" : 0, "status" : "COMPLETED", "description" : "getcallsite at SQLExecution.scala:48", "submissiontime" : "2016/11/08 15:39:00", "duration" : "2 s", "runningjobs" : [ ], "successedjobs" : [ 0 ], "failedjobs" : [ ] ], "sqls" : [ { "id" : 0, "status" : "COMPLETED", "description" : "getcallsite at SQLExecution.scala:48", "submissiontime" : "2016/11/08 15:39:00", "duration" : "2 s", "runningjobs" : [ ], "successedjobs" : [ 0 ], "failedjobs" : [ ] ] 结果分析 : 通过这个命令, 可以查询当前应用的所有 SQL 语句的信息 ( 即结果中 sqls 的部分 ), 执行时间最长的 SQL 语句的信息 ( 即结果中 longestdurationofcompletedsql 的部分 ) 每个 SQL 语句的信息如下表 3 文档版本 01 ( ) 352

363 7 Spark 应用开发 表 7-34 SQL 的常用信息 参数 id status runningjobs successedjobs failedjobs 描述 SQL 语句的 ID SQL 语句的执行状态, 有 RUNNING COMPLETED FAILED 三种 SQL 语句产生的 job 中, 正在执行的 job 列表 SQL 语句产生的 job 中, 执行成功的 job 列表 SQL 语句产生的 job 中, 执行失败的 job 列表 JDBC/ODBC Server 相关的命令 : 获取连接数, 正在执行的 SQL 数, 所有 session 信息, 所有 SQL 的信息 命令 : curl applications/application_ _0053/sqlserver?mode=monitoring --insecure 其中 为 ResourceManager 主节点的业务 IP,26001 为 ResourceManager 的端口号,application_ _0053 为在 YARN 中的应用 ID 结果 : { "sessionnum" : 1, "runningsqlnum" : 0, "sessions" : [ { "user" : "spark", "ip" : " ", "sessionid" : "9dfec575-48b a-71711d3d7a97", "starttime" : "2016/10/29 15:21:10", "finishtime" : "", "duration" : "1 minute 50 seconds", "totalexecute" : 1 ], "sqls" : [ { "user" : "spark", "jobid" : [ ], "groupid" : "e49ff81a-230f-4892-a209-a48abea2d969", "starttime" : "2016/10/29 15:21:13", "finishtime" : "2016/10/29 15:21:14", "duration" : "555 ms", "statement" : "show tables", "state" : "FINISHED", "detail" : "== Parsed Logical Plan ==\nshowtablescommand None\n\n== Analyzed Logical Plan ==\ntablename: string, istemporary: boolean\nshowtablescommand None\n\n== Cached Logical Plan ==\nshowtablescommand None\n\n== Optimized Logical Plan == \nshowtablescommand None\n\n== Physical Plan ==\nexecutedcommand ShowTablesCommand None \n\ncode Generation: true" ] 结果分析 : 通过这个命令, 可以查询当前 JDBC/ODBC 应用的 session 连接数, 正在执行的 SQL 数, 所有的 session 和 SQL 信息 每个 session 的信息如下表 4, 每个 SQL 的信息如下表 7-36 文档版本 01 ( ) 353

364 7 Spark 应用开发 表 7-35 session 常用信息 参数 user ip sessionid starttime finishtime duration totalexecute 描述该 session 连接的用户 session 所在的节点 IP session 的 ID session 开始连接的时间 session 结束连接的时间 session 连接时长在该 session 上执行的 SQL 数 表 7-36 sql 常用信息 参数 user jobid groupid starttime finishtime duration statement detail 描述 SQL 执行的用户 SQL 语句包含的 job id 列表 SQL 所在的 group id SQL 开始时间 SQL 结束时间 SQL 执行时长对应的语句对应的逻辑计划, 物理计划 Streaming 相关的命令 : 获取平均输入频率, 平均调度时延, 平均执行时长, 总时延平均值 命令 : curl applications/networkwordcountapplication_ _0008/streaming?mode=monitoring -- insecure 其中 为 ResourceManager 主节点的业务 IP,26001 为 ResourceManager 的端口号,application_ _0008 为在 YARN 中的应用 ID,NetworkWordCount 是 Spark 应用的 name 结果 : { "avginputrate" : "0.00 events/sec", "avgschedulingdelay" : "1 ms", "avgprocessingtime" : "72 ms", "avgtotaldelay" : "73 ms" 结果分析 : 文档版本 01 ( ) 354

365 7 Spark 应用开发 Thrift Server 接口介绍 简介 通过这个命令, 可以查询当前 Streaming 应用的平均输入频率, 平均调度时延, 平均执行时长, 总时延平均值 Thrift Server 是 Hive 中的 HiveServer2 的另外一个实现, 它底层使用了 Spark SQL 来处理 SQL 语句, 从而比 Hive 拥有更高的性能 ThriftServer 是一个 JDBC 接口, 用户可以通过 JDBC 连接 ThriftServer 来访问 SparkSQL 的数据 ThriftServer 在启动的时候, 会启动一个 SparkSQL 的应用程序, 而通过 JDBC 连接进来的客户端共同分享这个 sparksql 应用程序的资源, 也就是说不同的用户之间可以共享数据 ThriftServer 启动时还会开启一个侦听器, 等待 JDBC 客户端的连接和提交查询 所以, 在配置 ThriftServer 的时候, 至少要配置 ThriftServer 的主机名和端口, 如果要使用 Hive 数据的话, 还要提供 Hive Metastore 的 URIs Thrift Server 默认在安装节点上的 端口起一个 JDBC 服务, 可以通过 Beeline 或者 JDBC 客户端代码来连接它, 从而执行 SQL 命令 如果您需要了解 Thrift Server 的其他信息, 请参见 Spark 官网 : docs/1.5.1/sql-programming-guide.html#distributed-sql-engine Beeline 开源社区提供的 Beeline 连接方式, 请参见 : Hive/HiveServer2+Clients JDBC 客户端代码 通过 JDBC 客户端代码连接 Thrift Server, 来访问 SparkSQL 的数据 增强特性 对比开源社区,MRS 还提供了两个增强特性,Thrift Server HA 方案和设置 Thrift Server 连接的超时时间 Thrift Server HA 方案, 当 Thrift Server 主节点发生故障时, 备节点能够主动切换为主节点, 为集群提供服务 Beeline 和 JDBC 客户端代码两种连接方式的操作相同 连接 HA 模式下的 Thrift Server, 连接字符串和非 HA 模式下的区别在于需要将 ip:port 替换为 ha-cluster, 使用到的其他参数见表 7-37 表 7-37 客户端参数列表 参数名称含义默认值 spark.thriftserver. ha.enabled 是否启用 HA 模式, 设置为 true 表示启用 如果启用了 HA, 需要在连接字符串中将 host:port 修改为 ha-cluster 否则会自动退出 HA 模式 false 文档版本 01 ( ) 355

366 7 Spark 应用开发 参数名称含义默认值 spark.thriftserver. zookeeper.dir spark.deploy.zook eeper.url spark.thriftserver.r etry.times ThriftServer 在 ZooKeeper 上存储元数据的路径, 同服务端的同名参数, 且需要和服务端配置一致 在此目录下存储命名为 active_thriftserver 的子目录, 用于存储 Hive Thrift Server 的 IP 和端口号 ZooKeeper 的 URL 路径, 同服务端的同名参数, 且需要和服务端配置一致 尝试连接服务端的最大次数 如果设置为负数或零, 客户端将不会重新尝试连接服务端 / thriftserve r - 5 spark.thriftserver.r etry.wait.time 重连服务端时的尝试时间间隔, 单位秒 10 表 7-37 中的参数应配置在客户端 classpath 下的 hive-site.xml 文件中, 例 : <?xml version="1.0" encoding="utf-8" standalone="no"?> <configuration> <property> <name>spark.thriftserver.ha.enabled</name> <value>true</value> </property> </configuration> spark.deploy.zookeeper.url 参数也可以通过在连接字符串中的 zk.quorum 代替, 例 :!connect jdbc:hive2://ha-cluster/default;zk.quorum=spark25:2181,spark 26:2181,spark27:2181 设置客户端与 Thrift Server 连接的超时时间 Beeline 在网络拥塞的情况下, 这个特性可以避免 beeline 由于无限等待服务端的返回而挂起 使用方式如下 : 启动 beeline 时, 在后面追加 --sockettimeout=n, 其中 n 表示等待服务返回的超时时长, 单位为秒, 默认为 0 ( 表示永不超时 ) 建议根据业务场景, 设置为业务所能容忍的最大等待时长 JDBC 客户端代码 HiveODBC 接口介绍 在网络拥塞的情况下, 这个特性可以避免客户端由于无限等待服务端的返回而挂起 使用方式如下 : 在执行 DriverManager.getConnection 方法获取 JDBC 连接前, 添加 DriverManager.setLoginTimeout(n) 方法来设置超时时长, 其中 n 表示等待服务返回的超时时长, 单位为秒, 类型为 Int, 默认为 0 ( 表示永不超时 ) 建议根据业务场景, 设置为业务所能容忍的最大等待时长 HiveODBC 接口介绍 HiveODBC 只支持 ODBC 3.5 以上版本 HiveODBC 支持接口请参考 ODBC 提供的元数据接口说明如表 1 所示 文档版本 01 ( ) 356

367 7 Spark 应用开发 表 7-38 元数据接口说明 接口名称主要参数说明 SQLTables CatalogName 参数只支持空字符串 SchemaName 参数 TableName 参数 TableType 参数 只支持空字符串 支持精确匹配和模糊匹配 模糊匹配支持下划线 (_) (%) 百分号 目前未实现, 可填写 NULL 或空字符串 常用命令介绍 常用命令 Spark 命令详细的使用方法参考官方网站的描述 : quick-start.html Spark 常用的命令如下所示 : spark-shell 提供了一个简单学习 API 的方法, 类似于交互式数据分析的工具 同时支持 Scala 和 Python 两种语言 在 Spark 目录下, 执行./bin/spark-shell 即可进入 Scala 交互式界面从 HDFS 中获取数据, 再操作 RDD 示例 : 一行代码可以实现统计一个文件中所有单词 scala> sc.textfile("hdfs:// :9000//wordcount_data.txt").flatmap(l => l.split(" ")).map(w => (w,1)).reducebykey(_+_).collect() spark-submit 用于提交 Spark 应用到 Spark 集群中运行, 返回运行结果 需要指定 class master jar 包以及入参 示例 : 执行 jar 包中的 GroupByTest 例子, 入参为 4 个, 指定集群运行模式是 local 单核运行./bin/spark-submit --class org.apache.spark.examples.groupbytest --master local[1] lib/spark-examples snapshot-hadoop2.6.0.jar spark-sql 可用于 local 模式或者集群模式运行 Hive 元数据服务以及命令行查询 如果需要查看其逻辑计划, 只需在 SQL 语句前面加上 explain extended 即可 示例 : Select key from src group by key run-example 用来运行或者调试 Spark 开源社区中的自带的 example 示例 : 执行 SparkPi./run-example SparkPi 100 文档版本 01 ( ) 357

368 7 Spark 应用开发 7.7 FAQ 如何添加自定义代码的依赖包 问题 回答 用户在开发 Spark 程序时, 会添加样例程序外的自定义依赖包 针对自定义代码的依赖包, 如何使用 IDEA 添加到工程中? 步骤 1 步骤 2 在 IDEA 主页面, 选择 File > Project Structures... 进入 Project Structure 页面 选择 Libraries 页签, 然后在如下页面, 单击 +, 添加本地的依赖包 图 7-39 添加依赖包 步骤 3 步骤 4 单击 Apply 加载依赖包, 然后单击 OK 完成配置 由于运行环境不存在用户自定义的依赖包, 您还需要在编包时添加此依赖包 以便生成的 jar 包已包含自定义的依赖包, 确保 Spark 程序能正常运行 1. 在 Project Structure 页面, 选择 Artifacts 页签 2. 在右侧窗口中单击 +, 选择 Library Files 添加依赖包 文档版本 01 ( ) 358

369 7 Spark 应用开发 图 7-40 添加 Library Files 3. 选择需要添加的依赖包, 然后单击 OK 图 7-41 Choose Library 文档版本 01 ( ) 359

370 7 Spark 应用开发 4. 单击 Apply 加载依赖包, 然后单击 OK 完成配置 ---- 结束 如何处理自动加载的依赖包 问题 回答 在使用 IDEA 导入工程前, 如果 IDEA 工具中已经进行过 Maven 配置时, 会导致工具自动加载 Maven 配置中的依赖包 当自动加载的依赖包与应用程序不配套时, 导致工程 Build 失败 如何处理自动加载的依赖包? 建议在导入工程后, 手动删除自动加载的依赖 步骤如下 : 1. 在 IDEA 工具中, 选择 File > Project Structures..., 2. 选择 Libraries, 选中自动导入的依赖包, 右键选择 Delete 运行 SparkStreamingKafka 样例工程时报 类不存在 问题 问题 回答 通过 spark-submit 脚本提交 KafkaWordCount (org.apache.spark.examples.streaming.kafkawordcount) 任务时, 日志中报 Kafka 相关的类不存在的错误 KafkaWordCount 样例为 Spark 开源社区提供的 Spark 部署时, 如下 jar 包存放在客户端的 $SPARK_HOME/lib/streamingClient 目录以及服务端的 /opt/huawei/bigdata/fusioninsight/spark/spark/lib/streamingclient 目录 : kafka-clients jar kafka_ jar spark-streaming-kafka_ jar 由于 $SPARK_HOME/lib/streamingClient/* 默认没有添加到 classpath, 所以需要手动配置 在提交应用程序运行时, 在命令中添加如下参数即可 : --jars $SPARK_CLIENT_HOME/lib/streamingClient/kafka-clients jar, $SPARK_CLIENT_HOME/lib/streamingClient/kafka_ jar, $SPARK_CLIENT_HOME/lib/streamingClient/park-streaming-kafka_ jar 用户自己开发的应用程序以及样例工程都支持上述参数 但是 Spark 开源社区提供的 KafkaWordCount 等样例程序, 不仅需要添加 --jars 参数, 还需要配置其他, 否则会报 ClassNotFoundException 错误,yarn-client 和 yarn-cluster 模式下稍有不同 yarn-client 模式下 在除 --jars 参数外, 在客户端 spark-defaults.conf 配置文件中, 将 spark.driver.extraclasspath 参数值中添加客户端依赖包路径, 如 $SPARK_HOME/lib/streamingClient/* 文档版本 01 ( ) 360

371 7 Spark 应用开发 yarn-cluster 模式下 除 --jars 参数外, 还需要配置其他, 有三种方法任选其一即可, 具体如下 : 在客户端 spark-defaults.conf 配置文件中, 在 spark.yarn.cluster.driver.extraclasspath 参数值中添加服务端的依赖包路径, 如 /opt/huawei/bigdata/fusioninsight/spark/spark/lib/streamingclient/* 将各服务端节点的 spark-examples_ jar 包删除 在客户端 spark-defaults.conf 配置文件中, 修改或增加配置选项 spark.driver.userclasspathfirst = true 执行 Spark Core 应用, 尝试收集大量数据到 Driver 端, 当 Driver 端内存不足时, 应用挂起不退出 问题 执行 Spark Core 应用, 尝试收集大量数据到 Driver 端, 当 Driver 端内存不足时, 应用挂起不退出, 日志内容如下 16/04/19 15:56:22 ERROR Utils: Uncaught exception in thread task-result-getter-2 java.lang.outofmemoryerror: Java heap space at java.lang.reflect.array.newarray(native Method) at java.lang.reflect.array.newinstance(array.java:75) at java.io.objectinputstream.readarray(objectinputstream.java:1671) at java.io.objectinputstream.readobject0(objectinputstream.java:1345) at java.io.objectinputstream.defaultreadfields(objectinputstream.java:2000) at java.io.objectinputstream.readserialdata(objectinputstream.java:1924) at java.io.objectinputstream.readordinaryobject(objectinputstream.java:1801) at java.io.objectinputstream.readobject0(objectinputstream.java:1351) at java.io.objectinputstream.defaultreadfields(objectinputstream.java:2000) at java.io.objectinputstream.readserialdata(objectinputstream.java:1924) at java.io.objectinputstream.readordinaryobject(objectinputstream.java:1801) at java.io.objectinputstream.readobject0(objectinputstream.java:1351) at java.io.objectinputstream.readarray(objectinputstream.java:1707) at java.io.objectinputstream.readobject0(objectinputstream.java:1345) at java.io.objectinputstream.readobject(objectinputstream.java:371) at org.apache.spark.serializer.javadeserializationstream.readobject(javaserializer.scala:71) at org.apache.spark.serializer.javaserializerinstance.deserialize(javaserializer.scala:91) at org.apache.spark.scheduler.directtaskresult.value(taskresult.scala:94) at org.apache.spark.scheduler.taskresultgetter$$anon$3$$anonfun$run$1.apply$mcv $sp(taskresultgetter.scala:66) at org.apache.spark.scheduler.taskresultgetter$$anon$3$$anonfun$run$1.apply(taskresultgetter.scala: 57) at org.apache.spark.scheduler.taskresultgetter$$anon$3$$anonfun$run$1.apply(taskresultgetter.scala: 57) at org.apache.spark.util.utils$.loguncaughtexceptions(utils.scala:1716) at org.apache.spark.scheduler.taskresultgetter$$anon$3.run(taskresultgetter.scala:56) at java.util.concurrent.threadpoolexecutor.runworker(threadpoolexecutor.java:1142) at java.util.concurrent.threadpoolexecutor$worker.run(threadpoolexecutor.java:617) at java.lang.thread.run(thread.java:745) Exception in thread "task-result-getter-2" java.lang.outofmemoryerror: Java heap space at java.lang.reflect.array.newarray(native Method) at java.lang.reflect.array.newinstance(array.java:75) at java.io.objectinputstream.readarray(objectinputstream.java:1671) at java.io.objectinputstream.readobject0(objectinputstream.java:1345) at java.io.objectinputstream.defaultreadfields(objectinputstream.java:2000) at java.io.objectinputstream.readserialdata(objectinputstream.java:1924) at java.io.objectinputstream.readordinaryobject(objectinputstream.java:1801) at java.io.objectinputstream.readobject0(objectinputstream.java:1351) at java.io.objectinputstream.defaultreadfields(objectinputstream.java:2000) at java.io.objectinputstream.readserialdata(objectinputstream.java:1924) at java.io.objectinputstream.readordinaryobject(objectinputstream.java:1801) at java.io.objectinputstream.readobject0(objectinputstream.java:1351) at java.io.objectinputstream.readarray(objectinputstream.java:1707) 文档版本 01 ( ) 361

372 7 Spark 应用开发 at java.io.objectinputstream.readobject0(objectinputstream.java:1345) at java.io.objectinputstream.readobject(objectinputstream.java:371) at org.apache.spark.serializer.javadeserializationstream.readobject(javaserializer.scala:71) at org.apache.spark.serializer.javaserializerinstance.deserialize(javaserializer.scala:91) at org.apache.spark.scheduler.directtaskresult.value(taskresult.scala:94) at org.apache.spark.scheduler.taskresultgetter$$anon$3$$anonfun$run$1.apply$mcv $sp(taskresultgetter.scala:66) at org.apache.spark.scheduler.taskresultgetter$$anon$3$$anonfun$run$1.apply(taskresultgetter.scala: 57) at org.apache.spark.scheduler.taskresultgetter$$anon$3$$anonfun$run$1.apply(taskresultgetter.scala: 57) at org.apache.spark.util.utils$.loguncaughtexceptions(utils.scala:1716) at org.apache.spark.scheduler.taskresultgetter$$anon$3.run(taskresultgetter.scala:56) at java.util.concurrent.threadpoolexecutor.runworker(threadpoolexecutor.java:1142) at java.util.concurrent.threadpoolexecutor$worker.run(threadpoolexecutor.java:617) at java.lang.thread.run(thread.java:745) 回答 用户尝试收集大量数据到 Driver 端, 如果 Driver 端的内存不足以存放这些数据, 那么就会抛出 OOM(OutOfMemory) 的异常, 然后 Driver 端一直在进行 GC, 尝试回收垃圾来存放返回的数据, 导致应用长时间挂起 解决措施 : 如果用户需要在 OOM 场景下强制将应用退出, 那么可以在启动 Spark Core 应用时, 在客户端配置文件 $SPARK_HOME/conf/spark-defaults.conf 中的配置项 spark.driver.extrajavaoptions 中添加如下内容 : -XX:OnOutOfMemoryError='kill -9 %p' Spark 应用名在使用 yarn-cluster 模式提交时不生效 问题 Spark 应用名在使用 yarn-cluster 模式提交时不生效, 在使用 yarn-client 模式提交时生效, 如图 7-42 所示, 第一个应用是使用 yarn-client 模式提交的, 正确显示我们代码里设置的应用名 Spark Pi, 第二个应用是使用 yarn-cluster 模式提交的, 设置的应用名没有生效 图 7-42 提交应用 回答 导致这个问题的主要原因是,yarn-client 和 yarn-cluster 模式在提交任务时 setappname 的执行顺序不同导致,yarn-client 中 setappname 是在向 yarn 注册 Application 之前读取, yarn-cluser 模式则是在向 yarn 注册 Application 之后读取, 这就导致 yarn-cluster 模式设置的应用名不生效 解决措施 : 在 spark-submit 脚本提交任务时用 --name 设置应用名和 sparkconf.setappname(appname) 里面的应用名一样 比如我们代码里设置的应用名为 Spark Pi, 用 yarn-cluster 模式提交应用时可以这样设置, 在 --name 后面添加应用名, 执行的命令如下 : 文档版本 01 ( ) 362

373 7 Spark 应用开发./spark-submit --class org.apache.spark.examples.sparkpi --master yarn --deploy-mode cluster --name SparkPi lib/spark-examples*.jar 如何采用 Java 命令提交 Spark 应用 问题 回答 除了 spark-submit 命令提交应用外, 如何采用 Java 命令提交 Spark 应用? 您可以通过 org.apache.spark.launcher.sparklauncher 类采用 java 命令方式提交 Spark 应用 详细步骤如下 : 步骤 1 定义 org.apache.spark.launcher.sparklauncher 类 默认提供了 SparkLauncherJavaExample 和 SparkLauncherScalaExample 示例, 您需要根据实际业务应用程序修改示例代码中的传入参数 如果您使用 Java 语言开发程序, 您可以参考如下示例, 编写 SparkLauncher 类 public static void main(string[] args) throws Exception { System.out.println("com.huawei.bigdata.spark.examples.SparkLauncherExample <mode> <jarparh> <app_main_class> <appargs>"); SparkLauncher launcher = new SparkLauncher(); launcher.setmaster(args[0]).setappresource(args[1]) // Specify user app jar path.setmainclass(args[2]); if (args.length > 3) { String[] list = new String[args.length - 3]; for (int i = 3; i < args.length; i++) { list[i-3] = args[i]; // Set app args launcher.addappargs(list); // Launch the app Process process = launcher.launch(); // Get Spark driver log new Thread(new ISRRunnable(process.getErrorStream())).start(); int exitcode = process.waitfor(); System.out.println("Finished! Exit code is " + exitcode); 如果您使用 Scala 语言开发程序, 您可以参考如下示例, 编写 SparkLauncher 类 def main(args: Array[String]) { println(s"com.huawei.bigdata.spark.examples.sparklauncherexample <mode> <jarparh> <app_main_class> <appargs>") val launcher = new SparkLauncher() launcher.setmaster(args(0)).setappresource(args(1)) // Specify user app jar path.setmainclass(args(2)) if (args.drop(3).length > 0) { // Set app args launcher.addappargs(args.drop(3): _*) // Launch the app val process = launcher.launch() // Get Spark driver log new Thread(new ISRRunnable(process.getErrorStream)).start() val exitcode = process.waitfor() println(s"finished! Exit code is $exitcode") 文档版本 01 ( ) 363

374 7 Spark 应用开发 步骤 2 根据业务逻辑, 开发对应的 Spark 应用程序, 并设置用户编写的 Spark 应用程序的主类等常数 如果您使用的是普通模式, 准备业务应用代码及其相关配置即可 步骤 3 调用 org.apache.spark.launcher.sparklauncher.launch() 方法, 将用户的应用程序提交 1. 将 SparkLauncher 程序和用户应用程序分别生成 Jar 包, 并上传至运行此应用的 Spark 节点中 SparkLauncher 程序的编译依赖包为 spark-launcher_ jar 用户应用程序的编译依赖包根据代码不同而不同, 需用户根据自己编写的代码进行加载 2. 将运行程序的依赖 Jar 包上传至需要运行此应用的节点中, 例如 $SPARK_HOME/ lib 路径 用户需要将 SparkLauncher 类的运行依赖包和应用程序运行依赖包上传至客户端的 lib 路径 文档中提供的示例代码, 其运行依赖包在客户端 lib 中已存在 说明 SparkLauncher 的方式依赖 Spark 客户端, 即运行程序的节点必须已安装 Spark 客户端, 且客户端可用 运行过程中依赖客户端已配置好的环境变量 运行依赖包和配置文件, 3. 在 Spark 应用程序运行节点, 执行如下命令使用 SparkLauncher 方式提交 ---- 结束 java -cp $SPARK_HOME/conf:$SPARK_HOME/lib/*:SparkLauncherExample.jar com.huawei.bigdata.spark.examples.sparklauncherexample yarn-client /opt/female/ FemaleInfoCollection.jar com.huawei.bigdata.spark.examples.femaleinfocollection <inputpath> SparkSQL UDF 功能的权限控制机制 问题 回答 SparkSQL 中 UDF 功能的权限控制机制是怎样的? 目前已有的 SQL 语句无法满足用户场景时, 用户可使用 UDF 功能进行自定义操作 为确保数据安全以及 UDF 中的恶意代码对系统造成破坏,SparkSQL 的 UDF 功能只允许具备 admin 权限的用户注册, 由 admin 用户保证自定义的函数的安全性 由于 kafka 配置的限制, 导致 Spark Streaming 应用运行失败 问题 使用运行的 Spark Streaming 任务回写 kafka 时,kafka 上接收不到回写的数据, 且 kafka 日志报错信息如下 : :46:19,017 INFO [kafka-network-thread ] Closing socket connection to / due to invalid request: Request of length is not valid, it is larger than the maximum size of bytes. kafka.network.processor (Logging.scala:68) :46:19,155 INFO [kafka-network-thread ] Closing socket connection to / kafka.network.processor (Logging.scala:68) :46:19,270 INFO [kafka-network-thread ] Closing socket connection to / 文档版本 01 ( ) 364

375 7 Spark 应用开发 due to invalid request: Request of length is not valid, it is larger than the maximum size of bytes. kafka.network.processor (Logging.scala:68) :46:19,513 INFO [kafka-network-thread ] Closing socket connection to / due to invalid request: Request of length is not valid, it is larger than the maximum size of bytes. kafka.network.processor (Logging.scala:68) :46:19,763 INFO [kafka-network-thread ] Closing socket connection to / due to invalid request: Request of length is not valid, it is larger than the maximum size of bytes. kafka.network.processor (Logging.scala:68) [main] INFO org.apache.hadoop.mapreduce.job - Counters: 50 回答 如下图所示,Spark Streaming 应用中定义的逻辑为, 从 kafka 中读取数据, 执行对应处理之后, 然后将结果数据回写至 kafka 中 例如 :Spark Streming 中定义了批次时间, 如果数据传入 Kafka 的速率为 10MB/s, 而 Spark Streaming 中定义了每 60s 一个批次, 回写数据总共为 600MB 而 Kafka 中定义了接收数据的阈值大小为 500MB 那么此时回写数据已超出阈值 此时, 会出现上述错误 图 7-43 应用场景 解决措施 : 方式一 : 推荐优化 Spark Streaming 应用程序中定义的批次时间, 降低批次时间, 可避免超过 kafka 定义的阈值 一般建议以 5-10 秒 / 次为宜 方式二 : 将 kafka 的阈值调大, 建议在 MRS Manager 中的 Kafka 服务进行参数设置, 将 socket.request.max.bytes 参数值根据应用场景, 适当调整 如何使用 IDEA 远程调试 问题 回答 在 Spark 二次开发中如何使用 IDEA 远程调试? 以调试 SparkPi 程序为例, 演示如何进行 IDEA 的远程调试 : 1. 打开工程, 在菜单栏中选择 Run > Edit Configurations 2. 在弹出的配置窗口中用鼠标左键单击左上角的 + 号, 在下拉菜单中选择 Remote, 如图 7-44 所示 文档版本 01 ( ) 365

376 7 Spark 应用开发 图 7-44 选择 Remote 3. 选择对应要调试的源码模块路径, 并配置远端调试参数 Host 和 Port, 如图 2 所示 其中 Host 为 Spark 运行机器 IP 地址,Port 为调试的端口号 ( 确保该端口在运行机器上没被占用 ) 图 7-45 配置参数 说明 当改变 Port 端口号时,For JDK1.4.x 对应的调试命令也跟着改变, 比如 Port 设置为 5006, 对应调试命令会变更为 -Xdebug - Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006, 这个调试命令在启动 Spark 程序时要用到 4. 执行以下命令, 远端启动 Spark 运行 SparkPi./spark-submit --master yarn-client --driver-java-options "-Xdebug - Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" --class org.apache.spark.examples.sparkpi /opt/client/spark/spark/examples/jars/sparkexamples-<version>.jar org.apache.spark.examples.sparkpi,opt/client/spark/spark/examples/jars/sparkexamples-<version>.jar: 用户调试时需要换成自己的主类和 jar 包路径 文档版本 01 ( ) 366

377 7 Spark 应用开发 -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006: 需要换成 3 获取到的 For JDK1.4.x 对应的调试端口 5. 设置调试断点 在 IDEA 代码编辑窗口左侧空白处单击鼠标左键设置相应代码行断点, 如图 4 所示, 在 SparkPi.scala 的 29 行设置断点 图 7-46 设置断点 6. 启动调试 在 IDEA 菜单栏中选择 Run > Debug 'Unnamed' 开启调试窗口, 接着开始 SparkPi 的调试, 比如单步调试 查看调用栈 跟踪变量值等, 如图 5 所示 文档版本 01 ( ) 367

378 7 Spark 应用开发 图 7-47 调试 使用 IBM JDK 产生异常, 提示 Problem performing GSS wrap 信息 问题 回答 使用 IBM JDK 产生异常, 提示 Problem performing GSS wrap 信息 问题原因 : 在 IBM JDK 下建立的 JDBC connection 时间超过登录用户的认证超时时间 ( 默认一天 ), 导致认证失败 说明 IBM JDK 的机制跟 Oracle JDK 的机制不同,IBM JDK 在认证登录后的使用过程中做了时间检查却没有检测外部的时间更新, 导致即使显式调用 relogin 也无法得到刷新 解决措施 : 通常情况下, 在发现 JDBC connection 不可用的时候, 可以关闭该 connection, 重新创建一个 connection 继续执行 文档版本 01 ( ) 368

379 7 Spark 应用开发 Structured Streaming 的 cluster 模式, 在数据处理过程中终止 ApplicationManager, 应用失败 问题 Structured Streaming 的 cluster 模式, 在数据处理过程中终止 ApplicationManager, 执行应用时显示如下异常 :46:02,393 INFO main client token: Token { kind: YARN_CLIENT_TOKEN, service: diagnostics: User class threw exception: org.apache.spark.sql.analysisexception: This query does not support recovering from checkpoint location. Delete hdfs://hacluster/structuredtest/checkpoint/ offsets to start over.; ApplicationMaster host: ApplicationMaster RPC port: 0 queue: default start time: final status: FAILED tracking URL: user: spark2x org.apache.spark.internal.logging$class.loginfo(logging.scala:54) Exception in thread "main" org.apache.spark.sparkexception: Application application_ _0052 finished with failed status 回答 原因分析 : 显示该异常是因为 recoverfromcheckpointlocation 的值判定为 false, 但却配置了 checkpoint 目录 参数 recoverfromcheckpointlocation 的值为代码中 outputmode == OutputMode.Complete() 语句的判断结果 (outputmode 的默认输出方式为 append ) 处理方法 : 编写应用时, 用户可以根据具体情况修改数据的输出方式, 调用 outputmode 方法修改输出方式的操作可参见 DataSight Spark V100R002CXX Spark2.1 API Reference 将输出方式修改为 complete, recoverfromcheckpointlocation 的值会判定为 true 此时配置了 checkpoint 目录时就不会显示异常 7.8 开发规范 规则 Spark 应用中, 需引入 Spark 的类 对于 Java 开发语言, 正确示例 : // 创建 SparkContext 时所需引入的类 import org.apache.spark.api.java.javasparkcontext //RDD 操作时引入的类 import org.apache.spark.api.java.javardd // 创建 SparkConf 时引入的类 import org.apache.spark.sparkconf 对于 Scala 开发语言, 正确示例 : // 创建 SparkContext 时所需引入的类 import org.apache.spark.sparkcontext //RDD 操作时引入的类 文档版本 01 ( ) 369

380 7 Spark 应用开发 import org.apache.spark.sparkcontext._ // 创建 SparkConf 时引入的类 import org.apache.spark.sparkconf 自己抛出的异常必须要填写详细的描述信息 说明 : 便于问题定位 正确示例 : // 抛出异常时, 写出详细描述信息 throw new IOException("Writing data error! Data: " + data.tostring()); 错误示例 : throw new IOException("Writing data error! "); Java 与 Scala 函数有区别, 在编写应用时, 需要弄清楚每个函数的功能 RDD 是不可改变的, 也就是说,RDD 的元素对象是不能更改的, 因此, 在用 Java 和 Scala 编写需要弄清楚每个函数的功能 下面举个例子 场景 : 现有用户位置数据, 按照时间排序生成用户轨迹 在 Scala 中, 按时间排序的代码如下 : /* 函数实现的功能是得到某个用户的位置轨迹 * 参数 trajectory: 由两部分组成 - 用户名和位置点 ( 时间, 经度, 维度 ) private def gettimesofoneuser(trajectory: (String, Seq[(String, Float, Float)]), zone: Zone, arrive: Boolean): Int = { // 先将用户位置点按时间排序 val sorted: Seq[(String, Float, Float)] = trajectory._2.sortby(x => x._1); 若用 java 实现上述功能, 则需要将对 trajectory._2 重新生成对象, 而不能直接对 trajectory._2 进行排序操作 原因是,java 不支持 Collections.sort(trajectory._2) 这个操作, 是由于该操作改变了 trajectory._2 这个对象本身, 这违背了 RDD 元素不可更改这条规则 ; 而 Scala 代码之所以能够正常运行, 是因为 sortby( ) 这个函数生成了一个新的对象, 它并不对 trajectory._2 直接操作 下面分别列出 java 实现的正确示例和错误示例 正确示例 : // 将用户的位置点从新生成一个对象 List<Tuple3< String, Float, Float >> list = new ArrayList<Tuple3< String, Float, Float >>( trajectory._2); // 对新对象进行排序 Collections.sort(list); 错误示例 : // 直接对用户位置点按照时间排序 Collections.sort(trajectory._2); 集群模式下, 应注意 Driver 和 worker 节点之间的参数传递 在 Spark 编程时, 总是有一些代码逻辑中需要根据输入参数来判断, 这种时候往往会想到这种做法, 将参数设置为全局变量, 先给定一个空值 (null), 在 main 函数中, 实例化 SparkContext 对象之前对这个变量赋值 然而, 在集群模式下, 执行程序的 jar 包会被发送到每个 Worker 上执行 如果只在 main 函数的节点上全局变量改变了, 而未传给执行任务的函数中, 将会报空指针异常 文档版本 01 ( ) 370

381 7 Spark 应用开发 正确示例 : object Test { private var testarg: String = null; def main(args: Array[String]) { testarg = ; val sc: SparkContext = new SparkContext( ); sc.textfile( ).map(x => testfun(x, testarg)); private def testfun(line: String, testarg: String): String = { testarg.split( ); return ; 错误示例 : // 定义对象 object Test { // 定义全局变量, 赋为空值 (null); 在 main 函数中, 实例化 SparkContext 对象之前对这个变量赋值 private var testarg: String = null; //main 函数 def main(args: Array[String]) { testarg = ; val sc: SparkContext = new SparkContext( ); sc.textfile( ).map(x => testfun(x)); private def testfun(line: String): String = { testarg.split(...); return ; 运行错误示例, 在 Spark 的 local 模式下能正常运行, 而在集群模式情况下, 会在红色加粗代码处报错, 提示空指针异常, 这是由于在集群模式下, 执行程序的 jar 包会被发送到每个 Worker 上执行, 当执行到 testfun 函数时, 需要从内存中取出 testarg 的值, 但是 testarg 的值只在启动 main 函数的节点改变了, 其他节点无法获取这些变化, 因此它们从内存中取出的就是初始化这个变量时的值 null, 这就是空指针异常的原因 应用程序结束之前必须调用 SparkContext.stop 利用 spark 做二次开发时, 当应用程序结束之前必须调用 SparkContext.stop() 说明 利用 Java 语言开发时, 应用程序结束之前必须调用 JavaSparkContext.stop(). 利用 Scala 语言开发时, 应用程序结束之前必须调用 SparkContext.stop(). 以 Scala 语言开发应用程序为例, 分别介绍下正确示例与错误示例 正确示例 : // 提交 spark 作业 val sc = new SparkContext(conf) 文档版本 01 ( ) 371

382 7 Spark 应用开发 // 具体的任务... // 应用程序结束 sc.stop() 错误示例 : // 提交 spark 作业 val sc = new SparkContext(conf) // 具体的任务... 如果不添加 SparkContext.stop, 界面会显示失败 如图 7-48, 同样的任务, 前一个程序是没有添加 SparkContext.stop, 后一个程序添加了 SparkContext.stop() 图 7-48 添加 SparkContext.stop() 和不添加的区别 多线程安全登录方式 如果有多线程进行 login 的操作, 当应用程序第一次登录成功后, 所有线程再次登录时应该使用 relogin 的方式 login 的代码样例 : private Boolean login(configuration conf){ boolean flag = false; UserGroupInformation.setConfiguration(conf); try { UserGroupInformation.loginUserFromKeytab(conf.get(PRINCIPAL), conf.get(keytab)); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; relogin 的代码样例 : public Boolean relogin(){ boolean flag = false; try { UserGroupInformation.getLoginUser().reloginFromKeytab(); System.out.println("UserGroupInformation.isLoginKeytabBased(): " +UserGroupInformation.isLoginKeytabBased()); flag = true; catch (IOException e) { e.printstacktrace(); return flag; 文档版本 01 ( ) 372

383 7 Spark 应用开发 自建用户执行 sparksql 命令时, 需赋予用户相应写权限 建议 用户可通过系统提供的 spark 用户来进行 sparksql 操作, 也可通过在 MRS Manager 界面新建用户来执行 sparksql 操作 在通过自建用户执行 sparksql 相应写操作时, 可通过为用户选择 supergroup 组, 赋予用户 SystemAdministrator 角色等方式赋予用户相应写操作的权限 ; 如果新建用户为 hadoop 组用户且没有赋予特定角色, 在一些涉及到写入操作的场景下可能会因不具备相应操作权限引起异常 RDD 多次使用时, 建议将 RDD 持久化 RDD 在默认情况下的存储级别是 StorageLevel.NONE, 即既不存磁盘也不放在内存中, 如果某个 RDD 需要多次使用, 可以考虑将该 RDD 持久化, 方法如下 : 调用 spark.rdd 中的 cache() persist() persist(newlevel: StorageLevel) 函数均可将 RDD 持久化,cache() 和 persist() 都是将 RDD 的存储级别设置为 StorageLevel.MEMORY_ONLY,persist(newLevel: StorageLevel) 可以为 RDD 设置其他存储级别, 但是要求调用该方法之前 RDD 的存储级别为 StorageLevel.NONE 或者与 newlevel 相同, 也就是说,RDD 的存储级别一旦设置为 StorageLevel.NONE 之外的级别, 则无法改变 如果想要将 RDD 去持久化, 那么可以调用 unpersist(blocking: Boolean = true), 将该 RDD 从持久化列表中移除, 并将 RDD 的存储级别重新设置为 StorageLevel.NONE 调用有 shuffle 过程的算子时, 尽量提取需要使用的信息 该类算子称为宽依赖算子, 其特点是父 RDD 的一个 partition 影响子 RDD 得多个 partition,rdd 中的元素一般都是 <key, value> 对 执行过程中都会涉及到 RDD 的 partition 重排, 这个操作称为 shuffle 由于 shuffle 类算子存在节点之间的网络传输, 因此对于数据量很大的 RDD, 应该尽量提取需要使用的信息, 减小其单条数据的大小, 然后再调用 shuffle 类算子 常用的有如下几种 : combinebykey() : RDD[(K, V)] => RDD[(K, C)], 是将 RDD[(K, V)] 中 key 相同的数据的所有 value 转化成为一个类型为 C 的值 groupbykey() 和 reducebykey() 是 combinebykey 的两种具体实现, 对于数据聚合比较复杂而 groupbykey 和 reducebykey 不能满足使用需求的场景, 可以使用自己定义的聚合函数作为 combinebykey 的参数来实现 distinct(): RDD[T] => RDD[T], 作用是去除重复元素的算子 其处理过程代码如下 : map(x => (x, null)).reducebykey((x, y) => x, numpartitions).map(_._1) 这个过程比较耗时, 尤其是数据量很大时, 建议不要直接对大文件生成的 RDD 使用 join() : (RDD[(K, V)], RDD[(K, W)]) => RDD[(K, (V, W))], 作用是将两个 RDD 通过 key 做连接 如果 RDD[(K, V)] 中某个 key 有 X 个 value, 而 RDD[(K, W)] 中相同 key 有 Y 个 value, 那么最终在 RDD[(K, (V, W))] 中会生成 X*Y 条记录 文档版本 01 ( ) 373

384 7 Spark 应用开发 在一条语句中如果连续调用多个方法, 每个方法占用一行, 以符号. 对齐 这样做, 可以增强代码可读性, 明确代码执行流程 代码示例 : val data: RDD[String] = sc.textfile(inputpath).map(line => (line.split(",")(0), line)).groupbykey().collect().sortby(pair => pair._1).flatmap(pair => pair._2); 异常捕获尽量不要直接 catch (Exception ex), 应该把异常细分处理 可以设计更合理异常处理分支 明确方法功能, 精确 ( 而不是近似 ) 地实现方法设计, 一个函数仅完成一件功能, 即使简单功能也编写方法实现 虽然为仅用一两行就可完成的功能去编方法好象没有必要, 但用方法可使功能明确化, 增加程序可读性, 亦可方便维护 测试 下面举个例子说明 不推荐的做法 : // 该示例中 map 方法中, 实现了对数据映射操作 val data:rdd[(string, String)] = sc.textfile(input).map(record => { val elems = record.split(","); (elems(0) + "," + elems(1), elems(2)); ); 推荐的做法 : // 将上述示例中的 map 方法中的操作提取出来, 增加了程序可读性, 便于维护和测试 val data:rdd[(string, String)] = sc.textfile(input).map(record => desomething(record)); def desomething(record: String): (String, String) = { val elems = x.split(","); return (elems(0) + "," + elems(1), elems(2)); 文档版本 01 ( ) 374

385 8 Storm 应用开发 8 Storm 应用开发 8.1 概述 应用开发简介 目标读者 简介 常用概念 本文档提供给需要 Storm 二次开发的用户使用 本指南主要适用于具备 Java 开发经验的开发人员 Storm 是一个分布式的 可靠的 容错的数据流处理系统 它会把工作任务委托给不同类型的组件, 每个组件负责处理一项简单特定的任务 Storm 的目标是提供对大数据流的实时处理, 可以可靠地处理无限的数据流 Storm 有很多适用的场景 : 实时分析 在线机器学习 持续计算和分布式 ETL 等, 易扩展 支持容错, 可确保数据得到处理, 易于构建和操控 Storm 有如下几个特点 : 适用场景广泛 易扩展, 可伸缩性高 保证无数据丢失 容错性好 多语言 易于构建和操控 Topology 拓扑是一个计算流图 其中每个节点包含处理逻辑, 而节点间的连线则表明了节点间的数据是如何流动的 Spout 文档版本 01 ( ) 375

386 8 Storm 应用开发 在一个 Topology 中产生源数据流的组件 通常情况下 Spout 会从外部数据源中读取数据, 然后转换为 Topology 内部的源数据 Bolt 在一个 Topology 中接受数据然后执行处理的组件 Bolt 可以执行过滤 函数操作 合并 写数据库等任何操作 Tuple 开发流程 一次消息传递的基本单元 Stream 流是一组 ( 无穷 ) 元素的集合, 流上的每个元素都属于同一个 schema; 每个元素都和逻辑时间有关 ; 即流包含了元组和时间的双重属性 流上的任何一个元素, 都可以用 Element<tuple,Time> 的方式来表示,tuple 是元组, 包含了数据结构和数据内容,Time 就是该数据的逻辑时间 keytab 文件 存放用户信息的密钥文件 应用程序采用此密钥文件在 MRS 产品中进行 API 方式认证 本文档主要基于 Java API 进行 Storm 拓扑的开发 开发流程如图 8-1 所示 : 文档版本 01 ( ) 376

387 8 Storm 应用开发 图 8-1 拓扑开发流程 8.2 环境准备 本开发指南提供了 MRS 产品 Storm 组件基于开源 Storm 的 Eclipse 样例工程和常用接口说明, 便于开发者快速熟悉 Storm 开发 开发环境准备分为应用开发客户端和应用提交客户端 ; 应用开发一般是在 Windows 环境下进行 ; 应用提交一般是在 Linux 环境下进行 Windows 开发环境准备 文档版本 01 ( ) 377

388 8 Storm 应用开发 开发环境简介 在进行二次开发时, 要准备的开发环境如表 8-1 所示 : 表 8-1 开发环境 准备项 操作系统 说明 Windows 系统, 推荐 Windows 7 以上版本 安装 JDK 开发环境的基本配置 版本要求 :1.7 或者 1.8 说明基于安全考虑, 服务端只支持 TLS 1.1 和 TLS 1.2 加密协议, IBM JDK 默认 TLS 只支持 1.0, 若使用 IBM JDK, 请配置启动参数 com.ibm.jsse2.overridedefaulttls 为 true, 设置后可以同时支持 TLS1.0/1.1/1.2 详情请参见 : knowledgecenter/zh/ssyke2_8.0.0/ com.ibm.java.security.component.80.doc/securitycomponent/jsse2docs/ matchsslcontext_tls.html#matchsslcontext_tls 安装和配置 Eclipse 网络 用于开发 Storm 应用程序的工具 确保客户端与 Storm 服务主机在网络上互通 准备 Eclipse 与 JDK 操作场景 操作步骤 开发环境可以搭建在 Windows 环境下 步骤 1 安装 Eclipse 程序 安装要求如下 : Eclipse 使用 3.0 及以上版本 步骤 2 安装 JDK 程序 安装要求如下 : JDK 使用 1.7 及或者 1.8 版本, 支持 IBM JDK 和 Oracle JDK 说明 若使用 IBM JDK, 请确保 Eclipse 中的 JDK 配置为 IBM JDK 若使用 Oracle JDK, 请确保 Eclipse 中的 JDK 配置为 Oracle JDK 不同的 Eclipse 不要使用相同的 workspace 和相同路径下的示例工程 ---- 结束 文档版本 01 ( ) 378

389 8 Storm 应用开发 配置并导入工程 背景信息 前提条件 操作步骤 Storm 客户端安装程序目录中包含了 Storm 开发样例工程, 将工程导入到 Eclipse 开始样例学习 确保本地 PC 的时间与集群的时间差要小于 5 分钟, 若无法确定, 请联系系统管理员 集群的时间可通过 MRS Manager 页面右上角查看 步骤 1 步骤 2 步骤 3 步骤 4 在 Storm 示例工程根目录, 执行 mvn install 进行编译 在 Storm 示例工程根目录, 执行 mvn eclipse:eclipse 创建 Eclipse 工程 在应用开发环境中, 导入样例工程到 Eclipse 开发环境 1. 选择 File > Import > General > Existing Projects into Workspace > Next >Browse 显示 浏览文件夹 对话框 2. 选择样例工程文件夹, 单击 Finish 设置 Eclipse 的文本文件编码格式, 解决乱码显示问题 1. 在 Eclipse 的菜单栏中, 选择 Window > Preferences 弹出 Preferences 窗口 2. 在左边导航上选择 General > Workspace, 在 Text file encoding 区域, 选中 Other, 并设置参数值为 UTF-8, 单击 Apply 后, 单击 OK, 如图 8-2 所示 文档版本 01 ( ) 379

390 8 Storm 应用开发 图 8-2 设置 Eclipse 的编码格式 步骤 5 Windows 环境下直接提交任务到集群场景, 需要将集群 ip 映射配到本地 host 文件中 以 Windows7 为例, 路径为 C:\Windows\System32\drivers\etc\hosts 例如, 集群有 3 个节点 , , 则需要检查 hosts 文件中是否配置了以下内容 : 结束 Linux 客户端环境准备 背景信息 安装 Linux 客户端用于拓扑的提交 前提条件 确认 Storm 组件已经安装, 并正常运行 客户端机器的时间与集群的时间要保持一致, 时间差要小于 5 分钟 操作步骤 步骤 1 下载 Storm 客户端程序 1. 登录 MRS Manager 系统 2. 选择 服务管理 > Storm > 下载客户端 > 完整客户端, 下载客户端程序到本地机器 文档版本 01 ( ) 380

391 8 Storm 应用开发 步骤 2 登陆到客户端下载的目标 ECS 步骤 3 在 Linux 系统中, 使用如下命令解压客户端压缩包 : tar -xvf MRS_Storm_Client.tar tar -xvf MRS_Storm_ClientConfig.tar 步骤 4 步骤 5 进入 MRS_Services_ClientConfig 中, 执行 install.sh 脚本安装客户端, 将客户端安装到一个空文件夹, 命令为 :./install.sh /opt/storm_client( 此处 /opt/storm_client 表示的是 Storm 安装目录, 此目录必为空目录, 且必须是绝对路径 ) 初始化客户端环境变量 进入安装目录 /opt/storm_client 执行以下命令, 导入环境变量信息 source bigdata_env 步骤 6 开启 Kerberos 认证的集群, 需要申请人机用户, 并进行安全登录 1. 从管理员处获取一个 人机 用户, 用于服务认证 例如 : 帐号 john 说明获取的用户需要属于 storm 组 2. 执行 kinit 命令进行 人机 用户的安全登录 kinit 用户名例如 : kinit john 然后按照提示输入密码, 无异常提示返回, 则完成了用户的 kerberos 认证 步骤 7 执行如下命令 : storm list 如果可以正常打印出 storm 集群正在运行的任务信息, 则说明客户端安装成功 ---- 结束 8.3 开发程序 典型场景说明 场景说明 通过典型场景, 我们可以快速学习和掌握 Storm 拓扑的构造和 Spout/Bolt 开发过程 一个动态单词统计系统, 数据源为持续生产随机文本的逻辑单元, 业务处理流程如下 : 数据源持续不断地发送随机文本给文本拆分逻辑, 如 apple orange apple 单词拆分逻辑将数据源发送的每条文本按空格进行拆分, 如 apple, orange, apple, 随后将每个单词逐一发给单词统计逻辑 单词统计逻辑每收到一个单词就进行加一操作, 并将实时结果打印输出, 如 : apple:1 文档版本 01 ( ) 381

392 8 Storm 应用开发 开发思路 功能分解 orange:1 apple:2 根据上述场景进行功能分解, 如表 8-2 所示 : 表 8-2 在应用中开发的功能 序号 步骤 代码示例 1 创建一个 Spout 用来生成随机文本 请参见创建 Spout 2 创建一个 Bolt 用来将收到的随机文本拆分成一个个单词 3 创建一个 Blot 用来统计收到的各单词次数 请参见创建 Bolt 请参见创建 Bolt 4 创建 topology 请参见创建 Topology 代码样例说明 部分代码请参考代码样例说明, 完整代码请参考 Storm-examples 示例工程 创建 Spout 功能介绍 代码样例 Spout 是 Storm 的消息源, 它是 Topology 的消息生产者, 一般来说消息源会从一个外部源读取数据并向 Topology 中发送消息 (Tuple) 一个消息源可以发送多条消息流 Stream, 可以使用 OutputFieldsDeclarer.declarerStream 来定义多个 Stream, 然后使用 SpoutOutputCollector 来发射指定的 Stream 下面代码片段在 com.huawei.storm.example.common.randomsentencespout 类中, 作用在于将收到的字符串拆分成单词 /** * {@inheritdoc public void nexttuple() { Utils.sleep(100); String[] sentences = new String[] {"the cow jumped over the moon", "an apple a day keeps the doctor away", "four score and seven years ago", "snow white and the seven dwarfs", 文档版本 01 ( ) 382

393 8 Storm 应用开发 "i am at two with nature"; String sentence = sentences[random.nextint(sentences.length)]; collector.emit(new Values(sentence)); 创建 Bolt 功能介绍 代码样例 所有的消息处理逻辑都被封装在各个 Bolt 中 Bolt 包含多种功能 : 过滤 聚合等等 如果 Bolt 之后还有其他拓扑算子, 可以使用 OutputFieldsDeclarer.declareStream 定义 Stream, 使用 OutputCollector.emit 来选择要发射的 Stream 下面代码片段在 com.huawei.storm.example.common.splitsentencebolt 类中, 作用在于拆分每条语句为单个单词并发送 /** * {@inheritdoc public void execute(tuple input, BasicOutputCollector collector) { String sentence = input.getstring(0); String[] words = sentence.split(" "); for (String word : words) { word = word.trim(); if (!word.isempty()) { word = word.tolowercase(); collector.emit(new Values(word)); 下面代码片段在 com.huawei.storm.example.wordcount.wordcountbolt 类中, public void execute(tuple tuple, BasicOutputCollector collector) { String word = tuple.getstring(0); Integer count = counts.get(word); if (count == null) { count = 0; count++; counts.put(word, count); System.out.println("word: " + word + ", count: " + count); 创建 Topology 功能介绍 一个 Topology 是 Spouts 和 Bolts 组成的有向无环图 应用程序是通过 storm jar 的方式提交, 则需要在 main 函数中调用创建 Topology 的函数, 并在 storm jar 参数中指定 main 函数所在类 文档版本 01 ( ) 383

394 8 Storm 应用开发 代码样例 下面代码片段在 com.huawei.storm.example.wordcount.wordcounttopology 类中, 作用在于构建应用程序并提交 public static void main(string[] args) throws Exception { TopologyBuilder builder = buildtopology(); 可 /* * 任务的提交认为三种方式 * 1 命令行方式提交, 这种需要将应用程序 jar 包复制到客户端机器上执行客户端命令提交 * 2 远程方式提交, 这种需要将应用程序的 jar 包打包好之后在 Eclipse 中运行 main 方法提交 * 3 本地提交, 在本地执行应用程序, 一般用来测试 * 命令行方式和远程方式安全和普通模式都支持 * 本地提交仅支持普通模式 * * 用户同时只能选择一种任务提交方式, 默认命令行方式提交, 如果是其他方式, 请删除代码注释即 */ submittopology(builder, SubmitType.CMD); private static void submittopology(topologybuilder builder, SubmitType type) throws Exception { switch (type) { case CMD: { cmdsubmit(builder, null); break; case REMOTE: { remotesubmit(builder); break; case LOCAL: { localsubmit(builder); break; /** * 命令行方式远程提交 * 步骤如下 : * 打包成 Jar 包, 然后在客户端命令行上面进行提交 * 远程提交的时候, 要先将该应用程序和其他外部依赖 ( 非 example 工程提供, 用户自己程序依赖 ) 的 jar 包打包成一个大的 jar 包 * 再通过 storm 客户端中 storm -jar 的命令进行提交 * * 如果是安全环境, 客户端命令行提交之前, 必须先通过 kinit 命令进行安全登录 * * 运行命令如下 : *./storm jar../example/example.jar com.huawei.streaming.storm.example.wordcounttopology */ private static void cmdsubmit(topologybuilder builder, Config conf) throws AlreadyAliveException, InvalidTopologyException, NotALeaderException, AuthorizationException { if (conf == null) { conf = new Config(); 文档版本 01 ( ) 384

395 8 Storm 应用开发 conf.setnumworkers(1); StormSubmitter.submitTopologyWithProgressBar(TOPOLOGY_NAME, conf, builder.createtopology()); private static void localsubmit(topologybuilder builder) throws InterruptedException { Config conf = new Config(); conf.setdebug(true); conf.setmaxtaskparallelism(3); LocalCluster cluster = new LocalCluster(); cluster.submittopology(topology_name, conf, builder.createtopology()); Thread.sleep(10000); cluster.shutdown(); private static void remotesubmit(topologybuilder builder) throws AlreadyAliveException, InvalidTopologyException, NotALeaderException, AuthorizationException, IOException { Config config = createconf(); String userjarfilepath = " 替换为用户 jar 包地址 "; System.setProperty(STORM_SUBMIT_JAR_PROPERTY, userjarfilepath); // 安全模式下的一些准备工作 if (issecuritymodel()) { securityprepare(config); config.setnumworkers(1); StormSubmitter.submitTopologyWithProgressBar(TOPOLOGY_NAME, config, builder.createtopology()); private static TopologyBuilder buildtopology() { TopologyBuilder builder = new TopologyBuilder(); builder.setspout("spout", new RandomSentenceSpout(), 5); builder.setbolt("split", new SplitSentenceBolt(), 8).shuffleGrouping("spout"); builder.setbolt("count", new WordCountBolt(), 12).fieldsGrouping("split", new Fields("word")); return builder; 8.4 运行应用 生成示例 Jar 包 操作场景 通过命令行生成示例代码的 jar 包 操作步骤 在 Storm 示例代码根目录执行如下命令打包 :"mvn package" 执行成功后, 将会在 target 目录生成 storm-examples-1.0.jar 文档版本 01 ( ) 385

396 8 Storm 应用开发 提交拓扑 Linux 中安装客户端时提交拓扑 操作场景 在 Linux 环境下可以使用 storm 命令行完成拓扑的提交 前提条件 已安装 Storm 客户端 当客户端所在主机不是集群中的节点时, 需要在客户端所在节点的 hosts 文件中设置主机名和 IP 地址映射 主机名和 IP 地址请保持一一对应 已执行生成示例 Jar 包步骤, 打出 storm-examples-1.0.jar, 并放置到 /opt/jartarget/ 操作步骤 步骤 1 步骤 2 步骤 3 安全模式下, 请先进行安全认证, 参见 Linux 客户端环境准备 提交拓扑 以 wordcount 为例, 其它拓扑请参照相关开发指引 进入 Storm 客户端目录 storm /bin, 执行命令 :storm jar /opt/jartarget/storm-examples-1.0.jar com.huawei.storm.example.wordcount.wordcounttopology 执行 storm list 命令, 查看已经提交的应用程序, 如果发现名称为 word-count 的应用程序, 则说明任务提交成功 查看结果 操作步骤 说明 如果业务设置为本地模式, 且使用命令行方式提交时, 请确保提交环境为普通模式环境, 当前不支持安全环境下使用命令提交本地模式的业务 ---- 结束 步骤 1 参考 访问开源组件 UI 界面 章节, 访问 Storm Web 界面 步骤 2 在 Storm UI 中点击 word-count 应用, 查看应用程序运行情况, 如图 8-3 所示 文档版本 01 ( ) 386

397 8 Storm 应用开发 图 8-3 Storm 应用程序执行界面 Topology stats 统计了最近各个不同时间段的算子之间发送数据的总数据量 Spouts 中统计了 spout 算子从启动到现在发送的消息总量 Bolts 中统计了 Count 算子和 split 算子的发送消息总量, 如图 8-4 所示 图 8-4 Storm 应用程序算子发送数据总量 ---- 结束 8.5 更多信息 文档版本 01 ( ) 387

398 8 Storm 应用开发 Storm-Kafka 开发指引 操作场景 应用开发操作步骤 本文档主要说明如何使用 Storm-Kafka 工具包, 完成 Storm 和 Kafka 之间的交互 包含 KafkaSpout 和 KafkaBolt 两部分 KafkaSpout 主要完成 Storm 从 Kafka 中读取数据的功能 ; KafkaBolt 主要完成 Storm 向 Kafka 中写入数据的功能 本章节代码样例基于 Kafka 新 API, 对应 Eclipse 工程中 com.huawei.storm.example.kafka.newkafkatopology.java 本章节只适用于 MRS 产品 Storm 与 Kafka 组件间的访问 本章中描述的 jar 包的具体版本信息请以实际情况为准 步骤 1 步骤 2 步骤 3 步骤 4 确认 MRS 产品 Storm 和 Kafka 组件已经安装, 并正常运行 已搭建 Storm 示例代码工程, 将 storm-examples 导入到 Eclipse 开发环境, 参见 Windows 开发环境准备 用 WinScp 工具将 Storm 客户端安装包导入 Linux 环境并安装客户端, 参见 Linux 客户端环境准备 如果集群启用了安全服务, 需要从管理员处获取一个 人机 用户, 用于认证, 并且获取到该用户的 keytab 文件 将获取到的文件拷贝到示例工程的 src/main/resources 目录 说明 获取的用户需要同时属于 storm 组和 kafka 组 步骤 5 下载并安装 Kafka 客户端程序, 参见 Kafka 应用开发 ---- 结束 代码样例 创建拓扑 : public static void main(string[] args) throws Exception { // 设置拓扑配置 Config conf = new Config(); // 配置安全插件 setsecurityplugin(conf); if (args.length >= 2) { // 用户更改了默认的 keytab 文件名, 这里需要将新的 keytab 文件名通过参数传入 conf.put(config.topology_keytab_file, args[1]); // 定义 KafkaSpout KafkaSpout kafkaspout = new KafkaSpout<String, String>( getkafkaspoutconfig(getkafkaspoutstreams())); // CountBolt CountBolt countbolt = new CountBolt(); //SplitBolt SplitSentenceBolt splitbolt = new SplitSentenceBolt(); 文档版本 01 ( ) 388

399 8 Storm 应用开发 // KafkaBolt 配置信息 conf.put(kafkabolt.kafka_broker_properties, getkafkaproducerprops()); KafkaBolt<String, String> kafkabolt = new KafkaBolt<String, String>(); kafkabolt.withtopicselector(new DefaultTopicSelector(OUTPUT_TOPIC)).withTupleToKafkaMapper( new FieldNameBasedTupleToKafkaMapper("word", "count")); // 定义拓扑 TopologyBuilder builder = new TopologyBuilder(); builder.setspout("kafka-spout", kafkaspout, 10); builder.setbolt("split-bolt", splitbolt,10).shufflegrouping("kafka-spout", STREAMS[0]); builder.setbolt("count-bolt", countbolt, 10).fieldsGrouping( "split-bolt", new Fields("word")); builder.setbolt("kafka-bolt", kafkabolt, 10).shuffleGrouping("count-bolt"); // 命令行提交拓扑 StormSubmitter.submitTopology(args[0], conf, builder.createtopology()); 部署运行及结果查看 步骤 1 获取相关配置文件, 获取方式如下 : 安全模式 : 参见步骤 4 获取 keytab 文件 普通模式 : 无 步骤 2 在 Storm 示例代码根目录执行如下命令打包 :"mvn package" 执行成功后, 将会在 target 目录生成 storm-examples-1.0.jar 步骤 3 使用 Kafka 客户端创建拓扑中所用到的 Topic, 执行命令 :./kafka-topics.sh --create --topic input --partitions 2 --replication-factor 2 --zookeeper {ip:port/kafka./kafka-topics.sh --create --topic output --partitions 2 --replication-factor 2 --zookeeper {ip:port/kafka 说明 --zookeeper 后面填写的是 ZooKeeper 地址, 需要改为安装集群时配置的 ZooKeeper 地址 安全模式下, 需要 kafka 管理员用户创建 Topic 步骤 4 在 Linux 系统中完成拓扑的提交 提交命令示例 ( 拓扑名为 kafka-test): storm jar /opt/jartarget/storm-examples-1.0.jar com.huawei.storm.example.kafka.newkafkatopology kafka-test 说明 安全模式下, 在提交 storm-examples-1.0.jar 之前, 请确保已经进行 kerberos 安全登录, 并且 keytab 方式下, 登录用户和所上传 keytab 所属用户必须是同一个用户 安全模式下,kafka 需要用户有相应 Topic 的访问权限, 因此首先需要给用户赋权, 再提交拓扑 Kafka 用户赋权详见 Kafka 开发指南 -> 更多信息 章节 步骤 5 拓扑提交成功后, 可以向 Kafka 中发送数据, 观察是否有相关信息生成 在 Linux 系统中进入 Kafka 客户端所在目录, 在 Kafka/kafka/bin 目录下启动 consumer 观察数据是否生成 执行命令 :./kafka-console-consumer.sh --bootstrap-server {ip:port --topic output --new-consumer --consumer.config../../../kafka/kafka/config/consumer.properties 同时在 Linux 系统中进入 Kafka 客户端所在目录, 在 Kafka/kafka/bin 目录下启动 producer, 向 Kafka 中写入数据 执行命令 : 文档版本 01 ( ) 389

400 8 Storm 应用开发./kafka-console-producer.sh --broker-list {ip:port --topic input --producer.config../../../ Kafka/kafka/config/producer.properties 向 input 中写入测试数据, 可以观察到 output 中有对应的数据产生, 则说明 Storm-Kafka 拓扑运行成功 ---- 结束 Storm-JDBC 开发指引 操作场景 应用开发操作步骤 本文档主要说明如何使用开源 Storm-JDBC 工具包, 完成 Storm 和 JDBC 之间的交互 Storm-JDBC 中包含两类 Bolt:JdbcInsertBolt 和 JdbcLookupBolt 其中,JdbcLookupBolt 主要负责从数据库中查数据,JdbcInsertBolt 主要向数据库中存数据 当然, JdbcLookupBolt 和 JdbcInsertBolt 中也可以增加处理逻辑对数据进行处理 本章节只适用与 MRS 产品 Storm 与 JDBC 组件间的访问 本章中描述的 jar 包的具体版本信息请以实际情况为准 步骤 1 步骤 2 步骤 3 确认 Storm 组件已经安装, 且正常运行 下载 Storm 客户端, 将 Storm 样例工程导入到 Eclipse 开发环境, 参见 Windows 开发环境准备 用 WinScp 工具将 Storm 客户端导入 Linux 环境并安装, 具体请参见 Linux 客户端环境准备 ---- 结束 数据库配置 Derby 数据库配置过程 步骤 1 首先应下载一个数据库, 可根据具体场景选择最适合的数据库 该任务以 Derby 数据库为例 Derby 是一个小型的,java 编写的, 易于使用却适合大多数应用程序的开放源码数据库 步骤 2 Derby 数据库的获取 在官网下载最新版的 Derby 数据库 ( 本示例使用 ), 通过 WinScp 等工具传入 Linux 客户端, 并解压 步骤 3 在 Derby 的安装目录下, 进入 bin 目录, 输入如下命令 : export DERBY_INSTALL=/opt/db-derby bin export CLASSPATH=$DERBY_INSTALL/lib/derbytools.jar:$DERBY_INSTALL\lib \derbynet.jar:. export DERBY_HOME=/opt/db-derby bin. setnetworkservercp./startnetworkserver -h 主机名 步骤 4 执行./ij 命令, 输入 connect 'jdbc:derby:// 主机名 :1527/example;create=true';, 建立连接 文档版本 01 ( ) 390

401 8 Storm 应用开发 说明 执行./ij 命令前, 需要确保已配置 java_home, 可通过 which java 命令检查是否已配置 步骤 5 数据库建立好后, 可以执行 sql 语句进行操作, 需要建立两张表 ORIGINAL 和 GOAL, 并向 ORIGINAL 中插入一组数据, 命令如下 :( 表名仅供参考, 可自行设定 ) CREATE TABLE GOAL(WORD VARCHAR(12),COUNT INT ); CREATE TABLE ORIGINAL(WORD VARCHAR(12),COUNT INT ); INSERT INTO ORIGINAL VALUES('orange',1),('pineapple',1),('banana',1), ('watermelon',1); ---- 结束 代码样例 SimpleJDBCTopology 代码样例 ( 代码中涉及到的 IP 端口请修改为实际的 IP 及端口 ) public class SimpleJDBCTopology { private static final String WORD_SPOUT = "WORD_SPOUT"; private static final String COUNT_BOLT = "COUNT_BOLT"; private static final String JDBC_INSERT_BOLT = "JDBC_INSERT_BOLT"; private static final String JDBC_LOOKUP_BOLT = ("unchecked") public static void main(string[] args) throws Exception{ //connectionprovider 配置 Map hikariconfigmap = Maps.newHashMap(); hikariconfigmap.put("datasourceclassname", "org.apache.derby.jdbc.clientdatasource"); hikariconfigmap.put("datasource.servername", " ");// 请改为实际的 IP hikariconfigmap.put("datasource.portnumber", "1527");// 请改为实际的端口 致 hikariconfigmap.put("datasource.databasename", "example"); //hikariconfigmap.put("datasource.user", " user ");// 此示例不需要配置用户名和密码 //hikariconfigmap.put("datasource.password", " password "); hikariconfigmap.put("connectiontestquery", "select COUNT from GOAL"); // 表名需与建表时保持一 Config conf = new Config(); ConnectionProvider connectionprovider = new HikariCPConnectionProvider(hikariConfigMap); //JdbcLookupBolt 实例化 Fields outputfields = new Fields("WORD", "COUNT"); List<Column> queryparamcolumns = Lists.newArrayList(new Column("WORD", Types.VARCHAR)); SimpleJdbcLookupMapper jdbclookupmapper = new SimpleJdbcLookupMapper(outputFields, queryparamcolumns); String selectsql = "select COUNT from ORIGINAL where WORD =?"; JdbcLookupBolt wordlookupbolt = new JdbcLookupBolt(connectionProvider, selectsql, jdbclookupmapper); //JdbcInsertBolt 实例化 String tablename = "GOAL"; JdbcMapper simplejdbcmapper = new SimpleJdbcMapper(tableName, connectionprovider); JdbcInsertBolt userpersistancebolt = new JdbcInsertBolt(connectionProvider, simplejdbcmapper).withtablename("goal").withquerytimeoutsecs(30); WordSpout wordspout = new WordSpout();TopologyBuilder builder = new TopologyBuilder(); builder.setspout(word_spout, wordspout); builder.setbolt(jdbc_lookup_bolt, wordlookupbolt, 1).fieldsGrouping(WORD_SPOUT,new Fields("WORD")); builder.setbolt(jdbc_insert_bolt, userpersistancebolt,1).fieldsgrouping(jdbc_lookup_bolt,new Fields("WORD"));StormSubmitter.submitTopology(args[0], conf, builder.createtopology()); 文档版本 01 ( ) 391

402 8 Storm 应用开发 部署运行 步骤 1 步骤 2 在 Storm 示例代码根目录执行如下命令打包 :"mvn package" 执行成功后, 将会在 target 目录生成 storm-examples-1.0.jar 执行命令提交拓扑 提交命令示例 ( 拓扑名为 jdbc-test): storm jar /opt/jartarget/storm-examples-1.0.jar com.huawei.storm.example.jdbc.simplejdbctopology jdbc-test ---- 结束 结果查看 当拓扑提交完成后, 我们可以去数据库中查看对应表中是否有数据插入, 具体过程如下 : 执行 SQL 语句 select * from goal; 查询 GOAL 表中的数据, 如果 GOAL 表中有数据添加, 则表明整个拓扑运行成功 Storm-HDFS 开发指引 操作场景 应用开发操作步骤 本章节只适用于 Storm 和 HDFS 交互的场景 本章中描述的 jar 包的具体版本信息请以实际情况为准 安全模式下登录方式分为两种, 票据登录和 keytab 文件登录, 两种方式操作步骤基本一致, 票据登录方式为开源提供的能力, 后期需要人工上传票据, 存在可靠性和易用性问题, 因此推荐使用 keytab 方式 步骤 1 步骤 2 确认 Storm 和 HDFS 组件已经安装, 并正常运行 将 storm-examples 导入到 Eclipse 开发环境, 请参见 Windows 开发环境准备 步骤 3 如果集群启用了安全服务, 按登录方式需要进行以下配置 : keytab 方式 : 需要从管理员处获取一个 人机 用户, 用于认证, 并且获取到该用户的 keytab 文件 票据方式 : 从管理员处获取一个 人机 用户, 用于后续的安全登录, 开启 Kerberos 服务的 renewable 和 forwardable 开关并且设置票据刷新周期, 开启成功后重启 kerberos 及相关组件 说明 获取的用户需要属于 storm 组 Kerberos 服务的 renewable forwardable 开关和票据刷新周期的设置在 Kerberos 服务的配置页面的 系统 标签下, 票据刷新周期的修改可以根据实际情况修改 kdc_renew_lifetime 和 kdc_max_renewable_life 的值 步骤 4 下载并安装 HDFS 客户端, 参见 准备 Linux 客户端运行环境 步骤 5 获取 HDFS 相关配置文件 获取方法如下 : 在安装好的 HDFS 客户端目录下找到目录 /opt/client/hdfs/hdfs/hadoop/etc/ hadoop, 在该目录下获取到配置文件 core-site.xml 和 hdfs-site.xml 文档版本 01 ( ) 392

403 8 Storm 应用开发 Eclipse 代码样例 如果使用 keytab 登录方式, 按步骤 3 获取 keytab 文件 ; 如果使用票据方式, 则无需获取额外的配置文件 将获取到的这些文件拷贝到示例工程的 src/main/resources 目录 说明 获取到的 keytab 文件默认文件名为 user.keytab, 若用户需要修改, 可直接修改文件名, 但在提交任务时需要额外上传修改后的文件名作为参数 ---- 结束 创建 Topology public static void main(string[] args) throws Exception { TopologyBuilder builder = new TopologyBuilder(); // 分隔符格式, 当前采用 代替默认的, 对 tuple 中的 field 进行分隔 // HdfsBolt 必选参数 RecordFormat format = new DelimitedRecordFormat().withFieldDelimiter(" "); // 同步策略, 每 1000 个 tuple 对文件系统进行一次同步 // HdfsBolt 必选参数 SyncPolicy syncpolicy = new CountSyncPolicy(1000); // 文件大小循环策略, 当文件大小到达 5M 时, 从头开始写 // HdfsBolt 必选参数 FileRotationPolicy rotationpolicy = new FileSizeRotationPolicy(5.0f, Units.MB); // 写入 HDFS 的目的文件 // HdfsBolt 必选参数 FileNameFormat filenameformat = new DefaultFileNameFormat().withPath("/user/foo/"); // 创建 HdfsBolt HdfsBolt bolt = new HdfsBolt().withFileNameFormat(fileNameFormat).withRecordFormat(format).withRotationPolicy(rotationPolicy).withSyncPolicy(syncPolicy); //Spout 生成随机语句 builder.setspout("spout", new RandomSentenceSpout(), 1); builder.setbolt("split", new SplitSentence(), 1).shuffleGrouping("spout"); builder.setbolt("count", bolt, 1).fieldsGrouping("split", new Fields("word")); // 增加 Kerberos 认证所需的 plugin 到列表中, 安全模式必选 setsecurityconf(conf,authenticationtype.keytab); Config conf = new Config(); // 将客户端配置的 plugin 列表写入 config 指定项中, 安全模式必配 conf.put(config.topology_auto_credentials, auto_tgts); if(args.length >= 2) { // 用户更改了默认的 keytab 文件名, 这里需要将新的 keytab 文件名通过参数传入 conf.put(config.storm_client_keytab_file, args[1]); // 命令行提交拓扑 StormSubmitter.submitTopology(args[0], conf, builder.createtopology()); 文档版本 01 ( ) 393

404 8 Storm 应用开发 部署运行及结果查看 步骤 1 步骤 2 在 Storm 示例代码根目录执行如下命令打包 :"mvn package" 执行成功后, 将会在 target 目录生成 storm-examples-1.0.jar 执行命令提交拓扑 keytab 方式下, 若用户修改了 keytab 文件名, 如修改为 huawei.keytab, 则需要在命令中增加第二个参数进行说明, 提交命令示例 ( 拓扑名为 hdfs-test): storm jar /opt/jartarget/storm-examples-1.0.jar com.huawei.storm.example.hdfs.simplehdfstopology hdfs-test huawei.keytab 说明 安全模式下在提交 source.jar 之前, 请确保已经进行 kerberos 安全登录, 并且 keytab 方式下, 登录用户和所上传 keytab 所属用户必须是同一个用户 步骤 3 步骤 4 拓扑提交成功后, 请登录 HDFS 集群查看 /user/foo 目录下是否有文件生成 如果使用票据登录, 则需要使用命令行定期上传票据, 具体周期由票据刷新截止时间而定, 步骤如下 : 1. 在安装好的 storm 客户端目录的 Storm/storm /conf/storm.yaml 文件尾部新起一行添加如下内容 : topology.auto-credentials: - backtype.storm.security.auth.kerberos.autotgt 2. 执行命令 :./storm upload-credentials hdfs-test ---- 结束 Storm-OBS 开发指引 操作场景 应用开发操作步骤 本章节只适用于 MRS 产品中 Storm 和 OBS 交互的场景 本章中描述的 jar 包的具体版本信息请以实际情况为准 步骤 1 步骤 2 步骤 3 确认 Storm 已经安装, 并正常运行 将 storm-examples 导入到 Eclipse 开发环境, 请参见 Windows 开发环境准备 下载并安装 HDFS 客户端, 参见准备 Linux 客户端运行环境 步骤 4 获取相关配置文件 获取方法如下 : 在安装好的 HDFS 客户端目录下找到目录 /opt/clienthdfs/hdfs/hadoop/etc/ hadoop, 在该目录下获取到配置文件 core-site.xml 和 hdfs-site.xml 将这些文件拷贝到示例工程的 src/main/resources 目录 并在 core-site.xml 中增加如下配置项 : <property> <name>fs.s3a.connection.ssl.enabled</name> <value>true</value> </property> <property> <name>fs.s3a.endpoint</name> <value></value> 文档版本 01 ( ) 394

405 8 Storm 应用开发 Eclipse 代码样例 </property> <property> <name>fs.s3a.access.key</name> <value></value> </property> <property> <name>fs.s3a.secret.key</name> <value></value> </property> 具体 AK,SK 等的获取, 请参考 OBS 相关帮助 ---- 结束 创建 Topology private static final String DEFAULT_FS_URL = "s3a://mybucket"; public static void main(string[] args) throws Exception { TopologyBuilder builder = new TopologyBuilder(); // 分隔符格式, 当前采用 代替默认的, 对 tuple 中的 field 进行分隔 // HdfsBolt 必选参数 RecordFormat format = new DelimitedRecordFormat().withFieldDelimiter(" "); // 同步策略, 每 1000 个 tuple 对文件系统进行一次同步 // HdfsBolt 必选参数 SyncPolicy syncpolicy = new CountSyncPolicy(1000); // 文件大小循环策略, 当文件大小到达 5M 时, 从头开始写 // HdfsBolt 必选参数 FileRotationPolicy rotationpolicy = new FileSizeRotationPolicy(5.0f, Units.KB); // 写入 HDFS 的目的文件 // HdfsBolt 必选参数 FileNameFormat filenameformat = new DefaultFileNameFormat().withPath("/user/foo/"); // 创建 HdfsBolt HdfsBolt bolt = new HdfsBolt().withFsUrl(DEFAULT_FS_URL).withFileNameFormat(fileNameFormat).withRecordFormat(format).withRotationPolicy(rotationPolicy).withSyncPolicy(syncPolicy); //Spout 生成随机语句 builder.setspout("spout", new RandomSentenceSpout(), 1); builder.setbolt("split", new SplitSentence(), 1).shuffleGrouping("spout"); builder.setbolt("count", bolt, 1).fieldsGrouping("split", new Fields("word")); Config conf = new Config(); // 命令行提交拓扑 StormSubmitter.submitTopology(args[0], conf, builder.createtopology()); 文档版本 01 ( ) 395

406 8 Storm 应用开发 部署运行及结果查看 步骤 1 步骤 2 在 Storm 示例代码根目录执行如下命令打包 :"mvn package" 执行成功后, 将会在 target 目录生成 storm-examples-1.0.jar 执行命令提交拓扑 提交命令示例 ( 拓扑名为 obs-test): storm jar /opt/jartarget/storm-examples-1.0.jar com.huawei.storm.example.obs.simpleobstopology s3a://my-bucket obs-test 步骤 3 拓扑提交成功后请登录 OBS Browser 查看 ---- 结束 Storm-HBase 开发指引 操作场景 应用开发操作步骤 本章节只适用于 MRS 产品中 Storm 和 HBase 交互的场景 本章中描述的 jar 包的具体版本信息请以实际情况为准 安全模式下登录方式分为两种, 票据登录和 keytab 文件登录, 两种方式操作步骤基本一致 票据登录方式为开源提供的能力, 存在票据过期问题, 后期需要人工上传票据, 并且可靠性和易用性较差, 因此推荐使用 keytab 方式 步骤 1 步骤 2 确认 Storm 和 HBase 组件已经安装, 并正常运行 将 storm-examples 导入到 Eclipse 开发环境, 请参见 Windows 开发环境准备 步骤 3 如果集群启用了安全服务, 按登录方式分为以下两种 : keytab 方式 : 需要从管理员处获取一个 人机 用户, 用于认证, 并且获取到该用户的 keytab 文件 票据方式 : 从管理员处获取一个 人机 用户, 用于后续的安全登录, 开启 Kerberos 服务的 renewable 和 forwardable 开关并且设置票据刷新周期, 开启成功后重启 kerberos 及相关组件 说明 获取的用户需要属于 storm 组 Kerberos 服务的 renewable forwardable 开关和票据刷新周期的设置在 Kerberos 服务的配置页面的 系统 标签下, 票据刷新周期的修改可以根据实际情况修改 kdc_renew_lifetime 和 kdc_max_renewable_life 的值 步骤 4 下载并安装 HBase 客户端程序, 参见 HBase 开发指南 步骤 5 获取相关配置文件 获取方法如下 : 在安装好的 hbase 客户端目录下找到目录 /opt/clienthbase/hbase/hbase/conf, 在该目录下获取到 core-site.xml hdfs-site.xml hbase-site.xml 配置文件 将这些文件拷贝到示例工程的 src/main/resources 目录 如果使用 keytab 登录方式, 按步骤 3 获取 keytab 文件 ; 如果使用票据方式, 则无需获取额外的配置文件 文档版本 01 ( ) 396

407 8 Storm 应用开发 Eclipse 代码样例 说明 获取到的 keytab 文件默认文件名为 user.keytab, 若用户需要修改, 可直接修改文件名, 但在提交任务时需要额外上传修改后的文件名作为参数 ---- 结束 创建 Topology public static void main(string[] args) throws Exception { Config conf = new Config(); // 增加 kerberos 认证所需的 plugin 到列表中, 安全模式需要设置 setsecurityconf(conf,authenticationtype.keytab); if(args.length >= 2) { // 用户更改了默认的 keytab 文件名, 这里需要将新的 keytab 文件名通过参数传入 conf.put(config.storm_client_keytab_file, args[1]); //hbase 的客户端配置, 这里只提供了 hbase.rootdir 配置项, 参数可选 Map<String, Object> hbconf = new HashMap<String, Object>(); if(args.length >= 3) { hbconf.put("hbase.rootdir", args[2]); // 必配参数, 若用户不输入, 则该项为空 conf.put("hbase.conf", hbconf); //spout 为随机单词 spout WordSpout spout = new WordSpout(); WordCounter bolt = new WordCounter(); //HbaseMapper, 用于解析 tuple 内容 SimpleHBaseMapper mapper = new SimpleHBaseMapper().withRowKeyField("word").withColumnFields(new Fields("word")).withCounterFields(new Fields("count")).withColumnFamily("cf"); //HBaseBolt, 第一个参数为表名 //withconfigkey("hbase.conf") 将 hbase 的客户端配置传入 HBaseBolt HBaseBolt hbase = new HBaseBolt("WordCount", mapper).withconfigkey("hbase.conf"); // wordspout ==> countbolt ==> HBaseBolt TopologyBuilder builder = new TopologyBuilder(); builder.setspout(word_spout, spout, 1); builder.setbolt(count_bolt, bolt, 1).shuffleGrouping(WORD_SPOUT); builder.setbolt(hbase_bolt, hbase, 1).fieldsGrouping(COUNT_BOLT, new Fields("word")); // 命令行提交拓扑 StormSubmitter.submitTopology(args[0], conf, builder.createtopology()); 部署运行及结果查看 步骤 1 步骤 2 在 Storm 示例代码根目录执行如下命令打包 :"mvn package" 执行成功后, 将会在 target 目录生成 storm-examples-1.0.jar 执行命令提交拓扑 keytab 方式下, 若用户修改了 keytab 文件名, 如修改为 huawei.keytab, 则需要在命令中增加第二个参数进行说明, 提交命令示例 ( 拓扑名为 hbase-test): 文档版本 01 ( ) 397

408 8 Storm 应用开发 storm jar /opt/jartarget/storm-examples-1.0.jar com.huawei.storm.example.hbase.simplehbasetopology hbase-test huawei.keytab 说明 安全模式下在提交 source.jar 之前, 请确保已经进行 kerberos 安全登录, 并且 keytab 方式下, 登录用户和所上传 keytab 所属用户必须是同一个用户 因为示例中的 HBaseBolt 并没有建表功能, 在提交之前确保 hbase 中存在相应的表, 若不存在需要手动建表,hbase shell 建表语句如下 create 'WordCount', 'cf' 安全模式下 hbase 需要用户有相应表甚至列族和列的访问权限, 因此首先需要在 hbase 所在集群上使用 hbase 管理员用户登录, 之后在 hbase shell 中使用 grant 命令给提交用户申请相应表的权限, 如示例中的 WordCount, 成功之后再使用提交用户登录并提交拓扑 步骤 3 步骤 4 拓扑提交成功后请自行登录 HBase 集群查看 WordCount 表是否有数据生成 如果使用票据登录, 则需要使用命令行定期上传票据, 具体周期由票据刷新截止时间而定, 步骤如下 : 1. 在安装好的 storm 客户端目录的 Storm/storm /conf/storm.yaml 文件尾部新起一行添加如下内容 : topology.auto-credentials: - backtype.storm.security.auth.kerberos.autotgt 2. 执行命令 :./storm upload-credentials hbase-test ---- 结束 Flux 开发指引 操作场景 基本语法说明 本章节只适用于 MRS 产品中 Storm 组件使用 Flux 框架提交和部署拓扑的场景 本章中描述的 jar 包的具体版本信息请以实际情况为准 Flux 框架是 Storm 版本提供的提高拓扑部署易用性的框架 通过 Flux 框架, 用户可以使用 yaml 文件来定义和部署拓扑, 并且最终通过 storm jar 命令来提交拓扑的一种方式, 极大地方便了拓扑的部署和提交, 缩短了业务开发周期 使用 Flux 定义拓扑分为两种场景, 定义新拓扑和定义已有拓扑 1. 使用 Flux 定义新拓扑 使用 Flux 定义拓扑, 即使用 yaml 文件来描述拓扑, 一个完整的拓扑定义需要包含以下几个部分 : 拓扑名称 定义拓扑时需要的组件列表 拓扑的配置 拓扑的定义, 包含 spout 列表 bolt 列表和 stream 列表 定义拓扑名称 : name: "yaml-topology" 定义组件列表示例 : # 简单的 component 定义 components: 文档版本 01 ( ) 398

409 8 Storm 应用开发 - id: "stringscheme" classname: "org.apache.storm.kafka.stringscheme" # 使用构造函数定义 component - id: "defaulttopicselector" classname: "org.apache.storm.kafka.bolt.selector.defaulttopicselector" constructorargs: - "output" # 构造函数入参使用引用, 使用 `ref` 标志来说明引用 # 在使用引用时请确保被引用对象在前面定义 - id: "stringmultischeme" classname: "org.apache.storm.spout.schemeasmultischeme" constructorargs: - ref: "stringscheme" # 构造函数入参引用指定的 properties 文件中的配置项, 使用 `${` 标志来表示 # 引用 properties 文件时, 请在使用 storm jar 命令提交拓扑时使用 --filter my-prop.properties 的方式指明 properties 文件路径 - id: "zkhosts" classname: "org.apache.storm.kafka.zkhosts" constructorargs: - "${kafka.zookeeper.root.list" # 构造函数入参引用环境变量, 使用 `${ENV-[NAME]` 方式来引用 #NAME 必须是一个已经定义的环境变量 - id: "zkhosts" classname: "org.apache.storm.kafka.zkhosts" constructorargs: - "${ENV-ZK_HOSTS" # 使用 `properties` 关键字初始化内部私有变量 - id: spoutconfig classname: "org.apache.storm.kafka.spoutconfig" constructorargs: - ref: "zkhosts" - "input" - "/kafka/input" - "myid" properties: - name: "scheme" ref: "stringmultischeme" # 定义 KafkaBolt 使用的 properties - id: "kafkaproducerprops" classname: "java.util.properties" configmethods: - name: "put" args: - "bootstrap.servers" - "${metadata.broker.list" - name: "put" args: - "acks" - "1" - name: "put" args: - "key.serializer" - "org.apache.kafka.common.serialization.stringserializer" - name: "put" args: - "value.serializer" - "org.apache.kafka.common.serialization.stringserializer" 定义拓扑的配置示例 : config: # 简单配置项 topology.workers: 1 # 配置项值为列表, 使用 `[]` 表示 文档版本 01 ( ) 399

410 8 Storm 应用开发 topology.auto-credentials: ["class1","class2"] # 配置项值为 map 结构 kafka.broker.properties: metadata.broker.list: "${metadata.broker.list" producer.type: "async" request.required.acks: "0" serializer.class: "kafka.serializer.stringencoder" 定义 spout/bolt 列表示例 : # 定义 spout 列表 spouts: - id: "spout1" classname: "org.apache.storm.kafka.kafkaspout" constructorargs: - ref: "spoutconfig" parallelism: 1 # 定义 bolt 列表 bolts: - id: "bolt1" classname: "com.huawei.storm.example.hbase.wordcounter" parallelism: 1 # 使用方法来初始化对象, 关键字为 `configmethods` - id: "bolt2" classname: "org.apache.storm.hbase.bolt.hbasebolt" constructorargs: - "WordCount" - ref: "mapper" configmethods: - name: "withconfigkey" args: ["hbase.conf"] parallelism: 1 - id: "kafkabolt" classname: "org.apache.storm.kafka.bolt.kafkabolt" configmethods: - name: "withtopicselector" args: - ref: "defaulttopicselector" - name: "withproducerproperties" args: [ref: "kafkaproducerprops"] - name: "withtupletokafkamapper" args: - ref: "fieldnamebasedtupletokafkamapper" 定义 stream 列表示例 : # 定义流式需要制定分组方式, 关键字为 `grouping`, 当前提供的分组方式关键字有 : #`ALL`,`CUSTOM`,`DIRECT`,`SHUFFLE`,`LOCAL_OR_SHUFFLE`,`FIELDS`,`GLOBAL`, 和 `NONE`. # 其中 `CUSTOM` 为用户自定义分组 # 简单流定义, 分组方式为 SHUFFLE streams: - name: "spout1 --> bolt1" from: "spout1" to: "bolt1" grouping: type: SHUFFLE # 分组方式为 FIELDS, 需要传入参数 - name: "bolt1 --> bolt2" from: "bolt1" to: "bolt2" grouping: type: FIELDS args: ["word"] # 分组方式为 CUSTOM, 需要指定用户自定义分组类 - name: "bolt-1 --> bolt2" 文档版本 01 ( ) 400

411 8 Storm 应用开发 应用开发操作步骤 from: "bolt-1" to: "bolt-2" grouping: type: CUSTOM customclass: classname: "org.apache.storm.testing.ngrouping" constructorargs: 使用 Flux 定义已有拓扑 如果已经拥有拓扑 ( 例如已经使用 java 代码定义了拓扑 ), 仍然可以使用 Flux 框架来提交和部署, 这时需要在现有的拓扑定义 ( 如 MyTopology.java) 中实现 gettopology() 方法, 在 java 中定义如下 : public StormTopology gettopology(config config) 或者 public StormTopology gettopology(map<string, Object> config) 这时可以使用如下 yaml 文件来定义拓扑 : name: "existing-topology" # 拓扑名可随意指定 topologysource: classname: "custom-class" # 请指定客户端类 当然, 仍然可以指定其他方法名来获得 StormTopology( 非 gettopology() 方法 ), yaml 文件示例如下 : name: "existing-topology" topologysource: classname: "custom-class " methodname: "gettopologywithdifferentmethodname" 说明 指定的方法必须接受一个 Map<String, Object> 类型或者 Config 类型的入参, 并且返回 backtype.storm.generated.stormtopology 类型的对象, 和 gettopology() 方法相同 步骤 1 步骤 2 确认 Storm 组件已经安装, 并正常运行 如果业务需要连接其他组件, 请同时安装该组件并运行 将 storm-examples 导入到 Eclipse 开发环境, 请参见 Windows 开发环境准备 步骤 3 参考 storm-examples 工程 src/main/resources/flux-examples 目录下的相关 yaml 应用示例, 开发客户端业务 步骤 4 获取相关配置文件 说明 本步骤只适用于业务中有访问外部组件需求的场景, 如 HDFS HBase 等, 获取方式请参见 Storm-HDFS 开发指引或者 Storm-HBase 开发指引 若业务无需获取相关配置文件, 请忽略本步骤 ---- 结束 Flux 配置文件样例 下面是一个完整的访问 Kafka 业务的 yaml 文件样例 : name: "simple_kafka" components: - id: "zkhosts" # 对象名称 classname: "org.apache.storm.kafka.zkhosts" # 完整的类名 constructorargs: # 构造函数 - "${kafka.zookeeper.root.list" # 构造函数的参数 文档版本 01 ( ) 401

412 8 Storm 应用开发 - id: "stringscheme" classname: "org.apache.storm.kafka.stringscheme" - id: "stringmultischeme" classname: "org.apache.storm.spout.schemeasmultischeme" constructorargs: - ref: "stringscheme" # 使用了引用, 值为前面定义的 stringscheme - id: spoutconfig classname: "org.apache.storm.kafka.spoutconfig" constructorargs: - ref: "zkhosts" # 使用了引用 - "input" - "/kafka/input" - "myid" properties: # 使用 properties 来设置本对象中的名为 scheme 的私有变量 - name: "scheme" ref: "stringmultischeme" - id: "defaulttopicselector" classname: "org.apache.storm.kafka.bolt.selector.defaulttopicselector" constructorargs: - "output" - id: "fieldnamebasedtupletokafkamapper" classname: "org.apache.storm.kafka.bolt.mapper.fieldnamebasedtupletokafkamapper" constructorargs: - "words" # 构造函数中第一个入参 - "count" # 构造函数中第二个入参 config: topology.workers: 1 # 设置拓扑的 worker 数量为 1 kafka.broker.properties: # 设置 kafka 相关的配置, 值为 map 结构 metadata.broker.list: "${metadata.broker.list" producer.type: "async" request.required.acks: "0" serializer.class: "kafka.serializer.stringencoder" spouts: - id: "kafkaspout" #spout 名称 classname: "storm.kafka.kafkaspout"#spout 的类名 constructorargs: # 使用构造函数的方式初始化 - ref: "spoutconfig" # 构造函数的入参使用了引用 parallelism: 1 # 该 spout 的并发设置为 1 bolts: - id: "splitbolt" classname: "com.huawei.storm.example.common.splitsentencebolt" parallelism: 1 - id: "countbolt" classname: "com.huawei.storm.example.kafka.countbolt" parallelism: 1 - id: "kafkabolt" classname: "org.apache.storm.kafka.bolt.kafkabolt" configmethods: # 使用调用对象内部方法的形式初始化对象 - name: "withtopicselector" # 调用的内部方法名 args: # 内部方法需要的入参 - ref: "defaulttopicselector" # 入参只有一个, 使用了引用 - name: "withtupletokafkamapper" # 调用第二个内部方法 args: - ref: "fieldnamebasedtupletokafkamapper" # 定义数据流 streams: - name: "kafkaspout --> splitbolt" # 第一个数据流名称, 只作为展示 from: "kafkaspout" # 数据流起点, 值为 spouts 中定义的 kafkaspout 文档版本 01 ( ) 402

413 8 Storm 应用开发 to: "splitbolt" # 数据流终点, 值为 bolts 中定义的 splitbolt grouping:# 定义分组方式 type: LOCAL_OR_SHUFFLE # 分组方式为 local_or_shuffle - name: "splitbolt --> countbolt" # 第二个数据流 from: "splitbolt" to: "countbolt" grouping: type: FIELDS # 分组方式为 fields args: ["word"] #fields 方式需要传入参数 - name: "countbolt --> kafkabolt" # 第三个数据流 from: "countbolt" to: "kafkabolt" grouping: type: SHUFFLE # 分组方式为 shuffle, 无需传入参数 部署运行及结果查看 步骤 1 步骤 2 步骤 3 使用如下命令打包 : mvn package 执行成功后, 将会在 target 目录生成 stormexamples-1.0.jar 将打好的 jar 包, 以及开发好的 yaml 文件及相关的 properties 文件拷贝至 storm 客户端所在主机的任意目录下, 如 /opt 执行命令提交拓扑 storm jar /opt/jartarget/storm-examples-1.0.jar org.apache.storm.flux.flux -- remote /opt/my-topology.yaml 如果设置业务以本地模式启动, 则提交命令如下 : storm jar /opt/jartarget/storm-examples-1.0.jar org.apache.storm.flux.flux -- local /opt/my-topology.yaml 说明 如果业务设置为本地模式, 请确保提交环境为普通模式环境, 当前不支持安全环境下使用命令提交本地模式的业务 如果使用了 properties 文件, 则提交命令如下 : storm jar /opt/jartarget/storm-examples-1.0.jar org.apache.storm.flux.flux -- remote /opt/my-topology.yaml --filter /opt/my-prop.properties 步骤 4 拓扑提交成功后请自行登录 storm UI 查看 对外接口 ---- 结束 Storm 采用的接口同开源社区版本保持一致, 详情请参见 : Storm-HDFS 采用的接口同开源社区版本保持一致, 详情参见 : Storm-HBase 采用的接口同开源社区版本保持一致, 详情参见 : Storm-Kafka 采用的接口同开源社区版本保持一致, 详情参见 : 文档版本 01 ( ) 403

414 8 Storm 应用开发 Storm-Redis 采用的接口同开源社区版本保持一致, 详情参见 : Storm-JDBC 采用的接口同开源社区版本保持一致, 详情参见 : 开发规范 规则 不允许将 storm.yaml 打包到应用程序 jar 包中 若将 storm.yaml 打包到应用程序 jar 包中, 应用提交后会导致应用程序使用的 storm.yaml 与集群中的 storm.yaml 冲突, 造成应用程序不可用 打包完成后, 可使用解压工具查看 jar 包根目录下是否存在该文件, 若存在则删除 不允许将 log4j 相关 jar 包打包到应用程序 jar 包中 若将 log4j 相关 jar 包打包到应用程序 jar 包中, 应用提交后会导致系统中的 log4j 与应用程序中的 log4j 冲突, 造成应用程序不可用 打包前, 需要排查 jar 包工程中是否存在 log4j 相关 jar 包, 若存在则删除后再重新打包 不允许将 storm-core-x.x.x.jar 打包到应用程序 jar 包中 若将 storm-core-x.x.x.jar 打包到应用程序 jar 包中, 应用提交后会导致系统中的 storm-core jar 与应用程序中的 storm-core-x.x.x.jar 冲突, 造成应用程序不可用 打包前, 需要排查 jar 包工程中是否存在 storm-core-x.x.x.jar, 若存在则删除后再重新打包 使用远程提交方式提交拓扑时, 应保证所打 jar 包和本地工程代码的一致性 建议 若提交的应用程序 jar 包与本地工程代码不一致, 将导致提交的应用在运行期间报错 一个 Topology 所使用的 worker 数量, 建议不要超过 12 个 虽然一个 Topology 所使用的 worker 数量越多, 能够处理的数据量越大, 但 worker 间通信的成本就越高, 建议不超过 12 个 worker 一个 Topology 的层级建议不要超过 6 层, 层级的增加会导致 Spout 吞吐量下降 一个 Topology 的层级越深, 数据在集群内的传输消耗越大, 因此导致性能显著下降 文档版本 01 ( ) 404

415 9 Kafka 应用开发 9 Kafka 应用开发 9.1 概述 应用开发简介 Kafka 简介 接口类型简介 常用概念 Kafka 是一个分布式的消息发布 - 订阅系统 它采用独特的设计提供了类似 JMS 的特性, 主要用于处理活跃的流式数据 Kafka 有很多适用的场景 : 消息队列 行为跟踪 运维数据监控 日志收集 流处理 事件溯源 持久化日志等 Kafka 有如下几个特点 : 高吞吐量 消息持久化到磁盘 分布式系统易扩展 容错性好 支持 online 和 offline 场景 Kafka 主要提供了的 API 主要可分 Producer API 和 Consumer API 两大类, 均提供有 Java API, 使用的具体接口说明请参考 Java API Topic Kafka 维护的同一类的消息称为一个 Topic Partition 每一个 Topic 可以被分为多个 Partition, 每个 Partition 对应一个可持续追加的 有序不可变的 log 文件 文档版本 01 ( ) 405

416 9 Kafka 应用开发 开发流程 Producer 将消息发往 Kafka topic 中的角色称为 Producer Consumer 从 Kafka topic 中获取消息的角色称为 Consumer Broker Kafka 集群中的每一个节点服务器称为 Broker Kafka 客户端角色包括 Producer 和 Consumer 两个角色, 其应用开发流程是相同的 开发流程中各个阶段的说明如图 9-1 和表 9-1 所示 图 9-1 Kafka 客户端程序开发流程 文档版本 01 ( ) 406

417 9 Kafka 应用开发 表 9-1 Kafka 客户端程序开发的流程说明 阶段说明参考文档 了解基本概念准备开发环境准备运行环境准备工程根据场景开发工程编译并运行程序查看程序运行结果 在开始开发客户端前, 需要了解 Kafka 的基本概念, 根据实际场景判断, 需要开发的角色是 Producer 还是 Consumer Kafka 的客户端程序当前推荐使用 java 语言进行开发, 并使用 Maven 工具构建工程 Kafka 的样例程序运行环境即 MRS 服务所 VPC 集群的节点 Kafka 提供了不同场景下的样例程序, 您可以下载样例工程进行程序学习 或者您可以根据指导, 新建一个 Kafka 工程 提供了 Producer 和 Consumer 相关 API 的使用样例, 包含了新旧 API 和多线程的使用场景, 帮助用户快速熟悉 Kafka 接口 指导用户将开发好的程序编译并打包, 上传到 VPC 的 Linux 节点运行 程序运行结果可以输出到 Linux 命令行页面 也可通过 Linux 客户端进行 Topic 数据消费的方式查看数据是否写入成功 常用概念准备 Maven 和 JDK - 导入样例工程开发程序调测程序调测程序 9.2 环境准备 开发环境简介 Kafka 开发应用时, 需要准备的开发环境如下表所示 : 表 9-2 开发环境 准备项 操作系统 说明 Windows 系统, 推荐 Windows 7 以上版本 文档版本 01 ( ) 407

418 9 Kafka 应用开发 准备项 安装 JDK 和 Maven 安装和配置 Eclipse 或 IntelliJ IDEA 网络 访问云服务器的安全认证 说明 开发环境的基本配置 JDK 版本要求 :1.7 或者 1.8 Maven 版本要求 :3.3.0 及以上 用于开发 Kafka 应用程序的工具 确保本地与 Kafka 服务所在的 VPC 的至少一个节点在网络上互通 本地可以通过密钥或密码方式登陆访问 Linux 弹性云服务器 准备 Maven 和 JDK 操作场景 开发环境搭建在 Windows 环境下 操作步骤 1. 开发环境安装 Eclipse 程序, 安装要求如下 : Eclipse 使用 3.0 及以上版本 IntelliJ IDEA 使用 15.0 以上版本 2. 开发环境安装 JDK 环境, 安装要求如下 : JDK 使用 1.7 或者 1.8 版本 支持 IBM JDK 和 Oracle JDK 导入样例工程 说明 若使用 IBM JDK, 请确保 Eclipse 或者 IntelliJ IDEA 中的 JDK 配置为 IBM JDK 若使用 Oracle JDK, 请确保 Eclipse 或者 IntelliJ IDEA 中的 JDK 配置为 Oracle JDK 不同的 Eclipse 不要使用相同的 workspace 和相同路径下的示例工程 3. 开发环境安装 Mave 环境, 安装版本 以上 操作步骤 1. 下载 MRS 服务样例工程, 并解压并找到 kafka-examples 目录 2. 使用 Eclipse 或者 IntelliJ IDEA 创建 Maven 工程 3. 将样例工程中的 src 目录 conf 目录和 pom.xml 文件复制进入 Maven 工程目录中, 并设置为 Sources root 目录 4. 将工程的编码格式设置为 UTF-8, 解决乱码问题 准备安全认证 前提条件 MRS 服务集群开启了 Kerberos 认证, 没有开启 Kerberos 认证的集群忽略该步骤 文档版本 01 ( ) 408

419 9 Kafka 应用开发 准备认证机制代码 在开启 Kerberos 认证的环境下, 各个组件之间的相互通信不能够简单的互通, 而需要在通信之前进行相互认证, 以确保通信的安全性 Kafka 应用开发需要进行 Kafka ZooKeeper Kerberos 的安全认证, 这些安全认证只需要生成一个 jaas 文件并设置相关环境变量即可 我们提供了 LoginUtil 相关接口来完成这些配置, 如下样例代码中只需要配置用户自己申请的机机帐号名称和对应的 keytab 文件名称即可 认证样例代码 : 设置 keytab 认证文件模块 /** * 用户自己申请的机机账号 keytab 文件名称 */ private static final String USER_KEYTAB_FILE = " 用户自己申请的机机账号 keytab 文件名称 "; /** * 用户自己申请的机机账号名称 */ private static final String USER_PRINCIPAL = " 用户自己申请的机机账号名称 "; MRS 服务 Kerberos 认证模块, 如果服务没有开启 kerberos 认证, 这块逻辑不执行 public static void securityprepare() throws IOException { String filepath = System.getProperty("user.dir") + File.separator + "conf" + File.separator; String krbfile = filepath + "krb5.conf"; String userkeytablefile = filepath + USER_KEYTAB_FILE; //windows 路径下分隔符替换 userkeytablefile = userkeytablefile.replace("\\", "\\\\"); krbfile = krbfile.replace("\\", "\\\\"); LoginUtil.setKrb5Config(krbFile); LoginUtil.setZookeeperServerPrincipal("zookeeper/hadoop.hadoop.com"); LoginUtil.setJaasFile(USER_PRINCIPAL, userkeytablefile); 说明 如果修改了集群 kerberos 域名, 需要在代码中增加 kerberos.domain.name 的配置, 并按照 hadoop.expr=tolowercase(%{default_realm%{kerberosserver) 规则配置正确的域名信息 例如 : 修改域名为 HUAWEI.COM, 则配置为 hadoop.huawei.com keytab 文件获取方式 9.3 开发程序 1. 访问开启 Kerberos 的 MRS Manager, 访问方式详见 MapReduce 服务用户指南 的 访问 MRS Manager-> 访问支持 Kerberos 认证的 Manager 章节 2. 进入 系统设置 -> 用户管理, 在指定的用户, 点击 更多 -> 下载认证凭据 下载 krb5.conf 和该用户的 keytab 文件压缩包 3. 将下载获取到的 zip 文件解压缩, 获取 krb5.conf 和该用户的 keytab 文件 4. 将 krb5.conf 和该用户的 keytab 文件拷贝到样例工程的 conf 目录中 文档版本 01 ( ) 409

420 9 Kafka 应用开发 典型场景说明 场景说明 Kafka 是一个分布式消息系统, 在此系统上我们可以做一些消息的发布和订阅操作, 假定用户要开发一个 Producer, 让其每秒向 Kafka 集群某 Topic 发送一条消息, 另外, 我们还需要实现一个 Consumer, 订阅该 Topic, 实时消费该类消息 开发思路 1. 使用 Linux 客户端创建一个 Topic 2. 开发一个 Producer 向该 Topic 生产数据 3. 开发一个 Consumer 消费该 Topic 的数据 样例代码说明 Old Producer API 使用样例 功能介绍 样例代码 Producer 是消息生产者的角色, 负责发布消息到 Kafka Broker 下面代码片段在 com.huawei.bigdata.kafka.example.old_producer 类中, 作用在于每秒向指定的 Topic 发送一条消息 ( 注意 :Old Producer API 仅支持通过不启用 Kerberos 认证模式端口访问未设置 ACL 的 Topic, 安全接口说明见安全接口说明 ) Old Producer API 的 run 方法中的逻辑 /* * 启动执行 producer, 每秒发送一条消息 */ public void run() { LOG.info("Old Producer: start."); int messageno = 1; while (true) { String messagestr = new String("Message_" + messageno); // 指定消息序号作为 key 值 String key = String.valueOf(messageNo); producer.send(new KeyedMessage<String, String>(topic, key, messagestr)); LOG.info("Producer: send " + messagestr + " to " + topic); messageno++; // 每隔 1s, 发送 1 条消息 try { Thread.sleep(1000); catch (InterruptedException e) { e.printstacktrace(); 文档版本 01 ( ) 410

421 9 Kafka 应用开发 Old Consumer API 使用样例 功能介绍 样例代码 每一个 Consumer 实例都属于一个 Consumer group, 每一条消息只会被同一个 Consumer group 里的一个 Consumer 实例消费 ( 不同的 Consumer group 可以同时消费同一条消息 ) 下面代码片段在 com.huawei.bigdata.kafka.example.old_consumer 类中, 作用在于订阅指定 Topic 的消息 ( 注意 : 旧 Consumer API 仅支持访问未设置 ACL 的 Topic, 安全接口说明见安全接口说明 ) Old Consumer API 线程 run 方法中的消费逻辑 /** * 启动执行 Consumer, 订阅 Kafka 上指定 topic 消息 */ public void run() { LOG.info("Consumer: start."); Map<String, Integer> topiccountmap = new HashMap<String, Integer>(); topiccountmap.put(topic, new Integer(1)); Map<String, List<KafkaStream<byte[], byte[]>>> consumermap = consumer.createmessagestreams(topiccountmap); List<KafkaStream<byte[], byte[]>> streams = consumermap.get(topic); LOG.info("Consumerstreams size is : " + streams.size()); for (KafkaStream<byte[], byte[]> stream : streams) { ConsumerIterator<byte[], byte[]> it = stream.iterator(); while (it.hasnext()) { LOG.info("Consumer: receive " + new String(it.next().message()) + " from " + topic); LOG.info("Consumer End."); Producer API 使用样例 功能介绍 样例代码 下面代码片段在 com.huawei.bigdata.kafka.example.producer 类中, 用于实现新 Producer API 向安全 Topic 生产消息 Producer 线程 run 方法中的消费逻辑 public void run() { LOG.info("New Producer: start."); int messageno = 1; // 指定发送多少条消息后 sleep1 秒 int intervalmessages=10; while (messageno <= messagenumtosend) 文档版本 01 ( ) 411

422 9 Kafka 应用开发 { String messagestr = "Message_" + messageno; long starttime = System.currentTimeMillis(); // 构造消息记录 ProducerRecord<Integer, String> record = new ProducerRecord<Integer, String>(topic, messageno, messagestr); if (isasync) { // 异步发送 producer.send(record, new DemoCallBack(startTime, messageno, messagestr)); else { try { // 同步发送 producer.send(record).get(); catch (InterruptedException ie) { LOG.info("The InterruptedException occured : {.", ie); catch (ExecutionException ee) { LOG.info("The ExecutionException occured : {.", ee); messageno++; if (messageno % intervalmessages == 0) { // 每发送 intervalmessage 条消息 sleep1 秒 try { Thread.sleep(1000); catch (InterruptedException e) { e.printstacktrace(); LOG.info("The Producer have send { messages.", messageno); Consumer API 使用样例 功能介绍 代码样例 下面代码片段在 com.huawei.bigdata.kafka.example.consumer 类中, 用于消费订阅的 Topic 消息 Consumer 线程的 dowork 方法逻辑, 该方法是 run 方法的重写 /** * 订阅 Topic 的消息处理函数 */ public void dowork() { // 订阅 consumer.subscribe(collections.singletonlist(this.topic)); 文档版本 01 ( ) 412

423 9 Kafka 应用开发 // 消息消费请求 ConsumerRecords<Integer, String> records = consumer.poll(waittime); // 消息处理 for (ConsumerRecord<Integer, String> record : records) { LOG.info("[NewConsumerExample], Received message: (" + record.key() + ", " + record.value() + ") at offset " + record.offset()); 多线程 Producer API 使用样例 功能介绍 代码样例 在 Producer API 使用样例基础上, 实现了多线程 Producer, 可启动多个 Producer 线程, 并通过指定相同 key 值的方式, 使每个线程对应向特定 Partition 发送消息 下面代码片段在 com.huawei.bigdata.kafka.example.producermultthread 类中, 用于实现多线程生产数据 生产者类线程类的 run 方法逻辑 /** * 生产者线程执行函数, 循环发送消息 */ public void run() { LOG.info("Producer: start."); // 用于记录消息条数 int messagecount = 1; // 每个线程发送的消息条数 int messagesperthread = 5; while (messagecount <= messagesperthread) { // 待发送的消息内容 String messagestr = new String("Message_" + sendthreadid + "_" + messagecount); // 此处对于同一线程指定相同 Key 值, 确保每个线程只向同一个 Partition 生产消息 Integer key = new Integer(sendThreadId); long starttime = System.currentTimeMillis(); // 构造消息记录 ProducerRecord<Integer, String> record = new ProducerRecord<Integer, String>(topic, key, messagestr); if (isasync) { // 异步发送 producer.send(record, new DemoCallBack(startTime, key, messagestr)); else { try { // 同步发送 producer.send(record).get(); catch (InterruptedException ie) { LOG.info("The InterruptedException occured : {.", ie); 文档版本 01 ( ) 413

424 9 Kafka 应用开发 catch (ExecutionException ee) { LOG.info("The ExecutionException occured : {.", ee); LOG.info("Producer: send " + messagestr + " to " + topic + " with key: " + key); messagecount++; // 每隔 1s, 发送 1 条消息 try { Thread.sleep(1000); catch (InterruptedException e) { e.printstacktrace(); ProducerMultThread 主类的线程启动逻辑 /** * 启动多个线程进行发送 */ public void run() { // 是否使用异步发送模式 final boolean asyncenable = false; // 指定的线程号, 仅用于区分不同的线程 for (int threadnum = 0; threadnum < PRODUCER_THREAD_COUNT; threadnum++) { ProducerThread producerthread = new ProducerThread(topic, asyncenable, threadnum); producerthread.start(); 多线程 Consumer API 使用样例 功能介绍 代码样例 在 Consumer API 使用样例基础上, 实现了多线程并发消费, 可根据 Topic 的 Partition 数目起相应个数的 Consumer 线程来对应消费消息 下面代码片段在 com.huawei.bigdata.kafka.example.consumermultthread 类中, 用于实现对指定 Topic 的并发消费 单个消费者线程的 dowork() 方法逻辑 (run 方法重写 ) /** * 订阅 Topic 的消息处理函数 */ public void dowork() { // 订阅 consumer.subscribe(collections.singletonlist(this.topic)); // 消息消费请求 ConsumerRecords<Integer, String> records = consumer.poll(waittime); // 消息处理 for (ConsumerRecord<Integer, String> record : records) { LOG.info(receivedThreadId+"Received message: (" + record.key() + ", " + record.value() 文档版本 01 ( ) 414

425 9 Kafka 应用开发 + ") at offset " + record.offset()); ConsumerMultThread 主类的线程启动逻辑 public void run() { LOG.info("Consumer: start."); for (int threadnum = 0; threadnum < CONCURRENCY_THREAD_NUM; threadnum++) { Consumer consumerthread = new Consumer(KafkaProperties.TOPIC,threadNum); consumerthread.start(); SimpleConsumer API 使用样例 功能介绍 代码样例 下面代码片段在 com.huawei.bigdata.kafka.example.simpleconsumerdemo 类中, 用于实现使用新 SimpleConsumer API 订阅 Topic, 并进行消息消费 ( 注意 :SimpleConsumer API 仅支持访问未设置 ACL 的 Topic, 安全接口说明见安全接口说明 ) SimpleConsumer API 属于 lowlevel 的 Consumer API 需要访问 zookeeper 元数据, 管理消费 Topic 队列的 offset, 一般情况不推荐使用 SimpleConsumer API 主方法需要传入三个参数, 最大消费数量 消费 Topic 消费的 Topic 分区 public static void main(string args[]) { // 允许读取的最大消息数 long maxreads = 0; try { maxreads = Long.valueOf(args[0]); catch (Exception e) { log.error("args[0] should be a number for maxreads.\n" + "args[1] should be a string for topic. \n" + "args[2] should be a number for partition."); return; if (null == args[1]) { log.error("args[0] should be a number for maxreads.\n" + "args[1] should be a string for topic. \n" + "args[2] should be a number for partition."); return; // 消费的消息主题 // String topic = KafkaProperties.TOPIC; String topic = args[1]; // 消息的消息分区 int partition = 0; try 文档版本 01 ( ) 415

426 9 Kafka 应用开发 { partition = Integer.parseInt(args[2]); catch (Exception e) { log.error("args[0] should be a number for maxreads.\n" + "args[1] should be a string for topic. \n" + "args[2] should be a number for partition."); // Broker List String bklist = KafkaProperties.getInstance().getValues("metadata.broker.list", "localhost: 9092"); Map<String, Integer> ipport = getipportmap(bklist); SimpleConsumerDemo example = new SimpleConsumerDemo(); try { example.run(maxreads, topic, partition, ipport); catch (Exception e) { log.info("oops:" + e); e.printstacktrace(); 样例工程配置文件说明 Conf 目录个各配置文件及重要参数配置说明 Producer API 配置项 表 9-3 producer.properties 文件配置项 参数描述备注 security.protocol 安全协议类型 生产者使用的安全协议类型, 当前 Kerberos 开启的模式下仅支持 SASL 协议, 需要配置为 SASL_PLAINTEXT kerberos.domain.name 域名 MRS 服务集群的 Kerberos 域名 sasl.kerberos.service.name 服务名 Kafka 集群运行, 所使用的 Kerberos 用户名 ( 需配置 为 kafka) Consumer API 配置项 文档版本 01 ( ) 416

427 9 Kafka 应用开发 表 9-4 consumer.properties 文件配置项 参数描述备注 security.protocol 安全协议类型 消费者使用的安全协议类型, 当前安全模式下 Kerberos 开启的模式下仅支持 SASL 协议, 需要配置为 SASL_PLAINTEXT kerberos.domain.name 域名 MRS 服务集群的 Kerberos 域名 group.id 消费者的 group id - auto.commit.interval.ms 是否自动提交 offset 布尔值参数, 默认值为 true sasl.kerberos.service.name 服务名 Kafka 集群运行, 所使用的 Kerberos 用户名 ( 需配置 为 kafka) 客户端信息配置项 表 9-5 client.properties 文件配置项 参数描述备注 metadata.broker.list 元数据 Broker 地址列表 通过此参数值, 创建与元数据 Broker 之间的连接, 需要直接访问元数据的 API 需要用到此参数 访问端口仅支持不开启 Kerberos 模式下的端口, 端口说明详见安全接口说明 kafka.client.zookeeper.princ ipal kafka 集群访问 zookeeper 的认证和域名 - bootstrap.servers Broker 地址列表 通过此参数值, 创建与 Broker 之间的连接 端口 配置项详见安全接口说明 zookeeper.connect zookeeper 地址列表 通过此参数, 访问 zookeeper, 末尾需要带上 kafka 服务名 kafka MRS 服务是否开启 Kerberos 认证配置项 文档版本 01 ( ) 417

428 9 Kafka 应用开发 表 9-6 kafkasecuritymode 文件配置项 参数描述备注 kafka.client.security.mode kafka 所在的 MRS 服务集群是否开启 Kerberos 认证配置项 若开启了 Kerberos 认证, 设置为 yes, 否则设置为 no log4j 日志配置项文件 log4j.properties log4j 日志框架的配置文件, 默认情况不输入样例工程运行日志 9.4 调测程序 在 Windows 中调测程序 前提条件 1. Windows 本地环境能直接访问 MRS 服务的 Kafka 的各个 Broker 示例 2. Maven 工程在编译软件, 例如 Eclipse 导入并编译完成 Producer 运行 1. 确保本地的 hosts 文件中配置了远程集群所有主机的主机名和业务 IP 映射关系 以 Windows7 为例, 路径为 C:\Windows\System32\drivers\etc\hosts 例如, 集群有 3 个节点 , , 则需要检查 hosts 文件中是否配置了以下内容 : 通过 Eclipse 可直接运行 Producer.java, 如图 9-2 所示 : 文档版本 01 ( ) 418

429 9 Kafka 应用开发 图 9-2 Eclipse 打开 Producer.java 右键运行 SimpleConsumerDemo 运行 3. 运行后弹出控制台窗口, 可以看到,Producer 正在向 topic 发送消息 1. 通过 Eclipse 可右键执行 Run As > Run Configurations, 如图 9-3 所示 : 文档版本 01 ( ) 419

430 9 Kafka 应用开发 图 9-3 Run Configurations 运行 2. 在弹出的配置框中, 填写三个参数 : 本次消费的最小消息数 Topic 名称 Partition 号, 中间用空格分割, 如图 9-4 所示 : 图 9-4 配置参数填写 文档版本 01 ( ) 420

PowerPoint 演示文稿

PowerPoint 演示文稿 Hadoop 生 态 技 术 在 阿 里 全 网 商 品 搜 索 实 战 阿 里 巴 巴 - 王 峰 自 我 介 绍 真 名 : 王 峰 淘 宝 花 名 : 莫 问 微 博 : 淘 莫 问 2006 年 硕 士 毕 业 后 加 入 阿 里 巴 巴 集 团 淘 及 搜 索 事 业 部 ( 高 级 技 术 与 家 ) 目 前 负 责 搜 索 离 线 系 统 团 队 技 术 方 向 : 分 布 式 计 算

More information

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 odps-sdk 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基 开放数据处理服务 ODPS SDK SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基础功能的主体接口, 搜索关键词 "odpssdk-core" 一些

More information

Spark读取Hbase中的数据

Spark读取Hbase中的数据 Spark 读取 Hbase 中的数据 Spark 和 Flume-ng 整合, 可以参见本博客 : Spark 和 Flume-ng 整合 使用 Spark 读取 HBase 中的数据 如果想及时了解 Spark Hadoop 或者 Hbase 相关的文章, 欢迎关注微信公共帐号 :iteblog_hadoop 大家可能都知道很熟悉 Spark 的两种常见的数据读取方式 ( 存放到 RDD 中 ):(1)

More information

ABOUT ME AGENDA 唐建法 / TJ MongoDB 高级方案架构师 MongoDB 中文社区联合发起人 Spark 介绍 Spark 和 MongoDB 案例演示

ABOUT ME AGENDA 唐建法 / TJ MongoDB 高级方案架构师 MongoDB 中文社区联合发起人 Spark 介绍 Spark 和 MongoDB 案例演示 完整的大数据解決方案 ABOUT ME AGENDA 唐建法 / TJ MongoDB 高级方案架构师 MongoDB 中文社区联合发起人 Spark 介绍 Spark 和 MongoDB 案例演示 Dataframe Pig YARN Spark Stand Alone HDFS Spark Stand Alone Mesos Mesos Spark Streaming Hive Hadoop

More information

Kubenetes 系列列公开课 2 每周四晚 8 点档 1. Kubernetes 初探 2. 上 手 Kubernetes 3. Kubernetes 的资源调度 4. Kubernetes 的运 行行时 5. Kubernetes 的 网络管理理 6. Kubernetes 的存储管理理 7.

Kubenetes 系列列公开课 2 每周四晚 8 点档 1. Kubernetes 初探 2. 上 手 Kubernetes 3. Kubernetes 的资源调度 4. Kubernetes 的运 行行时 5. Kubernetes 的 网络管理理 6. Kubernetes 的存储管理理 7. Kubernetes 包管理理 工具 Helm 蔺礼强 Kubenetes 系列列公开课 2 每周四晚 8 点档 1. Kubernetes 初探 2. 上 手 Kubernetes 3. Kubernetes 的资源调度 4. Kubernetes 的运 行行时 5. Kubernetes 的 网络管理理 6. Kubernetes 的存储管理理 7. Kubernetes

More information

1.JasperReport ireport JasperReport ireport JDK JDK JDK JDK ant ant...6

1.JasperReport ireport JasperReport ireport JDK JDK JDK JDK ant ant...6 www.brainysoft.net 1.JasperReport ireport...4 1.1 JasperReport...4 1.2 ireport...4 2....4 2.1 JDK...4 2.1.1 JDK...4 2.1.2 JDK...5 2.1.3 JDK...5 2.2 ant...6 2.2.1 ant...6 2.2.2 ant...6 2.3 JasperReport...7

More information

IBM Rational ClearQuest Client for Eclipse 1/ IBM Rational ClearQuest Client for Ecl

IBM Rational ClearQuest Client for Eclipse   1/ IBM Rational ClearQuest Client for Ecl 1/39 Balaji Krish,, IBM Nam LeIBM 2005 4 15 IBM Rational ClearQuest ClearQuest Eclipse Rational ClearQuest / Eclipse Clien Rational ClearQuest Rational ClearQuest Windows Web Rational ClearQuest Client

More information

培 训 机 构 介 绍 中 科 普 开 是 国 内 首 家 致 力 于 IT 新 技 术 领 域 的 领 航 者, 专 注 于 云 计 算 大 数 据 物 联 网 移 动 互 联 网 技 术 的 培 训, 也 是 国 内 第 一 家 开 展 Hadoop 云 计 算 的 培

培 训 机 构 介 绍  中 科 普 开 是 国 内 首 家 致 力 于 IT 新 技 术 领 域 的 领 航 者, 专 注 于 云 计 算 大 数 据 物 联 网 移 动 互 联 网 技 术 的 培 训, 也 是 国 内 第 一 家 开 展 Hadoop 云 计 算 的 培 Hadoop 2.0 培 训 Hadoop 2.0Training Hadoop 2.0 运 维 与 开 发 实 战 培 训 邀 请 函 培 训 机 构 介 绍 www.zkpk.org 中 科 普 开 是 国 内 首 家 致 力 于 IT 新 技 术 领 域 的 领 航 者, 专 注 于 云 计 算 大 数 据 物 联 网 移 动 互 联 网 技 术 的 培 训, 也 是 国 内 第 一 家 开

More information

RunPC2_.doc

RunPC2_.doc PowerBuilder 8 (5) PowerBuilder Client/Server Jaguar Server Jaguar Server Connection Cache Thin Client Internet Connection Pooling EAServer Connection Cache Connection Cache Connection Cache Connection

More information

Microsoft PowerPoint - hbase_program(0201).ppt

Microsoft PowerPoint - hbase_program(0201).ppt TSMC 教育訓練課程 HBase Programming < V 0.20 > 王耀聰陳威宇 Jazz@nchc.org.tw waue@nchc.org.tw Outline HBase 程式編譯方法 HBase 程式設計 常用的 HBase API 說明實做 I/O 操作搭配 Map Reduce 運算 其他用法補充 其他專案 2 HBase 程式編譯方法 此篇介紹兩種編譯與執行 HBase

More information

RUN_PC連載_12_.doc

RUN_PC連載_12_.doc PowerBuilder 8 (12) PowerBuilder 8.0 PowerBuilder PowerBuilder 8 PowerBuilder 8 / IDE PowerBuilder PowerBuilder 8.0 PowerBuilder PowerBuilder PowerBuilder PowerBuilder 8.0 PowerBuilder 6 PowerBuilder 7

More information

基于UML建模的管理管理信息系统项目案例导航——VB篇

基于UML建模的管理管理信息系统项目案例导航——VB篇 PowerBuilder 8.0 PowerBuilder 8.0 12 PowerBuilder 8.0 PowerScript PowerBuilder CIP PowerBuilder 8.0 /. 2004 21 ISBN 7-03-014600-X.P.. -,PowerBuilder 8.0 - -.TP311.56 CIP 2004 117494 / / 16 100717 http://www.sciencep.com

More information

PowerPoint 演示文稿

PowerPoint 演示文稿 Apache Spark 与 多 数 据 源 的 结 合 田 毅 @ 目 录 为 什 么 会 用 到 多 个 数 据 源 Spark 的 多 数 据 源 方 案 有 哪 些 已 有 的 数 据 源 支 持 Spark 在 GrowingIO 的 实 践 分 享 为 什 么 会 用 到 多 个 数 据 源 从 数 据 本 身 来 看 大 数 据 的 特 性 之 一 :Variety 数 据 的 多 样

More information

新・解きながら学ぶJava

新・解きながら学ぶJava 481! 41, 74!= 40, 270 " 4 % 23, 25 %% 121 %c 425 %d 121 %o 121 %x 121 & 199 && 48 ' 81, 425 ( ) 14, 17 ( ) 128 ( ) 183 * 23 */ 3, 390 ++ 79 ++ 80 += 93 + 22 + 23 + 279 + 14 + 124 + 7, 148, 16 -- 79 --

More information

AL-M200 Series

AL-M200 Series NPD4754-00 TC ( ) Windows 7 1. [Start ( )] [Control Panel ()] [Network and Internet ( )] 2. [Network and Sharing Center ( )] 3. [Change adapter settings ( )] 4. 3 Windows XP 1. [Start ( )] [Control Panel

More information

获取 Access Token access_token 是接口的全局唯一票据, 接入方调用各接口时都需使用 access_token 开发者需要进行妥善保存 access_token 的存储至少要保留 512 个字符空间 access_token 的有效期目前为 2 个小时, 需定时刷新, 重复

获取 Access Token access_token 是接口的全局唯一票据, 接入方调用各接口时都需使用 access_token 开发者需要进行妥善保存 access_token 的存储至少要保留 512 个字符空间 access_token 的有效期目前为 2 个小时, 需定时刷新, 重复 获取 Access Token access_token 是接口的全局唯一票据, 接入方调用各接口时都需使用 access_token 开发者需要进行妥善保存 access_token 的存储至少要保留 512 个字符空间 access_token 的有效期目前为 2 个小时, 需定时刷新, 重复 获取将导致上次获取的 access_token 失效 接入方可以使用 AppID 和 AppSecret

More information

學 科 100% ( 為 單 複 選 題, 每 題 2.5 分, 共 100 分 ) 1. 請 參 閱 附 圖 作 答 : (A) 選 項 A (B) 選 項 B (C) 選 項 C (D) 選 項 D Ans:D 2. 下 列 對 於 資 料 庫 正 規 化 (Normalization) 的 敘

學 科 100% ( 為 單 複 選 題, 每 題 2.5 分, 共 100 分 ) 1. 請 參 閱 附 圖 作 答 : (A) 選 項 A (B) 選 項 B (C) 選 項 C (D) 選 項 D Ans:D 2. 下 列 對 於 資 料 庫 正 規 化 (Normalization) 的 敘 ITE 資 訊 專 業 人 員 鑑 定 資 料 庫 系 統 開 發 與 設 計 實 務 試 卷 編 號 :IDS101 注 意 事 項 一 本 測 驗 為 單 面 印 刷 試 題, 共 計 十 三 頁 第 二 至 十 三 頁 為 四 十 道 學 科 試 題, 測 驗 時 間 90 分 鐘 : 每 題 2.5 分, 總 測 驗 時 間 為 90 分 鐘 二 執 行 CSF 測 驗 系 統 -Client

More information

509 (ii) (iii) (iv) (v) 200, , , , C 57

509 (ii) (iii) (iv) (v) 200, , , , C 57 59 (ii) (iii) (iv) (v) 500,000 500,000 59I 18 (ii) (iii) (iv) 200,000 56 509 (ii) (iii) (iv) (v) 200,000 200,000 200,000 500,000 57 43C 57 (ii) 60 90 14 5 50,000 43F 43C (ii) 282 24 40(1B) 24 40(1) 58

More information

Microsoft Word - MP2018_Report_Chi _12Apr2012_.doc

Microsoft Word - MP2018_Report_Chi _12Apr2012_.doc 人 力 資 源 推 算 報 告 香 港 特 別 行 政 區 政 府 二 零 一 二 年 四 月 此 頁 刻 意 留 空 - 2 - 目 錄 頁 前 言 詞 彙 縮 寫 及 注 意 事 項 摘 要 第 一 章 : 第 二 章 : 第 三 章 : 第 四 章 : 附 件 一 : 附 件 二 : 附 件 三 : 附 件 四 : 附 件 五 : 附 件 六 : 附 件 七 : 引 言 及 技 術 大 綱 人

More information

南華大學數位論文

南華大學數位論文 1 i -------------------------------------------------- ii iii iv v vi vii 36~39 108 viii 15 108 ix 1 2 3 30 1 ~43 2 3 ~16 1 2 4 4 5 3 6 8 6 4 4 7 15 8 ----- 5 94 4 5 6 43 10 78 9 7 10 11 12 10 11 12 9137

More information

李天命的思考藝術

李天命的思考藝術 ii iii iv v vi vii viii ix x 3 1 2 3 4 4 5 6 7 8 9 5 10 1 2 11 6 12 13 7 8 14 15 16 17 18 9 19 20 21 22 10 23 24 23 11 25 26 7 27 28 12 13 29 30 31 28 32 14 33 34 35 36 5 15 3 1 2 3 4 5 6 7 8 9 10 11

More information

皮肤病防治.doc

皮肤病防治.doc ...1...1...2...3...4...5...6...7...7...9...10... 11...12...14...15...16...18...19...21 I ...22...22...24...25...26...27...27...29...30...31...32...33...34...34...36...36...37...38...40...41...41...42 II

More information

性病防治

性病防治 ...1...2...3...4...5...5...6...7...7...7...8...8...9...9...10...10... 11... 11 I ...12...12...12...13...14...14...15...17...20...20...21...22...23...23...25...27...33...34...34...35...35 II ...36...38...39...40...41...44...49...49...53...56...57...57...58...58...59...60...60...63...63...65...66

More information

中国南北特色风味名菜 _一)

中国南北特色风味名菜 _一) ...1...1...2...3...3...4...5...6...7...7...8...9... 10... 11... 13... 13... 14... 16... 17 I ... 18... 19... 20... 21... 22... 23... 24... 25... 27... 28... 29... 30... 32... 33... 34... 35... 36... 37...

More information

全唐诗24

全唐诗24 ... 1... 1... 2... 2... 3... 3... 4... 4... 5... 5... 6... 6... 7... 7... 8... 8... 9... 9...10...10...10...11...12...12...12...13...13 I II...14...14...14...15...15...15...16...16...16...17...17...18...18...18...19...19...19...20...20...20...21...21...22...22...23...23...23...24

More information

chp6.ppt

chp6.ppt Java 软 件 设 计 基 础 6. 异 常 处 理 编 程 时 会 遇 到 如 下 三 种 错 误 : 语 法 错 误 (syntax error) 没 有 遵 循 语 言 的 规 则, 出 现 语 法 格 式 上 的 错 误, 可 被 编 译 器 发 现 并 易 于 纠 正 ; 逻 辑 错 误 (logic error) 即 我 们 常 说 的 bug, 意 指 编 写 的 代 码 在 执 行

More information

附录J:Eclipse教程

附录J:Eclipse教程 附 录 J:Eclipse 教 程 By Y.Daniel Liang 该 帮 助 文 档 包 括 以 下 内 容 : Eclipse 入 门 选 择 透 视 图 创 建 项 目 创 建 Java 程 序 编 译 和 运 行 Java 程 序 从 命 令 行 运 行 Java Application 在 Eclipse 中 调 试 提 示 : 在 学 习 完 第 一 章 后 使 用 本 教 程 第

More information

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM CHAPTER 6 SQL SQL SQL 6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM 3. 1986 10 ANSI SQL ANSI X3. 135-1986

More information

六域链联盟 SDChain-Matrix 节点搭建指南 2018/07/26 Version : 1.0.0

六域链联盟 SDChain-Matrix 节点搭建指南 2018/07/26 Version : 1.0.0 SDChain-Matrix 节点搭建指南 目录 1 环境要求... 3 2 软件下载... 4 3 安装部署... 4 3.1 部署可执行程序目录... 4 3.2 部署配置文件目录... 4 3.3 部署数据库文件目录... 4 3.4 部署日志文件目录... 4 3.5 部署依赖库文件目录... 4 4 配置参数... 5 5 启动运行... 7 5.1 普通模式启动... 7 5.2 加载启动模式...

More information

使用Spark SQL读取Hive上的数据

使用Spark SQL读取Hive上的数据 使用 Spark SQL 读取 Hive 上的数据 Spark SQL 主要目的是使得用户可以在 Spark 上使用 SQL, 其数据源既可以是 RDD, 也可以是外部的数据源 ( 比如 Parquet Hive Json 等 ) Spark SQL 的其中一个分支就是 Spark on Hive, 也就是使用 Hive 中 HQL 的解析 逻辑执行计划翻译 执行计划优化等逻辑, 可以近似认为仅将物理执行计划从

More information

エスポラージュ株式会社 住所 : 東京都江東区大島 東急ドエルアルス大島 HP: ******************* * 关于 Java 测试试题 ******

エスポラージュ株式会社 住所 : 東京都江東区大島 東急ドエルアルス大島 HP:  ******************* * 关于 Java 测试试题 ****** ******************* * 关于 Java 测试试题 ******************* 問 1 运行下面的程序, 选出一个正确的运行结果 public class Sample { public static void main(string[] args) { int[] test = { 1, 2, 3, 4, 5 ; for(int i = 1 ; i System.out.print(test[i]);

More information

捕捉儿童敏感期

捕捉儿童敏感期 目弽 2010 捕捉儿童敏感期 I a mao 2010-3-27 整理 早教资料每日分享 http://user.qzone.qq.com/2637884895 目弽 目彔 目弽... I 出版前言... - 1 竨一章 4 丢孝子癿敂感朏敀乞... - 1 - 妞妞 0 4 岁 海颖 妞妞癿妈妈... - 1 黑白相亝癿地斱... - 1 斵转... - 2 就丌要新帰子... - 2 小霸王...

More information

白 皮 书 英 特 尔 IT 部 门 实 施 Apache Hadoop* 英 特 尔 分 发 版 软 件 的 最 佳 实 践 目 录 要 点 概 述...1 业 务 挑 战...2 Hadoop* 分 发 版 注 意 事 项...3 Hadoop* 基 础 架 构 注 意 事 项

白 皮 书 英 特 尔 IT 部 门 实 施 Apache Hadoop* 英 特 尔 分 发 版 软 件 的 最 佳 实 践 目 录 要 点 概 述...1 业 务 挑 战...2 Hadoop* 分 发 版 注 意 事 项...3 Hadoop* 基 础 架 构 注 意 事 项 IT@Intel 白 皮 书 英 特 尔 IT 部 门 大 数 据 和 商 业 智 能 2013 年 10 月 英 特 尔 IT 部 门 实 施 Apache Hadoop* 英 特 尔 分 发 版 软 件 的 最 佳 实 践 要 点 概 述 仅 在 五 周 之 内, 我 们 就 实 施 了 基 于 Apache Hadoop* 英 特 尔 分 发 版 的 低 成 本 可 完 全 实 现 的 大 数

More information

epub83-1

epub83-1 C++Builder 1 C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r 1.1 1.1.1 1-1 1. 1-1 1 2. 1-1 2 A c c e s s P a r a d o x Visual FoxPro 3. / C / S 2 C + + B u i l d e r / C

More information

Hive:用Java代码通过JDBC连接Hiveserver

Hive:用Java代码通过JDBC连接Hiveserver Hive: 用 Java 代码通过 JDBC 连接 Hiveserver 我们可以通过 CLI Client Web UI 等 Hive 提供的用户接口来和 Hive 通信, 但这三种方式最常用的是 CLI;Client 是 Hive 的客户端, 用户连接至 Hive Server 在启动 Client 模式的时候, 需要指出 Hive Server 所在节点, 并且在该节点启动 Hive Server

More information

使用MapReduce读取XML文件

使用MapReduce读取XML文件 使用 MapReduce 读取 XML 文件 XML( 可扩展标记语言, 英语 :extensible Markup Language, 简称 : XML) 是一种标记语言, 也是行业标准数据交换交换格式, 它很适合在系统之间进行数据存储和交换 ( 话说 Hadoop H ive 等的配置文件就是 XML 格式的 ) 本文将介绍如何使用 MapReduce 来读取 XML 文件 但是 Had oop

More information

「香港中學文言文課程的設計與教學」單元設計範本

「香港中學文言文課程的設計與教學」單元設計範本 1. 2. 3. (1) (6) ( 21-52 ) (7) (12) (13) (16) (17) (20) (21) (24) (25) (31) (32) (58) 1 2 2007-2018 7 () 3 (1070) (1019-1086) 4 () () () () 5 () () 6 21 1. 2. 3. 1. 2. 3. 4. 5. 6. 7. 8. 9. ( ) 7 1. 2.

More information

全唐诗28

全唐诗28 ... 1... 1... 1... 2... 2... 2... 3... 3... 4... 4... 4... 5... 5... 5... 5... 6... 6... 6... 6... 7... 7... 7... 7... 8... 8 I II... 8... 9... 9... 9...10...10...10...11...11...11...11...12...12...12...13...13...13...14...14...14...15...15...15...16...16...16...17...17

More information

穨學前教育課程指引.PDF

穨學前教育課程指引.PDF i 1 1.1 1 1.2 1 4 2.1 4 2.2 5 2.3 7 2.4 9 2.5 11 2.6 1 2 1 5 3.1 1 5 3.2 1 5 19 4.1 19 4.2 19 4.3 2 1 4.4 29 4.5 38 4.6 4 3 4.7 47 50 5.1 5 0 5.2 5 0 5.3 6 2 5.4 9 4 5.5 1 2 6 ( ) 1 2 7 ( ) 1 31 ( ) 1

More information

C H A P T E R 7 Windows Vista Windows Vista Windows Vista FAT16 FAT32 NTFS NTFS New Technology File System NTFS

C H A P T E R 7 Windows Vista Windows Vista Windows Vista FAT16 FAT32 NTFS NTFS New Technology File System NTFS C H P T E R 7 Windows Vista Windows Vista Windows VistaFT16 FT32NTFS NTFSNew Technology File System NTFS 247 6 7-1 Windows VistaTransactional NTFS TxFTxF Windows Vista MicrosoftTxF CIDatomicity - Consistency

More information

眼病防治

眼病防治 ( 20 010010) 787 1092 1/32 498.50 4 980 2004 9 1 2004 9 1 1 1 000 ISBN 7-204-05940-9/R 019 1880.00 ( 20.00 ) ...1...1...2...3...5...5...6...7...9... 11...13...14...15...17...18...19...20...21 I II...21...22...23...24...25...27...27...28...29...30...31...33...33...34...36...38...39...40...41...42...43...45

More information

中国南北特色风味名菜 _八)

中国南北特色风味名菜 _八) ( 20 010010) 7871092 1/32 356.25 4 760 2004 8 1 2004 8 1 11 000 ISBN 7-204-05943-3/Z102 1026.00 ( 18.00 ) ...1...2...2...4...6...7...8...9... 10... 11... 12... 13... 13... 14... 15... 17... 18... 19...

More information

EJB-Programming-4-cn.doc

EJB-Programming-4-cn.doc EJB (4) : (Entity Bean Value Object ) JBuilder EJB 2.x CMP EJB Relationships JBuilder EJB Test Client EJB EJB Seminar CMP Entity Beans Session Bean J2EE Session Façade Design Pattern Session Bean Session

More information

緒 言 董 事 會 宣 佈, 為 能 更 具 效 率 調 配 本 集 團 內 的 資 金 有 效 降 低 集 團 的 對 外 貸 款, 並 促 進 本 集 團 內 公 司 間 的 結 算 服 務, 於 2016 年 9 月 30 日, 本 公 司 中 糧 財 務 與 管 理 公 司 訂 立 財 務

緒 言 董 事 會 宣 佈, 為 能 更 具 效 率 調 配 本 集 團 內 的 資 金 有 效 降 低 集 團 的 對 外 貸 款, 並 促 進 本 集 團 內 公 司 間 的 結 算 服 務, 於 2016 年 9 月 30 日, 本 公 司 中 糧 財 務 與 管 理 公 司 訂 立 財 務 香 港 交 易 及 結 算 所 有 限 公 司 及 香 港 聯 合 交 易 所 有 限 公 司 對 本 公 告 的 內 容 概 不 負 責, 對 其 準 確 性 或 完 整 性 亦 不 發 表 任 何 聲 明, 並 明 確 表 示, 概 不 對 因 本 公 告 全 部 或 任 何 部 分 內 容 而 產 生 或 因 倚 賴 該 等 內 容 而 引 致 的 任 何 損 失 承 擔 任 何 責 任 JOY

More information

穨ecr2_c.PDF

穨ecr2_c.PDF i ii iii iv v vi vii viii 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 26 27 2 28 29 30 31 32 33 34 35 36 37 38 39 40 3 4 41 42 43 5 44 45 46 6 47 48 49 50 51 52 1 53 2 54 55 3 56

More information

電腦相關罪行跨部門工作小組-報告書

電腦相關罪行跨部門工作小組-報告書 - ii - - iii - - iv - - v - - vi - - vii - - viii - (1) 2.1 (2) (3) 13.6 (4) 1.6 (5) 21 (6) (7) 210 (8) (9) (10) (11) ( ) ( 12) 20 60 16 (13) ( ) (

More information

i

i i ii iii iv v vi vii viii ===== 1 2 3 4 5 6 7 8 9 10 ==== 11 12 13 14 15 16 17 18 19 ==== ==== 20 .. ===== ===== ===== ===== ===== ======.. 21 22 ===== ===== ===== ===== 23 24 25 26 27 28 29 ==== ====

More information

发展党员工作手册

发展党员工作手册 发 展 党 员 工 作 问 答 目 录 一 总 论...9 1. 发 展 党 员 工 作 的 方 针 是 什 么? 如 何 正 确 理 解 这 个 方 针?... 9 2. 为 什 么 强 调 发 展 党 员 必 须 保 证 质 量?... 9 3. 如 何 做 到 慎 重 发 展?... 10 4. 如 何 处 理 好 发 展 党 员 工 作 中 的 重 点 与 一 般 的 关 系?...11 5.

More information

i

i 9 1 2 3 4 i 5 6 ii iii iv v vi vii viii 1 1 1 2 3 4 2 5 6 2 3 2.10 ( 2.11 ) ( 2.11 ) ( 2.9 ) 7 8 9 3 10 5% 2% 4 11 93% (2001 02 2003 04 ) ( ) 2,490 (100%) 5 12 25% (2.57% 25%) 6 (2001 02 2003 04 ) 13 100%

More information

39898.indb

39898.indb 1988 4 1998 12 1990 5 40 70.................................................. 40.............................................................. 70..............................................................

More information

untitled

untitled How to using M-Power Report API M-Power Report API 力 了 M-Power Report -- Java (Library) M-Power Report API 行 Java M-Power Report M-Power Report API ( 30 ) PDF/HTML/CSV/XLS JPEG/PNG/SVG 料 料 OutputStream

More information

ebook140-9

ebook140-9 9 VPN VPN Novell BorderManager Windows NT PPTP V P N L A V P N V N P I n t e r n e t V P N 9.1 V P N Windows 98 Windows PPTP VPN Novell BorderManager T M I P s e c Wi n d o w s I n t e r n e t I S P I

More information

目 錄 版 次 變 更 記 錄... 2 原 始 程 式 碼 類 型 之 使 用 手 冊... 3 一 安 裝 軟 體 套 件 事 前 準 備... 3 二 編 譯 流 程 說 明... 25 1

目 錄 版 次 變 更 記 錄... 2 原 始 程 式 碼 類 型 之 使 用 手 冊... 3 一 安 裝 軟 體 套 件 事 前 準 備... 3 二 編 譯 流 程 說 明... 25 1 科 技 部 自 由 軟 體 專 案 原 始 程 式 碼 使 用 手 冊 Source Code Manual of NSC Open Source Project 可 信 賴 的 App 安 全 應 用 框 架 -App 應 用 服 務 可 移 轉 性 驗 證 Trusted App Framework -Transferability Verification on App MOST 102-2218-E-011-012

More information

第 06 期 李祥池 : 基于 ELK 和 Spark Streaming 的日志分析系统设计与实现 1 日志 1.1 日志定义 IT 1.2 日志处理方案演进 v1.0 v2.0 Hadoop Storm Spark Hadoop/Storm/Spark v3.0 TB Splunk ELK SI

第 06 期 李祥池 : 基于 ELK 和 Spark Streaming 的日志分析系统设计与实现 1 日志 1.1 日志定义 IT 1.2 日志处理方案演进 v1.0 v2.0 Hadoop Storm Spark Hadoop/Storm/Spark v3.0 TB Splunk ELK SI 电子科学技术第 02 卷第 06 期 2015 年 11 月 Electronic Science & Technology Vol.02 No.06 Nov.2015 年 基于 ELK 和 Spark Streaming 的日志分析系统设计与实现 李祥池 ( 杭州华三通信技术有限公司北京研究所, 北京,100085) 摘要 : 在大数据时代 对数据平台各组件的运行状态实时监控与运行分析具有重要意义

More information

WinMDI 28

WinMDI 28 WinMDI WinMDI 2 Region Gate Marker Quadrant Excel FACScan IBM-PC MO WinMDI WinMDI IBM-PC Dr. Joseph Trotter the Scripps Research Institute WinMDI HP PC WinMDI WinMDI PC MS WORD, PowerPoint, Excel, LOTUS

More information

前 言 根 据 澳 门 特 别 行 政 区 第 11/1999 号 法 律 第 三 条 规 定, 审 计 长 执 行 其 职 责, 已 经 对 财 政 局 提 交 的 2011 年 度 澳 门 特 别 行 政 区 总 帐 目 ( 总 帐 目 ) 进 行 了 审 计 与 2010 年 度 相 同, 本 年 度 的 总 帐 目 由 政 府 一 般 综 合 帐 目 及 特 定 机 构 汇 总 帐 目, 两

More information

FileMaker 16 ODBC 和 JDBC 指南

FileMaker 16 ODBC 和 JDBC 指南 FileMaker 16 ODBC JDBC 2004-2017 FileMaker, Inc. FileMaker, Inc. 5201 Patrick Henry Drive Santa Clara, California 95054 FileMaker FileMaker Go FileMaker, Inc. FileMaker WebDirect FileMaker Cloud FileMaker,

More information

Microsoft Word - template.doc

Microsoft Word - template.doc HGC efax Service User Guide I. Getting Started Page 1 II. Fax Forward Page 2 4 III. Web Viewing Page 5 7 IV. General Management Page 8 12 V. Help Desk Page 13 VI. Logout Page 13 Page 0 I. Getting Started

More information

AL-MX200 Series

AL-MX200 Series PostScript Level3 Compatible NPD4760-00 TC Seiko Epson Corporation Seiko Epson Corporation ( ) Seiko Epson Corporation Seiko Epson Corporation Epson Seiko Epson Corporation Apple Bonjour ColorSync Macintosh

More information

27 :OPC 45 [4] (Automation Interface Standard), (Costom Interface Standard), OPC 2,,, VB Delphi OPC, OPC C++, OPC OPC OPC, [1] 1 OPC 1.1 OPC OPC(OLE f

27 :OPC 45 [4] (Automation Interface Standard), (Costom Interface Standard), OPC 2,,, VB Delphi OPC, OPC C++, OPC OPC OPC, [1] 1 OPC 1.1 OPC OPC(OLE f 27 1 Vol.27 No.1 CEMENTED CARBIDE 2010 2 Feb.2010!"!!!!"!!!!"!" doi:10.3969/j.issn.1003-7292.2010.01.011 OPC 1 1 2 1 (1., 412008; 2., 518052), OPC, WinCC VB,,, OPC ; ;VB ;WinCC Application of OPC Technology

More information

untitled

untitled CHONGQING INTERNATIONAL ENTERPRISE INVESTMENT CO.,LTD. I II III IV V VI ...7...10...11...13...13...14...15...15...21...32...50...53...54...56...56...56.59...61...61...66...69...72...74...74...75...75...75...77

More information

一、

一、 ... 1...24...58 - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - 8 - i. ii. iii. iv. i. ii. iii. iv. v. vi. vii. viii. ix. x. - 9 - xi. - 10 - - 11 - -12- -13- -14- -15- C. @ -16- @ -17- -18- -19- -20- -21- -22-

More information

雲端 Cloud Computing 技術指南 運算 應用 平台與架構 10/04/15 11:55:46 INFO 10/04/15 11:55:53 INFO 10/04/15 11:55:56 INFO 10/04/15 11:56:05 INFO 10/04/15 11:56:07 INFO

雲端 Cloud Computing 技術指南 運算 應用 平台與架構 10/04/15 11:55:46 INFO 10/04/15 11:55:53 INFO 10/04/15 11:55:56 INFO 10/04/15 11:56:05 INFO 10/04/15 11:56:07 INFO CHAPTER 使用 Hadoop 打造自己的雲 8 8.3 測試 Hadoop 雲端系統 4 Nodes Hadoop Map Reduce Hadoop WordCount 4 Nodes Hadoop Map/Reduce $HADOOP_HOME /home/ hadoop/hadoop-0.20.2 wordcount echo $ mkdir wordcount $ cd wordcount

More information

Symantec™ Sygate Enterprise Protection 防护代理安装使用指南

Symantec™ Sygate Enterprise Protection 防护代理安装使用指南 Symantec Sygate Enterprise Protection 防 护 代 理 安 装 使 用 指 南 5.1 版 版 权 信 息 Copyright 2005 Symantec Corporation. 2005 年 Symantec Corporation 版 权 所 有 All rights reserved. 保 留 所 有 权 利 Symantec Symantec 徽 标 Sygate

More information

WebSphere Studio Application Developer IBM Portal Toolkit... 2/21 1. WebSphere Portal Portal WebSphere Application Server stopserver.bat -configfile..

WebSphere Studio Application Developer IBM Portal Toolkit... 2/21 1. WebSphere Portal Portal WebSphere Application Server stopserver.bat -configfile.. WebSphere Studio Application Developer IBM Portal Toolkit... 1/21 WebSphere Studio Application Developer IBM Portal Toolkit Portlet Doug Phillips (dougep@us.ibm.com),, IBM Developer Technical Support Center

More information

使用Cassandra和Spark 2.0实现Rest API服务

使用Cassandra和Spark 2.0实现Rest API服务 使用 Cassandra 和 Spark 2.0 实现 Rest API 服务 在这篇文章中, 我将介绍如何在 Spark 中使用 Akkahttp 并结合 Cassandra 实现 REST 服务, 在这个系统中 Cassandra 用于数据的存储 我们已经见识到 Spark 的威力, 如果和 Cassandra 正确地结合可以实现更强大的系统 我们先创建一个 build.sbt 文件, 内容如下

More information

-i-

-i- -i- -ii- -iii- -iv- -v- -vi- -vii- -viii- -ix- -x- -xi- -xii- 1-1 1-2 1-3 1-4 1-5 1-6 1-7 1-8 1-9 1-10 1-11 1-12 1-13 1-14 1-15 1-16 1-17 1-18 1-19 1-20 1-21 2-1 2-2 2-3 2-4 2-5 2-6 2-7 2-8 2-9 2-10 2-11

More information

Microsoft Word - 强迫性活动一览表.docx

Microsoft Word - 强迫性活动一览表.docx 1 1 - / 2 - / 3 - / 4 - / 5 - I. 1. / 2. / 3. 4. 5. 6. 7. 8. 9 10 11. 12. 2 13. 14. 15. 16. 17. 18. 19. 20 21. 22 23. 24. / / 25. 26. 27. 28. 29. 30. 31. II. 1. 2 3. 4 3 5. 6 7 8. 9 10 11 12 13 14. 15.

More information

untitled

untitled 4.1AOP AOP Aspect-oriented programming AOP 來說 AOP 令 理 Cross-cutting concerns Aspect Weave 理 Spring AOP 來 AOP 念 4.1.1 理 AOP AOP 見 例 來 例 錄 Logging 錄 便 來 例 行 留 錄 import java.util.logging.*; public class HelloSpeaker

More information

Microsoft Word - Panel Paper on T&D-Chinese _as at 6.2.2013__final_.doc

Microsoft Word - Panel Paper on T&D-Chinese _as at 6.2.2013__final_.doc 二 零 一 三 年 二 月 十 八 日 會 議 討 論 文 件 立 法 會 CB(4)395/12-13(03) 號 文 件 立 法 會 公 務 員 及 資 助 機 構 員 工 事 務 委 員 會 公 務 員 培 訓 及 發 展 概 況 目 的 本 文 件 介 紹 公 務 員 事 務 局 為 公 務 員 所 提 供 培 訓 和 發 展 的 最 新 概 況, 以 及 將 於 二 零 一 三 年 推 出

More information

云数据库 RDS SDK

云数据库 RDS SDK 云数据库 RDS SDK SDK SDK 下载 SDK 下载 最新版本 java_sdk.zip python_sdk.zip php_sdk.zip c#_sdk.zip 历史版本 2015-11-3 java_sdk.zip python_sdk.zip php_sdk.zip c#_sdk.zip JAVA 教程 JAVA 创建 Access Key 登陆阿里云账号 打开 我的 Access

More information

Windows RTEMS 1 Danilliu MMI TCP/IP QEMU i386 QEMU ARM POWERPC i386 IPC PC104 uc/os-ii uc/os MMI TCP/IP i386 PORT Linux ecos Linux ecos ecos eco

Windows RTEMS 1 Danilliu MMI TCP/IP QEMU i386 QEMU ARM POWERPC i386 IPC PC104 uc/os-ii uc/os MMI TCP/IP i386 PORT Linux ecos Linux ecos ecos eco Windows RTEMS 1 Danilliu MMI TCP/IP 80486 QEMU i386 QEMU ARM POWERPC i386 IPC PC104 uc/os-ii uc/os MMI TCP/IP i386 PORT Linux ecos Linux ecos ecos ecos Email www.rtems.com RTEMS ecos RTEMS RTEMS Windows

More information

江苏宁沪高速公路股份有限公司.PDF

江苏宁沪高速公路股份有限公司.PDF - 1 - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - 8 - 33.33% ( ) ( ) ( ) 33.33% ( ) ( ) ( ) 1 1 1992 8 3200001100976 1997 6 27 H 12.22 2001 1 16 A 1.5 2001 12 3 503,774.75 14,914,399,845.00 13,445,370,274.00

More information

IP505SM_manual_cn.doc

IP505SM_manual_cn.doc IP505SM 1 Introduction 1...4...4...4...5 LAN...5...5...6...6...7 LED...7...7 2...9...9...9 3...11...11...12...12...12...14...18 LAN...19 DHCP...20...21 4 PC...22...22 Windows...22 TCP/IP -...22 TCP/IP

More information

Guava学习之Resources

Guava学习之Resources Resources 提供提供操作 classpath 路径下所有资源的方法 除非另有说明, 否则类中所有方法的参数都不能为 null 虽然有些方法的参数是 URL 类型的, 但是这些方法实现通常不是以 HTTP 完成的 ; 同时这些资源也非 classpath 路径下的 下面两个函数都是根据资源的名称得到其绝对路径, 从函数里面可以看出,Resources 类中的 getresource 函数都是基于

More information

绝妙故事

绝妙故事 980.00 III... 1... 1... 4... 5... 8...10...11...12...14...16...18...20...23...23...24...25...27...29...29...31...34...35...36...39...41 IV...43...44...46...47...48...49...50...51...52...54...56...57...59...60...61...62...63...66...67...68...69...70...72...74...76...77...79...80

More information

榫 卯 是 什 麼? 何 時 開 始 應 用 於 建 築 中? 38 中 國 傳 統 建 築 的 屋 頂 有 哪 幾 種 形 式? 40 大 內 高 手 的 大 內 指 什 麼? 42 街 坊 四 鄰 的 坊 和 街 分 別 指 什 麼? 44 北 京 四 合 院 的 典 型 格 局 是 怎 樣 的

榫 卯 是 什 麼? 何 時 開 始 應 用 於 建 築 中? 38 中 國 傳 統 建 築 的 屋 頂 有 哪 幾 種 形 式? 40 大 內 高 手 的 大 內 指 什 麼? 42 街 坊 四 鄰 的 坊 和 街 分 別 指 什 麼? 44 北 京 四 合 院 的 典 型 格 局 是 怎 樣 的 目 錄 中 華 醫 藥 以 醫 術 救 人 為 何 被 稱 為 懸 壺 濟 世? 2 什 麼 樣 的 醫 生 才 能 被 稱 為 華 佗 再 世? 4 中 醫 如 何 從 臉 色 看 人 的 特 質? 6 中 醫 怎 樣 從 五 官 看 病? 8 中 醫 看 舌 頭 能 看 出 些 什 麼 來? 10 中 醫 真 的 能 靠 一 個 枕 頭, 三 根 指 頭 診 病 嗎? 12 切 脈 能 判 斷

More information

尿路感染防治.doc

尿路感染防治.doc ...1...1...2...4...6...7...7...10...12...13...15...16...18...19...24...25...26...27...28 I II...29...30...31...32...33...34...36...37...37...38...40...40...41...43...44...46...47...48...48...49...52 III...55...56...56...57...58

More information

通过Hive将数据写入到ElasticSearch

通过Hive将数据写入到ElasticSearch 我在 使用 Hive 读取 ElasticSearch 中的数据 文章中介绍了如何使用 Hive 读取 ElasticSearch 中的数据, 本文将接着上文继续介绍如何使用 Hive 将数据写入到 ElasticSearch 中 在使用前同样需要加入 elasticsearch-hadoop-2.3.4.jar 依赖, 具体请参见前文介绍 我们先在 Hive 里面建个名为 iteblog 的表,

More information

Oracle 4

Oracle 4 Oracle 4 01 04 Oracle 07 Oracle Oracle Instance Oracle Instance Oracle Instance Oracle Database Oracle Database Instance Parameter File Pfile Instance Instance Instance Instance Oracle Instance System

More information

心理障碍防治(下).doc

心理障碍防治(下).doc ( 20 010010) 787 1092 1/32 498.50 4 980 2004 9 1 2004 9 1 1 1 000 ISBN 7-204-05940-9/R 019 1880.00 ( 20.00 ) ...1...2...2...3...4...5...6...7...8...9...10... 11...12...13...15...16...17...19...21 I ...23...24...26...27...28...30...32...34...37...39...40...42...42...44...47...50...52...56...58...60...64...68

More information

软件概述

软件概述 Cobra DocGuard BEIJING E-SAFENET SCIENCE & TECHNOLOGY CO.,LTD. 2003 3 20 35 1002 010-82332490 http://www.esafenet.com Cobra DocGuard White Book 1 1....4 1.1...4 1.2 CDG...4 1.3 CDG...4 1.4 CDG...5 1.5

More information

2. 我 沒 有 說 實 話, 因 為 我 的 鞋 子 其 實 是 [ 黑 色 / 藍 色 / 其 他 顏 色.]. 如 果 我 說 我 現 在 是 坐 著 的, 我 說 的 是 實 話 嗎? [ 我 說 的 對 還 是 不 對 ]? [ 等 對 方 回 答 ] 3. 這 是 [ 實 話 / 對 的

2. 我 沒 有 說 實 話, 因 為 我 的 鞋 子 其 實 是 [ 黑 色 / 藍 色 / 其 他 顏 色.]. 如 果 我 說 我 現 在 是 坐 著 的, 我 說 的 是 實 話 嗎? [ 我 說 的 對 還 是 不 對 ]? [ 等 對 方 回 答 ] 3. 這 是 [ 實 話 / 對 的 附 錄 美 國 國 家 兒 童 健 康 與 人 類 發 展 中 心 (NICHD) 偵 訊 指 導 手 冊 I. 開 場 白 1. 你 好, 我 的 名 字 是, 我 是 警 察 [ 介 紹 房 間 內 的 其 他 人, 不 過, 在 理 想 狀 態 下, 房 間 裡 不 該 有 其 他 人 ] 今 天 是 ( 年 月 日 ), 現 在 是 ( 幾 點 幾 分 ) 我 是 在 ( 地 點 ) 問 你

More information

iv 20 1 1.75 不 必 詫 異, 其 實 成 功 與 失 敗 之 間 就 是 由 這 樣 簡 單 的 工 作 習 慣 造 成 的 可 見, 習 慣 雖 小, 卻 影 響 深 遠 遍 數 名 載 史 冊 的 成 功 人 士, 哪 位 沒 有 幾 個 可 圈 可 點 的 習 慣 在 影 響 着

iv 20 1 1.75 不 必 詫 異, 其 實 成 功 與 失 敗 之 間 就 是 由 這 樣 簡 單 的 工 作 習 慣 造 成 的 可 見, 習 慣 雖 小, 卻 影 響 深 遠 遍 數 名 載 史 冊 的 成 功 人 士, 哪 位 沒 有 幾 個 可 圈 可 點 的 習 慣 在 影 響 着 iii 前 言 : 好 習 慣 成 就 好 人 生 論 語 說 : 性 相 近 也, 習 相 遠 也 其 意 是 說, 人 的 本 性 很 接 近, 但 由 於 習 慣 不 同, 便 相 去 甚 遠 習 慣 是 宇 宙 共 同 的 法 則, 具 有 無 法 阻 擋 的 巨 大 力 量 冬 天 來 了, 春 天 還 會 遠 嗎? 這 就 是 無 法 阻 擋 的 一 股 力 量 蘋 果 離 開 樹 枝

More information

untitled

untitled JavaEE+Android - 6 1.5-2 JavaEE web MIS OA ERP BOSS Android Android Google Map office HTML CSS,java Android + SQL Sever JavaWeb JavaScript/AJAX jquery Java Oracle SSH SSH EJB+JBOSS Android + 1. 2. IDE

More information

Microsoft Word - Paper on PA (Chi)_2016.01.19.docx

Microsoft Word - Paper on PA (Chi)_2016.01.19.docx 立 法 會 發 展 事 務 委 員 會 二 零 一 六 年 施 政 報 告 及 施 政 綱 領 有 關 發 展 局 的 措 施 引 言 行 政 長 官 在 二 零 一 六 年 一 月 十 三 日 發 表 題 為 創 新 經 濟 改 善 民 生 促 進 和 諧 繁 榮 共 享 的 二 零 一 六 年 施 政 報 告 施 政 報 告 夾 附 施 政 綱 領, 臚 列 政 府 推 行 的 新 措 施 和

More information

14A 0.1%5% 14A 14A.52 1 2 3 30 2

14A 0.1%5% 14A 14A.52 1 2 3 30 2 2389 30 1 14A 0.1%5% 14A 14A.52 1 2 3 30 2 (a) (b) (c) (d) (e) 3 (i) (ii) (iii) (iv) (v) (vi) (vii) 4 (1) (2) (3) (4) (5) 400,000 (a) 400,000300,000 100,000 5 (b) 30% (i)(ii) 200,000 400,000 400,000 30,000,000

More information

(Chi)_.indb

(Chi)_.indb 1,000,000 4,000,000 1,000,000 10,000,000 30,000,000 V-1 1,000,000 2,000,000 20,000,00010,000,0005,000,000 3,000,000 30 20% 35% 20%30% V-2 1) 2)3) 171 10,000,00050% 35% 171 V-3 30 V-4 50,000100,000 1) 2)

More information

穨_2_.PDF

穨_2_.PDF 6 7.... 9.. 11.. 12... 14.. 15.... 3 .. 17 18.. 20... 25... 27... 29 30.. 4 31 32 34-35 36-38 39 40 5 6 : 1. 2. 1. 55 (2) 2. : 2.1 2.2 2.3 3. 4. ( ) 5. 6. ( ) 7. ( ) 8. ( ) 9. ( ) 10. 7 ( ) 1. 2. 3. 4.

More information

Page i

Page i 况 1 1.1.1 1.1.2 1.1.3 2 2.1 2.1.1 2.1.2 2.1.3 2.1.4 Page i 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.3 2.3.1 Page ii 2.3.2 2.3.3 2.3.4 2.4 2.4.1 2.4.2 2.4.3 Page iii 2.5 2.5.1 2.6 2.6.1 2.6.2 3 3.1 3.1.1

More information

<4D6963726F736F667420576F7264202D203938BEC7A67EABD7B942B0CAC15AC075B3E6BF57A9DBA5CDC2B2B3B92DA5BFBD542E646F63>

<4D6963726F736F667420576F7264202D203938BEC7A67EABD7B942B0CAC15AC075B3E6BF57A9DBA5CDC2B2B3B92DA5BFBD542E646F63> 98 年 3 月 11 日 依 本 校 98 學 年 度 招 生 委 員 會 第 1 次 會 議 核 定 大 同 技 術 學 院 98 學 年 度 重 點 運 動 項 目 績 優 學 生 單 獨 招 生 簡 章 大 同 技 術 學 院 招 生 委 員 會 編 印 校 址 :600 嘉 義 市 彌 陀 路 253 號 電 話 :(05)2223124 轉 203 教 務 處 招 生 專 線 :(05)2223124

More information

停止混流接口 请注意 : 该功能需要联系 ZEGO 技术支持开通 1 接口调用说明 http 请求方式 : POST/FORM, 需使用 https 正式环境地址 access_token=access_token (http

停止混流接口 请注意 : 该功能需要联系 ZEGO 技术支持开通 1 接口调用说明 http 请求方式 : POST/FORM, 需使用 https 正式环境地址   access_token=access_token (http 停止混流接口 请注意 : 该功能需要联系 ZEGO 技术支持开通 1 接口调用说明 http 请求方式 : POST/FORM, 需使用 https 正式环境地址 https://webapi.zego.im/cgi/stop-mix? access_token=access_token (https://webapi.zego.im/cgi/stop-mix? access_token=access_token)

More information

内 容 提 要 将 JAVA 开 发 环 境 迁 移 到 Linux 系 统 上 是 现 在 很 多 公 司 的 现 实 想 法, 而 在 Linux 上 配 置 JAVA 开 发 环 境 是 步 入 Linux 下 JAVA 程 序 开 发 的 第 一 步, 本 文 图 文 并 茂 地 全 程 指

内 容 提 要 将 JAVA 开 发 环 境 迁 移 到 Linux 系 统 上 是 现 在 很 多 公 司 的 现 实 想 法, 而 在 Linux 上 配 置 JAVA 开 发 环 境 是 步 入 Linux 下 JAVA 程 序 开 发 的 第 一 步, 本 文 图 文 并 茂 地 全 程 指 内 容 提 要 将 JAVA 开 发 环 境 迁 移 到 Linux 系 统 上 是 现 在 很 多 公 司 的 现 实 想 法, 而 在 Linux 上 配 置 JAVA 开 发 环 境 是 步 入 Linux 下 JAVA 程 序 开 发 的 第 一 步, 本 文 图 文 并 茂 地 全 程 指 导 你 搭 建 Linux 平 台 下 的 JAVA 开 发 环 境, 包 括 JDK 以 及 集

More information

JavaIO.PDF

JavaIO.PDF O u t p u t S t ream j a v a. i o. O u t p u t S t r e a m w r i t e () f l u s h () c l o s e () public abstract void write(int b) throws IOException public void write(byte[] data) throws IOException

More information

世界名画及画家介绍(四).doc

世界名画及画家介绍(四).doc II...1...2...2...3...4...5...7...7...8...9...9...10... 11...12...13...14...15...15...16...18...18...19...20 III...21...21...22...24...24...25...26...27...28...29...30...30...31...33...33...34...35...36...36...37...38...39...40...41...42...43

More information

基于ECO的UML模型驱动的数据库应用开发1.doc

基于ECO的UML模型驱动的数据库应用开发1.doc ECO UML () Object RDBMS Mapping.Net Framework Java C# RAD DataSetOleDbConnection DataGrod RAD Client/Server RAD RAD DataReader["Spell"].ToString() AObj.XXX bug sql UML OR Mapping RAD Lazy load round trip

More information

1. 访 问 最 新 发 行 公 告 信 息 jconnect for JDBC 7.0 1. 访 问 最 新 发 行 公 告 信 息 最 新 版 本 的 发 行 公 告 可 以 从 网 上 获 得 若 要 查 找 在 本 产 品 发 布 后 增 加 的 重 要 产 品 或 文 档 信 息, 请 访

1. 访 问 最 新 发 行 公 告 信 息 jconnect for JDBC 7.0 1. 访 问 最 新 发 行 公 告 信 息 最 新 版 本 的 发 行 公 告 可 以 从 网 上 获 得 若 要 查 找 在 本 产 品 发 布 后 增 加 的 重 要 产 品 或 文 档 信 息, 请 访 发 行 公 告 jconnect for JDBC 7.0 文 档 ID:DC74874-01-0700-01 最 后 修 订 日 期 :2010 年 3 月 2 日 主 题 页 码 1. 访 问 最 新 发 行 公 告 信 息 2 2. 产 品 摘 要 2 3. 特 殊 安 装 说 明 2 3.1 查 看 您 的 jconnect 版 本 3 4. 特 殊 升 级 指 导 3 4.1 迁 移 3

More information

Microsoft PowerPoint - ch6 [相容模式]

Microsoft PowerPoint - ch6 [相容模式] UiBinder wzyang@asia.edu.tw UiBinder Java GWT UiBinder XML UI i18n (widget) 1 2 UiBinder HelloWidget.ui.xml: UI HelloWidgetBinder HelloWidget.java XML UI Owner class ( Composite ) UI XML UiBinder: Owner

More information

Flume-ng与Mysql整合开发

Flume-ng与Mysql整合开发 Flume-ng 与 Mysql 整合开发 我们知道,Flume 可以和许多的系统进行整合, 包括了 Hadoop Spark Kafka Hbase 等等 ; 当然, 强悍的 Flume 也是可以和 Mysql 进行整合, 将分析好的日志存储到 Mysql( 当然, 你也可以存放到 pg oracle 等等关系型数据库 ) 不过我这里想多说一些 :Flume 是分布式收集日志的系统 ; 既然都分布式了,

More information