Wednesday, August 28, 2013

MapReduce with Hadoop 1.2.1 Part One - Install Hadoop on Mac, Run MapReduce job to count words and produce sorted output


I’ve done a bit of work with Hadoop and MapReduce in the past years, and would like to use this blog post to share my experience. 

MapReduce is a software framework introduced by Google to support distributed computing on large data sets on clusters of computers. MapReduce offers an attractive, easy computational model that is data scalable. "Programs written in this functional style are automatically parallelized and executed on a large cluster of commodity machines. The run-time system takes care of the details of partitioning the input data, scheduling the program’s execution across a set of machines, handling machine failures, and managing the required inter-machine communication. This allows programmers without any experience with parallel and distributed systems to easily utilize the resources of a large distributed system."

Apache Hadoop is an open-source software framework that supports data-intensive distributed applications, licensed under the Apache v2 license. It supports the running of applications on large clusters of commodity hardware. 

Here is an illustration of how MapReduce works:






A Mapper
    Accepts (key,value) pairs from the input
    Produces intermediate (key,value) pairs, which are then shuffled
A Reducer
    Accepts intermediate (key,value) pairs
    Produces final (key,value) pairs for the output
A driver
    Specifies which inputs to use, where to put the outputs
    Chooses the mapper and the reducer to use






Install Hadoop 1.2.1 on Mac


Step 1: Enable SSH to localhost

Go to System Preferences > Sharing.
Make sure “Remote Login” is checked.

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

Make sure in the end you can ssh to the localhost 
$ ssh localhost


Step 2: Install Hadoop

Download the latest Hadoop core (Hadoop hadoop-1.2.1.tar.gz), untag it and move to 
/usr/local

$ tar xvfz hadoop-1.2.1.tar.gz 
$ mv hadoop-1.2.1 /usr/local/

Most likely, you need the root privilege to do the mv . 
$ sudo mv hadoop-1.2.1 /usr/local/ 


Step 3: Configure Hadoop

Change directory to the  where Hadoop is installed and configure the Hadoop. 

$ cd /usr/local/hadoop-1.2.1
$ mkdir /usr/local/hadoop-1.2.1/dfs/

Add the following to conf/core-site.xml

<property>
    <name>fs.default.name</name>
    <value>hdfs://localhost:9000</value>
</property>


Add the following to conf/hdfs-site.xml

<configuration>

<property>
<name>dfs.name.dir</name>
        <value>/usr/local/hadoop-1.2.1/dfs/name</value>
        <description>Path on the local filesystem where the NameNode stores the namespace and transactions logs persistently</description>
</property>


<property>
        <name>dfs.data.dir</name>
        <value>/usr/local/hadoop-1.2.1/dfs/data</value>
        <description>Comma separated list of paths on the local filesystem of a DataNode where it should store its blocks.</description>
</property>

<property>
    <name>dfs.replication</name>
    <value>1</value>
</property>

</configuration>


Add the following to conf/mapred-site.xml

<property>
    <name>mapred.job.tracker</name>
    <value>localhost:9001</value>
</property>


Add the followings to conf/hadoop-env.sh

export HADOOP_OPTS="-Djava.security.krb5.realm= -Djava.security.krb5.kdc="
export JAVA_HOME=/Library/Java/Home



Step 4: Configure environment variables for Hadoop

Edit ~/.bash_profile, to add the followings : 

## set Hadoop environment 
export HADOOP_HOME=/usr/local/hadoop-1.2.1
export HADOOP_CONF_DIR=/usr/local/hadoop-1.2.1/conf
export PATH=$PATH:$HADOOP_HOME/bin


Step 5: Format Hadoop filesystem

Run the hadoop command to format the HDFS system. 

$ hadoop namenode -format

Step 6: Start Hadoop

Run the start-all.sh command to start the Hadoop. 

$ start-all.sh

To verify that all Hadoop processes are running:

$ jps
9460 Jps
9428 TaskTracker
9344 JobTracker
9198 DataNode
9113 NameNode
9282 SecondaryNameNode



To run a sample Hadoop MapReduce job (calculating Pi) that comes from hadoop-examples-1.2.1.jar:
$ hadoop jar /usr/local/hadoop-1.2.1/./hadoop-examples-1.2.1.jar pi 10 100


To take a look at hadoop logs:
$ ls -altr /usr/local/hadoop-1.2.1/logs/


To stop hadoop:
$ stop-all.sh 

Web UI for Hadoop NameNode: http://localhost:50070/
Web UI for Hadoop JobTracker: http://localhost:50030/



Run MapReduce job to count words and produce sorted output 


Use mvn to start a Hadoop MapReduce Java project:
$ mvn archetype:generate -DgroupId=com.lei.hadoop -DartifactId=MapReduceWithHadoop -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false 


$ cd MapReduceWithHadoop/


Add the following Hadoop dependency to pom.xml: 
<dependency>
     <groupId>org.apache.hadoop</groupId>
     <artifactId>hadoop-core</artifactId>
     <version>1.2.1</version>
</dependency>


To build the target jar: 
$ mvn clean compile package 


To generate eclipse project: 
$ mvn eclipse:eclipse 


To run MapReduce job that counts words in text files at local folder wordcount_input, and generate result in sorted format: 
$ hadoop jar ./target/MapReduceWithHadoop-1.0-SNAPSHOT.jar com.lei.hadoop.countword.CountWordsV2 ./wordcount_input ./wordcount_output 


