Spark SQL CLI 實現分析

來源:互聯網
上載者:User

標籤:spark sql   hive   cli   

背景

本文主要介紹了Spark SQL裡目前的CLI實現,代碼之後肯定會有不少變動,所以我關注的是比較核心的邏輯。主要是對比了Hive CLI的實現方式,比較Spark SQL在哪塊地方做了修改,哪些地方與Hive CLI是保持一致的。可以先看下總結一節裡的內容。


Spark SQL的hive-thriftserver項目裡是其CLI實現代碼,下面先說明Hive CLI的主要實作類別和關係,再說明Spark SQL CLI的做法。


Hive CLI

核心啟動類是org.apache.hive.service.server.HiveServer2,啟動方式:

    try {      ServerOptionsProcessor oproc = new ServerOptionsProcessor("hiveserver2");      if (!oproc.process(args)) {        LOG.fatal("Error starting HiveServer2 with given arguments");        System.exit(-1);      }      HiveConf hiveConf = new HiveConf();      HiveServer2 server = new HiveServer2();      server.init(hiveConf);      server.start();    } catch (Throwable t) {      LOG.fatal("Error starting HiveServer2", t);      System.exit(-1);    }

HiveServer2繼承CompositeService類,CompositeService類內部維護一個serviceList,能夠加入、刪除、啟動、停止不同的服務。HiveServer2在init(hiveConf)的時候,會加入CLIService和ThriftCLIService兩個Service。根據傳輸模式,如果是http或https的話,就使用ThriftHttpCLIService,否則使用ThriftBinaryCLIService。無論是哪個ThriftCLIService,都傳入了CLIService的引用,thrift只是一個封裝。

加入了這些服務後,把服務都啟動起來。


CLIService也繼承自CompositeService,CLIService 在init的時候會加入SessionManager服務,並且根據hiveConf,從 hadoop shims裡得到UGI裡的serverUsername。

SessionManager管理hive串連的開啟、關閉等管理功能,已有的串連會維護在一個HashMap裡,value為HiveSession類,裡面大致是使用者名稱、密碼、hive配置等info。

所以CLIService裡幾乎所有的事情都是委託給SessionManager做的。

 

SessionManager內主要是OperationManager這個服務,是最重要的和執行邏輯有關的類,下面會具體說。

 

另外,關於ThriftCLIService,有兩個實現子類,子類只複寫了run()方法,設定thrift server相關的網路連接,其他對CLIService的調用邏輯都在父類ThriftCLIService本身裡面。


實際上,ThriftCLIService裡很多事情也是委託給CLIService做的。

 

那麼上面大致是Hive CLI、Thrift server啟動的流程,以及幾個主要類的相互關係。


Spark SQL CLI

根據上面Hive CLI的邏輯,看看Spark SQL的CLI是怎麼做的。

Spark裡的HiveThriftServer2(這個類名看起來有點奇怪)繼承了Hive的HiveServer2,並且複寫了init方法,其初始化的時候加入的是SparkSQLCLIService和ThriftBinaryCLIService兩個服務。前者繼承了Hive的CLIService,有一些不同的邏輯;後者直接使用的是Hive的類,但傳入的是SparkSQLCLIService的引用。


SparkSQLCLIService內部,類似Hive的CLIService,有一個SparkSQLSessionManager,繼承自Hive的SessionManager。也有得到serverUsername的邏輯,代碼和CLIService是一樣的。

 

SparkSQLSessionManager複寫了init這個方法,裡面有Spark自己的SparkSQLOperationManager服務,繼承自Hive的OperationManager類。

 

可能上面這幾個類有點看暈了,本質上都是一些封裝而已,沒什麼大的區別。真正重要的是SparkSQLOperationManager這個類裡面,定義了如何使用Spark SQL來處理query操作。


SparkSQLOperationManager關鍵邏輯

Hive的CLI Operation父類有如下的子類繼承體系,代表hive cli會處理的不同操作類型:

