我们运行的网站拥有大约15.000个实时用户(谷歌分析)(大约1000个请求/秒(性能计数器))。
我们有两台Web服务器支持负载均衡器。
有时每天有时会在一周内有一次我们的Web服务器停止执行请求并开始响应并出现错误,并且每个请求都会记录以下异常: " System.IndexOutOfRangeException - 索引超出了数组的范围。"
我们的环境:IIS 8.5,.Net 4.5.0,Mvc 5.1.0,Unity 3.5(与3.0相同),WebActivatorEx 2.0 在IIS中,工作进程1和其他设置具有默认值。
我们无法捕捉到任何特定情况造成此错误。应用程序池回收后,一切都没有问题。在每个请求都有错误响应之前,没有任何与之相关的错误。
过去有一个问题与相关的旧Unity版本有关: https://unity.codeplex.com/discussions/328841 http://unity.codeplex.com/workitem/11791 无法看到我能做些什么。
此处有例外情况:
System.IndexOutOfRangeException
Index was outside the bounds of the array.
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Collections.Generic.List`1.Enumerator.MoveNext()
at System.Linq.Enumerable.WhereListIterator`1.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Microsoft.Practices.Unity.NamedTypesRegistry.RegisterType(Type t, String name)
at Microsoft.Practices.Unity.UnityDefaultBehaviorExtension.OnRegisterInstance(Object sender, RegisterInstanceEventArgs e)
at System.EventHandler`1.Invoke(Object sender, TEventArgs e)
at Microsoft.Practices.Unity.UnityContainer.RegisterInstance(Type t, String name, Object instance, LifetimeManager lifetime)
at Microsoft.Practices.Unity.UnityContainerExtensions.RegisterInstance[TInterface](IUnityContainer container, TInterface instance, LifetimeManager lifetimeManager)
at DemoSite.News.Portal.UI.App_Start.UnityConfig.<>c__DisplayClass1.<RegisterTypes>b__0()
at DemoSite.News.Portal.Core.Controller.BaseController.Initialize(RequestContext requestContext)
at System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__4(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Async.AsyncResultWrapper.Begin[TState](AsyncCallback callback, Object callbackState, BeginInvokeDelegate`1 beginDelegate, EndInvokeVoidDelegate`1 endDelegate, TState invokeState, Object tag, Int32 timeout, SynchronizationContext callbackSyncContext)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
我的配置如下:
public static void RegisterTypes(IUnityContainer container)
{
var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
container.LoadConfiguration(section);
ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container));
}
初始化方法如下:
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
if (requestContext.RouteData.Values["ViewActionId"] != null)
{
int viewActionId;
if (!int.TryParse(requestContext.RouteData.Values["ViewActionId"].ToString(), out viewActionId))
return;
var cacheProvider = ServiceLocator.Current.GetInstance<ICacheProvider>();
List<ViewActionClass> viewActionClasses = null;
string cacheKey = CacheKeyCompute.ComputeCacheKey("ViewActionClass", CacheKeyTypes.DataCache,
new KeyValuePair<string, string>("viewActionId", viewActionId.ToString()));
_configuration = ServiceLocator.Current.GetInstance<IConfiguration>();
viewActionClasses =
cacheProvider.AddOrGetExistingWithLock<List<ViewActionClass>>(cacheKey, () =>
{
var viewActionClassBusiness =
ServiceLocator.Current.GetInstance<IViewActionClassBusiness>();
return viewActionClassBusiness.ViewActionClassGetByViewActionId(viewActionId);
});
ViewBag.ActionClass = viewActionClasses;
ViewBag.Configuration = _configuration;
}
base.Initialize(requestContext);
}
ICacheProvider,IConfiguration和IViewActionClassBusiness的注册xml
<type type="DemoSite.Runtime.Caching.ICacheProvider, DemoSite.Core"
mapTo="DemoSite.Runtime.Caching.ObjectCacheProvider, DemoSite.Core">
<lifetime type="containerControlledLifetimeManager" />
</type>
<type type="DemoSite.Core.Configuration.IConfiguration, DemoSite.Core"
mapTo="DemoSite.Core.Configuration.ConfigFileConfiguration, DemoSite.Core">
<lifetime type="containerControlledLifetimeManager" />
</type>
<type type="DemoSite.News.Business.IViewActionClassBusiness, DemoSite.News.Business"
mapTo="DemoSite.News.Business.Default.ViewActionClassBusiness, DemoSite.News.Business.Default">
<lifetime type="perRequestLifetimeManager" />
</type>
可能与高流量有关。 是否有人遇到类似的问题和任何解决方案?
提前致谢
答案 0 :(得分:9)
从堆栈跟踪中我可以看到,您在Web请求期间在容器中注册实例。 Unity中的RegisterType
和RegisterInstance
方法 不 线程安全(这可能适用于.NET中的大多数DI库)。这就解释了为什么这种情况发生在随机点和高负荷下。
最好只在启动时注册容器,以后不要更改容器。特别是使用依赖性倒置原则和依赖性注入模式,您可以集中处理对象图如何连接的知识,但是稍后通过执行新的注册将其再次分散。即使注册对Unity是线程安全的,你仍然很可能通过在运行时更改注册来引入竞争条件。
更新
您的代码具有导致问题的以下代码:
ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container));
这看起来非常无辜,但实际上它会导致并发错误和内存泄漏。
因为new
语句在lambda内,所以每次调用UnityServiceLocator
时都会创建一个新的ServiceLocator.Current
。这本身并不坏,但是UnityServiceLocator
的构造函数调用container.RegisterInstance
来在容器中注册自己。但正如我已经说过的那样:调用RegisterInstance`不是线程安全的。
但即使它是线程安全的,它仍然会导致应用程序中的内存泄漏,因为对RegisterInstance
的调用不会替换现有注册,而是将其附加到注册列表中。这意味着容器中的UnityServiceLocator
实例列表将继续增长,并最终导致系统崩溃并出现OutOfMemoryException。你真的很幸运,你首先遇到这个并发错误,因为OOM错误将更难追溯。
修复实际上非常简单:将UnityServiceLocator
的构造移出lambda并每次返回该单个实例:
var locator = new UnityServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => locator);
UnityServiceLocator
的行为在我看来是一个设计缺陷,因为RegisterInstance
不是线程安全的,UnityServiceLocator
不知道它创建了多少次,它永远不应该在其构造函数中调用RegisterInstance
- 或者至少 - 不要不检查注册该实例是否安全。
然而问题是删除对RegisterInstance
的调用是一个重大变化,但仍然可能是Unity团队的最佳解决方案。大多数用户可能不会注意到丢失的IServiceLocator
注册,如果他们这样做,Unity会在这种情况下传达明确的异常消息。另一种选择是让UnityServiceLocator
检查是否已经从容器中解析了任何实例,并且在这种情况下从UnityServiceLocator
的构造函数中抛出InvalidOperationException。