To see the result from output folder wordcount_output: 
$ tail wordcount_output/part-00000
5 Apache
5 a
6 distributed
8 data
8 to
8 of
10 and
10 Hadoop
11 for
12 A



Mapper for WordCount:
    
  // Maper to send <word, 1> to OutputCollector
  public void map(LongWritable key, Text value, 
    OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
   String line = (caseSensitive) ? value.toString() : value.toString().toLowerCase();
   for (String pattern : patternsToSkip) {
    line = line.replaceAll(pattern, "");
   }
     
   StringTokenizer tokenizer = new StringTokenizer(line);
   while (tokenizer.hasMoreTokens()) {
    word.set(tokenizer.nextToken());
    output.collect(word, one);
    reporter.incrCounter(Counters.INPUT_WORDS, 1);
   }
     
   if ((++numRecords % 100) == 0) {
    reporter.setStatus("Finished processing " + numRecords + " records " + "from the input file: " + inputFile);
   }
  }


Reducer for WordCount:
    
 public static class CountWordsV2Reduce extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
  // Reducer to sum up word count, and send them to  OutputCollector
  public void reduce(Text key, Iterator<IntWritable> values, 
    OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
   int sum = 0;
   while (values.hasNext()) {
    sum += values.next().get();
   }
   output.collect(key, new IntWritable(sum));
  }
 }


Mapper for WordSort:
    

 /**
  * 
  * @author stones333
  *
  */
 public static class SortWordsMap extends MapReduceBase 
  implements Mapper<LongWritable, Text, IntWritable, Text> {
  @Override
  public void map(LongWritable key, Text value, 
    OutputCollector<IntWritable, Text> collector, 
    Reporter reporter) throws IOException 
  {
   String line = value.toString();
   StringTokenizer stringTokenizer = new StringTokenizer(line);

   int number = 0; 
   String word = "";

   if(stringTokenizer.hasMoreTokens())
   {
    String strWord= stringTokenizer.nextToken();
    word = strWord.trim();
   }

   if( stringTokenizer.hasMoreElements())
   {
    String strNum = stringTokenizer.nextToken();
    number = Integer.parseInt(strNum.trim());
   }

   collector.collect(new IntWritable(number), new Text(word));

  }
 }



Reducer for WordSort:
    
 /**
  * 
  * @author stones333
  *
  */
 public static class SortWordsReduce extends MapReduceBase implements Reducer<IntWritable, Text, IntWritable, Text> {
  
  public void reduce(IntWritable key, Iterator<Text> values, OutputCollector<IntWritable, Text> output, Reporter reporter) throws IOException
     {
         while((values.hasNext()))
         {
          output.collect(key, values.next());
         }

     }
 }


You can access the code from https://github.com/leizou123/MapReduceWithHadoop

 $ git clone https://github.com/leizou123/MapReduceWithHadoop


Have fun and enjoy the journey.


Monday, April 15, 2013

Simple Java Demo to show how to wait threads to finish

Here is a small program that illustrate how to use CountDownLatch to wait Java threads to finish. The example is self-explanatory.
    

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * small demo to use CountDownLatch to wait for multiple threads to finish   
 * 
 * @author stones333
 *
 */
public class FixedThreadPool {

 private CountDownLatch countDownLatch=null;
 
 /**
  * 
  * @param count
  */
    public void startParallelWork(int count) {
        ExecutorService executorService = Executors.newFixedThreadPool( count );
 
        countDownLatch=new CountDownLatch(count); 
        for (int i = 0; i < count; i++) {
            Worker worker = new Worker(this.countDownLatch, i); 
            executorService.execute(worker);
        }
        executorService.shutdown();
    }

    /**
     * 
     * @throws InterruptedException
     */
    public void waitForThreadstoFinish() throws InterruptedException {
    
     System.out.println("START WAITING");
     if (countDownLatch!=null) {
      countDownLatch.await();
     }
     System.out.println("DONE WAITING");
    }

    
    static public class Worker implements Runnable {
     private CountDownLatch countDownLatch;
     private int sleepTime;
     
     
     /**
      * 
      * @param countDownLatch
      * @param sleepTime in second
      */
        public Worker(CountDownLatch countDownLatch, int sleepTime) {
   super();
   this.countDownLatch = countDownLatch;
   this.sleepTime = sleepTime;
  }



  @Override
        public void run() {
         
         System.out.println("Start-" + Thread.currentThread().getName());
         try {
    Thread.sleep( (long) sleepTime * 1000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
         System.out.println("End-" + Thread.currentThread().getName());
         if (this.countDownLatch!=null) {
          this.countDownLatch.countDown();
         }
        }

    }

    public static void main(String[] args) throws Exception {
     FixedThreadPool fixedThreadPool = new FixedThreadPool();
     fixedThreadPool.startParallelWork(10);
     fixedThreadPool.waitForThreadstoFinish();
    }
}

Tuesday, February 12, 2013

Quick Start Cassandra with Java Client Hector


I found Cassandra is very useful data store for different kinds of applications. 

Apache Cassandra is an open source, distributed, decentralized, elastically scalable, highly available, fault-tolerant, tunably consistent, column-oriented database that bases its distribution design on Amazon’s Dynamo and its data model on Google’s Bigtable. Created at Facebook, it is now used at some of the most popular sites on the Web. It is distributed and decentralized, elastic scalable, and with high availability, fault tolerance, tuneable consistency. 

Data in Cassandra is designed to be distributed over several machines/nodes operating together that appear as a single instance to the end user. Cassandra assigns data to nodes in the cluster by arranging them in a ring. A cluster is a container for keyspaces. And keyspace consists of column families and columns.  I like to view Cassandra column families as a four-dimensional hash:

[Keyspace][ColumnFamily][Key][Column]

I am using Cassandra  version 0.8 for this posting and you can find more information at  http://www.datastax.com/docs/0.8/

I following the wiki page to install Cassandra 0.8 at localhost port 9160. 

There are number of choices of client, they are listed at http://wiki.apache.org/cassandra/ClientOptions.   Personally, I like Hector

  


Here is mvn project file:
    
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lei.cassandra.sample</groupId>
  <artifactId>cassandra-quickstart-with-hector</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>cassandra-quickstart-with-hector</name>
  <url>http://maven.apache.org</url>

