-
I started by creating a mapper.py file like this, In the mapper i am reading one line from input at a time and then splitting it into pieces and writing it to output in
(word,1)format. In the mapper whatever i write in output gets passed back to Hadoop, so i could not use standard output for writing debug statements. So i configured file logger that generates debug.log in the current directory -
Next i created a reducer.py program that reads one line at a time and splits it on tab character. In the split first part is word and second is the count. Now one difference between java reducer and streaming reducer is in Java your reduce method gets input like this
(key, [value1, value2,value3]),(key1, [value1, value2,value3]). In streaming it gets called with one key and value every time like this(key,value1),(key,value2),(key,value3),(key1,value),(key1,value2),(key1,value3), so you will have to remember what key your processing and handle the change in key. In my reducer i am keeping track of current key, and for every value of the current key i keep accumulating it, when the key changes i use that opportunity to dump the old key and count -
One good part with developing using scripting is that you can test your code without hadoop as well. In this case once my mapper and reducer are ready i can test it on command line using
data | mapper | sort | reducerformat. In my case the mapper and reducer files are in /home/user/workspace/HadoopPython/streaming/ directory. and i have a sample file in home directory so i could test my program by executing it like thiscat ~/sample.txt | /home/user/workspace/HadoopPython/streaming/mapper.py | sort | /home/user/workspace/HadoopPython/streaming/reducer.py -
After working through bugs i copied aesop.txt in in root of my HDFS and then i could use following command to execute my map reduce program.
hadoop jar /usr/local/hadoop/share/hadoop/tools/lib/hadoop-streaming-2.4.0.jar -input aesop.txt -output output/wordcount -mapper /home/user/workspace/HadoopPython/streaming/mapper.py -reducer /home/user/workspace/HadoopPython/streaming/reducer.py -
Once the program is done executing i could see the output generated by it using following command
hdfs dfs -cat output/wordcount/part-00000
WordCount MapReduce program using Hadoop streaming and python
I wanted to learn how to use Hadoop Streaming, which allows us to use scripting language such as Python, Ruby,.. etc for developing Map Reduce program. The idea is instead of writing Java classes for Mapper and Reducer you develop 2 script files (something that can be executed from command line) one for mapper and other for reducer and pass it to Hadoop. Hadoop will communicate to the script files using standard input/output, which means for both mapper and reducer hadoop will pass input on standard input and your script file will read it from standard input. Once your script is done processing the data in either mapper or reducer it will write output to standard output that will get sent back to hadoop.
I decided to create Word Count program that takes a file as input and counts occurrence of every word in the file and writes it in output. I followed these steps
WordCount program writtten using Spark framework written in python language
In the WordCount(HelloWorld) MapReduce program entry i talked about how to build a simple WordCount program using MapReduce. I wanted to try developing same program using Apache Spark but using Python, so i followed these steps
- Download version of spark that is appropriate for your hadoop from Spark Download page. In my case i am using Cloudera CHD4 VM image for development so i did download CDH4 version
- I did extract the spark-1.0.0-bin-cdh4.tgz in /home/cloudera/software folder
-
Next step is to build a WordCount.py program like this. This program has 3 methods in this
- flatMap: This method takes a line as input and splits it on space and publishes those words
- map: This method takes a word as input and publishesh a tuple in
word, 1format - reduce: This method takes care of adding all the counters together
counts = distFile.flatMap(flatMap).map(map).reduceByKey(reduce)takes care of tying everything together -
Once WordCount.py is ready you can execute it like this by providing it path of the WordCount.py and input and output path
./bin/spark-submit --master local[4] /home/cloudera/workspace/spark/HelloSpark/WordCount.py file:///home/cloudera/sorttext.txt file:///home/cloudera/output/wordcount -
Once the program is done executing you can take a look at the output by executing following command
more /home/cloudera/output/wordcount/part-00000
Importing data from RDBMS into Hive using create-hive-table of sqoop
In the Importing data from RDBMS into Hive i blogged about how to import data from RDBMS into Hive using Sqoop. In that case the import command took care of both creating table in Hive based on RDMBS table as well as importing data from RDBMS into Hive.
But Sqoop can also be used to import data stored in HDFS text file into Hive. I wanted to try that out, so what i did is i created the contact table in Hive manually and then used the contact table that i exported as text file into HDFS as input
-
First i used sqoop import command to import content of Contact table into HDFS as text file. By default sqoop will use , for separating columns and newline for separating
After import is done i can see content of the text file by executingsqoop import --connect jdbc:mysql://macos/test --table contact -m 1hdfs dfs -cat contact/part-m-00000like this -
After that you can use sqoop to create table into hive based on schema of the CONTACT table in RDBMS. by executing following command
sqoop create-hive-table --connect jdbc:mysql://macos/test --table Address --fields-terminated-by ',' -
Last step is to use Hive for loading content of contact text file into contact table. by executing following command.
LOAD DATA INPATH 'contact' into table contact;
Importing data from RDBMS into Hive using sqoop
In the Importing data from RDBMS into Hadoop i blogged about how to import content of RDBMS into Hadoop Text file using Sqoop. But its more common to import the content of RDMBS into Hive. I wanted to try that out, so i decided to import content of the Contact table that i created in the Importing data from RDBMS into Hadoop entry in Contact table in Hive on my local machine. I followed these steps
- First take a look at content of Contact table in my local MySQL like this (
SELECT * from CONTACT) - Next step is to use sqoop import command like this
As you will notice this command is same as hive import command that i used in last blog entry to import content of RDMBS into text file, only difference is i had to addsqoop import --connect jdbc:mysql://macos/test --table Address -m 1 --hive-import--hive-importswitch - This command takes care of first creating Contact table into Hive and then importing content of CONTACT table from RDMBS into CONTACT table in Hive. Now i can see content of Contact table in Hive like this
Importing data from RDBMS into Hadoop using sqoop
Apache Sqoop lets you import content of RDBMS into Hadoop. By default it will import content of a table into hadoop text file with columns separated by , and rows separated by new line. I wanted to try this feature out so i decided to import table from MySQL database on my local machine into HDFS using Sqoop
- First i created a CONTACT table in my local like this
CREATE TABLE `CONTACT` ( `contactid` int(11) NOT NULL, `FNAME` varchar(45) DEFAULT NULL, `LNAME` varchar(45) DEFAULT NULL, `EMAIL` varchar(45) DEFAULT NULL, PRIMARY KEY (`contactid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -
Then i had to add few records into CONTACT table by using this syntax
INSERT INTO `test`.`CONTACT`(`contactid`,`FNAME`,`LNAME`,`EMAIL`)VALUES(1,'Sunil','Patil','sdpatil@gmail.com'); - Then on the command line i had to execute following command to run Sqoop so that it imports content of
This command tells sqoop to connect to test database in mysql on localhostsqoop import --connect jdbc:mysql://localhost/test --table Contactjdbc:mysql://localhost/testand import content of CONTACT table. - After executing the command when i looked into the HDFS i could see that there is Contact directory (same as table name, if you want to use different directory name then table name pass --target-dir argument ), that directory contains 4 files.
- Now if i look inside one of the part-m files i could see it has content of CONTACT table dumped inside it like this
- By default sqoop opens multiple threads to import content of the table. If you want you can control number of map jobs it runs. In my case the CONTACT table has only 12 rows so i want sqoop to run only 1 map job, so i used following command
sqoop import --connect jdbc:mysql://localhost/test --table Contact --target-dir contact1 -m 1
Reading content of ElasticSearch index into Pig Script
In the Using ElasticSearch for storing ouput of Pig Script , i built a sample for storing output of Pig Script into ElasticSearch. I wanted to try out the reverse, in which i wanted to use Index/Search Result in elastic search as input into Pig Script, so i built this sample
- First follow step 3 in the Using ElasticSearch for storing ouput of Pig Script to download and upload the ElasticSearch Hadoop jars into HDFS store.
- After that create a pig script like this,
In this script first 2 lines are used to make the ElasticSearch Hadoop related jars available to Pig. Then the DEFINE statement is creating alias for
org.elasticsearch.hadoop.pig.EsStorageand giving it a simple/user friendly name of ES. Then the 4th line is telling Pig to load the content ofpig/cricketindex on local elastic search into variable A. The last line is used for dumping content of variable A.REGISTER /user/root/elasticsearch-hadoop-2.0.0.RC1/dist/elasticsearch-hadoop-2.0.0.RC1.jar REGISTER /user/root/elasticsearch-hadoop-2.0.0.RC1/dist/elasticsearch-hadoop-pig-2.0.0.RC1.jar DEFINE ES org.elasticsearch.hadoop.pig.EsStorage; A = LOAD 'pig/cricket' USING ES; DUMP A;
v = LOAD 'pig/cricket' USING org.elasticsearch.pig.EsStorage command to load the content of ES and it kept throwing the following error. I realized that i was using the wrong package name
grunt> v = LOAD 'pig/cricket' USING org.elasticsearch.pig.EsStorage;
2014-05-14 15:56:48,873 [main] ERROR org.apache.pig.tools.grunt.Grunt - ERROR 1070: Could not resolve org.elasticsearch.pig.EsStorage using imports: [, java.lang., org.apache.pig.builtin., org.apache.pig.impl.builtin.]
Details at logfile: /root/pig_1400106825043.log
Using ElasticSearch for storing ouput of Pig Script
I wanted to learn how to use ElasticSearch for storing output of Pig Script. So i did create this simple text file that has names of cricket players and their role in the team and email id. Then i used Pig script for simply loading the text file into Elastic Search. I used following steps
- First i did create cricket.txt file that contains the crickets information like this
Virat Kohli batsman virat@bcci.com MahendraSingh Dhoni batsman mahendra@bcci.com Shikhar Dhawan batsman shikhar@bcci.com -
The next step was to upload the cicket.txt file to HDFS /user/root directory
hdfs dfs -copyFromLocal cricket.txt /user/root/cricket.txt - After that i did download the ElasticSearch Hadoop zip and i did expand it on my local. After that i decided to upload the whole elasticsearch-hadoop-2.0.0.RC1 directory to HDFS so that it is available from all the clusters
dfs dfs -copyFromLocal elasticsearch-hadoop-2.0.0.RC1/ /user/root/ -
Then i did create this cricketes.pig script which registers the ElasticSearch related jar files into pig as first step then, it loads the content of cricket.txt file into cricket variable and then stores that content into
pig/cricketindex on local host/* Register the elasticsearch hadoop related jar files */ REGISTER /user/root/elasticsearch-hadoop-2.0.0.RC1/dist/elasticsearch-hadoop-2.0.0.RC1.jar REGISTER /user/root/elasticsearch-hadoop-2.0.0.RC1/dist/elasticsearch-hadoop-pig-2.0.0.RC1.jar -- Load the content of /user/root/cricket.txt into Pig cricket = LOAD '/user/root/cricket.txt' AS( fname:chararray, lname:chararray, skill: chararray, email: chararray); DUMP cricket; -- Store the content of cricket variable into instance of elastic search on local server, into pig/crciket index STORE cricket into 'pig/cricket' USING org.elasticsearch.hadoop.pig.EsStorage;
pig/cricket index on ES and i could see the content of text file like this
Using elasticsearch as external data store with apache hive
ElasticSearch has this feature in which you can configure Hive table that actually points to index in ElasticSearch. I wanted to learn how to use this feature so i followed these steps
- First i did create contact/contact index and type in ElasticSearch and i did insert 4 records in it like this
- Next i did download ElasticSearch Hadoop zip file on my Hadoop VM by executing following command
I did expand the elasticsearch-hadoop-2.0.0.RC1.zip in the /root directorywget http://download.elasticsearch.org/hadoop/elasticsearch-hadoop-2.0.0.RC1.zip - Next i had to start the hive console by executing following command, take a look at how i had to add elasticsearch-hadoop-2.0.0.RC1.jar to the aux.jars.path
hive -hiveconf hive.aux.jars.path=/root/elasticsearch-hadoop-2.0.0.RC1/dist/elasticsearch-hadoop-2.0.0.RC1.jar -
Next i did define artists table in hive that points to contact index in the elasticsearch server like this
CREATE EXTERNAL TABLE artists ( fname STRING, lname STRING, email STRING) STORED BY 'org.elasticsearch.hadoop.hive.EsStorageHandler' TBLPROPERTIES('es.resource' = 'contact/contact', 'es.index.auto.create' = 'false') ; - Once the table is configured i could query it like any normal Hive table like this
Using ElasticSearch to store output of MapReduce program
I wanted to use use ElasticSearch for storing the output of MapReduce program. So i modified the WordCount(HelloWorld) MapReduce program
so that it stores output in ElasticSearch instead of Text File. You can download the complete project from here
-
First change the maven build script to declare dependency on
elasticsearch-hadoop-mrlike this, I had to try out few combination before this worked (Watch out for jackson mapper version mismatch) -
Next change your MapReduce Driver class, to use
EsOutputFormatas output format. You will have to set value ofes.nodesproperty to set the host and port of elastic search server that you want to use for storing output. THe value ofes.resourcepoints to the Index and type name of elastic search where output should be stored. In my case ElasticSearch is running on local machine.public int run(String[] args) throws Exception { if (args.length != 2) { System.err.printf("Usage: %s [generic options] <input> <output>\n", getClass().getSimpleName()); ToolRunner.printGenericCommandUsage(System.err); return -1; } Job job = new Job(); job.setJarByClass(WordCount.class); job.setJobName("WordCounter"); logger.info("Input path " + args[0]); logger.info("Oupput path " + args[1]); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); //Configuration for using ElasticSearch as OutputFormat Configuration configuration = job.getConfiguration(); configuration.set("es.nodes","localhost:9200"); configuration.set("es.resource","hadoop/wordcount2"); job.setOutputFormatClass(EsOutputFormat.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(MapWritable.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); job.setMapperClass(WordCountMapper.class); job.setReducerClass(WordCountReducer.class); int returnValue = job.waitForCompletion(true) ? 0:1; System.out.println("job.isSuccessful " + job.isSuccessful()); return returnValue; } - I had to start ElasticSearch 1.1 server on my local machine as last step before starting MapReduce program
- After running the program when i search wordcount2 index i found results like this
Using WebHDFS as input and output for MapReduce program
In the WordCount(HelloWorld) MapReduce program blog i talked about how to create simple WordCount MapReduce program. Then in the WebHDFS REST API entry i blogged about how to configure WebHDFS end point for your hadoop installation. I wanted to combine both those things so that my MapReduce program reads input using WebHDFS and writes output back to HDFS using WebHDFS.
First i changed the program arguments to use webhdfs URL for both input and output to MapReduce program.
hadoop jar WordCount.jar webhdfs://172.16.225.192:50070/test/startupdemo.txt webhdfs://172.16.225.192:50070/output
When i tried to run this program i got org.apache.hadoop.security.AccessControlException exception, so in this case it was taking the login user name on my machine (I run hadoop on vm and my Eclipse IDE with MapReduce on my machine directly) and using it to to run MapReduce program. Since the HDFS system does not allow user sunil to create any files in HDFS.
14/05/09 10:10:18 WARN mapred.LocalJobRunner: job_local_0001
org.apache.hadoop.security.AccessControlException: Permission denied: user=gpzpati, access=WRITE, inode="/":hdfs:supergroup:drwxr-xr-x
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.apache.hadoop.ipc.RemoteException.instantiateException(RemoteException.java:90)
at org.apache.hadoop.ipc.RemoteException.unwrapRemoteException(RemoteException.java:57)
at org.apache.hadoop.hdfs.web.WebHdfsFileSystem.validateResponse(WebHdfsFileSystem.java:280)
at org.apache.hadoop.hdfs.web.WebHdfsFileSystem.run(WebHdfsFileSystem.java:427)
at org.apache.hadoop.hdfs.web.WebHdfsFileSystem.mkdirs(WebHdfsFileSystem.java:469)
at org.apache.hadoop.fs.FileSystem.mkdirs(FileSystem.java:1731)
at org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter.setupJob(FileOutputCommitter.java:82)
at org.apache.hadoop.mapred.LocalJobRunner$Job.run(LocalJobRunner.java:236)
Caused by: org.apache.hadoop.security.AccessControlException: Permission denied: user=gpzpati, access=WRITE, inode="/":hdfs:supergroup:drwxr-xr-x
at org.apache.hadoop.hdfs.web.JsonUtil.toRemoteException(JsonUtil.java:167)
at org.apache.hadoop.hdfs.web.WebHdfsFileSystem.validateResponse(WebHdfsFileSystem.java:279)
... 5 more
So i changed the main method of my program to wrap it in UserGroupInformation.doAs() call. In that call i am overriding name of the user used for running MapReduce to hdfs. Now it works ok.
Note: In order for this program to work your NameNode and DataNodes should have valid name (network recognizable). Because when you run the MapReduce first it makes OPEN request to the webhdfs://172.16.225.192:50070/test/startupdemo.txt URL, the NameNode will send a redirect response with URL pointing to data node like this
$curl -i 'http://172.16.225.192:50070/webhdfs/v1/test/startupdemo.txt?op=OPEN'
HTTP/1.1 307 TEMPORARY_REDIRECT
Cache-Control: no-cache
Expires: Tue, 06 May 2014 21:44:56 GMT
Date: Tue, 06 May 2014 21:44:56 GMT
Pragma: no-cache
Expires: Tue, 06 May 2014 21:44:56 GMT
Date: Tue, 06 May 2014 21:44:56 GMT
Pragma: no-cache
Location: http://ubuntu:50075/webhdfs/v1/test/startupdemo.txt?op=OPEN&namenoderpcaddress=localhost:9000&offset=0
Content-Type: application/octet-stream
Content-Length: 0
Server: Jetty(6.1.26)
Now if your Datanode hostname which is ubuntu in my case is not directly addressable then it will fail with error like this, you can fix this issue by mapping name ubuntu to the right ip in your /etc/host file.
14/05/09 10:16:34 WARN mapred.LocalJobRunner: job_local_0001
java.net.UnknownHostException: ubuntu
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:223)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:431)
at java.net.Socket.connect(Socket.java:527)
at java.net.Socket.connect(Socket.java:476)
at sun.net.NetworkClient.doConnect(NetworkClient.java:163)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:424)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:538)
at sun.net.www.http.HttpClient.(HttpClient.java:214)
at sun.net.www.http.HttpClient.New(HttpClient.java:300)
at sun.net.www.http.HttpClient.New(HttpClient.java:319)
at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:987)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:923)
at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:841)
at sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2156)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1390)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:379)
at org.apache.hadoop.hdfs.web.WebHdfsFileSystem.validateResponse(WebHdfsFileSystem.java:264)
at org.apache.hadoop.hdfs.web.WebHdfsFileSystem.access$000(WebHdfsFileSystem.java:106)
at org.apache.hadoop.hdfs.web.WebHdfsFileSystem$OffsetUrlInputStream.checkResponseCode(WebHdfsFileSystem.java:688)
at org.apache.hadoop.hdfs.ByteRangeInputStream.openInputStream(ByteRangeInputStream.java:121)
at org.apache.hadoop.hdfs.ByteRangeInputStream.getInputStream(ByteRangeInputStream.java:103)
at org.apache.hadoop.hdfs.ByteRangeInputStream.read(ByteRangeInputStream.java:158)
at java.io.DataInputStream.read(DataInputStream.java:83)
at org.apache.hadoop.util.LineReader.readDefaultLine(LineReader.java:209)
at org.apache.hadoop.util.LineReader.readLine(LineReader.java:173)
at org.apache.hadoop.mapreduce.lib.input.LineRecordReader.nextKeyValue(LineRecordReader.java:114)
at org.apache.hadoop.mapred.MapTask$NewTrackingRecordReader.nextKeyValue(MapTask.java:458)
at org.apache.hadoop.mapreduce.task.MapContextImpl.nextKeyValue(MapContextImpl.java:76)
at org.apache.hadoop.mapreduce.lib.map.WrappedMapper$Context.nextKeyValue(WrappedMapper.java:85)
at org.apache.hadoop.mapreduce.Mapper.run(Mapper.java:139)
at org.apache.hadoop.mapred.MapTask.runNewMapper(MapTask.java:645)
at org.apache.hadoop.mapred.MapTask.run(MapTask.java:325)
at org.apache.hadoop.mapred.LocalJobRunner$Job.run(LocalJobRunner.java:263)
14/05/09 10:16:35 INFO mapred.JobClient: map 0% reduce 0%
14/05/09 10:16:35 INFO mapred.JobClient: Job complete: job_local_0001
14/05/09 10:16:35 INFO mapred.JobClient: Counters: 0
job.isSuccessful false
Subscribe to:
Posts (Atom)









