2010年1月8日 星期五

[Linux] 安裝 HadoopDB 於 Hadoop 0.19.2 @ Ubuntu 9.10

+ =HadoopDB
圖片來源:http://hadoop.apache.org/http://wiki.postgresql.org/


雖然,上頭圖片的結合並不代表 HadoopDB 的原意,但 HadoopDB 開發上使用了 PostgreSQL 作為範例。那什麼是 HadoopDB 呢?那就是提出並實作在 DataNode 上架設 Database 的模式,以此整合起來的一個服務架構。傳統 Databases 技術上的鑽研已不是短短幾年可以道盡,然而,目前火紅的 Hadoop ,在資料儲存上預設是採用 HDFS 模式,就是簡單的檔案儲存模式。如果把資料的管理擺在資料庫,那能不能將過去在資料庫上的效能帶入 Hadoop 裡呢?這就是 HadoopDB 想嘗試的方向。HadoopDB:An architectural hybrid of MapReduce and DBMS technologies,也有人用這樣來描述 HadoopDB:An Open Source Parallel Database



圖片來源:http://hadoopdb.sourceforge.net/guide/


上圖是 HadoopDB 的架構圖,有一個 SMS Planner 將 SQL 語法轉成 MapReduce Jobs ,而底層又有一次轉換,將 MapReduce 存取轉成 SQL 語法,在此不多探討,有興趣的請直接看它的 Paper 囉。


由於它是在 Hadoop-0.19.x 開發的,因此我還是用 Hadoop-0.19.2 來架設,至於架設部分可以參考這篇:[Linux] 安裝 Hadoop 0.20.1 Multi-Node Cluster @ Ubuntu 9.10,其中 0.19.2 與 0.20.1 安裝上只有些微的差別,在上述文章中的 hadoop-0.20.1/conf/core-site.xml 與 hadoop-0.20.1/conf/mapred-site.xml 
的內容,只需改寫在 hadoop-0.19.2/conf/hadoop-site.xml 即可。接著下面的介紹也將延續上則安裝教學,以 3-Node Cluster ,分別以 Cluster01、Cluster02 和 Cluster03 作為範例敘述,並且各台使用 hadoop 帳號來操作。



  1. 首先需建立 3-Node Cluster on Hadoop 0.19.x



  2. 以下若是用 hadoop@Cluster0X:~ 代表 Cluster01 ~ Cluster03 都要做的

  3. 對各台安裝設定 PostgreSQL

    • 安裝並為資料庫建立 hadoop 帳號,假定使用密碼為 1234

    • hadoop@Cluster0X:~$ sudo apt-get install postgresql

    • hadoop@Cluster0X:~$ sudo vim /etc/postgresql/8.4/main/pg_hba.conf

      • #local   all         all                               ident
        local   all         all                               trust
        # IPv4 local connections:
        #host    all         all         127.0.0.1/32          md5
        host    all         all         127.0.0.1/32          password
        host    all         all         192.168.0.1/16          password            # 加上Cluster 機器 IP 範圍
        # IPv6 local connections:
        #host    all         all         ::1/128               md5
        host    all         all         ::1/128               password



    • hadoop@Cluster0X:~$ sudo /etc/init.d/postgresql-8.4 restart

    • hadoop@Cluster0X:~$ sudo su - postgres

    • postgres@Cluster0X:~$ createuser hadoop

      • Shall the new role be a superuser? (y/n) y
        postgres@Cluster01:~$ psql
        psql (8.4.2)
        Type "help" for help.

        postgres=# alter user hadoop with password '1234';
        ALTER ROLE
        postgres=# \q



    • 測試其他機器可否連線

      • hadoop@Cluster01:~$ createdb testdb

      • hadoop@Cluster02:~$ psql -h Cluster01 testdb

        • 錯誤訊息

          • psql: FATAL:  no pg_hba.conf entry for host "192.168.56.168", user "hadoop", database "testdb", SSL on
            FATAL:  no pg_hba.conf entry for host "192.168.56.168", user "hadoop", database "testdb", SSL off



        • 正確訊息

          • Password:
            psql (8.4.2)
            SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
            Type "help" for help.

            testdb=#









  4. 設定 HadoopDB



    • hadoop@Cluster0X:~$ cp hadoopdb.jar HADOOP_HOME/lib/

    • hadoop@Cluster0X:~$ cp postgresql-8.4-701.jdbc4.jar HADOOP_HOME/lib/

    • hadoop@Cluster0X:~$ vim HADOOP_HOME/conf/hadoop-site.xml


      • <property>
        <name>hadoopdb.config.file</name>
        <value>HadoopDB.xml</value>
        <description>The name of the HadoopDB cluster configuration file</description>
        </property>



        <property>
        <name>hadoopdb.fetch.size</name>
        <value>1000</value>
        <description>The number of records fetched from JDBC ResultSet at once</description>
        </property>


        <property>
        <name>hadoopdb.config.replication</name>
        <value>false</value>
        <description>Tells HadoopDB Catalog whether replication is enabled.
        Replica locations need to be specified in the catalog.
        False causes replica information to be ignored.</description>
        </property>



    • hadoop@Cluster01:~$ vim nodes.txt

      • 192.168.56.168
        192.168.56.169
        192.168.56.170



    • hadoop@Cluster01:~$ vim Catalog.properties

      • #Properties for Catalog Generation
        ##################################
        nodes_file=nodes.txt
        # Relations Name and Table Name are the same
        relations_unchunked=raw
        relations_chunked=poi
        catalog_file=HadoopDB.xml
        ##
        #DB Connection Parameters
        ##
        port=5432
        username=hadoop
        password=1234
        driver=org.postgresql.Driver
        url_prefix=jdbc\:postgresql\://
        ##
        #Chunking properties
        ##
        # the number of databases on a node
        chunks_per_node=3
        # for udb0 ,udb1, udb2 ( 3 nodes = 0 ~ 2 )
        unchunked_db_prefix=udb
        # for cdb0 ,cdb1, ... , cdb8 ( 3 nodes x 3 chunks = 0~8 )
        chunked_db_prefix=cdb
        ##
        #Replication Properties
        ##
        dump_script_prefix=/root/dump_
        replication_script_prefix=/root/load_replica_
        dump_file_u_prefix=/mnt/dump_udb
        dump_file_c_prefix=/mnt/dump_cdb
        ##
        #Cluster Connection
        ##
        ssh_key=id_rsa-gsg-keypair



    • hadoop@Cluster01:~$ java -cp lib/hadoopdb.jar edu.yale.cs.hadoopdb.catalog.SimpleCatalogGenerator Catalog.properties

      • 產生的 HadoopDB.xml 類似下面:
        <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        <DBClusterConfiguration xmlns="http://edu.yale.cs.db.hadoop/DBConfigurationSchema">
            <Nodes Password="1234" Username="hadoop" Driver="org.postgresql.Driver" Location="192.168.56.168">
                <Relations id="raw">
                    <Partitions url="jdbc:postgresql://192.168.56.168:5432/udb0" id="0"/>
                </Relations>
                <Relations id="poi">
                    <Partitions url="jdbc:postgresql://192.168.56.168:5432/cdb0" id="0"/>
                    <Partitions url="jdbc:postgresql://192.168.56.168:5432/cdb1" id="1"/>
                    <Partitions url="jdbc:postgresql://192.168.56.168:5432/cdb2" id="2"/>
                </Relations>
            </Nodes>
            <Nodes Password="1234" Username="hadoop" Driver="org.postgresql.Driver" Location="192.168.56.169">
                <Relations id="raw">
                    <Partitions url="jdbc:postgresql://192.168.56.169:5432/udb1" id="1"/>
                </Relations>
                <Relations id="poi">
                    <Partitions url="jdbc:postgresql://192.168.56.169:5432/cdb3" id="3"/>
                    <Partitions url="jdbc:postgresql://192.168.56.169:5432/cdb4" id="4"/>
                    <Partitions url="jdbc:postgresql://192.168.56.169:5432/cdb5" id="5"/>
                </Relations>
            </Nodes>
            <Nodes Password="1234" Username="hadoop" Driver="org.postgresql.Driver" Location="192.168.56.170">
                <Relations id="raw">
                    <Partitions url="jdbc:postgresql://192.168.56.170:5432/udb2" id="2"/>
                </Relations>
                <Relations id="poi">
                    <Partitions url="jdbc:postgresql://192.168.56.170:5432/cdb6" id="6"/>
                    <Partitions url="jdbc:postgresql://192.168.56.170:5432/cdb7" id="7"/>
                    <Partitions url="jdbc:postgresql://192.168.56.170:5432/cdb8" id="8"/>
                </Relations>
            </Nodes>
        </DBClusterConfiguration>



    • hadoop@Cluster01:~$ hadoop dfs -put HadoopDB.xml HadoopDB.xml



  5. 建立資料表、測試資料匯入各台機器的資料庫中,並且在 Hive 上建立相對應的資料表

    • 在此以 raw 這個 talbe 當作範例。假設 HadoopDB.xml 對 raw 這個 table 敘述有 3 個,即上述範例的 udb0 、udb1 和 udb2,那就要分別去上頭指定的機器上建立資料庫

      • hadoop@Cluster01:~$ createdb udb0
        hadoop@Cluster02:~$ createdb udb1
        hadoop@Cluster03:~$ createdb udb2



    • 並且依輸入的資料建立資料表

      • hadoop@Cluster01:~$ psql udb0
        udb0=#
        CREATE TABLE raw (
        ID int,
        NAME varchar(300)
        );

      • 同理如 Cluster02 跟 Cluster03



    • 資料匯入

      • hadoop@Cluster01:~$ psql udb0
        udb0=# COPY RAW FROM '/home/hadoop/p0' WITH DELIMITER E'\t' ;

      • 關於 /home/hadoop/p0 的資料主要從原本依開始的大檔案,使用 HadoopDB 所提供的切割工具處理的

        • $ hadoop jar lib/hadoopdb.jar edu.yale.cs.hadoopdb.dataloader.GlobalHasher src_in_hdfs out_in_hdfs 3 '\n' 0

        • $ hadoop fs -get out_in_hdfs/part-00000 /home/hadoop/p0



      • 假設資料擺在 /home/haddop/p0 並且欄位以 tab 分隔

      • 同理也要處理 Cluster02 跟 Cluster03



    • 最後,在 Hive 上頭建立相對應的資料表 (只需用一台機器執行)


      • 假設 Hive 使用的資料表將儲存在 HDFS 的 /db

      • hadoop@Cluster01:~ $ hadoop dfs -mkdir /db

      • hadoop@Cluster01:~ $ SMS_dist/bin/hive
        CREATE EXTERNAL TABLE raw (
        ID int,
        NAME string
        )
        ROW FORMAT DELIMITED
        FIELDS TERMINATED BY '|'
        STORED AS
        INPUTFORMAT 'edu.yale.cs.hadoopdb.sms.connector.SMSInputFormat'
        OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
        LOCATION '/db/raw';

      • 其中 /db/raw 的 basename 要跟 table 名稱一樣(各台資料庫裡的資料表與Hive建立的資料表),另外對於資料欄位的型別也別忘了轉換囉





  6. 以上設定完後,即可在一台機器上(Ex: Cluster1) 執行 $ SMS_dist/bin/hive 看看成果

    • hadoop@Cluster01:~ $ SMS_dist/bin/hive
      hive> show tables;
      hive> select name from raw;