  <properties>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
 <dependency>
 <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
 </dependency>

 <dependency>
  <groupId>me.prettyprint</groupId>
  <artifactId>hector-core</artifactId>
  <version>1.0-2</version>
 </dependency>

 <dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.6.1</version>
 </dependency>

  </dependencies>
</project>



connect/disconnect from Cassandra cluster:
    
 /**
  * Connect to Cassandra's demo keyspace at localhost's cluster at 9160  
  */
 public void connect() {
  cluster = HFactory.getOrCreateCluster(DEF_CLUSTER, DEF_LOCALHOST_ID);
  System.out.println("Cluster instantiated");
  ConfigurableConsistencyLevel ccl = new ConfigurableConsistencyLevel();
  ccl.setDefaultReadConsistencyLevel(HConsistencyLevel.ONE);
  keyspace = HFactory.createKeyspace("demo", cluster, ccl);
 }

 /**
  * Disconnect from localhost's Cassandra cluster at 9160  
  */
 public void disconnect() {
  cluster.getConnectionManager().shutdown();
 }





To insert a row and its columns:
    
 /**
  * insert a row and its columns for the rowkey 
  * 
  * @return row's column values associated the rowkey
  */
    public List<Object> insertRow () {
     String rowkey = "mike_johnson";
     
     ColumnFamilyTemplate<String, String> userCFTemplate = new ThriftColumnFamilyTemplate<String, String>(
       this.getKeyspace(),
       DEF_USER_CF_NAME, StringSerializer.get(), StringSerializer.get());

     ColumnFamilyUpdater<String, String> updater = userCFTemplate.createUpdater(rowkey);
     updater.setString("full_name", "mike johnson");
     updater.setString("address", "90 Elm Street");
     updater.setString("state", "NY");
     updater.setString("gender", "male");
     updater.setString("email", "mikej@gmail.com");
     updater.setLong("birth_year", new Long(1980));
     try {
      userCFTemplate.update(updater);
      System.out.println("value inserted");
     } catch (HectorException e) {
      System.out.println("Error during insertion : " + e.getMessage() );

     }  
     
     return this.getColumnValues("mike_johnson");
    }


 /**
  * insert a row and its columns with the rowkey using Mutator  
  * 
  * @return row's column values associated the rowkey
  */
    public List<Object> insertRowV2 () {
     Mutator<String> mutator = HFactory.createMutator(this.getKeyspace(), StringSerializer.get() );
     mutator.addInsertion("michael_johnson", "users", HFactory.createStringColumn("full_name", "michael johnson"));
     mutator.addInsertion("michael_johnson", "users", HFactory.createStringColumn("address", "9890 Elm Street"));
     mutator.addInsertion("michael_johnson", "users", HFactory.createStringColumn("state", "CT"));
     mutator.addInsertion("michael_johnson", "users", HFactory.createStringColumn("gender", "male"));
     mutator.addInsertion("michael_johnson", "users", HFactory.createStringColumn("email", "michael_johnson@gmail.com"));
     mutator.addInsertion("michael_johnson", "users", HFactory.createColumn("birth_year", new Long(1978), StringSerializer.get(), LongSerializer.get()));
     
     MutationResult mr = mutator.execute();
     
     return this.getColumnValues("michael_johnson");
    }




To query a column value for a key:
    

 /**
  * build the query for fetching a row's column
  * 
  * @return QueryResult
  */
    public QueryResult<HColumn<String,String>> query() {        
        ColumnQuery<String, String, String> columnQuery = HFactory.createStringColumnQuery(this.getKeyspace());
        columnQuery.setColumnFamily(DEF_USER_CF_NAME);
        columnQuery.setKey("john_smith");
        columnQuery.setName("full_name");
        QueryResult<HColumn<String, String>> result = columnQuery.execute();
        return result;
    }




To query columnis for a key:
    

 /**
  * build the query for fetching a row's columns 
  * 
  * @return QueryResult
  */
    public QueryResult<ColumnSlice<String,String>> query() {
        SliceQuery<String, String, String> sliceQuery = 
            HFactory.createSliceQuery(this.getKeyspace(), StringSerializer.get(), StringSerializer.get(), StringSerializer.get());
        sliceQuery.setColumnFamily(DEF_USER_CF_NAME);
        sliceQuery.setKey("mary_leaman");
        sliceQuery.setColumnNames("email","full_name","gender","address", "state");
        QueryResult<ColumnSlice<String, String>> result = sliceQuery.execute();
        return result;
    }



To fetch column values:
    

