I have been involved with ASP.NET deployments for some high load scenarios where the same exact copy of a web application is deployed to multiple servers and placed behind a network load balancer (NLB) – also known as a web farm.
Requests to the website first arrive at the NLB, and are then directed off to a web server that is available to handle the request (the web server allocated depends on a number of load balancing algorithms the NLB server can use).
When deploying an ASP.NET application to a web farm for load balancing there are a few things to consider.
Session information in ASP.NET simply speaking is the ability to associate some information against a particular user as they are clicking through your site by using the .NET Session object. Behind the scenes the user session is tracked through a single non-persistent cookie (removed when the browser is closed) named ASP.NET_SessionId. The cookie contains a single ID that associates that user back to their session information on the server.
The actual information can be stored on the server in one of three ways – but only two of which will work when deploying to a web farm:
InProc – This is the default. InProc stores the session information in memory of the ASP.NET worker process on the server. This cannot be used with load balanced servers since if the user is sent to another server on a subsequent request, that server will not have that user’s session information available in its local worker process memory space.
StateServer – With this option, all web servers in the web farm store session information in the memory of a single process (a Windows service called “ASP.NET State Server”) preferably on a separate machine. This option works for web farms, but is 15% slower than InProc. This however of course is offset by having the extra servers processing requests.
SQLServer – Using SQL Server, session information is serialized and stored to a SQL Server database. This is 25% slower than InProc, however SQL Server can be setup as a failover cluster which has reliability advantages over StateServer which is a single point of failure. This option can also survive a SQL Server restart; if StateServer on the other hand is restarted all session information will be lost.
Whether you choose StateServer or SQLServer is a trade-off between reliability and speed.
ASP.NET hashes ViewState by default to ensure that it hasn’t been tampered with before being sent back to the server. Out of the box the key used to generate the hash is a random one generated when the application pool starts. There is also a decryption key that is used to encrypt forms authentication tokens (also auto generated).
This has two problems:
- If the Application Pool restarts (by sysadmin or automatically through inactivity or scheduled recycling) a new validation/decryption key will be generated, and a user on a subsequent post back will receive a ViewState validation error or be logged out.
- If the user is sent to another server in the web farm on post back, the other server will have it’s own validation/decryption key, and the user will receive a ViewState validation error or be logged out.
So in order to work with a web farm it is necessary that all servers in the farm use the same validationKey and decryptionKey values and not have them auto generated.
A good codeproject article describes how to do this here http://www.codeproject.com/KB/aspnet/machineKey.aspx
Also note that it is possible to encrypt ViewState (instead of just making it tamper proof) by setting the validation property to 3DES. This however incurs a performance hit so it is better to store sensitive information in the Session to avoid having to do this.
Storing / Sharing Files
Another problem exists when dealing with file uploads / downloads (when using the file system and not storing files in the database). All servers in the web farm need to be pointing to a common directory for the file store, otherwise if they are pointing to local directories the files will be split across them and sometimes exist and sometimes not depending on what server the user was sent to.
This can be overcome by setting up IIS Virtual Directories on the websites in the web farm that point to a common network share (on a file server somewhere on the network).
In order to test this concept locally for this blog post, I first created a directory c:\Test and turned on network sharing on this directory so that I can now browse to it through the UNC path \\HAEMOGLOBIN-PC\Test (in practice this would point to a share on another machine).
Within IIS7, the virtual directory “Uploads” can then be added that points to this share and will look something like this:
This allows the network share location to be maintained and controlled by system administrators – the only other thing to be aware of is setting up the correct permissions for the file share to be accessed and written to. This will depend on whether identity impersonation is enabled or what account the anonymous user is running as.
Coding against this is simple, saving a file is simply a process of using MapPath and the virtual directory name (this allows the system administrator to change the UNC location at a later point through IIS):
FileUpload1.SaveAs(MapPath(@"/Uploads/" + FileUpload1.FileName));
The above will be saved by IIS to \\HAEMOGLOBIN-PC\Test (as configured before and this could be on another machine) which in turn resolves to c:\Test on that machine.
In terms of providing a link to download a file - it would simply look like:
<a href="/Uploads/uploadedFile.zip">Uploaded File</a>
This will (once again through the IIS virtual directory mapping) download the file from the common file share location on the network and work for any server the user happens to be on in the web farm.
Hope this helps :)