Drill中實現HTTP storage plugin,drillplugin
Apache Drill可用於大資料的即時分析,引用一段介紹:
受到Google Dremel啟發,Apache的Drill項目是對大資料集進行互動式分析的分布式系統。Drill並不會試圖取代已有的大資料批處理架構(Big Data batch processing framework),如Hadoop MapReduce或流處理架構(stream processing framework),如S4和Storm。相反,它是要填充現有空白的——對大資料集的即時互動式處理
簡單來說,Drill可接收SQL查詢語句,然後後端從多個資料來源例如HDFS、MongoDB等擷取資料並分析產出分析結果。在一次分析中,它可以彙集多個資料來源的資料。而且基於分布式的架構,可以支援秒級查詢。
Drill在架構上是比較靈活的,它的前端可以不一定是SQL查詢語言,後端資料來源也可以接入Storage plugin來支援其他資料來源。這裡我就實現了一個從HTTP服務擷取資料的Storage plugin demo。這個demo可以接入基於GET請求,返回JSON格式的HTTP服務。源碼可從我的Github擷取:drill-storage-http
例子包括:
select name, length from http.`/e/api:search` where $p=2 and $q='avi'select name, length from http.`/e/api:search?q=avi&p=2` where length > 0
實現
要實現一個自己的storage plugin,目前Drill這方面文檔幾乎沒有,只能從已有的其他storage plugin源碼入手,例如mongodb的,參考Drill子項目drill-mongo-storage
。實現的storage plugin打包為jar放到jars
目錄,Drill啟動時會自動載入,然後web上配置指定類型即可。
主要需要實現的類包括:
AbstractStoragePluginStoragePluginConfigSchemaFactoryBatchCreatorAbstractRecordReaderAbstractGroupScan
AbstraceStoragePlugin
StoragePluginConfig
用於配置plugin,例如:
{ "type" : "http", "connection" : "http://xxx.com:8000", "resultKey" : "results", "enabled" : true}
它必須是可JSON序列化/還原序列化的,Drill會把storage配置儲存到/tmp/drill/sys.storage_plugins
中,例如windows下D:\tmp\drill\sys.storage_plugins
。
AbstractStoragePlugin
是plugin的主類,它必須配合StoragePluginConfig
,實現這個類時,建構函式必須遵循參數約定,例如:
public HttpStoragePlugin(HttpStoragePluginConfig httpConfig, DrillbitContext context, String name)
Drill啟動時會自動掃描AbstractStoragePlugin
實作類別(StoragePluginRegistry
),並建立StoragePluginConfig.class
到AbstractStoragePlugin constructor
的映射。AbstractStoragePlugin
需要實現的介面包括:
// 相應地需要實現AbstraceGroupScan // selection包含了database name和table name,可用可不用 public AbstractGroupScan getPhysicalScan(String userName, JSONOptions selection) // 註冊schema public void registerSchemas(SchemaConfig schemaConfig, SchemaPlus parent) throws IOException // StoragePluginOptimizerRule 用於最佳化Drill產生的plan,可實現也可不實現 public Set<StoragePluginOptimizerRule> getOptimizerRules()
Drill中的schema用於描述一個database,以及處理table之類的事務,必須要實現,否則任意一個SQL查詢都會被認為是找不到對應的table。AbstraceGroupScan
用於一次查詢中提供資訊,例如查詢哪些columns。
Drill在查詢時,有一種中間資料結構(基於JSON)叫Plan,其中又分為Logic Plan和Physical Plan。Logic Plan是第一層中間結構,用於完整表達一次查詢,是SQL或其他前端查詢語言轉換後的中間結構。完了後還要被轉換為Physical Plan,又稱為Exectuion Plan,這個Plan是被最佳化後的Plan,可用於與資料來源互動進行真正的查詢。StoragePluginOptimizerRule
就是用於最佳化Physical Plan的。這些Plan最終對應的結構有點類似於文法樹,畢竟SQL也可以被認為是一種程式語言。StoragePluginOptimizerRule
可以被理解為改寫這些文法樹的。例如Mongo storage plugin就實現了這個類,它會把where
中的filter轉換為mongodb自己的filter(如{‘$gt’: 2}),從而最佳化查詢。
這裡又牽扯出Apache的另一個項目:calcite,前身就是OptiQ。Drill中整個關於SQL的執行,主要是依靠這個項目。要玩轉Plan的最佳化是比較難的,也是因為文檔欠缺,相關代碼較多。
SchemaFactory
registerSchemas
主要還是調用SchemaFactory.registerSchemas
介面。Drill中的Schema是一種樹狀結構,所以可以看到registerSchemas
實際就是往parent中添加child:
public void registerSchemas(SchemaConfig schemaConfig, SchemaPlus parent) throws IOException { HttpSchema schema = new HttpSchema(schemaName); parent.add(schema.getName(), schema); }
HttpSchema
派生於AbstractSchema
,主要需要實現介面getTable
,因為我這個http storage plugin中的table實際就是傳給HTTP service的query,所以table是動態,所以getTable
的實現比較簡單:
public Table getTable(String tableName) { // table name can be any of string HttpScanSpec spec = new HttpScanSpec(tableName); // will be pass to getPhysicalScan return new DynamicDrillTable(plugin, schemaName, null, spec); }
這裡的HttpScanSpec
用於儲存查詢中的一些參數,例如這裡儲存了table name,也就是HTTP service的query,例如/e/api:search?q=avi&p=2
。它會被傳到AbstraceStoragePlugin.getPhysicalScan
中的JSONOptions
:
public AbstractGroupScan getPhysicalScan(String userName, JSONOptions selection) throws IOException { HttpScanSpec spec = selection.getListWith(new ObjectMapper(), new TypeReference<HttpScanSpec>() {}); return new HttpGroupScan(userName, httpConfig, spec); }
HttpGroupScan
後面會看到用處。
AbstractRecordReader
AbstractRecordReader
負責真正地讀取資料並返回給Drill。BatchCreator
則是用於建立AbstractRecordReader
。
public class HttpScanBatchCreator implements BatchCreator<HttpSubScan> { @Override public CloseableRecordBatch getBatch(FragmentContext context, HttpSubScan config, List<RecordBatch> children) throws ExecutionSetupException { List<RecordReader> readers = Lists.newArrayList(); readers.add(new HttpRecordReader(context, config)); return new ScanBatch(config, context, readers.iterator()); } }
既然AbstractRecordReader
負責真正讀取資料,那麼它肯定是需要知道傳給HTTP service的query的,但這個query最早是在HttpScanSpec
中,然後傳給了HttpGroupScan
,所以馬上會看到HttpGroupScan
又把參數資訊傳給了HttpSubScan
。
Drill也會自動掃描BatchCreator
的實作類別,所以這裡就不用關心HttpScanBatchCreator
的來曆了。
HttpSubScan
的實現比較簡單,主要是用來儲存HttpScanSpec
的:
public class HttpSubScan extends AbstractBase implements SubScan // 需要實現SubScan
回到HttpGroupScan
,必須實現的介面:
public SubScan getSpecificScan(int minorFragmentId) { // pass to HttpScanBatchCreator return new HttpSubScan(config, scanSpec); // 最終會被傳遞到HttpScanBatchCreator.getBatch介面 }
最終query被傳遞到HttpRecordReader
,該類需要實現的介面包括:setup
和next
,有點類似於迭代器。setup
中查詢出資料,然後next
中轉換資料給Drill。轉換給Drill時可以使用到VectorContainerWriter
和JsonReader
。這裡也就是Drill中傳說的vector資料格式,也就是列儲存資料。
總結
以上,就包含了plugin本身的建立,及查詢中query的傳遞。查詢中類似select titile, name
中的columns會被傳遞到HttpGroupScan.clone
介面,只不過我這裡並不關注。實現了這些,就可以通過Drill查詢HTTP service中的資料了。
而select * from xx where xx
中的where
filter,Drill自己會對查詢出來的資料做過濾。如果要像mongo plugin中構造mongodb的filter,則需要實現StoragePluginOptimizerRule
。
我這裡實現的HTTP storage plugin,本意是覺得傳給HTTP service的query可能會動態構建,例如:
select name, length from http.`/e/api:search` where $p=2 and $q='avi' # p=2&q=avi 就是動態構建,其值可以來源於其他查詢結果select name, length from http.`/e/api:search?q=avi&p=2` where length > 0 # 這裡就是靜態
第一條查詢就需要藉助StoragePluginOptimizerRule
,它會收集所有where中的filter,最終作為HTTP serivce的query。但這裡的實現還不完善。
總體而言,由於Drill項目相對較新,要進行擴充還是比較困難的。尤其是Plan最佳化部分。
原文地址: http://codemacro.com/2015/05/30/drill-http-plugin/
written by Kevin Lynx posted athttp://codemacro.com