Return-Path: Delivered-To: apmail-tomcat-users-archive@www.apache.org Received: (qmail 84447 invoked from network); 27 Jun 2007 17:38:01 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 27 Jun 2007 17:38:01 -0000 Received: (qmail 46951 invoked by uid 500); 27 Jun 2007 17:37:50 -0000 Delivered-To: apmail-tomcat-users-archive@tomcat.apache.org Received: (qmail 46512 invoked by uid 500); 27 Jun 2007 17:37:49 -0000 Mailing-List: contact users-help@tomcat.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: "Tomcat Users List" Delivered-To: mailing list users@tomcat.apache.org Received: (qmail 46497 invoked by uid 99); 27 Jun 2007 17:37:49 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 27 Jun 2007 10:37:49 -0700 X-ASF-Spam-Status: No, hits=-0.0 required=10.0 tests=SPF_HELO_PASS,SPF_PASS X-Spam-Check-By: apache.org Received-SPF: pass (herse.apache.org: domain of sbhobbs@uga.edu designates 128.192.1.121 as permitted sender) Received: from [128.192.1.121] (HELO puntd2.cc.uga.edu) (128.192.1.121) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 27 Jun 2007 10:37:44 -0700 Received: from [127.0.0.1] (asgfw.ucns.uga.edu [128.192.88.205]) by puntd2.cc.uga.edu (MOS 3.8.4-GA) with ESMTP id EOL12332; Wed, 27 Jun 2007 13:37:23 -0400 (EDT) Message-ID: <4682A053.2080409@uga.edu> Date: Wed, 27 Jun 2007 13:37:23 -0400 From: Brantley Hobbs Reply-To: sbhobbs@uga.edu Organization: ASG User-Agent: Thunderbird 2.0.0.4 (Windows/20070604) MIME-Version: 1.0 To: Tomcat Users List Subject: Re: Keeping busy site responsive References: <731261B16D0E2E41A948520946EACC7E41DB@biosrv06.ns.2000dom.biosignia> In-Reply-To: <731261B16D0E2E41A948520946EACC7E41DB@biosrv06.ns.2000dom.biosignia> Content-Type: multipart/mixed; boundary="------------020606020602050006000504" X-Virus-Checked: Checked by ClamAV on apache.org --------------020606020602050006000504 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit 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: users-unsubscribe@tomcat.apache.org > For additional commands, e-mail: users-help@tomcat.apache.org > > --------------020606020602050006000504 Content-Type: text/plain; name="UserLimitFilter.java" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="UserLimitFilter.java" /* * 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; /** *

* A {@code Filter} which, in combination with the {@link SessionCountListener}, * limits the number of active sessions for a context. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Filter {@code init-param}s
{@code param-name}{@code param-value} (example)DescriptionRequired?Default
{@code enabled}falsedetermines whether or not the filter is appliedNotrue
{@code redirectURL}http://www.google.com/search?hl=en&q=too+many+usersthe URL sent in a header redirect when the user limit is exceededYesN/A
{@code userLimit}50the maximum number of active sessions that may exist at one timeYesN/A
* * @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 {@code UserLimitFilter}. */ public UserLimitFilter() { } /** * Reads the {@code init-param}s and sets up filter options. * * @param config * the {@code 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 {@code userLimit} is exceeded.  It will * not redirect if they have a valid {@code HttpSession} already. * * @param request * the incoming request * @param response * the outgoing response * @param chain * the collection if {@code Filter}s the application defines * @throws IOException * if the next filter in the {@code chain} throws one * @throws ServletException * if the next filter in the {@code 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; } } --------------020606020602050006000504 Content-Type: text/plain; name="SessionCountListener.java" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="SessionCountListener.java" /* * 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; /** *

* A Listener which counts the number of active * {@link javax.servlet.http.HttpSession}s. *

* * @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 {@code SessionCountListener}. */ public SessionCountListener() { } /** * Increments the {@code HttpSession} count. * * @param evt * the event which triggered this Listener */ public void sessionCreated(HttpSessionEvent evt) { initContext(evt.getSession().getServletContext()); getAtomicCount().incrementAndGet(); } /** * Decrements the {@code 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 {@code HttpSession} count. * * @return ths current {@code HttpSession} count */ public static int getCount() { if (context == null) { return 0; } else { return getAtomicCount().get(); } } } --------------020606020602050006000504 Content-Type: text/plain; charset=us-ascii --------------------------------------------------------------------- To start a new topic, e-mail: users@tomcat.apache.org To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org For additional commands, e-mail: users-help@tomcat.apache.org --------------020606020602050006000504--