上述是採用 unchunked 當作範例,如果改用 chunked 模式,假設有三台機器,並且打算使用三個資料庫,那設定上就會變成 3 x 3 的數量,也就是共有 9 個資料庫要設定,包括建立資料庫、建立資料表,匯入資料等等的,因此實作上應該寫隻 script 處理,在 HadoopDB 網站上有一個建立 /my_data 目錄範例,我把它稍微改寫:


#!/usr/bin/python
import sys, os, thread, commands
import getopt  
       
DEBUG_MODE = True
                       
completed = {} 
create_db_cmd_list = [ ''
        , 'createdb testdb'
        , 'echo "CREATE TABLE Helo ( ID int );" | psql testdb'
        , 'dropdb testdb'
]              
                       
cmd_list = []
cmd_list.extend( create_db_cmd_list )

def ParseHadoopXML( file_path ) :
        return 
               
def executeThread(node, *args ):
        #Make sure key is accessible and is the correct key name.
        #os.system("ssh -i key -o 'StrictHostKeyChecking=no' %s \'mkdir /my_data \'" %(node))
        #You could replace the mkdir command with another command line or add more command lines,
        # as long as you prefix the command with the ssh connection.

        if DEBUG_MODE :
                print "\tShow Only"
        for cmd in cmd_list :
                if cmd == None or cmd == '' :
                        continue;
                cmd = cmd.strip()
                if cmd == '' :
                        continue;
                cmd_exec = "ssh %s \'%s\'" % (node , cmd )
                print "\t" , cmd_exec
                if DEBUG_MODE == False :
                        os.system( cmd_exec )
        completed[node] = "true"

def main( argv=None ):
        hostfile = "nodes.txt"
        internalips = open(hostfile,'r').readlines()

        for i in internalips:
                os.system('sleep 1')

                node_info = i.strip() ,
                thread.start_new_thread(executeThread, node_info  )

        while (len(completed.keys()) < len(internalips)):
                os.system('sleep 2')

        print "Execution Completed"

if __name__ == "__main__":
        main()


最後,我則是來個大改寫,若要使用十分建議先在用在虛擬環境,看看流程對不對,當然,最好是自己先手動設定過,等流程清楚後再設計符合自己的需求。使用上要先準備的資料:



  1. 將原始擺在 HDFS ,預設資料以 new line 分格,每筆資料以 Tab 分格欄位,例如:

    • 1    changyy
      2    hello world
      3    hadoop



  2. 在本機端建立 nodes.txt ,裡頭敘述 Cluster 各台機器的 IP,用 New Line('\n') 符號分格,例如:

    • 192.168.56.168
      192.168.56.169
      192.168.56.170



  3. 建立資料表的欄位敘述,預設此用 table_create 檔案,例如:

    • ID int ,
      NAME varchar(250)



  4. 最後,可以透過 $python this.py -help 查看有什麼可以設定,只不過是用很破的英文描述


純粹 show 出將會執行的指令,請留意它將會刪除哪些目錄、資料庫等等
$ python this.py --source_dir_in_hdfs src

真正運行
$ python this.py --source_dir_in_hdfs src -go

預設是 unchunked ,若想設定可以用 --chunk_num 設定
$ python this.py --source_dir_in_hdfs src --chunk_num 3


實際運行例子:




  • hadoop@Cluster01:~$ cat nodes.txt
    192.168.56.168
    192.168.56.169
    192.168.56.170

  • hadoop@Cluster01:~$ cat table_create
    ID int,
    NAME varchar(250)

  • hadoop@Cluster01:~$ python batch_setup.py --source_dir_in_hdfs src

     Current Status is just Debug Mode for show all commands
     please set '-g' or '--go' option to execute them after check all commands.(look at the 'rm -rf' and 'hadoop fs -rmr')

    $ /usr/bin/java -cp /home/hadoop/lib/hadoopdb.jar edu.yale.cs.hadoopdb.catalog.SimpleCatalogGenerator /tmp/Catalog.properties

    => Start to put the HadoopDB.xml into HDFS

    $ /home/hadoop/bin/hadoop fs -rmr HadoopDB.xml
    $ /home/hadoop/bin/hadoop fs -put HadoopDB.xml HadoopDB.xml

    => The data source(src) would be partitioned into 3 parts(tmp_out_hadoopdb) by the delimiter (\n)

    $ /home/hadoop/bin/hadoop fs -rmr tmp_out_hadoopdb
    $ /home/hadoop/bin/hadoop jar /home/hadoop/lib/hadoopdb.jar edu.yale.cs.hadoopdb.dataloader.GlobalHasher src tmp_out_hadoopdb 3 '\n' 0

    => To configure your nodes...

        ssh 192.168.56.168 "dropdb udb_hadoopdb_0"
        ssh 192.168.56.168 "createdb udb_hadoopdb_0"
        ssh 192.168.56.168 "echo \"create table hadoopdb ( id int, name varchar(250) );\" | psql udb_hadoopdb_0"
        ssh 192.168.56.168 "rm -rf /tmp/out_for_global_parition"
        ssh 192.168.56.168 "/home/hadoop/bin/hadoop fs -get tmp_out_hadoopdb/part-00000 /tmp/out_for_global_parition"
        ssh 192.168.56.168 "echo \"COPY hadoopdb FROM '/tmp/out_for_global_parition' WITH DELIMITER E'\t';\" | psql udb_hadoopdb_0"
        ssh 192.168.56.168 "rm -rf /tmp/out_for_global_parition"
        ssh 192.168.56.170 "dropdb udb_hadoopdb_2"
        ssh 192.168.56.170 "createdb udb_hadoopdb_2"
        ssh 192.168.56.170 "echo \"create table hadoopdb ( id int, name varchar(250) );\" | psql udb_hadoopdb_2"
        ssh 192.168.56.170 "rm -rf /tmp/out_for_global_parition"
        ssh 192.168.56.170 "/home/hadoop/bin/hadoop fs -get tmp_out_hadoopdb/part-00002 /tmp/out_for_global_parition"
        ssh 192.168.56.170 "echo \"COPY hadoopdb FROM '/tmp/out_for_global_parition' WITH DELIMITER E'\t';\" | psql udb_hadoopdb_2"
        ssh 192.168.56.170 "rm -rf /tmp/out_for_global_parition"
        ssh 192.168.56.169 "dropdb udb_hadoopdb_1"
        ssh 192.168.56.169 "createdb udb_hadoopdb_1"
        ssh 192.168.56.169 "echo \"create table hadoopdb ( id int, name varchar(250) );\" | psql udb_hadoopdb_1"
        ssh 192.168.56.169 "rm -rf /tmp/out_for_global_parition"
        ssh 192.168.56.169 "/home/hadoop/bin/hadoop fs -get tmp_out_hadoopdb/part-00001 /tmp/out_for_global_parition"
        ssh 192.168.56.169 "echo \"COPY hadoopdb FROM '/tmp/out_for_global_parition' WITH DELIMITER E'\t';\" | psql udb_hadoopdb_1"
        ssh 192.168.56.169 "rm -rf /tmp/out_for_global_parition"
    $ /home/hadoop/bin/hadoop fs -rmr tmp_out_hadoopdb

    => To setup the external table for Hive

    $ /home/hadoop/bin/hadoop fs -mkdir /db
    $ /home/hadoop/bin/hadoop fs -rmr /db/hadoopdb
    $  echo "drop table hadoopdb;" | /home/hadoop/SMS_dist/bin/hive
    $  echo "create external table hadoopdb ( id int, name string )  ROW FORMAT DELIMITED FIELDS TERMINATED BY '|' STORED AS  INPUTFORMAT 'edu.yale.cs.hadoopdb.sms.connector.SMSInputFormat'  OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'  LOCATION '/db/hadoopdb'; " | /home/hadoop/SMS_dist/bin/hive


    => All Execution Completed...



#!/usr/bin/python
# At Python 2.6.4
# Yuan-Yi Chang
# 2010/01/07 15:09
#
import sys, os, thread, commands
import re, os.path
from optparse import OptionParser

