This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
|
Maintaining Session State on Your Web Farm
Marco Tabini |
IIS and ASP provide several methods to track a user's session on the server. But when you have several servers running concurrently, you have to modify your approach. |
There's no worse time to find out that you made a mistake when planning your Web site than when you need a quick fix to a seemingly simple problem. Off you walk after cheerfully replying "no problem" to your boss's request for a rapid resolutionwith just enough time to catch the next flight to a nonextradition country.
The problem with planning mistakes is, of course, that they are difficult to predict. Careful thought in the design phase doesn't always help you, especially when the scenarios in which Web problems arise seem far-fetched and the technology that you are using appears to be foolproof. As a result, you are forced to rewrite entire portions of code for what looked like a trivial matter in the design phase, and your "no problem" turns into a nightmare from which you just can't seem to wake up. For the sake of discussion, let's say your Web site has become so successful that the IT department has been stuffing your only Web server with hardware to the point that it requires a separate power plant just to run the cooling system. Sadly, you can only fit so many processors in any motherboard. You are faced with adding another computer and placing a router whose only task is to dispatch clients to either machine, depending on its overall workload, in front of the pool. That's where the unthinkable happens, and you grab your phone book looking for your travel agent so you can buy that one-way ticket.
A One-way Trip to Hell
|
|
Cookies can only be used to store strings, which means that you will have to use at least 32 characters (two for each byte) to display a GUID. On a large scale, this is an awful waste of space because, while each character in the string can be used to save seven bits, only four are used. To reduce the amount of bandwidth wasted, IIS converts the GUID to base 36 (using digits from zero to nine and characters from uppercase A to uppercase Z), which results in a maximum of 25 characters being used.
The well-known problem with IIS sessions is that because they are entirely based on cookies, if the user turns off this feature the server will not be able to maintain the session state anymore. The real trouble comes because the server doesn't know that the cookies it sends out are being refused. As a result, the Session object is still available to the ASP scripts, but the values stored in it only have page-wide scope. Whatever is stored in the Session object in one script will be lost by the time the next one will be loaded. A typical solution consists of two basic steps. The first one determines whether cookies are being refused, and the second either advises the user that they must change the browser's settings or provides some alternative means of maintaining the session state. Finding out whether the cookies that you send out are being refused is relatively easy. All you have to do is make the first page of your site a simple redirection: |
|
If in the Default2.asp script the value "Cookies" in the Session object is still available, then you know for sure that cookies are turned on at the other end. Be careful not to fall into the trap of storing the cookie's value into the global.asa file and then attempting to read it in default.asp (assuming that's the entry page of your site). Even though they are two different files, the server will execute both of them as part of the connection that retrieves the first page. As a result, the values set when global.asa is executed will still be available in the main page because the connection between the browser and the server has not been lost.
Now, for a more challenging thought, let's consider a lesser-known problem with sessions. (It's not well-known because until a short while ago it was extremely rare.) Because the internal database of session states is maintained in memory by the server, sessions are not only dependent on the availability of cookies on the target machine, but their scope is also limited to the individual server machine. If an increasingly common capacity problem forces you to create a server farm to run your site, sessions become unsuitable for your environment and can get you in real trouble if you've heavily based your scripts on them.
Alas, the Return Ticket
When you're developing COM components, the big question is which language you should use. Whenever possible, I use Visual Basic® for prototyping because it's much easier to change your mind about the way the components work than when you're using Visual C++®. Visual C++ is my language of choice for a production environment because of its stability and performance, plus its support for a wider variety of threading models. Since my goal here was not to provide a product, but to pass along a solution to a problem, most of the code in this article has been written using Visual Basic 6.0. You can convert my components to Visual C++ if you decide that the improved performance impact will be worth the additional development time. My Web server is a Pentium II-based, 350 MHz machine running Windows NT 4.0 Service Pack 4, SQL Server™ 6.5 on 128MB of RAM. As configured, this system was able to handle well over a hundred concurrent users without showing any signs of slowing down significantly. If you need to use a server farm you will probably have (or at least plan for) a far greater number of connections, and moving to Visual C++ might be a good idea.
Your Friend, the Database
Storing Data
System Architecture
|
|
ElementCollection also offers the LastModified property, which can be used to synchronize its data with the master data stored in the database.
SessionObject offers the richest functionality of all the classes and performs most of the work. At its core, it maintains a collection of ElementCollection objects, which together constitute a cache of all the sessions currently available on the system. This way, if a user keeps returning to the same machine, the system will not have to reload all the data from the Sessions table every time it needs it. SessionObject also retrieves sessions from the database and saves them back if needed. The SaveSession method iterates through the an ElementCollection object and builds a single string that contains all the data: |
|
The LoadSession method retrieves the single string from the database, separates the individual values using the Split function provided by Visual Basic, and stores them back into an instance of ElementCollection.
When an ASP page requests a session, SessionObject creates a new instance of Session and uses it as a wrapper around the appropriate instance of ElementCollection. Session simply provides passthrough methods to its instance of ElementCollection, adding some functionality of its own only when it is destroyed (which will usually happen at the end of the page). The Class_Terminate method, in fact, forces SessionObject to save the session back to the database (while still keeping a copy in memory for caching purposes), ensuring the appropriate data synchronization.
Synchronizing Data
Deploying the Solution
Possible Improvements
|
http://msdn.microsoft.com/workshop/server/nextgen/sessiondata.asp and http://msdn.microsoft.com/library/devprods/vs6/vbasic/vbcon98/vbconstoringstateinobjects.htm |
From the October 1999 issue of Microsoft Internet Developer.
|