|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 5
Ajax improves single-user responsiveness. Server-push technologies like Comet improve application responsiveness for collaborative, multi-user applications without the overhead of regular polling.
The client aspect of the server push techniques -- such as hidden iframes, XMLHttpRequest streaming, and some Dojo and jQuery libraries that facilitate asynchronous communication -- are outside this article's scope. Instead, our interest
is on the server side, specifically how the Servlet 3.0 specification helps implement interactive applications with server
push.
Comet (streaming and long polling) always occupies a channel waiting on the server to push back state changes. The same channel can't be reused by the client to send ordinary requests. So Comet applications typically work with two connections per client. If the application stays in thread-per-request mode, an outstanding thread will be associated with each Comet connection, resulting in more threads than the number of users. Comet applications that use the thread-per-request approach can scale only at the prohibitive expense of huge thread consumption.
Lack of asynchronous support in the Servlet 2.5 specification has caused server vendors to devise workarounds by weaving proprietary
classes and interfaces that promote scalable Comet programming into the Servlet 2.5 API. For example, Tomcat has a CometProcessor class, Jetty 6 has Continuations, and Grizzly has a CometEngine class. With Servlet 3.0, you no longer need to sacrifice portability when programming scalable Comet applications. The asynchronous
feature shown in Listing 1 consolidates great design ideas from multiple vendors and presents a universal, portable solution
for efficient thread usage in Comet applications.
Let's look at a real-world example -- an online auction Web site. When buyers bid for an item online, it annoys them that they must keep refreshing the page to see the latest/highest bid. This is a hassle especially when the bidding is about to end. With server-push technologies, no page refresh or high-frequency Ajax polling is needed. The server simply pushes the item's bid price to the registered clients whenever a new bid is placed, as shown in Listing 2.
@WebServlet(name="myServlet", urlPatterns={"/auctionservice"}, asyncSupported=true)
public class MyServlet extends HttpServlet {
// track bid prices
public void doGet(HttpServletRequest request, HttpServletResponse response) {
AsyncContext aCtx = request.startAsync(request, response);
// This could be a cluser-wide cache.
ServletContext appScope = request.getServletContext();
Map<String, List<AsyncContext>> aucWatchers = (Map<String, List<AsyncContext>>)appScope.getAttribute("aucWatchers");
List<AsyncContext> watchers = (List<AsyncContext>)aucWatchers.get(request.getParameter("auctionId"));
watchers.add(aCtx); // register a watcher
}
// place a bid
public void doPost(HttpServletRequest request, HttpServletResponse response) {
// run in a transactional context
// save a new bid
AsyncContext aCtx = request.startAsync(request, response);
ServletContext appScope = request.getServletContext();
Queue<Bid> aucBids = (Queue<Bid>)appScope.getAttribute("aucBids");
aucBids.add((Bid)request.getAttribute("bid")); // a new bid event is placed queued.
}
}
@WebServletContextListener
public class BidPushService implements ServletContextListener{
public void contextInitialized(ServletContextEvent sce) {
Map<String, List<AsyncContext>> aucWatchers = new HashMap<String, List<AsyncContext>>();
sce.getServletContext().setAttribute("aucWatchers", aucWatchers);
// store new bids not published yet
Queue<Bid> aucBids = new ConcurrentLinkedQueue<Bid>();
sce.getServletContext().setAttribute("aucBids", aucBids);
Executor bidExecutor = Executors.newCachedThreadPool();
final Executor watcherExecutor = Executors.newCachedThreadPool();
while(true)
{
if(!aucBids.isEmpty()) // There are unpublished new bid events.
{
final Bid bid = aucBids.poll();
bidExecutor.execute(new Runnable(){
public void run() {
List<AsyncContext> watchers = aucWatchers.get(bid.getAuctionId());
for(final AsyncContext aCtx : watchers)
{
watcherExecutor.execute(new Runnable(){
public void run() {
// publish a new bid event to a watcher
aCtx.getResponse().getWriter().print("A new bid on the item was placed. The current price ..., next bid price is ...");
};
});
}
}
});
}
}
}
public void contextDestroyed(ServletContextEvent sce) {
}
}
Listing 2 is quite self-explanatory. For all the auction watchers, the uncommitted, long-lived response objects are stored
in the AsynchContext objects, which are cached in an application-scoped Map. The original threads initiating the request/response loop are then returned to the thread pool and ready to serve other
tasks. Whenever a new bid is placed, threads initiated in the ServletContextListener object pick it up from a queue, and then push the event to all the watchers (stored response objects) registered for the
same auction. The new bid data is streamed to the connected browsers without being explicitly requested by the clients. These
response objects are either uncommitted for streaming, or committed for long polling. As you can see, no threads are consumed
for watchers when no bid event is happening on the sever side.