BIN_JAVA = '/usr/bin/java'
BIN_HADOOP = '/home/hadoop/bin/hadoop'
BIN_HIVE = '/home/hadoop/SMS_dist/bin/hive'
JAR_HADOOPDB = '/home/hadoop/lib/hadoopdb.jar'

completed = {}
cmd_for_node = {}

def initHadoopDB(    data_in_hdfs = None , data_delimiter = '\n' , data_field_delimiter = '\t' ,
            data_partition_out = None ,
            nodes_in_file = 'nodes.txt' , chunks_per_node = 3 ,
            table_name = None , table_field_info = None ,
            db_user = 'hadoop' , db_pass='1234' , db_field_delimiter = '|' , hive_db_dir_in_hdfs = '/db' ,
  
         tmp_path_for_catelog = '/tmp/Catalog.properties' ,
out_hadoop_xml = 'HadoopDB.xml' , hadoop_xml_in_hdfs = 'HadoopDB.xml' ,
            DEBUG_MODE = True ) :
    if data_in_hdfs is None :
        print 'Please input the path of the data source in HDFS'
        return False
    if data_partition_out is None :
        print 'Please input the path for the data source parition in HDFS'
        return False
    if table_name is None or re.match( '/[a-z0-9_]+/'  , table_name ) :
        print 'Please input the table name with [a-z0-9_] only'
        return False
    if table_field_info is None or os.path.isfile( table_field_info ) is False :
        print 'Please check the "table_field_info" : ' + str(table_field_info)
        return False
        
    if os.path.isfile( nodes_in_file ) is False :
        print 'Please check the "nodes_in_file" : ' + nodes_in_file
        return False
    if chunks_per_node < 0 :
        print 'Please check the "chunks_per_node" : ' + chunks_per_node + ' , 0 for no chunk'
        return False

    data_delimiter = data_delimiter.replace( '\n' , '\\n' ).replace( '\t' , '\\t' )
    data_field_delimiter = data_field_delimiter.replace( '\t' , '\\t' ).replace( '\n' , '\\n' )
    db_field_delimiter = db_field_delimiter.replace( '\t' , '\\t' ).replace( '\n' , '\\n' )

    make_catelog = ''
    #Properties for Catalog Generation'
    ##################################
    make_catelog += 'nodes_file='+nodes_in_file+'\n'
    if chunks_per_node < 2 :
        make_catelog += 'relations_chunked=no_use' + '\n'
        make_catelog += 'relations_unchunked='+table_name + '\n'
    else:
        make_catelog += 'relations_unchunked=' + 'no_use' + '\n'
        make_catelog += 'relations_chunked='+table_name + '\n'
    make_catelog += 'catalog_file=' + out_hadoop_xml + '\n'
    ##
    #DB Connection Parameters
    ##
    make_catelog += 'port=5432' + '\n'
    make_catelog += 'username=' + db_user + '\n'
    make_catelog += 'password=' + db_pass + '\n'
    make_catelog += 'driver=org.postgresql.Driver' + '\n'
    make_catelog += 'url_prefix=jdbc\\:postgresql\\://'+ '\n'
    ##
    #Chunking properties
    ##
    make_catelog += 'chunks_per_node=' + str(chunks_per_node) + '\n'
    make_catelog += 'unchunked_db_prefix=udb_' + table_name + '_' + '\n'
    make_catelog += 'chunked_db_prefix=cdb_'+ table_name + '_' + '\n'
    ##
    #Replication Properties
    ##
    make_catelog += 'dump_script_prefix=/root/dump' + '\n'
    make_catelog += 'replication_script_prefix=/root/load_replica_' + '\n'
    make_catelog += 'dump_file_u_prefix=/mnt/dump_udb' + '\n'
    make_catelog += 'dump_file_c_prefix=/mnt/dump_cdb'+ '\n'
    ##
    #Cluster Connection
    ##
    make_catelog += 'ssh_key=id_rsa-gsg-keypair' + '\n'
    
    try:
        f = open( tmp_path_for_catelog , 'w' )
        f.write( make_catelog )
        f.close()
    except:
        print 'Error to write a catelog:'+tmp_path_for_catelog
        return False
  
 cmd_exec = BIN_JAVA + ' -cp ' + JAR_HADOOPDB + '
edu.yale.cs.hadoopdb.catalog.SimpleCatalogGenerator ' +
tmp_path_for_catelog
    if DEBUG_MODE :
        print '$ ' + cmd_exec
    else:
        os.system( cmd_exec )

    if os.path.isfile( out_hadoop_xml ) is False :
        print 'Please check the "out_hadoop_xml" : ' + out_hadoop_xml
        return False

    print '\n=> Start to put the HadoopDB.xml into HDFS\n'

    if DEBUG_MODE :
        print '$ ' + BIN_HADOOP + ' fs -rmr ' + hadoop_xml_in_hdfs
        print '$ ' + BIN_HADOOP + ' fs -put ' + out_hadoop_xml + ' ' + hadoop_xml_in_hdfs
    else:
        os.system( BIN_HADOOP + ' fs -rmr ' + hadoop_xml_in_hdfs )
        os.system( BIN_HADOOP + ' fs -put ' + out_hadoop_xml + ' ' + hadoop_xml_in_hdfs  )

    partition_num = 0
    node_list = []
    try:
        tmp_list = open( nodes_in_file ,'r').readlines()
        for line in tmp_list :
            line = line.strip()
            if line <> '' :
                node_list.append( line )
        partition_num = len( node_list )
    except:
        print 'Please check the "nodes_in_file" : ' + nodes_in_file
        return False

    if partition_num > 1 :
  
     cmd_exec = BIN_HADOOP + ' jar ' + JAR_HADOOPDB + '
edu.yale.cs.hadoopdb.dataloader.GlobalHasher ' + data_in_hdfs + ' ' +
data_partition_out + ' ' + str(partition_num) + ' \'' + data_delimiter
+ '\' 0 '

        print '\n=> The data
