WORK IN PROGRESS, GET THE STABLE SOURCE FROM THE DOWNLOADS TAB

Fixes items 26218 and 26057

-OutputCaching is now enabled by fixing UrlRewriting to happen on PostResolveRequestCache
-Load balancing support using the Dispatcher which calles the nodes listed in distributedNodes now calls all servers asynchronously (much faster) and does not prevent all servers from failing if one node fails

[TFS Changeset #64009]
This commit is contained in:
boxbinary
2010-02-16 06:18:20 +00:00
parent ea6b8f61fe
commit 42050ba664
4 changed files with 244 additions and 86 deletions

View File

@@ -40,7 +40,7 @@ namespace umbraco.presentation
//So everything is moved to beginRequest.
}
protected void Application_PostAuthorizeRequest(object sender, EventArgs e)
protected void Application_PostResolveRequestCache(object sender, EventArgs e)
{
// process rewrite here so forms authentication can Authorize based on url before the original url is discarded
this.UmbracoRewrite(sender, e);
@@ -281,7 +281,11 @@ namespace umbraco.presentation
ApplicationStart(context);
context.BeginRequest += new EventHandler(Application_BeginRequest);
context.AuthorizeRequest += new EventHandler(Application_AuthorizeRequest);
context.PostAuthorizeRequest += new EventHandler(Application_PostAuthorizeRequest);
// Alex Norcliffe - 2010 02 - Changed this behaviour as it disables OutputCaching due to Rewrite happening too early in the chain
// context.PostAuthorizeRequest += new EventHandler(Application_PostAuthorizeRequest);
context.PostResolveRequestCache += new EventHandler(Application_PostResolveRequestCache);
context.PreRequestHandlerExecute += new EventHandler(Application_PreRequestHandlerExecute);
context.Error += new EventHandler(Application_Error);
mApp = context;

View File

@@ -30,7 +30,7 @@ namespace umbraco.presentation.cache {
/// <remarks/>
public CacheRefresher() {
this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + SystemDirectories.Webservices + "/cacheRefresher.asmx";
this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + IOHelper.ResolveUrl(SystemDirectories.Webservices) + "/cacheRefresher.asmx";
}
/// <remarks/>

View File

@@ -1,8 +1,14 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Xml;
using umbraco.BusinessLogic;
using umbraco.interfaces;
using umbraco.IO;
namespace umbraco.presentation.cache {
namespace umbraco.presentation.cache
{
/// <summary>
/// Dispatcher is used to handle Umbraco's load balancing.
/// Distributing calls to all registered load balanced servers, ensuring that content are synced and cached on all servers.
@@ -11,117 +17,260 @@ namespace umbraco.presentation.cache {
///
/// Dispatcher can refresh/remove content, templates and macros.
/// Load balanced servers are registered in umbracoSettings.config.
///
/// UPDATE 2010 02 - Alex Norcliffe - Refactored Dispatcher to support parallel dispatch threads, and preventing failure of whole dispatch
/// if one node fails. Still needs more work to get it to Enterprise level though but this is for 4.1
/// </summary>
public class dispatcher {
private static string _login = BusinessLogic.User.GetUser(UmbracoSettings.DistributedCallUser).LoginName;
private static string _password = BusinessLogic.User.GetUser(UmbracoSettings.DistributedCallUser).GetPassword();
public class dispatcher
{
private static readonly string _login = User.GetUser(UmbracoSettings.DistributedCallUser).LoginName;
private static readonly string _password = User.GetUser(UmbracoSettings.DistributedCallUser).GetPassword();
private static readonly string _webServicesUrl;
/// <summary>
/// Initializes a new instance of the <see cref="dispatcher"/> class.
/// </summary>
public dispatcher() {
static dispatcher()
{
_webServicesUrl = IOHelper.ResolveUrl(SystemDirectories.Webservices);
}
/// <summary>
/// Sends a request to all registered load-balanced servers to refresh node with the specified Id
/// using the specified ICacheRefresher with the guid uniqueIdentifier.
/// using the specified ICacheRefresher with the guid factoryGuid.
/// </summary>
/// <param name="uniqueIdentifier">The unique identifier of the ICacheRefresher used to refresh the node.</param>
/// <param name="factoryGuid">The unique identifier of the ICacheRefresher used to refresh the node.</param>
/// <param name="Id">The id of the node.</param>
public static void Refresh(Guid uniqueIdentifier, int Id) {
try {
using (CacheRefresher cr = new CacheRefresher())
{
foreach (XmlNode n in UmbracoSettings.DistributionServers.SelectNodes("./server"))
{
cr.Url = "http://" + xmlHelper.GetNodeValue(n) + SystemDirectories.Webservices + "/cacheRefresher.asmx";
cr.RefreshById(uniqueIdentifier, Id, _login, _password);
}
}
} catch (Exception ee) {
BusinessLogic.Log.Add(
BusinessLogic.LogTypes.Error,
BusinessLogic.User.GetUser(0),
-1,
"Error refreshing '" + new Factory().GetNewObject(uniqueIdentifier).Name + "' with id '" + Id.ToString() + "', error: '" + ee.ToString() + "'");
}
public static void Refresh(Guid factoryGuid, int Id)
{
InvokeDispatchMethod(DispatchType.RefreshByNumericId, factoryGuid, Id, Guid.Empty);
}
/// <summary>
/// Sends a request to all registered load-balanced servers to refresh the node with the specified guid
/// using the specified ICacheRefresher with the guid uniqueIdentifier.
/// using the specified ICacheRefresher with the guid factoryGuid.
/// </summary>
/// <param name="uniqueIdentifier">The unique identifier of the ICacheRefresher used to refresh the node.</param>
/// <param name="factoryGuid">The unique identifier of the ICacheRefresher used to refresh the node.</param>
/// <param name="Id">The guid of the node.</param>
public static void Refresh(Guid uniqueIdentifier, Guid Id) {
try {
using (CacheRefresher cr = new CacheRefresher())
{
foreach (XmlNode n in UmbracoSettings.DistributionServers.SelectNodes("./server"))
{
cr.Url = "http://" + xmlHelper.GetNodeValue(n) + SystemDirectories.Webservices + "/cacheRefresher.asmx";
cr.RefreshByGuid(uniqueIdentifier, Id, _login, _password);
}
}
} catch (Exception ee) {
BusinessLogic.Log.Add(
BusinessLogic.LogTypes.Error,
BusinessLogic.User.GetUser(0),
-1,
"Error refreshing '" + new Factory().GetNewObject(uniqueIdentifier).Name + "' with id '" + Id.ToString() + "', error: '" + ee.ToString() + "'");
}
public static void Refresh(Guid factoryGuid, Guid Id)
{
InvokeDispatchMethod(DispatchType.RefreshByGuid, factoryGuid, 0, Id);
}
/// <summary>
/// Sends a request to all registered load-balanced servers to refresh all nodes
/// using the specified ICacheRefresher with the guid uniqueIdentifier.
/// using the specified ICacheRefresher with the guid factoryGuid.
/// </summary>
/// <param name="uniqueIdentifier">The unique identifier.</param>
public static void RefreshAll(Guid uniqueIdentifier) {
try {
using (CacheRefresher cr = new CacheRefresher())
{
foreach (XmlNode n in UmbracoSettings.DistributionServers.SelectNodes("./server"))
{
cr.Url = "http://" + xmlHelper.GetNodeValue(n) + SystemDirectories.Webservices + "/cacheRefresher.asmx";
cr.RefreshAll(uniqueIdentifier, _login, _password);
}
}
} catch (Exception ee) {
BusinessLogic.Log.Add(
BusinessLogic.LogTypes.Error,
BusinessLogic.User.GetUser(0),
-1,
"Error refreshing all in '" + new Factory().GetNewObject(uniqueIdentifier).Name + "', error: '" + ee.ToString() + "'");
}
/// <param name="factoryGuid">The unique identifier.</param>
public static void RefreshAll(Guid factoryGuid)
{
InvokeDispatchMethod(DispatchType.RefreshAll, factoryGuid, 0, Guid.Empty);
}
/// <summary>
/// Sends a request to all registered load-balanced servers to remove the node with the specified id
/// using the specified ICacheRefresher with the guid uniqueIdentifier.
/// using the specified ICacheRefresher with the guid factoryGuid.
/// </summary>
/// <param name="uniqueIdentifier">The unique identifier.</param>
/// <param name="factoryGuid">The unique identifier.</param>
/// <param name="Id">The id.</param>
public static void Remove(Guid uniqueIdentifier, int Id) {
public static void Remove(Guid factoryGuid, int Id)
{
InvokeDispatchMethod(DispatchType.RemoveById, factoryGuid, Id, Guid.Empty);
}
try {
using ( CacheRefresher cr = new CacheRefresher()) {
foreach (XmlNode n in UmbracoSettings.DistributionServers.SelectNodes("./server"))
/// <summary>
/// Invokes the relevant dispatch method.
/// </summary>
/// <param name="dispatchType">Type of the dispatch.</param>
/// <param name="factoryGuid">The factory GUID.</param>
/// <param name="numericId">The numeric id.</param>
/// <param name="guidId">The GUID id.</param>
private static void InvokeDispatchMethod(DispatchType dispatchType, Guid factoryGuid, int numericId, Guid guidId)
{
string name = GetFactoryObjectName(factoryGuid);
try
{
using (var cacheRefresher = new CacheRefresher())
{
var asyncResultsList = new List<IAsyncResult>();
LogStartDispatch();
// Go through each configured node submitting a request asynchronously
foreach (XmlNode n in GetDistributedNodes())
{
cr.Url = "http://" + xmlHelper.GetNodeValue(n) + SystemDirectories.Webservices + "/cacheRefresher.asmx";
cr.RemoveById(uniqueIdentifier, Id, _login, _password);
SetWebServiceUrlFromNode(cacheRefresher, n);
// Add the returned WaitHandle to the list for later checking
switch (dispatchType)
{
case DispatchType.RefreshAll:
asyncResultsList.Add(cacheRefresher.BeginRefreshAll(factoryGuid, _login, _password, null,
null));
break;
case DispatchType.RefreshByGuid:
asyncResultsList.Add(cacheRefresher.BeginRefreshByGuid(factoryGuid, guidId, _login,
_password, null, null));
break;
case DispatchType.RefreshByNumericId:
asyncResultsList.Add(cacheRefresher.BeginRefreshById(factoryGuid, numericId, _login,
_password, null, null));
break;
case DispatchType.RemoveById:
asyncResultsList.Add(cacheRefresher.BeginRemoveById(factoryGuid, numericId, _login,
_password, null, null));
break;
}
}
List<WaitHandle> waitHandlesList;
IAsyncResult[] asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList);
int errorCount = 0;
// Once for each WaitHandle that we have, wait for a response and log it
// We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis
for (int waitCalls = 0; waitCalls < asyncResults.Length; waitCalls++)
{
int handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15));
try
{
// Find out if the call succeeded
switch (dispatchType)
{
case DispatchType.RefreshAll:
cacheRefresher.EndRefreshAll(asyncResults[waitCalls]);
break;
case DispatchType.RefreshByGuid:
cacheRefresher.EndRefreshByGuid(asyncResults[waitCalls]);
break;
case DispatchType.RefreshByNumericId:
cacheRefresher.EndRefreshById(asyncResults[waitCalls]);
break;
case DispatchType.RemoveById:
cacheRefresher.EndRemoveById(asyncResults[waitCalls]);
break;
}
}
catch (WebException ex)
{
LogDispatchNodeError(ex);
errorCount++;
}
catch (Exception ex)
{
LogDispatchNodeError(ex);
errorCount++;
}
}
LogDispatchBatchResult(errorCount);
}
} catch (Exception ee) {
BusinessLogic.Log.Add(
BusinessLogic.LogTypes.Error,
BusinessLogic.User.GetUser(0),
-1,
"Error refreshing '" + new Factory().GetNewObject(uniqueIdentifier).Name + "' with id '" + Id.ToString() + "', error: '" + ee.ToString() + "'");
}
catch (Exception ee)
{
LogDispatchBatchError(ee);
}
}
private static void LogDispatchBatchError(Exception ee)
{
Log.Add(
LogTypes.Error,
User.GetUser(0),
-1,
string.Format("Error refreshing distributed list: '{0}'", ee));
}
private static void LogDispatchBatchResult(int errorCount)
{
Log.Add(
LogTypes.System,
User.GetUser(0),
-1,
string.Format("Distributed server push completed with {0} nodes reporting an error",
errorCount == 0 ? "no" : errorCount.ToString()));
}
private static void LogDispatchNodeError(Exception ex)
{
Log.Add(
LogTypes.Error,
User.GetUser(0),
-1,
string.Format("Error refreshing a node in the distributed list: '{0}'", ex));
}
private static void LogDispatchNodeError(WebException ex)
{
Log.Add(
LogTypes.Error,
User.GetUser(0),
-1,
string.Format("Error refreshing a node in the distributed list: '{0}', URI attempted: {1}", ex,
ex.Response.ResponseUri));
}
/// <summary>
/// Sets the web service URL for a CacheRefresher from an XmlNode.
/// </summary>
/// <param name="cr">The CacheRefresher.</param>
/// <param name="n">The XmlNode.</param>
private static void SetWebServiceUrlFromNode(CacheRefresher cr, XmlNode n)
{
cr.Url = "http://" + xmlHelper.GetNodeValue(n) + _webServicesUrl + "/cacheRefresher.asmx";
}
private static string GetFactoryObjectName(Guid uniqueIdentifier)
{
ICacheRefresher cacheRefresher = new Factory().GetNewObject(uniqueIdentifier);
return cacheRefresher != null ? cacheRefresher.Name : "<error determining factory type>";
}
private static void LogStartDispatch()
{
Log.Add(
LogTypes.System,
User.GetUser(0),
-1,
"Submitting calls to distributed servers");
}
/// <summary>
/// Gets the node list of DistributionServers from config.
/// </summary>
/// <returns></returns>
private static XmlNodeList GetDistributedNodes()
{
return UmbracoSettings.DistributionServers.SelectNodes("./server");
}
private static IAsyncResult[] GetAsyncResults(List<IAsyncResult> asyncResultsList,
out List<WaitHandle> waitHandlesList)
{
IAsyncResult[] asyncResults = asyncResultsList.ToArray();
waitHandlesList = new List<WaitHandle>();
foreach (IAsyncResult asyncResult in asyncResults)
{
waitHandlesList.Add(asyncResult.AsyncWaitHandle);
}
return asyncResults;
}
#region Nested type: DispatchType
private enum DispatchType
{
RefreshAll,
RefreshByNumericId,
RefreshByGuid,
RemoveById
}
#endregion
}
}
}

View File

@@ -47,7 +47,12 @@
<add key="umbracoProfileUrl" value="profiler" />
<add key="umbracoUseSSL" value="false" />
<add key="umbracoUseMediumTrust" value="false" />
<add key="umbracoContentXMLUseLocalTemp" value="false"/> <!-- Set to true to write this to the local CodeGenDir instead, in SAN / NAS situations -->
<!--
Set this to true to enable storing the xml cache locally to the IIS server even if the app files are stored centrally on a SAN/NAS
Alex Norcliffe 2010 02 for 4.1
-->
<add key="umbracoContentXMLUseLocalTemp" value="false"/>
</appSettings>
<system.net>
<mailSettings>
@@ -227,4 +232,4 @@
</assemblyBinding>
</runtime>
</configuration>
</configuration>