`

Using MongoDB for a Java Web App’s HttpSession(转)

 
阅读更多
处理tomcat的session

NoSql Sessions with Jetty7 and Jetty8

转 http://www.jamesward.com/2011/11/30/using-mongodb-for-a-java-web-apps-httpsession

Since the web’s inception we’ve been using it as a glorified green screen. In this model all web application interactions and the state associated with those interactions, is handled by the server. This model is a real pain to scale. Luckily the model is shifting to more of a Client/Server approach where the UI state moves to the client (where it should be). But for many of today’s applications we still have to deal with server-side state. Typically that state is just stored in memory. It’s fast but if we need more than one server (for failover or load-balancing) then we usually need to replicate that state across our servers. To keep web clients talking to the same server (usually for performance and consistency) our load-balancers have implemented sticky sessions. Session replication and sticky sessions are really just a by-product of putting client state information in memory. Until we all move to stateless web architectures we need to find more scalable and maintainable ways to handle session state.

Jetty has recently added support for a pluggable session state manager. This allows us to move away from sticky sessions and session replication and instead use external systems to store session state. Jetty provides a MongoDB implementation out-of-the-box but presumably it wouldn’t be very hard to add other implementations like Memcached. Jetty has some good documentation on how to configure this with XML. Lets walk through a sample application using Jetty and MongoDB for session state and then deploy that application on the cloud with Heroku.

First lets cover some Heroku basics. Heroku runs applications on “dynos”. You can think of dynos as isolated execution environments for your application. An application on Heroku can have web dynos and non-web dynos. Web dynos will be used for handling HTTP requests for your application. Non-web dynos can be used for background processing, one-off processes, scheduled jobs, etc. HTTP (or HTTPS) requests to your application are automatically load balanced across your web dynos. Heroku does not use sticky sessions so it is up to you to insure that if you have more than one web dyno or if a dyno is restarted, that your users’ sessions will not be lost.

Heroku does not have any special/custom APIs and does not dictate which app server you use. This means you have to bring your app server with you. There are a variety of ways to do that but the preferred approach is to specify your app server as a dependency in your application build descriptor (Maven, sbt, etc).

You must tell Heroku what process needs to be run when a new dyno is started. This is defined in a file called “Procfile” that must be in the root directory of your project.

Heroku provides a really nifty and simple way to provision new external systems that you can use in your application. These are called “add-ons”. There are tons of Heroku add-ons but for this example we will be using the MongoHQ add-on that provides a MongoDB instance.

With that in mind lets walk through a simple application that uses Jetty’s MongoDB-backed sessions. You can get all of this code from github or just clone the github repo:

git clone git://github.com/jamesward/jetty-mongo-session-test.git


First lets setup a Maven build that will include the Jetty and MongoDB driver dependencies. We will use “appassembler-maven-plugin” to generate a script that starts the Jetty server. Here is the pom.xml Maven build descriptor:
<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.heroku.test</groupId>
    <version>1.0-SNAPSHOT</version>
    <name>jettySessionTest</name>
    <artifactId>jettySessionTest</artifactId>
    <packaging>jar</packaging>
 
    <dependencies>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-webapp</artifactId>
            <version>8.0.3.v20111011</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-nosql</artifactId>
            <version>8.0.3.v20111011</version>
        </dependency>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>2.6.5</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>appassembler-maven-plugin</artifactId>
                <version>1.1.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>assemble</goal>
                        </goals>
                        <configuration>
                            <assembleDirectory>target</assembleDirectory>
                            <programs>
                                <program>
                                    <mainClass>com.heroku.test.Main</mainClass>
                                    <name>webapp</name>
                                </program>
                            </programs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
 
</project>


The “appassembler-maven-plugin” references a class “com.heroku.test.Main” that hasn’t been created yet. We will get to that in a minute. First lets create a simple servlet that will store an object in the session. Here is the servlet from the “src/main/java/com/heroku/test/servlet/TestServlet.java” file:

package com.heroku.test.servlet;
 
 
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
public class TestServlet extends HttpServlet {
 
    private class CountHolder implements Serializable {
 
        private static final long serialVersionUID = 1L;
        private Integer count;
        private Date time;
 
        public CountHolder() {
            count = 0;
            time = new Date();
        }
 
        public Integer getCount() {
            return count;
        }
 
        public void plusPlus() {
            count++;
        }
 
        public void setTime(Date time) {
            this.time = time;
        }
    }
 
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
 
        HttpSession session = req.getSession();
 
        CountHolder count;
 
        if(session.getAttribute("count") != null) {            
            count = (CountHolder) session.getAttribute("count");
        } else {
            count = new CountHolder();
        }
 
        count.setTime(new Date());
        count.plusPlus();
 
        System.out.println("Count: " + count.getCount());
 
        session.setAttribute("count", count);
 
        resp.getWriter().print("count = " + count.getCount());
    }
 
}


As you can see there is nothing special here. We are using the regular HttpSession normally, storing and retrieving a Serializable object named CountHolder. The application simply displays the number or times the servlet has been accessed by a user (where “user” really means a request that passes the same JSESSIONID cookie as a previous request).

Now lets map that servlet to the “/” URL pattern in the web app descriptor (src/main/webapp/WEB-INF/web.xml):
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
 
    <servlet>
        <servlet-name>Test Servlet</servlet-name>
        <servlet-class>com.heroku.test.servlet.TestServlet</servlet-class>
    </servlet>
 
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.ico</url-pattern>
    </servlet-mapping>
 
    <servlet-mapping>
        <servlet-name>Test Servlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
 
</web-app>


I put a servlet mapping in for “.ico” because some browsers automatically request “favicon.ico”, and those requests if not mapped to something will map to our servlet and make the count appear to jump.

Now lets create that “com.heroku.test.Main” class that will configure Jetty and start it. One reason we are using a Java class to start Jetty is because we are trying to avoid putting the MongoDB connection information in a text file. Heroku add-ons place their connection information for the external systems in environment variables. We could copy that information into a plain XML Jetty config file but that is an anti-pattern because if the add-on provider needs to change the connection information (perhaps for failover purposes) then our application would stop working until we manually updated the config file. So our simple Main class will just read the connection information from an environment variable and configure Jetty at runtime. Here is the source for the “src/main/java/com/heroku/test/Main.java” file:

package com.heroku.test;
 
import java.util.Date;
import java.util.Random;
 
import org.eclipse.jetty.nosql.mongodb.MongoSessionIdManager;
import org.eclipse.jetty.nosql.mongodb.MongoSessionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.webapp.WebAppContext;
 
import com.mongodb.DB;
import com.mongodb.MongoURI;
 
public class Main {
 
    public static void main(String[] args) throws Exception{
        String webappDirLocation = "src/main/webapp/";
 
        String webPort = System.getenv("PORT");
        if(webPort == null || webPort.isEmpty()) {
            webPort = "8080";
        }
 
        Server server = new Server(Integer.valueOf(webPort));
        WebAppContext root = new WebAppContext();
 
        MongoURI mongoURI = new MongoURI(System.getenv("MONGOHQ_URL"));
        DB connectedDB = mongoURI.connectDB();
 
        if (mongoURI.getUsername() != null) {
            connectedDB.authenticate(mongoURI.getUsername(), mongoURI.getPassword());
        }
 
        MongoSessionIdManager idMgr = new MongoSessionIdManager(server, connectedDB.getCollection("sessions"));
 
        Random rand = new Random((new Date()).getTime());
        int workerNum = 1000 + rand.nextInt(8999);
 
        idMgr.setWorkerName(String.valueOf(workerNum));
        server.setSessionIdManager(idMgr);
 
        SessionHandler sessionHandler = new SessionHandler();
        MongoSessionManager mongoMgr = new MongoSessionManager();
        mongoMgr.setSessionIdManager(server.getSessionIdManager());
        sessionHandler.setSessionManager(mongoMgr);
 
        root.setSessionHandler(sessionHandler);
        root.setContextPath("/");
        root.setDescriptor(webappDirLocation+"/WEB-INF/web.xml");
        root.setResourceBase(webappDirLocation);
        root.setParentLoaderPriority(true);
 
        server.setHandler(root);
 
        server.start();
        server.join();
    }
 
}


As you can see the MongoSessionManager is being configured based on the MONGOHQ_URL environment variable, the Jetty server is being configured to use the MongoSessionManager and pointed to the web app location, and then Jetty is started.

Now lets give it a try! If you want to run everything locally then you will need to have Maven 3 and MongoDB installed and started. Then run the Maven build

mvn package


This will use the appassembler-maven-plugin to generate the start script which sets up the CLASSPATH and then runs the com.heroku.test.Main class. Before we run we need to set the environment variable to point to our local MongoDB:

On Windows:
set MONGOHQ_URL=mongodb://127.0.0.1:27017/test


On Linux/Mac:
export MONGOHQ_URL=mongodb://127.0.0.1:27017/test


Now run the generated start script:
On Windows:
	target\bin\webapp.bat
	


On Linux/Mac:
		export MONGOHQ_URL=mongodb://127.0.0.1:27017/test
		export PORT=9090
		sh target/bin/webapp
	


Now in your browser make a few requests to:
http://localhost:9090/

Verify that the session is consistent between the two servers.

Now lets deploy this app on the cloud with Heroku. As mentioned earlier we need a file named “Procfile” in the root directory that will tell Heroku what process to run when a dyno is started. Here is the Procfile for this application:

web: sh target/bin/webapp


To create and deploy the application you will need to install git & the Heroku Toolbelt, create an Heroku, and since we will be using add-ons you will need to verify your Heroku account. Each application your create on Heroku gets 750 free dyno hours per month. So as long as you don’t go above that and you stick with the free tier of the MongoHQ add-on, then you won’t be billed for anything.

Login to Heroku using the heroku command line interface:

heroku login


If you haven’t already setup an SSH key for Heroku then the login process will walk you through that.

In the root directory of this project create the app on Heroku with the “cedar” stack and the free MongoHQ add-on:

heroku create --stack cedar --addons mongohq:free


Upload your application to Heroku using git:
git push heroku master


Open the application in your browser:

heroku open


If you want to add more dynos handling web requests then run:

heroku scale web=2


To see what is running on Heroku run:

heroku ps


If you want to turn off all of the dynos for your application just scale to 0:

heroku scale web=0


To see the logging information for your application run:
heroku logs


To see a live version of this demo visit:
http://young-wind-7462.herokuapp.com/

Well, there you go. You’ve learned how to avoid sticky sessions and session replication by moving session state to an external MongoDB system that can be scaled independently of the web tier. You’ve also learned how to run this on the cloud with Heroku. Let me know if you have any questions.

BTW: I’d like to thank John Simone from Heroku for writing most of the code for this demo.
分享到:
评论

相关推荐

    MongoDB for Java Developers

    MongoDB for Java Developers Design, build, and deliver efficient Java applications using the most advanced NoSQL database

    mongodb driver for java 源码

    MongoDB Java驱动程序是Java开发者用来与MongoDB数据库进行交互的官方库。源码分析将帮助我们深入理解其内部工作原理,优化应用性能,并有可能自定义功能以满足特定需求。以下是对MongoDB Java驱动2.5.3版本源码的...

    MongoDBjava各版本驱动下载

    MongoDB Java驱动是Java开发者与MongoDB数据库交互的重要工具,它允许Java应用程序通过标准的Java API来执行查询、插入、更新和删除等操作。在Java中使用MongoDB,首先需要安装并配置对应的驱动版本,以确保与正在...

    Mongodb连接池for java

    标题“MongoDB连接池for Java”指的是在Java环境中,针对MongoDB数据库实现的连接池解决方案。这种解决方案通常基于特定的Java驱动程序,如MongoDB的Java驱动程序(com.mongodb.client.MongoClients),它提供了连接...

    MongoDB、Java与对象关系映射

    ### MongoDB、Java与对象关系映射 #### MongoDB简介与特性 MongoDB作为一种强大的NoSQL数据库,在处理非结构化数据方面有着显著的优势。它通过使用JSON(JavaScript Object Notation)格式来存储和检索数据,简化...

    MongoDB for Java Developers的随书阅读代码

    《MongoDB for Java Developers》这本书正是针对这一主题,帮助开发者深入理解和使用MongoDB与Java的结合。 该书的随书阅读代码包含了书中多个章节的实例,这些代码分布在名为"micai-mongodb-chapter6"、"micai-...

    mongodb_java_2.6_API

    本篇将深入探讨"mongodb_java_2.6_API",即MongoDB 2.6版本的Java驱动程序API,了解如何使用Java进行MongoDB的开发。 1. **MongoDB Java驱动程序概述** MongoDB的Java驱动程序是Java开发者与MongoDB服务器通信的...

    MongoDB spring hibernate java 集成demo

    MongoDB spring hibernate java 集成demo

    php MongoDB for CI

    总之,“php MongoDB for CI”扩展使CI框架能够无缝地与MongoDB数据库配合,为开发人员提供了一个强大而灵活的工具,以适应不断变化的Web开发需求。无论是在性能、可扩展性还是数据处理上,结合CI和MongoDB都是现代...

    windows 64位mongodb安装包+java api文档

    在这个压缩包中,你将找到专为Windows 64位系统优化的MongoDB安装程序,以及Java API的文档,这对于使用Java进行MongoDB集成开发的开发者来说极其重要。 MongoDB的安装过程: 1. **下载与解压**:首先,你需要下载...

    Mongodb + GridFS +Java 操作Mongodb中存储的文件

    目前,Java驱动通常使用的是MongoDB Java Driver,可以在Maven仓库中找到对应的依赖,例如: ```xml &lt;groupId&gt;org.mongodb &lt;artifactId&gt;mongodb-driver-sync &lt;version&gt;4.3.0 ``` 接下来,我们需要配置MongoDB...

    mongodb的java驱动包

    这是一个java的mongodb的驱动包,使用它就能够与用java去连接mongodb服务器,和操作mongodb

    MongoDB for Java Developers(PACKT,2015)

    The NoSQL movement is growing in relevance, attracting more and more developers....By the end of this book, you will know everything you need to integrate MongoDB in your Java applications

    MongoDB Driver -JAVA 2.5.3 API

    MongoDB Driver for Java 2.5.3是官方提供的用于Java开发者与MongoDB数据库交互的API。这个API允许程序员高效地执行各种操作,包括插入、查询、更新和删除MongoDB中的数据。MongoDB是一个高性能、无模式的文档型...

    java操作mongodb存储文件实例

    将一系列图片文件存储到MongoDB中 java操作mongodb存储文件

    java 执行cmd命令及mongodb脚本

    Java执行CMD命令及MongoDB脚本是开发过程中常见的任务,特别是在集成系统或者自动化运维场景下。下面将详细讲解这两个主题。 一、Java执行CMD命令 在Java中,我们可以使用Runtime类或ProcessBuilder类来执行操作...

    mongoDB文件存储_java_MongoDB_

    在Java开发中,MongoDB常被用于处理结构化和半结构化的数据。本篇文章将深入探讨MongoDB如何进行文件存储,特别是针对大文件的处理。 首先,MongoDB提供了GridFS(Grid File System)规范,这是一个用于存储和检索...

    MongoDB Java Driver 简单操作

    为了方便开发者使用 Java 进行开发,MongoDB 提供了官方的 Java 驱动程序(MongoDB Java Driver),使得 Java 应用能够轻松地与 MongoDB 数据库进行交互。 #### 二、基本概念与连接 在开始使用 MongoDB Java Driver...

Global site tag (gtag.js) - Google Analytics