Clinton,
I implemented this as a filter/listener combination.
The listener tracks session initialized events and increments an atomic
Integer (decrementing it at session destroyed), saving it as a context
attribute.
The filter uses the following logic:
1. If we're less than or equal to the number of allowed sessions, allow
this one through.
2. If we've exceeded the limit, check to see if this user already has
an established session (using the request's getSession(false) call). If
they have an established session, allow them through.
3. If they don't match either of the above, send them a redirect to a
page letting them know to try back later.
This approach seems to work well (at least until the box staggers to a
halt simply issuing redirects). I've hit a box with numbers that are at
least double what the limit is and the people that have established
sessions don't notice a thing. Because it's simple filters and
listeners, it should be fairly portable. I've attached the source with
this mail. There might be a couple of dependancies you can get rid of
(like our log manager class), but it should be pretty easy to drop in.
Hope this helps.
B.
Parham, Clinton wrote:
Tomcat Experts:
How do I keep my web application responsive for users already half way
through an enrollment process when traffic volume is high?
Here's the scenario: I have a set of 5 web pages that users must work
through to successfully enroll themselves. Assume the server can handle
250 concurrent requests (maxThreads). While traffic volume is under 250,
enrollments complete normally. But once volume exceeds 250 and saturates
the acceptCount/backlog queue, users half way through enrollments cannot
complete their enrollment (connections are refused) because new users
keep bombarding the site.
What would be acceptable is for new users to see a 'site is busy
message' while enrollments in progress are completed. As enrollments
complete and concurrent threads drop below 250, new users are allowed
through.
I have already considered maxActiveSessions but I don't think this will
solve the problem. If maxThreads is reached and the acceptCount/backlog
queue is exhausted, then the users with active sessions and already
partly through enrollment won't be able to get back in to the site to
complete their enrollment - right?
Adding more servers to handle the load is not preferred because most of
the time they will be underutilized. Enrollments that experience high
traffic don't happen that often but when they do, we need to support
them.
Thank you for your time.
---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
/*
* UserLimitFilter.java
*
* Created on May 11, 2007, 8:27 AM
*/
package edu.uga.asg.apojee.web.util;
import edu.uga.asg.apojee.logging.Log;
import edu.uga.asg.apojee.logging.LogManager;
import edu.uga.asg.apojee.logging.LogCategory;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.http.HttpSession;
/**
* <p>
* A [EMAIL PROTECTED] Filter} which, in combination with the [EMAIL PROTECTED]
SessionCountListener},
* limits the number of active sessions for a context.
* </p>
*
* <table border="1">
* <thead>
* <tr>
* <th colspan="5">Filter [EMAIL PROTECTED] init-param}s</th>
* </tr>
* <tr>
* <th>[EMAIL PROTECTED] param-name}</th>
* <th>[EMAIL PROTECTED] param-value} (example)</th>
* <th>Description</th>
* <th>Required?</th>
* <th>Default</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>[EMAIL PROTECTED] enabled}</td>
* <td>false</td>
* <td>determines whether or not the filter is applied</td>
* <td>No</td>
* <td>true</td>
* </tr>
* <tr>
* <td>[EMAIL PROTECTED] redirectURL}</td>
* <td>http://www.google.com/search?hl=en&q=too+many+users</td>
* <td>the URL sent in a header redirect when the user limit is
exceeded</td>
* <td>Yes</td>
* <td><em>N/A</em></td>
* </tr>
* <tr>
* <td>[EMAIL PROTECTED] userLimit}</td>
* <td>50</td>
* <td>the maximum number of active sessions that may exist at one
time</td>
* <td>Yes</td>
* <td><em>N/A</em></td>
* </tr>
* </tbody>
* </table>
*
* @author Brantley Hobbs (UGA ASG)
* @since APOJEE Core 1.0.1
*/
public class UserLimitFilter implements Filter {
private static final Log log =
LogManager.getLog(LogCategory.APPLICATION);
private String className = this.getClass().getName();
private boolean enabled = true;
private String redirectURL = null;
private Integer userLimit = null;
/**
* Creates a new instance of [EMAIL PROTECTED] UserLimitFilter}.
*/
public UserLimitFilter() {
}
/**
* Reads the [EMAIL PROTECTED] init-param}s and sets up filter options.
*
* @param config
* the [EMAIL PROTECTED] Filter}'s configuration options as
defined in web.xml
*/
public void init(FilterConfig config) throws ServletException {
//Default value of filterEnabled flag is true. Check for
false.....
if (config.getInitParameter("enabled") != null) {
String filterStatus =
config.getInitParameter("enabled");
if (filterStatus.equalsIgnoreCase("n")
|| filterStatus.equalsIgnoreCase("no")
|| filterStatus.equalsIgnoreCase("0")
|| filterStatus.equalsIgnoreCase("f")
||
filterStatus.equalsIgnoreCase("false")
) {
this.setEnabled(false);
log.fatal(className + ":init() - User Limit
Filter disabled by configuration request!");
return;
}
}
//Get redirect location
if (config.getInitParameter("redirectURL") != null) {
this.setRedirectURL(config.getInitParameter("redirectURL"));
} else {
log.fatal(className + ":init() - No redirectURL
specified! This is a required parameter! Disabling filter!");
this.setEnabled(false);
return;
}
//Get redirect location
if (config.getInitParameter("userLimit") != null) {
this.setUserLimit(new
Integer(config.getInitParameter("userLimit")));
} else {
log.fatal(className + ":init() - No user limit
specified! This is a required parameter! Disabling filter!");
this.setEnabled(false);
return;
}
if (this.isEnabled()) {
log.warn(className + ":init() - User Limit Filter
enabled and ready to process requests...");
}
}
/**
* Redirects the user if the [EMAIL PROTECTED] userLimit} is
exceeded. It will
* not redirect if they have a valid [EMAIL PROTECTED] HttpSession}
already.
*
* @param request
* the incoming request
* @param response
* the outgoing response
* @param chain
* the collection if [EMAIL PROTECTED] Filter}s the application
defines
* @throws IOException
* if the next filter in the [EMAIL PROTECTED] chain} throws one
* @throws ServletException
* if the next filter in the [EMAIL PROTECTED] chain} throws one
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse)
response;
//Don't do anything if the filter is not enabled....
if (!isEnabled()) {
chain.doFilter(request, response);
return;
}
//If there's a session established, don't block them....
if (httpRequest.getSession(false) != null) {
log.debug(className + ":doFilter() - This user already
has a session established. Allow them through....");
chain.doFilter(request, response);
return;
}
//Now check to see if we've exceeded the number of allowed
sessions....
HttpSession session = httpRequest.getSession();
int sessionCount = SessionCountListener.getCount();
if (sessionCount > getUserLimit()) {
//Too many users!
log.info(className + ":doFilter() - Too many users!
Redirecting user to [" + getRedirectURL() + "].");
httpResponse.sendRedirect(getRedirectURL());
session.invalidate();
return;
}
//We made it this far, let them through....
chain.doFilter(request,response);
return;
}
/**
* Performs no action.
*/
public void destroy() {
}
private boolean isEnabled() {
return enabled;
}
private void setEnabled(boolean enabled) {
this.enabled = enabled;
}
private String getRedirectURL() {
return redirectURL;
}
private void setRedirectURL(String redirectURL) {
this.redirectURL = redirectURL;
}
private Integer getUserLimit() {
return userLimit;
}
private void setUserLimit(Integer userLimit) {
this.userLimit = userLimit;
}
}
/*
* SessionCountListener.java
*
* Created on May 14, 2007, 4:42 PM
*/
package edu.uga.asg.apojee.web.util;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* <p>
* A Listener which counts the number of active
* [EMAIL PROTECTED] javax.servlet.http.HttpSession}s.
* </p>
*
* @author Brantley Hobbs (UGA ASG)
* @author Mark Lewis (UGA ASG)
* @since APOJEE Core 1.0.1
* @see UserLimitFilter
*/
public class SessionCountListener implements HttpSessionListener {
private static final String contextParamName =
"$$__%%ACTIVE_SESSION_COUNT";
private static ServletContext context = null;
/**
* Creates a new instance of [EMAIL PROTECTED] SessionCountListener}.
*/
public SessionCountListener() {
}
/**
* Increments the [EMAIL PROTECTED] HttpSession} count.
*
* @param evt
* the event which triggered this Listener
*/
public void sessionCreated(HttpSessionEvent evt) {
initContext(evt.getSession().getServletContext());
getAtomicCount().incrementAndGet();
}
/**
* Decrements the [EMAIL PROTECTED] HttpSession} count.
*
* @param evt
* the event which triggered this Listener
*/
public void sessionDestroyed(HttpSessionEvent evt) {
initContext(evt.getSession().getServletContext());
getAtomicCount().decrementAndGet();
}
private void initContext(ServletContext context) {
if (this.context == null) {
this.context = context;
}
}
private static AtomicInteger getAtomicCount() {
AtomicInteger value = (AtomicInteger)
context.getAttribute(contextParamName);
if (value == null) {
value = new AtomicInteger(0);
context.setAttribute(contextParamName, value);
}
return value;
}
/**
* Returns the current [EMAIL PROTECTED] HttpSession} count.
*
* @return ths current [EMAIL PROTECTED] HttpSession} count
*/
public static int getCount() {
if (context == null) {
return 0;
} else {
return getAtomicCount().get();
}
}
}
---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]