上半部分ExecuteStatementOperation子類體系是實際和查詢相關的操作,下半部分是一些中繼資料讀取操作。SparkSQLOperationManager實際改寫的就是ExecuteStatementOperation子類的執行邏輯,而中繼資料相關的操作還是沿用hive本來的處理邏輯。

 

原本hive的ExecuteStatementOperation處理邏輯是這樣的:

  public static ExecuteStatementOperation newExecuteStatementOperation(      HiveSession parentSession, String statement, Map<String, String> confOverlay, boolean runAsync) {    String[] tokens = statement.trim().split("\\s+");    String command = tokens[0].toLowerCase();    if ("set".equals(command)) {      return new SetOperation(parentSession, statement, confOverlay);    } else if ("dfs".equals(command)) {      return new DfsOperation(parentSession, statement, confOverlay);    } else if ("add".equals(command)) {      return new AddResourceOperation(parentSession, statement, confOverlay);    } else if ("delete".equals(command)) {      return new DeleteResourceOperation(parentSession, statement, confOverlay);    } else {      return new SQLOperation(parentSession, statement, confOverlay, runAsync);    }  }

ExecuteStatementOperation也分兩部分,HiveCommandOperation和SQLOperation。

不同的ExecuteStatementOperation子類最終由對應的CommandProcessor子類來完成操作請求。


那Spark是如何改寫ExecuteStatementOperation的執行邏輯的呢?

最核心的邏輯如下:

      def run(): Unit = {        logInfo(s"Running query ‘$statement‘")        setState(OperationState.RUNNING)        try {          result = hiveContext.sql(statement)          logDebug(result.queryExecution.toString())          val groupId = round(random * 1000000).toString          hiveContext.sparkContext.setJobGroup(groupId, statement)          iter = result.queryExecution.toRdd.toLocalIterator          dataTypes = result.queryExecution.analyzed.output.map(_.dataType).toArray          setHasResultSet(true)        } catch {          // Actually do need to catch Throwable as some failures don‘t inherit from Exception and          // HiveServer will silently swallow them.          case e: Throwable =>            logError("Error executing query:",e)            throw new HiveSQLException(e.toString)        }        setState(OperationState.FINISHED)      }

statement是一個String,即query本身,調用HiveContext的sql()方法,返回的是一個SchemaRDD。HiveContext的這段邏輯如下:

  override def sql(sqlText: String): SchemaRDD = {    // TODO: Create a framework for registering parsers instead of just hardcoding if statements.    if (dialect == "sql") {      super.sql(sqlText)    } else if (dialect == "hiveql") {      new SchemaRDD(this, HiveQl.parseSql(sqlText))    }  else {      sys.error(s"Unsupported SQL dialect: $dialect.  Try ‘sql‘ or ‘hiveql‘")    }  }

調完sql()後返回的是一個帶被解析過了的基礎邏輯計劃的SchemaRDD。後續,

logDebug(result.queryExecution.toString())

這一步觸發了邏輯執行計畫的進一步分析、最佳化和變成物理執行計畫的幾個過程。之後,

result.queryExecution.toRdd

toRdd這步是觸發計算並返回結果。這幾個邏輯在之前Spark SQL源碼分析的文章裡都提到過。

除了上面這部分,還有一些schema轉化、資料類型轉化的邏輯,是因為Catalyst這邊,有自己的資料行表示方法,也有自己的dataType,而且schema這塊呢,在產生SchemaRDD的時候也轉化過一次。所以在返回執行結果的時候,需要有轉換回Hive的TableSchema、FieldSchema的邏輯。

 

以上說明了Spark SQL是如何把query的執行轉換到Spark SQL裡的。


總結

基本上Spark SQL在CLI這塊的實現很靠近Hive Service項目裡的CLI模組,主要類繼承體系、執行邏輯差不多都一樣。Spark SQL修改的關鍵邏輯在CLIService內的SessionManager內的OperationManager裡,將非中繼資料查詢操作的query丟給了Spark SQL的Hive工程裡的HiveContext.sql()來完成,通過返回的SchemaRDD,來進一步得到結果資料、得到中間執行計畫的Schema資訊。



全文完 :)




聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.