本文講解php通過sql查詢hadoop中的資料。主要使用的技術是:php通過thrift向hive提交sql查詢,hive將sql查詢轉換成hadoop任務,等到hadoop執行完畢後,返回一個結果uri,然後我們只需要讀取這個uri中的內容。
Thrift的誕生是為瞭解決不同語言之間的訪問的問題,可以支援多種程式語言,如c++、php、java、python等。Thrift是由facebook開發的輕量級跨語言的服務架構,現在已經移交到apache基金會下。和它類似的有google出的protocol buffer和ice。Thrift的一大優勢是支援的語言很豐富,它使用自己的IDL語言來服務介面和資料交換的格式。
Hive是可以使用類似sql的語言訪問HBase。而HBase是一個開源的nosql產品,它實現了google bigtable論文的一個開源產品,和hadoop、hdfs一起,可以用來儲存和處理海量column family資料。
Thrift的官方網址:http://thrift.apache.org/
一.伺服器端下載安裝thrift
在hadoop和hbase都已經安裝好的叢集上安裝thrift。
(1)下載:wget http://mirror.bjtu.edu.cn/apache//thrift/0.8.0/thrift-0.8.0.tar.gz,下載thrift用戶端源碼包。
(2)解壓:tar -xzf thrift-0.8.0.tar.gz
(3)編譯安裝:如果是源碼編譯的,首先需要執行./bootstrap.sh建立./configure檔案;
接下來執行./configure;
再執行make && make install
(4)啟動:./bin/hbase-daemon.sh start thrift
Thrift預設監聽的連接埠是9090。
參考連結:http://blog.csdn.net/hguisu/article/details/7298456
二.建立.thrift檔案
Thrift編譯器安裝好之後,需要建立一個thrift檔案。該檔案為一個介面定義檔案,需要定義thrift的類型(types)和服務(services)。該檔案中定義的服務(services)是由伺服器端實現的,並由用戶端進行調用。Thrift編譯器的作用是將你寫的thrift檔案產生為用戶端介面源碼,該介面源碼是由不同的用戶端庫和你寫的服務來產生的。為了通過thrift檔案產生不同語言的介面源碼,我們需要運行:
thrift --gen <language> <Thrift filename>
三.thrift檔案描述
支援的變數類型
類型 描述 bool #true, false byte #8位的有符號整數 i16 #16位的有符號整數 i32 #32位的有符號整數 i64 #64位的有符號整數 double #64位的浮點數 string #UTF-8編碼的字串 binary #字元數組 struct #結構體 list<type> #有序的元素列表,類似於STL的vector set<type> #無序的不重複元素集,類似於STL的set map<type1,type2> #key-value型的映射,類似於STL的map exception #是一個繼承於本地語言的exception基類 service #服務包含多個函數介面(純虛函數)
四.從服務端到用戶端如何開發1.簡單的helloworld程式
這裡使用python做服務端,php做用戶端,當然也可以使用c++來做服務端。下面開始介紹開發流程。
(1)首先我們在伺服器端寫個helloworld.thrift檔案,如下所示:
service HelloWorld{ string ping(1: string name), string getpng(), }
(2)在伺服器端編譯helloworld.thrift
編譯helloworld.thrift檔案,會產生伺服器端和用戶端相應語言的介面源碼。
/usr/local/thrift/bin/thrift -r --gen py helloworld.thrift
/usr/local/thrift/bin/thrift -r --gen php helloworld.thrift
#會在目前的目錄下產生 gen-* 目錄。
產生的gen-py目錄放在伺服器上,產生的gen-php目錄放在用戶端上。
(3)編寫伺服器端代碼
import sys sys.path.append('./gen-py') from helloworld import HelloWorld from helloworld.ttypes import * from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol from thrift.server import TServer class HellowordHandler: def __init__ (self): pass def ping (self, name): print name + ' from server.' return "%s from server." % name def getpng (self): f = open("./logo.png", "rb") c = f.read() f.close() return c handler = HellowordHandler() processor = HelloWorld.Processor(handler) transport = TSocket.TServerSocket(9090) tfactory = TTransport.TBufferedTransportFactory() pfactory = TBinaryProtocol.TBinaryProtocolFactory() server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) # You could do one of these for a multithreaded server #server = TServer.TThreadedServer(processor, transport, tfactory, pfactory) #server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory) print 'Starting the server...' server.serve() print 'done.'
(4)編寫用戶端代碼
先將gen-php目錄拷貝到用戶端上。
<?php try{ //包含thrift用戶端庫檔案 $GLOBALS['THRIFT_ROOT'] = './php/src'; require_once $GLOBALS['THRIFT_ROOT'].'/Thrift.php'; require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php'; require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php'; require_once $GLOBALS['THRIFT_ROOT'].'/transport/THttpClient.php'; require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php'; error_reporting(E_NONE); //包含helloworld介面檔案 $GEN_DIR = './gen-php'; require_once $GEN_DIR.'/helloworld/HelloWorld.php'; error_reporting(E_ALL); $socket = new TSocket('*.*.*.*', 9090); $transport = new TBufferedTransport($socket, 1024, 1024); $protocol = new TBinaryProtocol($transport); $client = new HelloWorldClient($protocol); $transport->open(); $a = $client->ping('xyq '); echo $a; $transport->close(); }catch(TException $tx){ print 'TException: '.$tx->getMessage()."/n"; } ?>
最後給出一篇參考連結:http://blog.csdn.net/heiyeshuwu/article/details/5982222
2.thrift官網上給出的例子
Apache thrift能夠讓你在一個簡單的.thrift檔案中,定義資料類型和服務介面。將該.thrift檔案作為輸入檔案,通過編譯器編譯產生服務端和用戶端源碼,從而構建了RPC用戶端和伺服器端之間的跨語言編程。
下面直接給出關鍵代碼。
(1)thrift定義檔案
/*定義的介面資料類型*/struct UserProfile { 1: i32 uid, 2: string name, 3: string blurb}/*定義的介面函數*/service UserStorage { void store(1: UserProfile user), UserProfile retrieve(1: i32 uid)}
(2)用戶端python實現
# Make an object up = UserProfile(uid=1, name="Test User", blurb="Thrift is great") # Talk to a server via TCP sockets, using a binary protocol transport = TSocket.TSocket("localhost", 9090) transport.open() protocol = TBinaryProtocol.TBinaryProtocol(transport) # Use the service we already defined service = UserStorage.Client(protocol) service.store(up) # Retrieve something as well up2 = service.retrieve(2)
(3)伺服器端c++實現
class UserStorageHandler : virtual public UserStorageIf { public: UserStorageHandler() { // Your initialization goes here } void store(const UserProfile& user) { // Your implementation goes here printf("store\n"); } void retrieve(UserProfile& _return, const int32_t uid) { // Your implementation goes here printf("retrieve\n"); } }; int main(int argc, char **argv) { int port = 9090; shared_ptr<UserStorageHandler> handler(new UserStorageHandler()); shared_ptr<TProcessor> processor(new UserStorageProcessor(handler)); shared_ptr<TServerTransport> serverTransport(new TServerSocket(port)); shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory()); shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory()); TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory); server.serve();}
3.實戰經曆
(1).thrift介面檔案
檔案名稱為hive.thrift,如下所示:
namespace java com.gj.data.hive.thrift/** * 向HADOOP提交HIVE任務類。典型的用法是提交任務,輪詢任務是否完成,取得任務的結果URI,讀取結果檔案。 *這裡展示的是用戶端為java語言時的用法。 * long taskId = client.submitTask("abc@gj.com", "web", "select * from table1 where dt = '2013-04-10' limit 10;"); * if (taskId <=0) { * System.out.println("error submit"); * return; * } //輪詢任務是否完成 * int count = 50; * while(count >= 0) { * try { * Thread.sleep(30 * 1000); * } catch (InterruptedException ex) {} * if (client.isTaskFinished(taskId)) { * System.out.println(client.getResultURI(taskId)); * break; * } * count --; * } */service Hive { /** 提交任務 * user - 使用者名稱,工作郵箱,如abc@xxx.com * env - 提交的環境。目前支援兩個環境: mobile - 移動端, web - 主站。 * sql - 提交的sql語句。 * 傳回值:成功提交返回大於0的任務id值,此id用於後續查詢。失敗返回0或-1. */ i64 submitTask(1:string user, 2:string env, 3:string sql); /** 查看任務是否完成 * taskId - 任務號 * 傳回值:完成返回true,未完成返回false */ bool isTaskFinished(1:i64 taskId); /** 取得任務結果的URI,可以用此URI獲得結果資料 * taskId - 任務號 * 傳回值:任務有結果,返回URI,否則返回Null 字元串 */ string getResultURI(1:i64 taskId); /** 取得使用者的所有工作清單 * user - 使用者名稱,完整的ganji郵箱 * 傳回值:任務號列表,如果沒有任務,返回空 */ list<i64> getTasksByUserName(1:string user);}
(2).產生php與hbase的介面檔案(伺服器端實現)
/usr/local/thrift/bin/thrift --gen php hive.thrift
然後會在gen-php目錄下產生了Hive.php和Hive_types.php檔案。
然後把Hive.php和Hive_types.php檔案拷貝到:用戶端php開發的目錄下。
(3).配置php用戶端
php作為用戶端使用thrift時,需要進行如下配置。
(a)準備thrift php用戶端基礎類
這些基礎類可以從thrift的源碼包中找到。在thriftsrc/lib/php/src下,一班有如下目錄和檔案:ext/,protocol/,transport/目錄,和thrift.php、autoload.php檔案。我們把這些目錄和檔案拷貝到用戶端的/server/www/thrift_part/thrift-0.5.0/目錄下。
(b)配置php的thrift擴充,使其支援thrift
如果php想要使用thrift,還需要安裝php的thrift擴充。
如下所示:
首先下載相應的php的thrift擴充,進行解壓;
進入源碼下的ext/thrift_protocol;
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config --enable-thrift_protocol
make
make install
然後把產生的thrift_protocol.so檔案配置到php.ini並重啟apache服務。
(4).php用戶端的實現
檔案名稱為:updateHiveData.php
<?php $GLOBALS['THRIFT_ROOT'] = '/server/www/third_part/thrift-0.5.0'; require_once $GLOBALS['THRIFT_ROOT'].'/Thrift.php'; require_once $GLOBALS['THRIFT_ROOT'].'/packages/scribe/scribe.php'; require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php'; require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php'; require_once $GLOBALS['THRIFT_ROOT'].'/transport/THttpClient.php'; require_once $GLOBALS['THRIFT_ROOT'].'/transport/TFramedTransport.php'; require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php'; //產生的檔案 require_once dirname(__FILE__) . '/Hive.php'; //require_once dirname(__FILE__) .'/hive_types.php'; ERROR_REPORTING(E_ALL); INI_SET('DISPLAY_ERRORS','ON'); $socket = new TSocket('hive.corp.gj.com',13080); $socket->setDebug(TRUE); // 設定接收逾時(毫秒) $socket->setSendTimeout(10000); $socket->setRecvTimeout(10000); //$transport = new TBufferedTransport($socket, 1024, 1024); $transport = new TFramedTransport($socket); $protocol = new TBinaryProtocol($transport); $client = new HiveClient($protocol); try{ $transport->open(); }catch(TException $tx){ echo $tx->getMessage(); } //擷取各個類目某一天的 PV UV $taskId = $client->submitTask('xxx@xxx.com','web',"select regexp_extract(gjch, '^/([^/]+)', 1), count(*), count(distinct uuid) from table1 where dt = '2013-04-22' and gjch regexp '[^@]*/detail' GROUP BY regexp_extract(gjch, '^/([^/]+)', 1);"); if($taskId <= 0){echo 'error submit'; exit; } echo $taskId . "\n"; $count = 50; while($count > 0){ try{ //sleep以秒為單位,這裡3分鐘輪詢一次 sleep(3*60); }catch(TException $tx){}if($client->isTaskFinished($taskId)){ //echo $client->getResultURI($taskId); $url = $client->getResultURI($taskId); //echo $url; $handle = fopen($url,"rb"); $content = stream_get_contents($handle); echo $content; fclose($handle); break; } $count--; } $transport->close();?>
由於伺服器端不是本人負責,所以當時只根據thrift定義檔案,實現了php用戶端。運行結果如下:
其中這裡url是$client->getResultURI取得的結果,網頁內容是這個uri對應的內容。
五.thrift類說明
TSocket:採用TCP socket進行資料轉送;
Transport類(傳輸層):
負責資料轉送,介紹幾個常用類:
TBufferedTransport:對某個Transport對象操作的資料進行buffer,即從buffer中讀取資料進行傳輸,或者將資料直接寫入buffer;
TFramedTransport:同TBufferdTransport類型,也會對資料進行buffer,同時它支援定長資料發送和接受;
TFileTransport:檔案(日誌)傳輸類,允許client將檔案傳給server,語序server將受到的資料寫到檔案中;
Protocol類(協議):
負責資料編碼,主要有以下幾個常用類:
TBinaryProtocol:二進位編碼;
TJSONProtocol:JSON編碼。