 /**
  * getColumnVlues as a list objects from the rowkey
  *  
  * @param rowkey
  * 
  * @return row's column values associated the rowkey
  */
    public List<Object> getColumnValues (String rowkey) {
     List<Object> listColumnObj = new LinkedList<Object> ();
     
  try {
      ColumnFamilyTemplate<String, String> userCFTemplate = new ThriftColumnFamilyTemplate<String, String>(
        this.getKeyspace(),
        DEF_USER_CF_NAME, StringSerializer.get(), StringSerializer.get());

   ColumnFamilyResult<String, String> result = userCFTemplate.queryColumns(rowkey);
   String full_name = result.getString("full_name");
   String address = result.getString("address");
   String state = result.getString("state");
   String gender = result.getString("gender");
   String email = result.getString("email");
   Long birth_year = result.getLong("birth_year");
   
   System.out.println("full_name [" + full_name + "]");
   System.out.println("address [" + address + "]");
   System.out.println("state [" + state + "]");
   System.out.println("gender [" + gender + "]");
   System.out.println("birth_year [" + birth_year + "]");
   System.out.println("email [" + email + "]");
   
   listColumnObj.add(full_name);
   listColumnObj.add(address);
   listColumnObj.add(state);
   listColumnObj.add(gender);
   listColumnObj.add(email);
   listColumnObj.add(birth_year);
   
     } catch (HectorException e) {
      System.out.println("Error reading the column with key " + rowkey);
      System.out.println(e.getMessage());
     }

  return listColumnObj;
    }



To delete a row:
    

 /**
  * delete the row with rowkey
  * 
  * @return row's column values associated the rowkey
  */
    public List<Object> deleteRow() {
     
     List<Object> list = this.insertRow();
     String rowkey = "mike_johnson";
     ColumnFamilyTemplate<String, String> userCFTemplate = new ThriftColumnFamilyTemplate<String, String>(
       this.getKeyspace(),
       DEF_USER_CF_NAME, StringSerializer.get(), StringSerializer.get());
     try {
      userCFTemplate.deleteRow(rowkey) ;
      System.out.println("Row deleted .... ");
     } catch (HectorException e) {
      System.out.println("Error during deletion : " + e.getMessage() );
     }  
     
     return this.getColumnValues("mike_johnson");
    }


 /**
  * delete the row with rowkey using Mutator
  * 
  * @return row's column values associated the rowkey
  */
    public List<Object> deleteRowV2() {
     
     List<Object> list = this.insertRowV2();
     String rowkey = "michael_johnson";
     
        Mutator<String> mutator = HFactory.createMutator(this.getKeyspace(), StringSerializer.get() );
        mutator.addDeletion(rowkey, "users", null, StringSerializer.get() );
        MutationResult mr = mutator.execute();
     return this.getColumnValues(rowkey);
    }


To delete a row column :
    

 /**
  * delete a column value associated with rowkey 
  * 
  * @return row's column values associated the rowkey 
  */
    public List<Object> deleteRowColumn () {
     
     List<Object> list = this.insertRow();
     String rowkey = "mike_johnson";
     
     ColumnFamilyTemplate<String, String> userCFTemplate = new ThriftColumnFamilyTemplate<String, String>(
       this.getKeyspace(),
       DEF_USER_CF_NAME, StringSerializer.get(), StringSerializer.get());

     try {
      userCFTemplate.deleteColumn(rowkey, "email") ;
      System.out.println("Column deleted .... ");
     } catch (HectorException e) {
      System.out.println("Error during deletion : " + e.getMessage() );

     }  
     return this.getColumnValues("mike_johnson");
    }




To list all rows:
    

 /**
  * Demo to list all rows 
  * 
  * @param maxRowCount
  * @return
  */
 private List<Object> listAllEntries(int maxRowCount) {
     
     List<Object> list = new ArrayList<Object> ();

     RangeSlicesQuery<String, String, ByteBuffer> rangeSlicesQuery = 
          HFactory.createRangeSlicesQuery(this.getKeyspace(), StringSerializer.get(), StringSerializer.get(), ByteBufferSerializer.get())
            .setColumnFamily(DEF_USER_CF_NAME)
            .setRange(null, null, false, 10)
            .setRowCount(maxRowCount);

        String last_key = null;

        while (true) {
         rangeSlicesQuery.setKeys(last_key, null);

         QueryResult<OrderedRows<String, String, ByteBuffer>> result = rangeSlicesQuery.execute();
         OrderedRows<String, String, ByteBuffer> rows = result.get();
         Iterator<Row<String, String, ByteBuffer>> rowsIterator = rows.iterator();


            if (last_key != null && rowsIterator != null) {
             rowsIterator.next();   
            }

            while (rowsIterator.hasNext()) {
             Row<String, String, ByteBuffer> row = rowsIterator.next();
             last_key = row.getKey();

             if (row.getColumnSlice().getColumns().isEmpty()) {
              continue;
             }

             System.out.println("rowKey [" + row.getKey() + "]");
             
             for(HColumn<String, ByteBuffer> cols : row.getColumnSlice().getColumns())
             {
              String colName = cols.getName();
           System.out.println("colName [" + colName + "] colValue [" + getColumnValue(colName, cols.getValue() ) + "]");
             }
             list.add(row);
             
            }

            if (rows.getCount() < maxRowCount) {
                break;
            }
        }
        
        return list;
    }




Demo MultigetSliceQuery with multiple keys:
    