source('+data_in_hdfs+') would be partitioned into
'+str(partition_num)+' parts('+data_partition_out+') by the delimiter
('+data_delimiter+')\n'
        if DEBUG_MODE :
            print '$ ' + BIN_HADOOP + ' fs -rmr ' + data_partition_out
            print '$ ' + cmd_exec
        else:
            os.system( BIN_HADOOP + ' fs -rmr ' + data_partition_out )
            os.system( cmd_exec )
    else:
        print '\n=> The number of datanodes should be > 1\n'
        return False

    HadoopDB_Info = ''
    try:
        HadoopDB_Info = open( out_hadoop_xml , 'r' ).read()
    except:
        print 'Error at read "out_hadoop_xml" : ' + out_hadoop_xml
        return False
    if HadoopDB_Info is '' :
        print 'The info in the file is empty : ' + HadoopDB_Info
        return False

    DB_TABLE_CREATE_INFO = ''
    try:
        DB_TABLE_CREATE_INFO = open( table_field_info , 'r' ).read().strip()
    except:
        print 'Error at read "table_field_info" : ' + table_field_info
        return False

    if DB_TABLE_CREATE_INFO is '' :
        print 'The info in the file is empty : ' + DB_TABLE_CREATE_INFO
        return False
    DB_TABLE_CREATE_INFO = DB_TABLE_CREATE_INFO.replace( "\n" , ' ' ).replace( '"' , '\\"' ).lower()
    DB_TABLE_CREATE_INFO = 'create table ' + table_name + ' ( ' + DB_TABLE_CREATE_INFO + ' );'

    #print node_list
    partition_index = 0
    for node in node_list:
        cmd_for_node[ node ] = []
        if chunks_per_node is 0 :    # use unchunked mode
            db_list = re.findall( '' + node +':[\d]+/(udb_' + table_name + '_'+'[\w]+)' , HadoopDB_Info )
            for sub_db in db_list :
                # Create Database & Table
                cmd_for_node[ node ].append( 'dropdb ' + sub_db )
                cmd_for_node[ node ].append( 'createdb ' + sub_db )
                cmd_for_node[ node ].append( 'echo "'+DB_TABLE_CREATE_INFO+'" | psql '+ sub_db )
                cmd_for_node[ node ].append( 'rm -rf /tmp/out_for_global_parition' )
  
             cmd_for_node[ node ].append( BIN_HADOOP + ' fs -get ' +
data_partition_out + '/part-%0.5d /tmp/out_for_global_parition' %
partition_index )
                cmd_for_node[ node ].append( 'echo
"COPY '+table_name+' FROM \'/tmp/out_for_global_parition\' WITH
DELIMITER E\''+data_field_delimiter+'\';" | psql '+ sub_db )
                cmd_for_node[ node ].append( 'rm -rf /tmp/out_for_global_parition' )
        else:
            db_list = re.findall( '' + node +':[\d]+/(cdb_' + table_name + '_'+'[\w]+)' , HadoopDB_Info )
            if db_list <> None :
                cmd_for_node[ node ].append( 'rm -rf /tmp/*out_for_global_parition' )
  
             cmd_for_node[ node ].append( BIN_HADOOP + ' fs -get ' +
data_partition_out + '/part-%0.5d /tmp/out_for_global_parition' %
partition_index )
                cmd_for_node[ node ].append( 'cd
/tmp; ' + BIN_JAVA + ' -cp ' + JAR_HADOOPDB + '
edu.yale.cs.hadoopdb.dataloader.LocalHasher out_for_global_parition ' +
str( chunks_per_node ) + ' \'' + data_delimiter + '\' 0 ' )
                sub_part = 0
                for sub_db in db_list :
                    # Create Database & Table
                    cmd_for_node[ node ].append( 'dropdb ' + sub_db )
                    cmd_for_node[ node ].append( 'createdb ' + sub_db )
                    cmd_for_node[ node ].append( 'echo "'+DB_TABLE_CREATE_INFO+'" | psql '+ sub_db )
  
                 cmd_for_node[ node ].append( 'echo "COPY
'+table_name+' FROM \'/tmp/'+str(sub_part)+'-out_for_global_parition\'
WITH DELIMITER E\''+data_field_delimiter+'\';" | psql '+ sub_db )

                    sub_part = sub_part + 1
                    #cmd_for_node[ node ].append( 'rm -rf /tmp/'+str(sub_part)+'-out_for_global_parition' )
                
                cmd_for_node[ node ].append( 'rm -rf /tmp/*out_for_global_parition' )

        partition_index = partition_index + 1
    
    print '\n=> To configure your nodes...\n'

    for node in node_list:
        thread.start_new_thread( executeThreadForNode , ( node, DEBUG_MODE ) )

    while (len(completed.keys()) < len(node_list) ) :
        os.system('sleep 2')

    if DEBUG_MODE :
        print '$ ' + BIN_HADOOP + ' fs -rmr ' + data_partition_out
    else:
        os.system( BIN_HADOOP + ' fs -rmr ' + data_partition_out )

    print '\n=> To setup the external table for Hive\n'
    if DEBUG_MODE :
        print '$ ' + BIN_HADOOP + ' fs -mkdir ' + hive_db_dir_in_hdfs
        print '$ ' + BIN_HADOOP + ' fs -rmr ' + hive_db_dir_in_hdfs + '/' + table_name
    else:
        os.system( BIN_HADOOP + ' fs -mkdir ' + hive_db_dir_in_hdfs )
        os.system( BIN_HADOOP + ' fs -rmr ' + hive_db_dir_in_hdfs + '/' + table_name  )
    
    cmd_exec = ' echo "drop table '+table_name+';" | ' + BIN_HIVE
    if DEBUG_MODE :
        print '$ ' + cmd_exec
    else:
        os.system( cmd_exec )

    create_hive_external_table =     ' ROW FORMAT DELIMITED FIELDS TERMINATED BY \'' + db_field_delimiter + '\''
    create_hive_external_table +=    ' STORED AS '
    create_hive_external_table +=    ' INPUTFORMAT \'edu.yale.cs.hadoopdb.sms.connector.SMSInputFormat\' '
    create_hive_external_table +=    ' OUTPUTFORMAT \'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat\' '
    create_hive_external_table +=    ' LOCATION \'' + hive_db_dir_in_hdfs + '/' + table_name + '\'; '

  
 DB_TABLE_CREATE_INFO = DB_TABLE_CREATE_INFO.replace( ";" , ' '
).replace( 'precision' , '' ).replace( 'create table' , 'create
external table' )
    DB_TABLE_CREATE_INFO = re.sub( 'varchar\([\d]+\)|text' , 'string' , DB_TABLE_CREATE_INFO )
    create_hive_external_table = DB_TABLE_CREATE_INFO + create_hive_external_table

    cmd_exec = ' echo "'+create_hive_external_table+'" | ' + BIN_HIVE
    if DEBUG_MODE :
        print '$ ' + cmd_exec
    else:
        os.system( cmd_exec )

def executeThreadForNode(node,DEBUG_MODE=True, *args ):
    for cmd in cmd_for_node[node] :
        if cmd == None or cmd == '' :
            continue;
        cmd = cmd.strip()
        if cmd == '' :
            continue;
        cmd = cmd.replace( '"' , '\\"' )
        cmd_exec = "ssh %s \"%s\"" % (node , cmd )
        print "\t" , cmd_exec
        if DEBUG_MODE == False :
            os.system( cmd_exec )
    completed[node] = "true"

def main( argv=None ):

    parser = OptionParser()
  
 parser.add_option( "-H" , "--source_dir_in_hdfs" ,
dest="source_dir_in_hdfs" , default=None, help="dir for data source in
HDFS" )
    parser.add_option( "-D" , "--source_data_delimiter" ,
dest="source_data_delimiter" , default='\n' , help="record delimtier
for the source" )
    parser.add_option( "-F" ,
"--source_field_delimiter" , dest="source_field_delimiter" ,
default='\t' , help="field delimiter for a record" )
  
 parser.add_option( "-P" , "--source_partition_dir" ,
dest="source_partition_dir" , default="tmp_out_hadoopdb" , help="temp
dir in HDFS for source partition" )
    parser.add_option( "-N"
, "--node_list_file" , dest="node_list_file" , default="nodes.txt" ,
help="path for a file saved each node's IP address" )
    parser.add_option( "-c" , "--chunk_num" , dest="chunk_num" , default=0 , help="number of databases for each node" )
  
 parser.add_option( "-t" , "--table_name" , dest="table_name" ,
default="hadoopdb" , help="table name for creation on Hive and
databases" )
    parser.add_option( "-i" , "--table_field_info_file"
, dest="table_field_info_file" , default="table_create", help="file for
table field definition only" )
    parser.add_option( "-u" ,
"--db_username" , dest="db_username" , default="hadoop" ,
help="username for login the databases on each node" )
  
 parser.add_option( "-p" , "--db_password" , dest="db_password" ,
default="1234" , help="password for login the databases on each node" )
  
 parser.add_option( "-d" , "--db_field_delimiter" ,
dest="db_field_delimiter" , default="|" , help="field delimiter for the
databases" )
    parser.add_option( "-w" , "--hive_db_dir" ,
dest="hive_db_dir" , default='/db' , help="path in HDFS for Hive to
save the tables" )
    parser.add_option( "-f" , "--catalog_properties"
, dest="catalog_properties" , default='/tmp/Catalog.properties' ,
help="output file for Catalog.Properties" )
    parser.add_option(
"-x" , "--hadoopdb_xml" , dest="hadoopdb_xml" , default="HadoopDB.xml"
, help="output file for HadoopDB.xml" )
    parser.add_option( "-y"
, "--hadoopdb_xml_in_hdfs" , dest="hadoopdb_xml_in_hdfs" ,
default="HadoopDB.xml" , help="filename for HadoopDB.xml in HDFS" )
  
 parser.add_option( "-g" , "--go" , action="store_false" , dest="mode"
, default=True , help="set it to execute the commands" )

    ( options, args ) = parser.parse_args()
    #print options
    #return    

  
 #initHadoopDB(    data_in_hdfs='src' , data_partition_out='tmp_out' ,
table_name='justtest' , table_field_info='table_create'  )

    if options.source_dir_in_hdfs is None :
        print "Please input the source dir in HDFS by '--source_dir_in_hdfs' "
        return
    if os.path.isfile( options.node_list_file ) is False :
        print "Please check the '" + options.node_list_file + "' path and setup by '--node_list_file'"

    if options.mode is True :
  
     print "\n Current Status is just Debug Mode for show all
commands\n please set '-g' or '--go' option to execute them after check
all commands.(look at the 'rm -rf' and 'hadoop fs -rmr')\n"
    
  
 initHadoopDB( data_in_hdfs = options.source_dir_in_hdfs,
data_delimiter = options.source_data_delimiter, data_field_delimiter =
options.source_field_delimiter,data_partition_out =
options.source_partition_dir, nodes_in_file = options.node_list_file,
chunks_per_node = options.chunk_num, table_name = options.table_name,
table_field_info = options.table_field_info_file, db_user =
options.db_username, db_pass = options.db_password, db_field_delimiter
= options.db_field_delimiter, hive_db_dir_in_hdfs =
options.hive_db_dir, tmp_path_for_catelog = options.catalog_properties,
out_hadoop_xml = options.hadoopdb_xml, hadoop_xml_in_hdfs =
options.hadoopdb_xml_in_hdfs, DEBUG_MODE = options.mode )

    print "\n\n=> All Execution Completed..."

if __name__ == "__main__":
    main()


另外有一些常見問題也順便紀錄:


hadoop@Cluster01:~$ echo "drop table justest;" | /home/hadoop/SMS_dist/bin/hive
Hive history file=/tmp/hadoop/hive_job_log_hadoop_201001081445_409015456.txt
hive> drop table justest;
FAILED:
Error in metadata: javax.jdo.JDOFatalDataStoreException: Failed to
start database 'metastore_db', see the next exception for details.
NestedThrowables:
java.sql.SQLException: Failed to start database 'metastore_db', see the next exception for details.
FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask
hive>


這類訊息是因為同時有兩個 client 在用 hive,同一時間只能有一個 client 在操作。



hadoop@Cluster01:~$ sudo apt-get install postgresql
hadoop@Cluster01:~$ sudo vim /etc/postgresql/8.4/main/pg_hba.conf


#local   all         all                               ident
local   all         all                               trust
# IPv4 local connections:
#host    all         all         127.0.0.1/32          md5
host    all         all         127.0.0.1/32          password
host    all         all         10.0.0.1/8          password            # 加上Cluster機器上的範圍
# IPv6 local connections:
#host    all         all         ::1/128               md5
host    all         all         ::1/128               password


hadoop@Cluster01:~$ sudo vim /etc/postgresql/8.4/main/postgresql.conf


listen_addresses = '*'


hadoop@Cluster01:~$ sudo /etc/init.d/postgresql-8.4 restart
hadoop@Cluster01:~$ sudo su - postgres
postgres@Cluster01:~$ createuser hadoop


Shall the new role be a superuser? (y/n) y


postgres@Cluster01:~$ psql

psql (8.4.2)
Type "help" for help.

postgres=# alter user hadoop with password '1234';
ALTER ROLE
postgres=# \q

postgres@Cluster01:~$ exit


logout



[PHP] 使用官方 Plurk API 實作簡單的機器人 - 靠機器人救 Karma!以 Yahoo News 為例

Yahoo! News & Plurk


靠機器人救 Karma!由於 Karma 值的不穩定,乾脆實做一隻 Plurk 機器人好了,這次實做參考 [PHP] Official Plurk API 之PHP - cURL 使用教學


Update @ 2011/07/26:新增縮網址的程式碼,因為新聞連結長度超過 144 字數的限制。


整體上就是實做一個撈 Yahoo 新聞的小程式,理論上是要用 RSS 才對,但因為我只想看焦點新聞,所以就改撈他的首頁進行處理,可以自行更新 getNews 函數吧!而這隻程式可以搭配 Windows 的排程或是 Unix 的 Crontab 運作,而程式環境上還要求 PHP-Curl 的支援囉!由於它非常陽春,所以我連帳密錯誤都沒確認就是了 :P


<?php
$cookie_db = '/tmp/http_cookie';
$log_db = '/tmp/job_log';
$hash_db = '/tmp/http_yahoo';
$bin_grep = '/bin/grep';

$plurk_api_key = 'YOUR_PLURK_API_KEY';
$plurk_id = 'LOGIN_ID';
$plurk_passwd = 'LOGIN_PASSWORD';

if( file_exists( $log_db ) )
    return;

        $result = do_act(
            'http://www.plurk.com/API/Users/login' ,
            'POST' ,
            array(
                'api_key'       => $plurk_api_key ,
                'username'      => $plurk_id ,
                'password'      => $plurk_passwd
            ) ,
            $cookie_db
        );

$source = getNews();
foreach( $source as $data )
{
    if( !check_exists( $data['url'] ) )
    {
        $target_url = NULL;
        if( strlen( $data['url'] ) < 100 )
           $target_url = $data['url'];
        else
            $target_url = getTinyurl( $data['url'] );

        if( !empty( $target_url ) )
        {
            $plurk = '[News]'.$target_url.' ('.$data['title'].')';
            $result = do_act(
                'http://www.plurk.com/API/Timeline/plurkAdd' ,
                'POST' ,
                array(
                    'api_key'       => $plurk_api_key ,
                    'qualifier'     => 'shares' , // loves , likes , shares , ...
                    'content'       => $plurk
                ) ,
                $cookie_db
            );
        }
        put_db( $data['url'] );
    }
}
exit;

function check_exists( $url )
{
    global $hash_db , $bin_grep;
    if( !file_exists( $hash_db ) )
        return false;
    $cmd = $bin_grep.' -c "'. md5($url) .'" '.$hash_db;
    $result = shell_exec( $cmd );
    $result = trim( $result );
    //echo "[$cmd]\n[$result]\n";
    return !empty( $result );
}
function put_db( $url )
{
    global $hash_db , $log_db ;
    if( !file_exists( $hash_db ) )
        $fp = fopen( $hash_db , 'w' );
    else
        $fp = fopen( $hash_db , 'a' );
    if( $fp )
    {
        fwrite( $fp , md5($url) . "\n" );
        fclose( $fp );
    }
    else
        file_put_contents( $log_db , "Error" );
}
function getNews()
{
    global $log_db;
    $out = array();
    $raw = file_get_contents( 'http://tw.yahoo.com/' );
    $pattern = '<label>';
    $raw = stristr( $raw , $pattern );
    if( empty( $raw ) )
    {
        file_put_contents( $log_db , "Parser Error 1" );
        return $out;
    }
    $raw = substr( $raw , strlen( $pattern ) );
    $pattern = '<ol>';
    $finish = strpos( $raw , $pattern );
    if( $finish < 0 )
    {
        file_put_contents( $log_db , "Parser Error 2" );
        return $out;
    }
    $raw = substr( $raw , 0 , $finish );
    if( empty( $raw ) )
        return $out;
    $pattern = '{<h3[^>]*>[^<]*<a href="(.*?)"[^>]*>(.*?)</a></h3>}is';
    if( preg_match_all( $pattern , $raw , $matches ) )
    {
        for( $i=0 , $cnt=count( $matches[1] ) ; $i<$cnt ; ++$i )
        {
            array_push( $out , array(
                'url' => strstr( $matches[1][$i] , 'http:' ) ,
                'title' => $matches[2][$i] )
            );
        }
    }
    else
        file_put_contents( $log_db , "Parser Error 3" );
    return $out;
}
function do_act( $target_url , $type , $data , $cookie_file = NULL )
{
        $ch = curl_init();
        if( $type == 'GET' )    // GET
        {
                $target_url .= http_build_query( $data );
                curl_setopt($ch, CURLOPT_URL, $target_url );
        }
        else                    // POST
        {
                curl_setopt( $ch , CURLOPT_URL , $target_url );
                curl_setopt( $ch , CURLOPT_POST , true );
                curl_setopt( $ch , CURLOPT_POSTFIELDS , http_build_query( $data ) );
        }

        if( isset( $cookie_file ) )     // cookie
        {
                curl_setopt( $ch , CURLOPT_COOKIEFILE , $cookie_file );
                curl_setopt( $ch , CURLOPT_COOKIEJAR , $cookie_file );
        }

        curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true );
        //curl_setopt( $ch , CURLOPT_FOLLOWLOCATION , true );
        //curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER , false );

        $result = curl_exec( $ch );
        curl_close( $ch );
        return $result;
}
function getTinyurl( $url )
{
        $new_url = @file_get_contents( 'http://tinyurl.com/api-create.php?url='.urlencode($url) );
        $new_url = trim( $new_url );
        if( !empty( $new_url ) )
            return $new_url;
        return NULL;
}
?>


2010年1月4日 星期一

[Linux] 安裝 Hadoop 0.20.1 Multi-Node Cluster @ Ubuntu 9.10


圖片來源:http://hadoop.apache.org/


今天共用了三台機器,終於真正架了一個 Hadoop Cluster ,在這之前都只是安裝在一台機器上:[Linux] 安裝單機版 Hadoop 0.20.1 Single-Node Cluster (Pseudo-Distributed) @ Ubuntu 9.04。由於想嘗試 HadoopDB
,因此在工作場所裡透過 VMWare Workstation 安裝 Hadoop 0.19.2 版,並且碰到一堆有的沒有的問題,連
Hadoop 的 wordcount 範例都跑不起來!盡管最後有設定成功,但太不熟了,回寢室後再試一次,順便替自己架設個人把玩的開發環境啦!


環境:Windows 7 x64 + VirtualBox
3.12 r56127 ,預計安裝 3 台機器的 Cluster 囉。原本打算使用 ubuntu-9.10-server-amd64.iso
,但 VirtualBox 啟動有些問題,因此改成 ubuntu-9.10-server-i386.iso ,並且在安裝過程中,給予 20
GB 硬碟空間,以及最後建立 hadoop 使用者,接下來的架設方式是設定好一台虛擬機器後,透過複製虛擬機器的方式來新增機器。


安裝好一台虛擬機器後,緊接著進行更新與軟體的安裝



  • VirtualBox 環境設定

    • 新增網卡

      • 預設只有一張網卡,代號應該是 eth0 ,通常會用它來連外,常用的設置是 NAT 方式

      • 建議再加一張網卡,用來讓自己的電腦(Host OS)能連進虛擬機器(Guest OS),詳情可參考這篇 讓本機可連虛擬機器,虛擬機器可連外 - VirtualBox 網路設定,雖然這並非需要,但有這個就可以透過 ssh client (Putty)連進去,而不需要透過 VirualBox 的介面。

      • 接下來還需要建立一個固定的 IP ,而第二張 eth1 網卡採用 VirtualBox 之 「僅限主機」介面卡 (Host-Only)
        即可,它提供 DHCP 服務,而 eth1 除了可以用來與自己的電腦溝通,還可以另外再對 eth1 多設定一個 Static IP 供
        Cluster 內部溝通



    • 在 Linux or Ubuntu 的機器上,若設定完網卡後,或是使用複製的虛擬機器時,透過 $ ifconfig 卻看不到網路卡,可以使用以下方式試試

      • $ sudo rm /etc/udev/rules.d/70-persistent-net.rules
        $ sudo reboot



    • 透過 $ ifconfig 可以看到 eth1 網卡後,接著使用 $ sudo dhclient eth1 更新來獲得一組 IP ,而 Host-Only 本來就有提供 DHCP 服務,此時主要用來測試網路的設定是否正常

    • 如果都沒問題,那就可以設定讓它開機就自動弄好,並且將 eth1:0 設定成 Static IP

      • $ sudo vim /etc/network/interfaces
        # The loopback network interface
        auto lo
        iface lo inet loopback

        # The primary network interface
        auto eth0
        iface eth0 inet dhcp

        # Host-Only
        auto eth1
        iface eth1 inet dhcp

        auto eth1:0
                iface eth1:0 inet static
                address 192.168.56.168
                netmask 255.255.0.0

        • 之所以設定成 192.168.56.168 是因為預設的 Host-Only 是 192.168.56.x 網域的,用同網域的另一個好處是自己的電腦 (Host OS)可以用這個固定 IP 來連線,不需每次要用就得查詢一次,但實際使用上此 DHCP 都分配 192.168.56.101 之後的 IP ,因此也需要小心設定,以免衝 IP 囉。





    • 設定完後,除了可以透過 $ sudo reboot 啟用 IP ,也可以透過指令更新 $ sudo /etc/init.d/network restart



  • 更新系統

    • $ sudo apt-get update
      $ sudo apt-get dist-upgrade
      $ sudo apt-get upgrade

    • 另外還有個東西叫做 aptitude 對我而言也挺新鮮的,詳情請逛逛 Aptitude - Ubuntu 正體中文 Wiki


      • $ sudo aptitude safe-upgrade
        $ sudo aptitude full-upgrade
        $ sudo aptitude dist-upgrade





  • 安裝軟體或相關準備

    • 建立暫放軟體的地方 /home/tmp

      • $ sudo mkdir /home/tmp ; sudo chown -R hadoop:hadoop /home/tmp ;



    • $ sudo apt-get install ssh rsync 

      • 有些東西已有安裝,執行上道指令只試再次確認囉,像是 openssh-server 等。安裝完 openssh-server 後,建議可以透過 Putty 登入,接下來的操作會比較方便。



    • 安裝 Java 環境

      • 第一種方式:$ sudo apt-get install sun-java6-sdk

      • 第二種方式:為了方便製作成 Portable 環境,採用類似 Tarball 的安裝方式,請至 Java SE Downloads - Sun Developer Network (SDN) 下載 Linux 的 SDK6 (Ex: jdk-6u17-linux-i586.bin),目前打算用 Java 1.6 的開發環境。如果使用 64-Bit 環境,下載時可以選擇 Linux x64 。

        • 將下載得來的 jdk-6u17-linux-i586.bin 擺在 /home/tmp (可以用sftp傳進去)。





    • 下載 Hadoop

      • $ cd /home/tmp ;
        $ wget http://ftp.mirror.tw/pub/apache/hadoop/core/hadoop-0.20.1/hadoop-0.20.1.tar.gz

      • 以下是我自己額外的使用

        • $ wget http://ftp.mirror.tw/pub/apache/hadoop/core/hadoop-0.19.2/hadoop-0.19.2.tar.gz

        • $ wget 'http://downloads.sourceforge.net/project/hadoopdb/hadoopdb/0.1.1/hadoopdb-all-0.1.1.0.tar.gz?use_mirror=nchc'

        • $ wget http://jdbc.postgresql.org/download/postgresql-8.4-701.jdbc4.jar

        • 好用的 screen

          • $ vim ~/.screenrc

            • caption always "%{bw}%M/%d %c %{wb} %-w%{c}%n %t%{w}%+w%{k} %=%{G}[%H] %l%"











  • 經過上述的軟體設定後,再接著做事之前,可以考慮先備份一下目前的虛擬環境,以方便往後取出備份

    • "c:\Program Files\Sun\VirtualBox\VBoxManage.exe" clonevdi Cluster01.vdi Cluster_backup.vdi

    • 由於 VirtualBox 的 *.vdi 有一些硬體資訊載裡頭(EX:UUID),所以無法像 VMWare 那樣可以選 I copy it 的選項來自動幫你處理,因此就需要透過上述的指令來複製虛擬機器。



  • 設置統一的 Hadoop 環境

    • 選擇你想要安裝的版本進行設定,此例以 hadoop 0.20.1

    • $ cd /home/tmp ;
      $ tar -xvf hadoop-0.20.1.tar.gz;
      $ sh jdk-6u17-linux-i586.bin ;

    • 接著把 hadoop-0.20.1 和 jdk1.6.0_17 搬到家目錄吧!

      • $ cp -r hadoop-0.20.1/* ~/
        $ mv jdk1.6.0_17 ~/



    • 設定 Java 環境

      • $ vim conf/hadoop-env.sh

        • export JAVA_HOME=/home/hadoop/jdk1.6.0_17





    • 設定停用 IPv6

      • $ vim conf/hadoop-env.sh



        • HADOOP_OPTS=-Djava.net.preferIPv4Stack=true



      • 可能是因為 Hadoop 對 IPv6 網路支援不穩,常常會莫名連不到其他機器,設定這個至少在此刻還滿重要的!



    • 設定 /etc/hosts 與 /etc/hostname ,以三台電腦為例,目前這台當作 Cluster01 ,假設之後還會有 Cluster02 和 Cluster03 ( elalic 網友提醒!請勿使用底線"_"來描述電腦,有連不到機器的現象發生)


      • $ sudo vim /etc/hostname
        Cluster01

      • $ sudo vim /etc/hosts
        #127.0.1.1      Cluster01   (此行代表註解)
        192.168.56.168  Cluster01
        192.168.56.169  Cluster02
        192.168.56.170  Cluster03



    • 設定 conf/core-site.xml 、 conf/mapred-site.xml


      • $ vim ~/conf/core-site.xml

        •         <property>
                          <name>fs.default.name</name>
                          <value>hdfs://Cluster01:9000</value>
                          <description>The name of the default file system. A URI whose
                          scheme and authority determine the FileSystem implementation. The
                          uri's scheme determines the config property (fs.SCHEME.impl) naming
                          the FileSystem implementation class.  The uri's authority is used to
                          determine the host, port, etc. for a filesystem.</description>
                  </property>



      • $ vim ~/conf/mapred-site.xml

        •         <property>
                          <name>mapred.job.tracker</name>
                          <value>Cluster01:9001</value>
                          <description>The host and port that the MapReduce job tracker runs
                          at.  If "local", then jobs are run in-process as a single map
                          and reduce task.
                          </description>
                  </property>





    • 再啟動 Hadoop 前,將另外兩台電腦備妥,使用複製虛擬機器的方式新增,並且使用 VirtualBox 開啟它們,使得同一時間有三台虛擬機器


      • "c:\Program Files\Sun\VirtualBox\VBoxManage.exe" clonevdi Cluster01.vdi Cluster02.vdi
        "c:\Program Files\Sun\VirtualBox\VBoxManage.exe" clonevdi Cluster01.vdi Cluster03.vdi

      • 接著在 VirutalBox [檔案]->[虛擬媒體管理員]->[硬碟]->[加入] ,將上述
        Cluster02.vdi 和 Cluster03.vdi 加入,接著回到 VirtualBox
        主畫面點選[新增]機器,設定好你要的名稱(Ex: Cluster02 和 Cluster03)以及 Linux/Ubuntu
        後,有個地方是要新增"虛擬硬碟",請選用"使用現有硬碟",依序分別挑選 Cluster02.vdi 和 Cluster03.vdi
        以開啟兩台虛擬機器。

      • 請幫它們加上二張網路卡 - 選擇 「僅限主機」介面卡 (VirtualBox Host-Only)

      • 開機後請先更新 /etc/hostname 及 /etc/network/interfaces

        • $ sudo vim /etc/hostname

          • 修改成 Cluster02 或 Cluster03



        • $ sudo vim /etc/network/interfaces

          • auto eth1:0
                    iface eth1:0 inet static
                    address 192.168.56.169 # or address 192.168.56.170
                    netmask 255.255.0.0





      • 機器啟動後,會發現無法連線以及看不到網路卡 ( $ ifconfig ) ,請執行以下動作即可解決

        • $ sudo rm /etc/udev/rules.d/70-persistent-net.rules
          $ sudo reboot





    • 以下在 Cluster01 操作


      • 設定 SSH 登入免密碼

        • $ ssh-keygen -t rsa -P ''
          $ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys


          • 可以用 ssh localhost 測試是否成功,若能不輸入密碼登入則代表成功



        • 將 Cluster 01 上的 authorized_keys 複製給 Cluster02 和 Cluster03

          • $ scp ~/.ssh/authorized_keys hadoop@Cluster02:~/.ssh/authorized_keys
            $ scp ~/.ssh/authorized_keys hadoop@Cluster03:~/.ssh/

            • 若出現 scp: /home/hadoop/.ssh/: Is a directory 這類訊息,則先去該台機器使用 ssh 囉,例如連去其他台機器等



          • 分別執行 $ ssh Cluster01 、 $ ssh Cluster02 和 $ssh Cluster03 試試,若可以免密碼登入即沒問題







      • 設定 conf/masters 和 conf/slaves

        • $ vim conf/masters

          • Cluster01



        • $ vim conf/slaves

          • Cluster02

            Cluster03





      • 初始化 NameNode

        • $ ~/bin/hadoop namenode -format





      • 啟動 Hadoop

        • $ ~/bin/start-all.sh


          • 應該會看到類似的訊息:
            hadoop@Cluster01:~$ ~/bin/start-all.sh
            starting namenode, logging to /home/hadoop/bin/../logs/hadoop-hadoop-namenode-Cluster01.out
            Cluster02: starting datanode, logging to /home/hadoop/bin/../logs/hadoop-hadoop-datanode-Cluster02.out
            Cluster03: starting datanode, logging to /home/hadoop/bin/../logs/hadoop-hadoop-datanode-Cluster03.out
            Cluster01: starting secondarynamenode, logging to /home/hadoop/bin/../logs/hadoop-hadoop-secondarynamenode-Cluster01.out
            starting jobtracker, logging to /home/hadoop/bin/../logs/hadoop-hadoop-jobtracker-Cluster01.out
            Cluster02: starting tasktracker, logging to /home/hadoop/bin/../logs/hadoop-hadoop-tasktracker-Cluster02.out
            Cluster03: starting tasktracker, logging to /home/hadoop/bin/../logs/hadoop-hadoop-tasktracker-Cluster03.out



        • $ ~/jdk1.6.0_17/bin/jps

          • 應該會看到類似的訊息:
            hadoop@Cluster01:~$ jdk1.6.0_17/bin/jps
            1783 Jps
            1631 SecondaryNameNode
            1693 JobTracker
            1470 NameNode



        • $ ~/bin/hadoop dfsadmin -report

          • 應該會看到類似的訊息:
            hadoop@Cluster01:~$ ~/bin/hadoop dfsadmin -report
            Configured Capacity: 39905583104 (37.16 GB)
            Present Capacity: 34398027776 (32.04 GB)
            DFS Remaining: 34397978624 (32.04 GB)
            DFS Used: 49152 (48 KB)
            DFS Used%: 0%
            Under replicated blocks: 0
            Blocks with corrupt replicas: 0
            Missing blocks: 0

            -------------------------------------------------
            Datanodes available: 2 (2 total, 0 dead)

            Name: 192.168.56.103:50010
            Decommission Status : Normal
            Configured Capacity: 19952791552 (18.58 GB)
            DFS Used: 24576 (24 KB)
            Non DFS Used: 2753777664 (2.56 GB)
            DFS Remaining: 17198989312(16.02 GB)
            DFS Used%: 0%
            DFS Remaining%: 86.2%
            Last contact: Mon Jan 04 22:59:29 CST 2010


            Name: 192.168.56.102:50010
            Decommission Status : Normal
            Configured Capacity: 19952791552 (18.58 GB)
            DFS Used: 24576 (24 KB)
            Non DFS Used: 2753777664 (2.56 GB)
            DFS Remaining: 17198989312(16.02 GB)
            DFS Used%: 0%
            DFS Remaining%: 86.2%
            Last contact: Mon Jan 04 22:59:28 CST 2010





        • 以 WordCount 測試 Hadoop 看看

          • $ ~/bin/hadoop fs -mkdir input

          • $ ~/bin/hadoop fs -put README.txt input/

          • $ ~/bin/hadoop jar hadoop-0.20.1-examples.jar wordcount input output
            10/01/04 23:01:44 INFO input.FileInputFormat: Total input paths to process : 1
            10/01/04 23:01:44 INFO mapred.JobClient: Running job: job_201001042257_0001
            10/01/04 23:01:45 INFO mapred.JobClient:  map 0% reduce 0%
            10/01/04 23:01:54 INFO mapred.JobClient:  map 100% reduce 0%
            10/01/04 23:02:06 INFO mapred.JobClient:  map 100% reduce 100%
            10/01/04 23:02:08 INFO mapred.JobClient: Job complete: job_201001042257_0001
            10/01/04 23:02:08 INFO mapred.JobClient: Counters: 17
            10/01/04 23:02:08 INFO mapred.JobClient:   Job Counters
            10/01/04 23:02:08 INFO mapred.JobClient:     Launched reduce tasks=1
            10/01/04 23:02:08 INFO mapred.JobClient:     Launched map tasks=1
            10/01/04 23:02:08 INFO mapred.JobClient:     Data-local map tasks=1
            10/01/04 23:02:08 INFO mapred.JobClient:   FileSystemCounters
            10/01/04 23:02:08 INFO mapred.JobClient:     FILE_BYTES_READ=1836
            10/01/04 23:02:08 INFO mapred.JobClient:     HDFS_BYTES_READ=1366
            10/01/04 23:02:08 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=3704
            10/01/04 23:02:08 INFO mapred.JobClient:     HDFS_BYTES_WRITTEN=1306
            10/01/04 23:02:08 INFO mapred.JobClient:   Map-Reduce Framework
            10/01/04 23:02:08 INFO mapred.JobClient:     Reduce input groups=0
            10/01/04 23:02:08 INFO mapred.JobClient:     Combine output records=131
            10/01/04 23:02:08 INFO mapred.JobClient:     Map input records=31
            10/01/04 23:02:08 INFO mapred.JobClient:     Reduce shuffle bytes=1836
            10/01/04 23:02:08 INFO mapred.JobClient:     Reduce output records=0
            10/01/04 23:02:08 INFO mapred.JobClient:     Spilled Records=262
            10/01/04 23:02:08 INFO mapred.JobClient:     Map output bytes=2055
            10/01/04 23:02:08 INFO mapred.JobClient:     Combine input records=179
            10/01/04 23:02:08 INFO mapred.JobClient:     Map output records=179
            10/01/04 23:02:08 INFO mapred.JobClient:     Reduce input records=131

          • $ ~/bin/hadoop dfs -cat output/p*



        • 關掉 hadoop


          • $ ./bin/stop-all.sh
            stopping jobtracker
            Cluster02: stopping tasktracker
            Cluster03: stopping tasktracker
            stopping namenode
            Cluster02: stopping datanode
            Cluster03: stopping datanode
            Cluster01: stopping secondarynamenode







    • 整體上就這樣,果然回家做一次就非常順,一次就成功啊!收工!




以下是一些參考資料



除此之外,在公司安裝 Hadoop 0.19.2 時,碰到了以下問題



但真正發現,出問題的是因為 datanode 有一台的 /etc/hosts 設定有問題,修正後就沒問題啦。


其他在 hadoop 0.20.1 碰到的問題



  • hdfs.DFSClient: DataStreamer Exception:
    org.apache.hadoop.ipc.RemoteException: java.io.IOException: File
    /path/file could only be replicated to 0 nodes, instead of 1

    • $ hadoop fs -mkdir in

      $ hadoop fs -put README.txt in

      10/01/19 08:35:13 WARN hdfs.DFSClient: DataStreamer Exception:
      org.apache.hadoop.ipc.RemoteException: java.io.IOException: File
      /user/hadoop/in/README.txt co

      uld only be replicated to 0 nodes, instead of 1

              at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getAdditionalBlock(FSNamesystem.java:1267)

              at org.apache.hadoop.hdfs.server.namenode.NameNode.addBlock(NameNode.java:422)

              at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

              at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

              at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

              at java.lang.reflect.Method.invoke(Method.java:597)

              at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:508)

              at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:959)

              at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:955)

              at java.security.AccessController.doPrivileged(Native Method)

              at javax.security.auth.Subject.doAs(Subject.java:396)

              at org.apache.hadoop.ipc.Server$Handler.run(Server.java:953)



              at org.apache.hadoop.ipc.Client.call(Client.java:739)

              at org.apache.hadoop.ipc.RPC$Invoker.invoke(RPC.java:220)

              at $Proxy0.addBlock(Unknown Source)

              at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

              at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

              at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

              at java.lang.reflect.Method.invoke(Method.java:597)

              at org.apache.hadoop.io.retry.RetryInvocationHandler.invokeMethod(RetryInvocationHandler.java:82)

              at org.apache.hadoop.io.retry.RetryInvocationHandler.invoke(RetryInvocationHandler.java:59)

              at $Proxy0.addBlock(Unknown Source)

              at org.apache.hadoop.hdfs.DFSClient$DFSOutputStream.locateFollowingBlock(DFSClient.java:2904)

              at org.apache.hadoop.hdfs.DFSClient$DFSOutputStream.nextBlockOutputStream(DFSClient.java:2786)

              at org.apache.hadoop.hdfs.DFSClient$DFSOutputStream.access$2000(DFSClient.java:2076)

              at org.apache.hadoop.hdfs.DFSClient$DFSOutputStream$DataStreamer.run(DFSClient.java:2262)



      10/01/19 08:35:13 WARN hdfs.DFSClient: Error Recovery for block null bad datanode[0] nodes == null

      10/01/19 08:35:13 WARN hdfs.DFSClient: Could not get block locations. Source file "/user/hadoop/in/README.txt" - Aborting...

      put: java.io.IOException: File /user/hadoop/in/README.txt could only be replicated to 0 nodes, instead of 1

    • 解法:請再三確認各台機器的 /etc/hosts 與 IP 狀況(ifconfig),如果都沒有問題,那等一陣子才試試,有時候環境剛初始化完會有這個現象,過一陣子之後就好了,另外可用 hadoop dfsadmin -report 來確認 datanode 是否正常跑起來,以及到各台機器用 jdk1.6.0_17/bin/jps 來查看 slaves ,正常要會有 TaskTracker 和 DataNode 。




2010年1月3日 星期日

2010


這張是在 Plurk 上頭看到別人分享的,感覺真的很快樂!


最近在想,是不是又該好好規畫生活了呢?雖然現況並不差,但總覺得要自我要求進步囉!想要珍惜時間,不單單只是一直花在程式、工作甚至遊戲上面,好像有很多事還等著我去嘗試囉,想要帶給家人更多的幸福囉!









2010年1月1日 星期五

Wretch 無名小站 API - 申請、使用完整教學

第一次使用時,因為我沒 public ip 的機器,所以就嘗試架在 EC2 上,奢侈啊。過了一陣子,我發表一篇文章紀錄:Wretch 無名小站 API - Yahoo! Developer,然後有人留言請教,於是我找了另一台機器,但我也發現碰到一樣的問題,但礙於時間關係,所以遲遲沒有幫忙,隨意 Google 關鍵字,可能是在做專題吧?!真是抱歉,現在我有空了,就在仔細紀錄一下吧!


關於 Yahoo API ,我只嘗試 無名小站 API ,主因是以前我自己曾使用 PHP 與 cURL 來模擬瀏覽器,以程式自動化完成登入,並且發表文章到無名網誌,但這種客制化的行為真的不如使用官方的 API 啦,因此還滿興奮有這個東西的出現,然而,我還是比較希望它提供 XMLRPC 。


扯遠了,現在開始教學吧!首先,一定要有一台 Public IP 供你使用,接著你需要去註冊一個 Domain Name 來使用,網路上很多免費的,像我就是使用 TWBBS.org ,但要留意的,並不是馬上申請就可以用,要等一陣子才行,可以試著用 "nslooup your_name.twbbs.org" 來試試,但最主要並不是你所在的機器能找到,而是 Yahoo 那邊的驗證機器能找到它。接著,請連到 http://tw.developer.yahoo.com/http://developer.yahoo.com/ 登入,在此以英文介面當作範例。


登入完後,請點選 My Project 並且在該頁面點選 New Project 。接著選一下有 "Wretch" 關鍵字吧!


2010/01/01 YDN - New Project


接著就是填寫一些資訊,比較重要的就是你的 Application 的網頁位置,此例是使用 Web-Based 模式


2010/01/01 YDN - New Project


記得把 Access Scopes 選則有關 private user data 囉,因為 Wretch API 是歸類在這部份的。並且在最後一項中選擇 Wretch API 啦


2010/01/01 YDN - New Project


接著就是關鍵的驗證部分,這也是為何我說要有一個 public ip 的機器,以及一個 domain name 啦,此驗證過程就是依照上頭的規定,在 Web Server 的目錄下擺放一個特定檔名的檔案,讓 Yahoo 那邊的機器去存取驗證,如果取得到,那就可以通過啦!


2010/01/01 YDN - New Project


在驗證過程中,可能會碰到一些問題,一種是你的檔名沒建好,所以它找不到,另一種比較神秘,它直接告訴你


Domain Verification Failed
There was an internal error while an application is being created. Please try agin.


像這個問題在一些討論區有提到,有可能 Applocation Name 已存在,或是 Description 沒填等等的問題,但我碰到的真正問題,其實是 Favicon URL 那欄!請為它建一個檔案吧,看起來它也會去抓這個位置來驗證,還真有點怪,它上頭明明寫 Optional !驗證成功則會顯示相關資訊囉


2010/01/01 YDN - New Project


這邊所要留意的有 Application ID 、Consumer Key 和 Consumer Secret ,其中以後兩者最為重要。緊接著,在你的 Web Server 擺上相關程式碼吧!讓我們來測測看啦!接著也可參考 Wretch 無名小站 API - Yahoo! Developer 這篇囉,上頭有其他測試的程式碼。



  • Wretch API 文件

  • 下載 PHP Library:Using Yahoo! Social SDK for PHP

    • 今天我用的版本 yahoo-yos-social-php-12c4a50



  • 讓你的 Web Server 支援 PHP - cURL ,在此就不教怎樣安裝 PHP5 了,以下動作別忘了重開 Web Server。


    • Ubuntu


      • # sudo apt-get install php5-curl



    • FreeBSD

      • # cd /usr/ports/lang/php5-extensions/ ; 

      • # make config ; // 請將 CURL support 勾選

      • # make install clean



    • Windows

      • 通常從官網下載的包裝中已經有囉,只差設定好位置以及啟用 cURL Library 啦

      • 編輯 php.ini (可能在 C:\Windows 目錄中 )

        • 設定好 extension_dir 位置

        • 把 extension=php_curl.dll 前面的 # 拿掉,以此啟用它








接著,就是把相關程式碼擺定位,以下以 Windows 平台描述



  • Yahoo Library

    • C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\wretch\yahoo-yos-social-php-12c4a50



  • 測試程式碼

    • C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\wretch\index.php

    • <?php
      require("yahoo-yos-social-php-12c4a50/lib/Yahoo.inc");  

      // Your Consumer Key (API Key) goes here.  
      define('CONSUMER_KEY', "######");

      // Your Consumer Secret goes here.  
      define('CONSUMER_SECRET', "######");

      // Your application ID goes here.  
      define('APPID', "######");  

      //$GLOBAL_YAHOO_LOGGER_DEBUG = true;
      //$GLOBAL_YAHOO_LOGGER_DEBUG_DESTINATION = "CONSOLE";

      $containerSession = YahooSession::requireSession(CONSUMER_KEY,CONSUMER_SECRET);
      $userSession = $containerSession->getSessionedUser();
      ?>




透過瀏覽器連過去你設定好的位置,若正常成功的話,它會蹦出要你輸入帳密的畫面囉,這個流程是將你導到 Yahoo 登入畫面


2010/01/01 YDN - New Project


並且完成登入後會詢問你是否允許這支程式存取你帳號的資料


2010/01/01 YDN - New Project


當然自己寫的程式比較 OK ,就允許吧!不然也沒得測。


只不過,以上的過程並沒有那麼寫意輕鬆,因為我還是跟之前一樣,卡在這個錯誤訊息


Call to a member function getSessionedUser() on a non-object


逛了幾個網站,才知道可以把 debug mode 打開,就是在引入 Yahoo.inc 後,加上片段程式


$GLOBAL_YAHOO_LOGGER_DEBUG = true;
$GLOBAL_YAHOO_LOGGER_DEBUG_DESTINATION = "CONSOLE";


此時,錯誤訊息


ERROR - Error making libcurl request(https://api.login.yahoo.com/oauth/v2/get_request_token): SSL certificate problem, verify that the CA cert is OK. Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed ERROR - OAuth call to get request token failed ERROR - Failed to create request token ERROR - Failed to create authorization URLs


喔耶,至少我看得懂了,當初別人問我時,還傻傻地去 trace code 東改西改,改到後面還是沒搞定,自以為是 Yahoo API 沒做好,畢竟你用 "Wretch API" 去 Google 的第一筆結果就是在抱怨,哈。關於這個問題,恰好我有用過 PHP cURL ,所以知道可以設定一個選項來避開它,但這個動作必須修改 Yahoo Library 程式碼,說真的我也很不願意,但至少這樣做可以搞定。 


@Yahoo.inc:1811
+curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER , false );


以上就是去 Yahoo.inc 這個檔案,我使用 curl_init 去搜尋,發現只有一處,很好,那我只要更新一處就行,在 curl_init 後面加上 curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER , false ); 就可以了。


以上是我在 Windows 平台得到的訊息以及解決方式,然而,我在 FreeBSD 機器上時,得到的錯誤訊息則不一樣了


DEBUG - HTTP request details
DEBUG OBJECT - Array
(
    [method] => POST
    [url] => https://api.login.yahoo.com/oauth/v2/get_request_token
    [code] => 401
    [requestHeaders] => Array
        (
            [0] => Accept: application/json
            [1] => Content-Type: application/x-www-form-urlencoded
        )

    [requestBody] => ######
    [responseHeaders] => Array
        (
            [Date] => ######
            [P3P] => ######
            [WWW-Authenticate] => OAuth oauth_problem=signature_invalid
            [Connection] => close
            [Transfer-Encoding] => chunked
            [Content-Type] => application/x-www-form-urlencoded
        )

    [responseBody] => oauth_problem=signature_invalid
)

ERROR - Failed to create request token: signature_invalid
ERROR - Failed to create request token
ERROR - Failed to create authorization URLs


那你在使用 YahooSession::requireSession(CONSUMER_KEY,CONSUMER_SECRET); 時,請改成加上 APPID 和當初認證的 Domain Name 應該就能解決囉


<?php

$containerSession = YahooSession::requireSession(CONSUMER_KEY,CONSUMER_SECRET , APPID , 'http://your_name.twbbs.org/' );
$userSession = $containerSession->getSessionedUser();

?>


最後,可以在把 debug mode 拿掉,完成轉址到 Yahoo 登入頁面囉!


以下是簡單的列出使用者在無名小站的個人資料、網誌文章和相簿列表,詳細部分請參考 Wretch API 文件


<?php

//error_reporting(E_ALL); ini_set("display_errors", 1);
require("yahoo-yos-social-php-12c4a50/lib/Yahoo.inc");  
// Your Consumer Key (API Key) goes here.  
define('CONSUMER_KEY', "######");
// Your Consumer Secret goes here.  
define('CONSUMER_SECRET', "######");
// Your application ID goes here.  
define('APPID', "######");  

//$GLOBAL_YAHOO_LOGGER_DEBUG = true;
//$GLOBAL_YAHOO_LOGGER_DEBUG_DESTINATION = "CONSOLE";

$session = YahooSession::requireSession( CONSUMER_KEY, CONSUMER_SECRET );
$yahoo_user = $session->getSessionedUser();

// Profile
$url = 'http://wretch.yahooapis.com/v1/profileService/'.$yahoo_user->guid;
$response = $session->client->get($url);
echo "[REQUEST] $url\n";
echo "<pre>";
print_r($response);
echo "</pre>";

// Blog
//$url = 'http://wretch.yahooapis.com/v1/blogService/'.$yahoo_user->guid.'/articles';
$response = $session->client->get($url);
echo "[REQUEST] $url\n";
echo "<pre>";
print_r($response);
echo "</pre>";

// Albums
$url = 'http://wretch.yahooapis.com/v1/albumService/'.$yahoo_user->guid.'/albums';
$response = $session->client->get($url);
echo "[REQUEST] $url\n";
echo "<pre>";
print_r($response);
echo "</pre>";

?>


以上回傳的格式(以 Profile 為例):


Array
(
[method] => GET
[url] => http://wretch.yahooapis.com/v1/profileService/####
[code] => 200
[requestHeaders] => Array
(
[0] => Accept: application/json
[1] => Authorization: OAuth realm="yahooapis.com",oauth_version="1.0",oauth_nonce="######",oauth_timestamp="######",oauth_consumer_key="######",oauth_token="######",oauth_signature_method="HMAC-SHA1",oauth_signature="######"
)

[requestBody] =>
[responseHeaders] => Array
(
[Date] => Fri, 01 Jan 2010 14:05:24 GMT
[P3P] => policyref="http://info.yahoo.com/w3c/p3p.xml", CP="######"
[Set-Cookie] => lang=zh-tw; expires=Mon, 27-Dec-2010 14:05:24 GMT; path=/; domain=.wretch.cc
[cache-control] => private,must-revalidate
[content-language] => en-US
[content-location] => http://www.wretch.cc/user/######
[content-type] => application/json; charset=UTF-8
[Vary] => Accept-Encoding
[Connection] => close
[Transfer-Encoding] => chunked
[Content-Encoding] => gzip
)

[responseBody] => {"profile":{"title":"######","desc":"######","nick":"######","birthday":null,"intro":"######","sex":"######","lang":"en-US","uri":"######"}}
)

其中 code 為 200 代表正常。


此次測試,是以一個新帳號測試並且完成開通無名帳號,只是一開始回傳的還是 302 或 404 ,猜測可能是資料還未同步好,過了一陣子再試的結果就是 200 囉。以上提供給各位參考囉。


其他相關參考