6、HIVE JDBC开发、UDF、体系结构、Thrift服务器、Driver、元数据库Metastore、数据库连接模式、单/多用户模式、远程服务模式、Hive技术原理解析、优化等(整理的笔记)
目錄:
 5 HIVE開發
 5.1 Hive JDBC開發
 5.2 Hive UDF
 6 Hive的體系結構
 6.2 Thrift服務器
 6.3 Driver
 6.4 元數據庫Metastore
 6.5 數據庫連接模式
 6.5.1 單用戶模式
 6.5.2 多用戶模式
 6.5.3 遠程服務模式
 7 Hive技術原理解析
 7.1 Hive工作原理
 7.2.1 Hive編譯器的組成
 7.2.2 Query Compiler
 7.2.3新版本Hive也支持Tez或Spark作為執行引擎
 8 Hive優化
5 HIVE開發
5.1 Hive JDBC開發
若想進行Hive JDBC的開發,需要開啟HiveServer2服務,這里要參考第二章節的”連接方式”中的HiveServer2/beeline章節。
其中一個Hive JDBC的案例如下:
啟動完HiveServer2服務,就提供JDBC服務了,類似:
import java.sql.SQLException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.sql.DriverManager;public class HiveJdbcClient {private static String driverName = "org.apache.hive.jdbc.HiveDriver";/*** @param args* @throws SQLException*/public static void main(String[] args) throws SQLException {try {Class.forName(driverName);} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();System.exit(1);}//replace "hive" here with the name of the user the queries should run asConnection con = DriverManager.getConnection("jdbc:hive2://localhost:10000/default", "hive", "");Statement stmt = con.createStatement();String tableName = "testHiveDriverTable";stmt.execute("drop table if exists " + tableName);stmt.execute("create table " + tableName + " (key int, value string)");// show tablesString sql = "show tables '" + tableName + "'";System.out.println("Running: " + sql);ResultSet res = stmt.executeQuery(sql);if (res.next()) {System.out.println(res.getString(1));}// describe tablesql = "describe " + tableName;System.out.println("Running: " + sql);res = stmt.executeQuery(sql);while (res.next()) {System.out.println(res.getString(1) + "\t" + res.getString(2));}// load data into table// NOTE: filepath has to be local to the hive server// NOTE: /tmp/a.txt is a ctrl-A separated file with two fields per lineString filepath = "/tmp/a.txt";sql = "load data local inpath '" + filepath + "' into table " + tableName;System.out.println("Running: " + sql);stmt.execute(sql);// select * querysql = "select * from " + tableName;System.out.println("Running: " + sql);res = stmt.executeQuery(sql);while (res.next()) {System.out.println(String.valueOf(res.getInt(1)) + "\t" + res.getString(2));}// regular hive querysql = "select count(1) from " + tableName;System.out.println("Running: " + sql);res = stmt.executeQuery(sql);while (res.next()) {System.out.println(res.getString(1));}} }5.2 Hive UDF
簡單UDF示例
 前期準備,要把hive的lib包導入到工程中,其中UDF依賴的是hive-exec-2.3.4.jar(針對hive2.3.4版本)。也就是說要把$HIVE_HOME\lib中的內容引入到工程中。若用到hadoop中的一些api,也把hadoop的相關的jar包也引入進去。
 1、先開發一個java類,繼承UDF,并重載evaluate方法。
2、打成jar包上傳到服務器
 3、將jar包添加到hive的classpath
 4、hive> add jar /home/udf/hivedata/udf.jar;
 5、hive>創建臨時函數與開發好的java class關聯
6、即可在HiveQL中使用自定義的函數toLowercase
hive> select toLowercase("ZHANGSAN") from dual; OK zhangsan Time taken: 0.122 seconds, Fetched: 1 row(s) hive>再如判斷身份證的一個UDF函數:
package com.xxx.xxx.udf;import java.text.SimpleDateFormat; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern;import org.apache.hadoop.hive.ql.exec.UDF;/*** 身份證校驗UDF* * @author xxx* @date 2019年5月29日* * 1.身份證號碼構成:6位地址編碼+8位生日+3位順序碼+1位校驗碼 * 2.驗證15位,18位證件號碼是否有效; * 3.15位號碼將直接轉為18位;【這一步不做轉化】* 4.校驗身份證號碼除了校驗位是否為數值,校驗省份、出生日期* 省份編碼[11, 12, 13, 14, 15, 21, 22, 23, 31, 32, 33, 34, 35, 36, 37, 41, 42, 43,44, 45, 46, 50, 51, 52, 53, 54, 61, 62, 63, 64, 65, 71, 81, 82, 91]* 5.校驗位不正確的會被正確的替代.【最后1位是根據前面17位生成的,如果不正確,會生成正確的檢驗碼進行修正.】* 6.出生日期邏輯有效性,即是否1864年前出生,是否當前日期后出生已校驗。* 7.1984年4月6日國務院發布《中華人民共和國居民身份證試行條例》,并且開始頒發第一代居民身份證,按壽命120歲驗證,年齡區間是1864年至今。 * 8.15位證件號碼轉換未考慮1900年前出生的人【這一步不做轉化】* 9.去除null,去除空串,去除非15位和18位* 10.判斷前17位是否含非數字字符.* 11.判斷非法日期,如生日所在日期(2019年2月29日)不存在.* * add jar hdfs://tqHadoopCluster/xxx/udf/idCardUdf.jar;* create temporary function cheakIdCardNumber as 'com.xxx.xxx.udf.IdNumber';* 這這就可以使用cheakIdCardNumber(String idCardNumber)進行校驗身份證了*/ public class IdNumber extends UDF {private final static int NEW_CARD_NUMBER_LENGTH = 18;private final static int OLD_CARD_NUMBER_LENGTH = 15;/** 開始年份 */private final static int YEAR_BEGIN = 1864;/** 18位身份證中最后一位校驗碼 */private final static char[] VERIFY_CODE = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};/** 18位身份證中,各個數字的生成校驗碼時的權值 */private final static int[] VERIFY_CODE_WEIGHT = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};/** 省份編碼集合 */private final static int[] PROVINCE_CODE = {11, 12, 13, 14, 15, 21, 22, 23, 31, 32, 33, 34, 35, 36, 37, 41, 42, 43,44, 45, 46, 50, 51, 52, 53, 54, 61, 62, 63, 64, 65, 71, 81, 82, 91};private final static Pattern PATTERN = Pattern.compile("[0-9]*");private final static ThreadLocal<SimpleDateFormat> THREADLOCAL = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected SimpleDateFormat initialValue() {// 直接返回nullreturn null;}};/*public static void main(String[] args) {IdNumber idNumber = new IdNumber();//String evaluate = idNumber.evaluate("125197791116622");// 125197 791116622// 12519719791116622X// 330304198705234817String evaluate = idNumber.evaluate("330304201902284815");System.out.println(evaluate);}*/public String evaluate(String cardNumber) {// 1.去除NULLif (cardNumber == null) {return null;} Date dNow = new Date();SimpleDateFormat ft = new SimpleDateFormat("yyyy");int yearEnd = Integer.parseInt(ft.format(dNow));cardNumber = cardNumber.trim();// 2.去除空字符串if (cardNumber.length() == 0) {return null;}// 3.去除長度不正確的,非15或18位if (NEW_CARD_NUMBER_LENGTH != cardNumber.length() & OLD_CARD_NUMBER_LENGTH != cardNumber.length()) {return null;}// 4.如果是18位if (NEW_CARD_NUMBER_LENGTH == cardNumber.length()) {// 4.1.前17位不為數值,返回nullif (isNumeric(cardNumber.substring(0, 17)) == false) {return null;}// 校驗位修正,判斷最后一位是否正確if (IdNumber.calculateVerifyCode(cardNumber) != cardNumber.charAt(17)) {//如果最后一位校驗碼不會,則修正校驗碼.cardNumber = contertToNewCardNumber(cardNumber, NEW_CARD_NUMBER_LENGTH);}// 身份證前2位是省份編碼, 如果編碼不在已知集合中, 返回null.if (isProvince(cardNumber.substring(0, 2)) == false) {return null;} // 非法日期轉換前后不一致, 如當前年份不存在2月29日, 返回nullif (isDate(cardNumber.substring(6, 14)) == false) {return null;}// 1984年4月6日國務院發布《中華人民共和國居民身份證試行條例》,并且開始頒發第一代居民身份證,按壽命120歲驗證,年齡區間是1864年至今。 小于1984的為非法.int birthdayYear = Integer.parseInt(cardNumber.substring(6, 10));if (birthdayYear < YEAR_BEGIN) {return null;}// 如果出生年月大于當前日期, 返回nullif (birthdayYear > yearEnd) {return null;}}// 5.如果是15位:15位身份證號碼不轉換為18位.if (OLD_CARD_NUMBER_LENGTH == cardNumber.length()) {// 前15位中含非數字字符, 返回nullif (isNumeric(cardNumber) == false) {return null;}// 省份編碼不在在已知集合中, 返回nullif (isProvince(cardNumber.substring(0, 2)) == false) {return null;}// 非法日期轉換前后不一致, 返回nullif (isDate("19" + cardNumber.substring(6, 12)) == false) {return null;}//將15位身份證轉化為18位,考慮1900年前出生的人。//cardNumber = contertToNewCardNumber(cardNumber, OLD_CARD_NUMBER_LENGTH);}return cardNumber;}/*** 校驗碼(第十八位數):* * 十七位數字本體碼加權求和公式 S = Sum(Ai * Wi), i = 0...16 ,先對前17位數字的權求和; * Ai:表示第i位置上的身份證號碼數字值 * Wi:表示第i位置上的加權因子 * Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2; * 計算模 Y = mod(S, 11) 通過模得到對應的校驗碼* Y: 0 1 2 3 4 5 6 7 8 9 10 * 校驗碼: 1 0 X 9 8 7 6 5 4 3 2* * @param cardNumber* @return*/private static char calculateVerifyCode(CharSequence cardNumber) {int sum = 0;for (int i = 0; i < NEW_CARD_NUMBER_LENGTH - 1; i++) {char ch = cardNumber.charAt(i);sum += ((int)(ch - '0')) * VERIFY_CODE_WEIGHT[i];}//VERIFY_CODE = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};return VERIFY_CODE[sum % 11];}/*** A-把15位身份證號碼轉換到18位身份證號碼 15位身份證號碼與18位身份證號碼的區別為: * 1. 15位身份證號碼中,"出生年份"字段是2位,轉換時需要補入"19",表示20世紀* 2. 15位身份證無最后一位校驗碼。18位身份證中,校驗碼根據根據前17位生成 B-18位身份證號碼校驗位修復* * @param cardNumber* @return*/private static String contertToNewCardNumber(String idCardNumber, int cardNumberLength) {StringBuilder buf = new StringBuilder(NEW_CARD_NUMBER_LENGTH);if (cardNumberLength == NEW_CARD_NUMBER_LENGTH) {buf.append(idCardNumber.substring(0, 17));buf.append(IdNumber.calculateVerifyCode(buf));} else if (cardNumberLength == OLD_CARD_NUMBER_LENGTH) {buf.append(idCardNumber.substring(0, 6));buf.append("19");buf.append(idCardNumber.substring(6));buf.append(IdNumber.calculateVerifyCode(buf));}return buf.toString();}/*** 功能:判斷字符串是否為數字* * @param str* @return*/private static boolean isNumeric(String str) {Matcher isNum = PATTERN.matcher(str);if (isNum.matches()) {return true;} else {return false;}}/*** 功能:判斷前兩位是在省份編碼集合內* @param province* @return*/private static boolean isProvince(String province) {int prov = Integer.parseInt(province);for (int i = 0; i < PROVINCE_CODE.length; i++) {if (PROVINCE_CODE[i] == prov) {return true;}}return false;}public static SimpleDateFormat getDateFormat() {SimpleDateFormat df = (SimpleDateFormat)THREADLOCAL.get();if (df == null) {df = new SimpleDateFormat("yyyyMMdd");df.setLenient(false);THREADLOCAL.set(df);}return df;}/*** 功能:判斷出生日期字段是否為日期* * @param str* @return*/private static boolean isDate(String birthDate) {try {SimpleDateFormat sdf = getDateFormat();//如果不合法,會拋異常, Unparseable date: "20190229"Date cacheBirthDate = sdf.parse(birthDate);if (sdf.format(cacheBirthDate).equals(birthDate) == false) {// 非法日期轉換前后不一致return false;}} catch (Exception e) {//e.printStackTrace();return false;}return true;} }6 Hive的體系結構
下面是Hive的架構圖:
 
 圖 1 Hive體系結構
6.1用戶接口
(1)、CLI:CLI啟動的時候,會同時啟動一個Hive副本。
 (2)、JDBC客戶端:封裝了Thrift,java應用程序,可以通過指定的主機和端口連接到在另外一個進程中運行的hive服務器。
 (3)、ODBC客戶端:ODBC驅動允許支持ODBC協議的應用程序連接到Hive
 (4)、WUI接口:通過瀏覽器訪問Hive
6.2Thrift服務器
基于socket通訊,支持跨語言。Hive Thrift服務簡化了在多編程語言中運行hive的命令。綁定支持C++,Java,PHP,Python和Ruby語言。
6.3 Driver
核心組件,整個Hive的核心,該組件包括Complier、Optimizer和Executor; 解釋器、編譯器、優化器完成HQL查詢語句從詞法分析、語法分析、編譯、優化以及查詢計劃的生成。生成的查詢計劃存儲在HDFS中,并在隨后有MapReduce調用執行。
6.4 元數據庫Metastore
Hive的數據由兩部分組成:數據文件和元數據。元數據用于存放Hive庫的基礎信息,它存儲在關系數據庫中,例如mysql、Oracle、derby。元數據包括:數據庫信息、表達的名字,表的列和分區及其屬性,表的屬性,表的數據所在目錄等。
 Hadoop
 Hive的數據文件存儲在HDFS中,大部分的查詢由MapReduce完成。(對于包含*的查詢,比如select * from tbl不會生成MapReduce作業)
6.5數據庫連接模式
6.5.1 單用戶模式
此模式連接到一個In-memory的數據庫Derby,Derby數據是Hive自帶的數據庫,默認只能有一個連接,用戶連接到Hive后,會在當前目錄生成一個metastore文件來存放元數據信息,不同的用戶連接到Hive看到的信息不一致。
6.5.2多用戶模式
通過網絡連接到一個關系型數據庫中,如MySQL,是最進程使用到的模式。這樣多個用戶操作的元數據信息統一放到關系型數據庫,這樣不同用戶可以操作相同的對象。
6.5.3 遠程服務模式
用于非Java客戶端訪問元數據,在服務器啟動MetaStoreServer,客戶端利用Thrift協議通過MetaStoreServer訪問元數據。
7 Hive技術原理解析
7.1 Hive工作原理
如下圖所示:
 
流程大致步驟為:
創建表時:
 1、解析用戶提交的Hive語句對其進行解析分解為表、字段、分區等Hive對象。根據解析到的信息構建對應的表、字段、分區等對象,從SEQUENCE_TABLE中獲取構建對象的最新的ID,與構建對象信息(名稱、類型等等)一同通過DAO方法寫入元數據庫的表中,成功后將SEQUENCE_TABLE中對應的最新ID+5.實際上常見的RDBMS都是通過這種方法進行組織的,其系統表中和Hive元素一樣顯示了這些ID信息。通過這些元數據可以很容易的讀取到數據。
工作原理的另外一種說法是:
 接收到一個sql,后面做的事情包括:
 1.詞法分析/語法分析
 使用antlr將SQL語句解析成抽象語法樹-AST
 2.語義分析
 從Megastore獲取模式信息,驗證SQL語句中隊表名,列名,以及數據類型的檢查和隱式轉換,以及Hive提供的函數和用戶自定義的函數(UDF/UAF)
 3.邏輯計劃生產
 生成邏輯計劃-算子樹
 4.邏輯計劃優化
 對算子樹進行優化,包括列剪枝,分區剪枝,謂詞下推等
 5.物理計劃生成
 將邏輯計劃生產包含由MapReduce任務組成的DAG的物理計劃
 6.物理計劃執行
 將DAG發送到Hadoop集群進行執行
 7.將查詢結果返回
流程如下圖:
 
7.2 Hive編譯過程
轉化過程:
 基本流程為:將HiveQL轉化為”抽象語法樹”—>”查詢塊”—>”邏輯查詢計劃””物理查詢計劃”最終選擇最佳決策的過程。
優化器的主要功能:
7.2.1 Hive編譯器的組成
 對于此的說明,網絡上的另外一種說明:
 
 
7.2.2 Query Compiler
 網絡上描述上述過程的另外一張圖(同樣是Query Compiler過程的另外一種畫法):
 
為了說明上述執行流程,需要輔助一個簡單的Hive查詢例子:
案例1-1:Hive執行以下語句:
INSERT OVERWRITE TABLE access_log_temp2 SELECT a.user, a.prono, a.maker, a.price FROM access_log_hbase a JOIN product_hbase p ON (a.prono = p.prono)具體執行流程:
 (1)Parser生成AST
 
 HQL生成的語法解析樹(AST):
 HIVE主要是利用UNDERLER處理得到上面的抽象語法樹(這棵抽象語法樹主要是根據underller的分詞規則來確定根結點和左右孩子的,Hive的這部分處理是利用underller進行的)。
 (2)Semantic Analyzer生成Query Block(QB)
 
Tip:
 第二步會將抽象語法樹按照QB的方式轉換為由QB組成的樹。通常情況下,每一個From子句會生成一個QB(Query Block)。通過查看具體有多少個QB就可查看Hive sql語句最終優化成多少個MapReduce的作業。
QB是由兩個子數據結構組成即MetaData(元數據信息)和ParseInfo。語義分析的主要過程是向QB中填充元數據,另一個過程則是摘取AST的子節點,存儲在ParseInfo中。Semantic Analyzer過程主要是經過以上兩個過程,然后把QB關聯起來形成一個由QB構成的圖.
Logical Plan generator會將上一步生成的Query Block按照前文講的幾種的Operator,轉換為Operator Tree。
 
 
 
 
 由于Hive查詢語句會將最后的查詢結果寫入到表access_log_temp2中,所以QB的MetaData中的Name To Destination…會轉化為Operator Tree中的FileSinkOperator(文件下沉)。
 最后會生成如下的OP Tree:
Logical Plan Generator (Result)
 
這里就得到了一個有向無環圖(DAG),當在傳統數據庫中會對上圖進行優化然后執行;而在Hadoop集群中這張圖是不行的,因為在集群中執行需要對這張圖進行優化、切片,分布式化轉化為MapReduce作業然后執行。
7.2.3新版本Hive也支持Tez或Spark作為執行引擎:
 
 
物理計劃可以通過hive的Explain命令輸出:
 例如:
8 Hive優化
1、join連接時的優化:當三個或多個以上的表進行join操作時,如果每個on使用相同的字段連接時只會產生一個mapreduce。
 2、join連接時的優化:當多個表進行查詢時,從左到右表的大小順序應該是從小到大。原因:hive在對每行記錄操作時會把其他表先緩存起來,直到掃描最后的表進行計算
 3、在where字句中增加分區過濾器。
 4、當可以使用left semi join 語法時不要使用inner join,前者效率更高。原因:對于左表中指定的一條記錄,一旦在右表中找到立即停止掃描。
 5、如果所有表中有一張表足夠小,則可置于內存中,這樣在和其他表進行連接的時候就能完成匹配,省略掉reduce過程。設置屬性即可實現,set hive.auto.covert.join=true; 用戶可以配置希望被優化的小表的大小 set hive.mapjoin.smalltable.size=2500000; 如果需要使用這兩個配置可置入$HOME/.hiverc文件中。
 6、同一種數據的多種處理:從一個數據源產生的多個數據聚合,無需每次聚合都需要重新掃描一次。
 例如:insert overwrite table student select * from employee; insert overwrite table person select * from employee;
 可以優化成:from employee insert overwrite table student select * insert overwrite table person select *
 7、limit調優:limit語句通常是執行整個語句后返回部分結果。set hive.limit.optimize.enable=true;
 8、開啟并發執行。某個job任務中可能包含眾多的階段,其中某些階段沒有依賴關系可以并發執行,開啟并發執行后job任務可以更快的完成。設置屬性:set hive.exec.parallel=true;
 9、hive提供的嚴格模式,禁止3種情況下的查詢模式。
 a:當表為分區表時,where字句后沒有分區字段和限制時,不允許執行。
 b:當使用order by語句時,必須使用limit字段,因為order by 只會產生一個reduce任務。
 c:限制笛卡爾積的查詢。
 10、合理的設置map和reduce數量。
 11、jvm重用??稍趆adoop的mapred-site.xml中設置jvm被重用的次數。
打個賞唄,您的支持是我堅持寫好博文的動力
 
總結
以上是生活随笔為你收集整理的6、HIVE JDBC开发、UDF、体系结构、Thrift服务器、Driver、元数据库Metastore、数据库连接模式、单/多用户模式、远程服务模式、Hive技术原理解析、优化等(整理的笔记)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 5、HIVE DML操作、load数据、
- 下一篇: 什么是小金存