 /**
  * build the query with multiple keys
  * 
  * @return QueryResult
  */
    public QueryResult<Rows<String,String,String>> query() {        
        MultigetSliceQuery<String, String, String> multigetSlicesQuery =
          HFactory.createMultigetSliceQuery( this.getKeyspace(), StringSerializer.get(), StringSerializer.get(), StringSerializer.get());
        multigetSlicesQuery.setColumnFamily(DEF_USER_CF_NAME);
        multigetSlicesQuery.setColumnNames("email","full_name","gender","address", "state");        
        multigetSlicesQuery.setKeys("mary_leaman","john_smith");
        QueryResult<Rows<String, String, String>> results = multigetSlicesQuery.execute();
        return results;
    }



You can access the code at Github:
https://github.com/leizou123/cassandra-quickstart-with-hector.git

Have fun and enjoy the journey.

Wednesday, January 23, 2013

Quick Start Maven archetype for simple Servlet application - Hello World

Hey Joe,

Maven is the better way to go. For a very simple "Hello World!" Servlet app, you can get it going in 5 minutes or less. Just follow my steps below.

You need to install mvn. 3..0.3 is the version that I am using.


lei:webapps lei$ mvn -version
Apache Maven 3.0.3 (r1075438; 2011-02-28 09:31:09-0800)
Maven home: /usr/local/apache-maven-3.0.3


Prerequisite: assume you have a Servlet container installed on your system. I have apache-tomcat-7 installed at /usr/local/apache-tomcat-7.0.12.

Step 1: Use Maven archetype maven-archetype-webapp to start the mvn project. 


lei:tmp lei$ mvn archetype:create -DgroupId=com.lei.webapp.quickstart  -DartifactId=webapp-quick-start   -DarchetypeArtifactId=maven-archetype-webapp  
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-archetype-plugin:2.2:create (default-cli) @ standalone-pom ---
[WARNING] This goal is deprecated. Please use mvn archetype:generate instead
[INFO] Defaulting package to group ID: com.lei.webapp.quickstart
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-webapp:RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.lei.webapp.quickstart
[INFO] Parameter: packageName, Value: com.lei.webapp.quickstart
[INFO] Parameter: package, Value: com.lei.webapp.quickstart
[INFO] Parameter: artifactId, Value: webapp-quick-start
[INFO] Parameter: basedir, Value: /Users/lei/tmp
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /Users/lei/tmp/webapp-quick-start
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.860s
[INFO] Finished at: Wed Jan 23 10:55:02 PST 2013
[INFO] Final Memory: 7M/265M
[INFO] ------------------------------------------------------------------------
lei:tmp lei$ 


Step 2: Add servlet API javax.servlet as a Maven dependency to the mvn project file pom.xml. 


lei:tmp lei$ cd webapp-quick-start 
lei:webapp-quick-start lei$ vi pom.xml 

Add the following to pom.xml: 

<dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
</dependency>


Step 3: Add HelloServlet.java

lei:webapp-quick-start lei$ mkdir -p src/main/java/com/lei/webapp/quickstart 
lei:webapp-quick-start lei$ vi src/main/java/com/lei/webapp/quickstart/HelloServlet.java 

Add the following Java code:

package com.lei.webapp.quickstart;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

// com.lei.webapp.quickstart.HelloServlet
public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws ServletException, IOException {

        PrintWriter out = response.getWriter();
        out.println( "Hello World!" );
        out.flush();
        out.close();
    }
}


Step 4: Modify web.xml:

lei:webapp-quick-start lei$ vi ./src/main/webapp/WEB-INF/web.xml 

Add the following content: 

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <servlet>
        <servlet-name>HelloWorld</servlet-name>
        <servlet-class>com.lei.webapp.quickstart.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloWorld</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

Step 5: Compile and package:

lei:webapp-quick-start lei$ mvn compile test package
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building webapp-quick-start Maven Webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ webapp-quick-start ---
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ webapp-quick-start ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ webapp-quick-start ---
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ webapp-quick-start ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ webapp-quick-start ---
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /Users/lei/tmp/webapp-quick-start/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ webapp-quick-start ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-surefire-plugin:2.7.2:test (default-test) @ webapp-quick-start ---
[INFO] No tests to run.
[INFO] Surefire report directory: /Users/lei/tmp/webapp-quick-start/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
There are no tests to run.

Results :

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ webapp-quick-start ---
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ webapp-quick-start ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ webapp-quick-start ---
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /Users/lei/tmp/webapp-quick-start/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ webapp-quick-start ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-surefire-plugin:2.7.2:test (default-test) @ webapp-quick-start ---
[INFO] No tests to run.
[INFO] Skipping execution of surefire because it has already been run for this configuration
[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ webapp-quick-start ---
[INFO] Packaging webapp
[INFO] Assembling webapp [webapp-quick-start] in [/Users/lei/tmp/webapp-quick-start/target/webapp-quick-start]
[INFO] Processing war project
[INFO] Copying webapp resources [/Users/lei/tmp/webapp-quick-start/src/main/webapp]
[INFO] Webapp assembled in [148 msecs]
[INFO] Building war: /Users/lei/tmp/webapp-quick-start/target/webapp-quick-start.war
[INFO] WEB-INF/web.xml already added, skipping
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.091s
[INFO] Finished at: Wed Jan 23 11:13:02 PST 2013
[INFO] Final Memory: 5M/265M
[INFO] ------------------------------------------------------------------------
lei:webapp-quick-start lei$ 

Step 6: Deploy the webapp-quick-start.war to the weapps, where you have your Tomcat installed:

lei:webapp-quick-start lei$ cp -v /Users/lei/tmp/webapp-quick-start/target/webapp-quick-start.war /usr/local/apache-tomcat-7.0.12/webapps 
/Users/lei/tmp/webapp-quick-start/target/webapp-quick-start.war -> /usr/local/apache-tomcat-7.0.12/webapps/webapp-quick-start.war


Step 7: Start the Tomcat, and verify the following URL to verify it's working. 

http://localhost:4080/webapp-quick-start/hello

As you can tell, I have my Tomcat installed on part 4080. 

Have fun and enjoy the journey.




Saturday, January 5, 2013

Quick Start CEP with Esper - Stock Trading

Event processing is a method of tracking and analyzing (processing) streams of information (data) about things that happen (events), and deriving a conclusion from them. Complex event processing (CEP) is event processing that combines data from multiple sources to infer events or patterns that suggest more complicated circumstances. The goal of complex event processing is to identify meaningful events (such as opportunities or threats) and respond to them as quickly as possible. These events may be sales leads, orders or customer service calls. Or, they may be news items, text messages, social media posts, stock market feeds, traffic reports, weather reports, or other kinds of data. An event may also be defined as a "change of state," when a measurement exceeds a predefined threshold of time, temperature, or other value. You can find more about CEP at the wiki page

I use Esper as the CEP engine for this blogger to illustrate the complex event processing. Esper is open-source software available under the GNU General Public License (GPL). You can find it at http://esper.codehaus.org/. 

The financial services was an early adopter of CEP technology, using complex event processing to structure and contextualize available data so that it could inform trading behavior, specifically algorithmic trading, by identifying opportunities or threats that indicate traders (or automatic trading systems) should buy or sell.

Here is an example. Suppose a day trader Joe wants to buy Google, symbol GOOG. His target price is set at $90, and he likes to buy at a dip where price hits 10% below the moving average. Here is the Esper code to illustrate the stock trade:
    
public class StockTrade {

 private static final Log log = LogFactory.getLog(StockTrade.class);

  public static class StockTradeListener implements UpdateListener {
   private double avgPrice=-1.0;
   private double minPrice=-1.0;
   private double targetPrice=0.0;
   private double dropPercetage=0.0;
   private boolean buyNow=false;
   
   public void update(EventBean[] newEvents, EventBean[] oldEvents) {
          
    if (newEvents == null || newEvents.length == 0) return;

    EventBean event = newEvents[0];
    avgPrice = event.get("AvgPrice") == null ? 0.0 :  (double)(((Double)event.get("AvgPrice")).doubleValue());
    minPrice = event.get("MinPrice") == null ? 0.0 :  (double)(((Double)event.get("MinPrice")).doubleValue());
    
    if (avgPrice>0 && minPrice>0) {
     log.info("minPrice=" + minPrice + " avgPrice=" + avgPrice);
     if (minPrice<=targetPrice && minPrice<= (avgPrice* (100.00-dropPercetage) /100.00  ) ) {
      buyNow=true;
      log.info("buyNow=" + buyNow);
     }
    }
   }

   public StockTradeListener(double targetPrice, double dropPercetage) {
   super();
   this.targetPrice = targetPrice;
   this.dropPercetage = dropPercetage;
  }

  public boolean isBuyNow() { return buyNow; }
 }

 public boolean runStockTrade(String symbal, long interval, 
   double [] arrayPrice, double targetPrice, double dropPercetage ) throws InterruptedException {


  log.info("symbal=" + symbal);
  log.info("interval=" + interval);
  log.info("targetPrice=" + targetPrice);
  log.info("percetageDrop=" + dropPercetage);
  log.info("arrayPrice=" + Arrays.toString(arrayPrice));
  
        // Configuration
        Configuration config = new Configuration();
        config.addEventTypeAutoName("com.lei.cep.event");
        EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider(config);
        epService.initialize();

        // Creating a Statement
        String expression = "select avg(price) as AvgPrice, min(price) as MinPrice from PriceEvent.win:time(30 sec)";
        EPStatement statement = epService.getEPAdministrator().createEPL(expression);

        // Adding a Listener
        StockTradeListener listener = new StockTradeListener(targetPrice, dropPercetage);
        statement.addListener(listener);

        for (int i=0; i<arrayPrice.length; i++) {
            // Sending events
            Thread.sleep(interval);
            PriceEvent event = new PriceEvent(symbal, arrayPrice[i]);
            epService.getEPRuntime().sendEvent(event);
            if ( listener.isBuyNow() ) return true; 
        }

  return false;
 }

}

Here is the event bean Java class:
 
public class PriceEvent {

 private String symbol;
    private double price;
 
    
    public PriceEvent(String symbol, double price) {
  super();
  this.symbol = symbol;
  this.price = price;
 }

 public double getPrice() { return price; }
 public void setPrice(double price) { this.price = price; }

 public String getSymbol() { return symbol; }
 public void setSymbol(String symbol) { this.symbol = symbol; }

 @Override
 public String toString() {
  return "PriceEvent [symbol=" + symbol + ", price=" + price + "]";
 }
    
    
}


Here is the how you can get the source:
 

$ git clone https://github.com/leizou123/CepSample.git 

$ cd CepSample

$ mvn compile test 




Here is the test result:

......
Running com.lei.cep.StockTradeTest 19:39:25,419 INFO [StockTrade] start testStockTradeNotBuy() 19:39:25,419 INFO [StockTrade] symbal=GOOG 19:39:25,419 INFO [StockTrade] interval=1000 19:39:25,419 INFO [StockTrade] targetPrice=90.0 19:39:25,419 INFO [StockTrade] percetageDrop=10.0 19:39:25,420 INFO [StockTrade] arrayPrice=[100.0, 99.0, 100.5, 101.01, 100.0, 102.01, 101.0, 99.5, 100.0, 101.0, 102.0, 100.0, 99.99, 98.87, 93.99, 95.09, 90.99, 89.8, 93.9] 19:39:26,919 INFO [StockTrade] minPrice=100.0 avgPrice=100.0 19:39:27,921 INFO [StockTrade] minPrice=99.0 avgPrice=99.5 19:39:28,923 INFO [StockTrade] minPrice=99.0 avgPrice=99.83333333333333 19:39:29,924 INFO [StockTrade] minPrice=99.0 avgPrice=100.1275 19:39:30,925 INFO [StockTrade] minPrice=99.0 avgPrice=100.102 19:39:31,927 INFO [StockTrade] minPrice=99.0 avgPrice=100.42 19:39:32,928 INFO [StockTrade] minPrice=99.0 avgPrice=100.50285714285714 19:39:33,930 INFO [StockTrade] minPrice=99.0 avgPrice=100.3775 19:39:34,931 INFO [StockTrade] minPrice=99.0 avgPrice=100.33555555555556 19:39:35,932 INFO [StockTrade] minPrice=99.0 avgPrice=100.402 19:39:36,934 INFO [StockTrade] minPrice=99.0 avgPrice=100.54727272727273 19:39:37,935 INFO [StockTrade] minPrice=99.0 avgPrice=100.50166666666667 19:39:38,937 INFO [StockTrade] minPrice=99.0 avgPrice=100.46230769230769 19:39:39,938 INFO [StockTrade] minPrice=98.87 avgPrice=100.34857142857143 19:39:40,940 INFO [StockTrade] minPrice=93.99 avgPrice=99.92466666666668 19:39:41,941 INFO [StockTrade] minPrice=93.99 avgPrice=99.6225 19:39:42,943 INFO [StockTrade] minPrice=90.99 avgPrice=99.11470588235295 19:39:43,944 INFO [StockTrade] minPrice=89.8 avgPrice=98.59722222222223 19:39:44,946 INFO [StockTrade] minPrice=89.8 avgPrice=98.35000000000001 19:39:44,946 INFO [StockTrade] end testStockTradeNotBuy() buy GOOG? no 19:39:44,947 INFO [StockTrade] start testStockTradeBuy() 19:39:44,947 INFO [StockTrade] symbal=GOOG 19:39:44,947 INFO [StockTrade] interval=1000 19:39:44,947 INFO [StockTrade] targetPrice=90.0 19:39:44,947 INFO [StockTrade] percetageDrop=10.0 19:39:44,947 INFO [StockTrade] arrayPrice=[100.0, 99.0, 100.5, 106.01, 103.0, 107.01, 107.0, 108.5, 109.0, 105.0, 105.01, 102.0, 103.91, 105.0, 107.5, 102.0, 101.0, 104.01, 101.0, 102.91, 101.0, 99.5, 106.0, 101.0, 102.0, 100.0, 98.0, 89.0, 93.0] 19:39:46,265 INFO [StockTrade] minPrice=100.0 avgPrice=100.0 19:39:47,266 INFO [StockTrade] minPrice=99.0 avgPrice=99.5 19:39:48,268 INFO [StockTrade] minPrice=99.0 avgPrice=99.83333333333333 19:39:49,270 INFO [StockTrade] minPrice=99.0 avgPrice=101.3775 19:39:50,271 INFO [StockTrade] minPrice=99.0 avgPrice=101.702 19:39:51,273 INFO [StockTrade] minPrice=99.0 avgPrice=102.58666666666666 19:39:52,275 INFO [StockTrade] minPrice=99.0 avgPrice=103.21714285714286 19:39:53,277 INFO [StockTrade] minPrice=99.0 avgPrice=103.8775 19:39:54,279 INFO [StockTrade] minPrice=99.0 avgPrice=104.44666666666666 19:39:55,281 INFO [StockTrade] minPrice=99.0 avgPrice=104.502 19:39:56,282 INFO [StockTrade] minPrice=99.0 avgPrice=104.54818181818182 19:39:57,284 INFO [StockTrade] minPrice=99.0 avgPrice=104.33583333333333 19:39:58,286 INFO [StockTrade] minPrice=99.0 avgPrice=104.30307692307693 19:39:59,288 INFO [StockTrade] minPrice=99.0 avgPrice=104.35285714285715 19:40:00,290 INFO [StockTrade] minPrice=99.0 avgPrice=104.56266666666667 19:40:01,292 INFO [StockTrade] minPrice=99.0 avgPrice=104.4025 19:40:02,293 INFO [StockTrade] minPrice=99.0 avgPrice=104.20235294117647 19:40:03,294 INFO [StockTrade] minPrice=99.0 avgPrice=104.19166666666666 19:40:04,296 INFO [StockTrade] minPrice=99.0 avgPrice=104.02368421052631 19:40:05,297 INFO [StockTrade] minPrice=99.0 avgPrice=103.968 19:40:06,299 INFO [StockTrade] minPrice=99.0 avgPrice=103.82666666666667 19:40:07,300 INFO [StockTrade] minPrice=99.0 avgPrice=103.63000000000001 19:40:08,302 INFO [StockTrade] minPrice=99.0 avgPrice=103.73304347826088 19:40:09,304 INFO [StockTrade] minPrice=99.0 avgPrice=103.61916666666667 19:40:10,306 INFO [StockTrade] minPrice=99.0 avgPrice=103.5544 19:40:11,308 INFO [StockTrade] minPrice=99.0 avgPrice=103.4176923076923 19:40:12,310 INFO [StockTrade] minPrice=98.0 avgPrice=103.21703703703704 19:40:13,311 INFO [StockTrade] minPrice=89.0 avgPrice=102.70928571428571 19:40:13,311 INFO [StockTrade] buyNow=true 19:40:13,311 INFO [StockTrade] end testStockTradeBuy() buy GOOG? yes Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 47.893 sec
......


I hope you like this quick start on CEP with Esper post, and I certainly enjoy the software journey.



Monday, October 15, 2012

JVM Tuning for Low Latency High Throughput on Multi Core Linux Box

Recently, I have been tuning JVM parameters to achieve low latency & high throughput on HotSpot VM. For server side Java application, low latency is a very important requirement. In many cases, Java garbage collector pause is the serious threat. Under a very strict SLA, let's say response time under 100ms or less, it requires good amount of engineering effort to make the application behave under high stress environment .  

In my experience, before any JVM parameters tuning, I have spent lots of time to identify and remove thread contention points in the application. My development environment is 4 core Linux machine, JDK 1.6,  JBoss, and required SLA less than 100ms. There are 600 plus different JVM parameters. This article is about tuning GC for low latency for server side Java  applications, and the focus will be on those parameters that have bigger impact on the achieving low latency & high throughput.


    
$ /usr/java/jdk1.6.0_29/bin/java -XX:+PrintFlagsFinal -version 
     


The Java heap is divided into three main sections: Young Generation, Old Generation and the Permanent Generation.











Young Generation: The Eden Space of the Young Generation holds all the newly created objects. When this section fills, the Scavenge Garbage Collector clears out of memory all objects that are unreferenced. Objects that survive this scavenge moved to the "From" Survivor Space. The Survivor Space is a section of the Young Generation for these intermediate‐life objects. It has two equally‐sized subspaces "To" and “From” which are used by its algorithm for fast switching and cleanup. Once the Scavange GC is complete, the pointers on the two spaces are reversed: "To" becomes "From" and "From" becomes "To".


Old Generation: Once an object survives a given number of Scavenge GCs, it is promoted (or tenured) from the "To" Space to the Old Generation. Objects in this space are never garbage collected except in the two cases: Full Garbage Collection or Concurrent Mark‐and‐Sweep Garbage Collection. If the Old Generation is full and there is no way for the heap to expand, an Out‐of‐Memory error (OOME) is thrown and the JVM will crash. 


Permanent Generation: The Permanent Generation is where class files are kept. These are the result of compiled classes and jsp pages. If this space is full, it triggers a Full Garbage Collection. If the Full Garbage Collection cannot clean out old unreferenced classes and there is no room left to expand the Permanent Space, an Out‐of‐ Memory error (OOME) is thrown and the JVM will crash. 



HotSpot JVM may use one of 6 combinations of garbage collectors listed below.


Young collector
Old collector
JVM option
Serial (DefNew)
Serial Mark-Sweep-Compact
-XX:+UseSerialGC
Parallel scavenge (PSYoungGen)
Serial Mark-Sweep-Compact (PSOldGen)
-XX:+UseParallelGC
Parallel scavenge (PSYoungGen)
Parallel Mark-Sweep-Compact (ParOldGen)
-XX:+UseParallelOldGC
Serial (DefNew)
Concurrent Mark Sweep
-XX:+UseConcMarkSweepGC
-XX:-UseParNewGC
Parallel (ParNew)
Concurrent Mark Sweep
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
G1
-XX:+UseG1GC




A List of Stop the World Pauses:

  • Young space collections 
  • Full GCs – All collectors 
  • System GCs – Called via JMX or the application 
  • CMS Initial Mark Phase 
  • CMS Remark Phase 
  • CMS Concurrent Mode Failure



In my experiment, CMS gives the best results for low latency & high throughput. Here is summary of what I have learned. 

  1. JVM tuning is application specific. In depth knowledge of the application will help. And one needs to take a holistic when tuning. 
  2. Young Collections are fast and efficient. It is important to give objects the opportunity to die young. Smaller Young Space helps. 
  3. CMS is concurrent and requires CPU and it will compete with the application during collections.
  4. CMS fragments the Old Space and it makes Object allocations are more complicated. 
  5. Sizing the heap correctly is critical. Undersized heaps will make CMS work overtime, and worse it would cause CMS Concurrent Mode Failure. 
  6. Sizing the young ratio is important: 1) Size the survivor spaces appropriately 2) Configure the Tenuring Threshold appropriately  
  7. CMS to wait for a Young GC before starting. 

Here is the list of the recommended JVM settings for low latency & high throughput:


-server
-Xms2048m 
-Xmx2048m 
-XX:+UseConcMarkSweepGC 
-XX:+UseParNewGC
-XX:+AggressiveOpts
-XX:+CMSParallelRemarkEnabled
-XX:+CMSScavengeBeforeRemark
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=65
-XX:CMSWaitDuration=300000
-XX:GCTimeRatio=19
-XX:NewSize=128m
-XX:MaxNewSize=128m
-XX:PermSize=64m
-XX:MaxPermSize=64m
-XX:SurvivorRatio=88
-XX:TargetSurvivorRatio=88
-XX:MaxTenuringThreshold=15
-XX:MaxGCMinorPauseMillis=1
-XX:MaxGCPauseMillis=5
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./gc_heap_dump/
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-XX:+PrintTenuringDistribution
-Xloggc:./gc_log.log




Compare with 600 plus parameters to play with, this is a much shorter list. I hope you like this post, and enjoy your journey.