【问题标题】:RavenDB throwing InvalidOperationException under high loadRavenDB 在高负载下抛出 InvalidOperationException
【发布时间】:2013-09-17 19:36:21
【问题描述】:

我不知道为什么,但是当我对 RavenDB 的并发写入进行负载测试时,在高负载下,我收到以下异常:“集合已修改;枚举操作可能无法执行。”

在高负载下,我说的是每秒 1000 个请求,更新 100 个文档。为了说明这一点,我将使用 RavenDB 来记录 MVC 站点上的操作,并且我正在运行一个测试工具来调用 log 方法以查看它是否可以处理记录这么多事情。每个文档代表用户在站点会话中的操作。

如果有帮助,具有相同请求数的更少文档可以正常运行。每秒请求数相对较高的较少文档也可以正常运行。

我从研究中了解到,有时在线程之间共享 IDocumentSession 是负责任的,但我不相信我的代码会这样做。

以下是相关的日志记录逻辑:

public class ActivityLogger : IActivityLogger
{
    private static ActivityLogger _instance;
    public static ActivityLogger Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new ActivityLogger();
            }
            return _instance;
        }
        set { _instance = value; }
    }

    private static DocumentStore docStoreInstance;
    // keeping this as a singleton - we only ever want one as it's expensive
    private static DocumentStore DocumentStore
    {
        get
        {
            if (docStoreInstance == null)
            {
                docStoreInstance = new DocumentStore() {ConnectionStringName = "RavenLogging"};
                docStoreInstance.Initialize();
            }
            return docStoreInstance;
        }
    }

    ...

    /// <summary>
    /// Logs the given LogItem to Raven, to the given UserSession
    /// </summary>
    public void Log(LogItem item, UserSession userSession)
    {
        if (userSession == null)
        {
            return;
        }
        if (!userSession.Activities.Contains(item))
        {
            userSession.Activities.Add(item);
            if (Cache != null)
                Cache.AddToSession(CachedObjects.USER_LOGGING_SESSION, userSession);
        }
        // done in a task since we don't want the app to wait for us to finish logging
        Task.Factory.StartNew(() =>
        {
            using (var session = DocumentStore.OpenSession())
            {
                try
                {
                    session.Store(userSession);
                    session.SaveChanges();
                }
                catch (Raven.Abstractions.Exceptions.ConcurrencyException ce)
                {
                    if (ce.ExpectedETag != ce.ActualETag)
                    {
                        // the user session is stale, reload it and try again
                        userSession = session.Load<UserSession>(userSession.Id);
                        Log(item, userSession);
                    }
                }
            }
        });
    }
}

这是我的测试工具中的代码

public void Begin()
{
    startTime = DateTime.Now;
    DateTime endTime = startTime.AddSeconds(Duration);
    int itemsLoggedThisSecond, secondsRemaining = 0;
    while (DateTime.Now < endTime)
    {
        // if we're still on the same second somehow, wait
        if (endTime.Subtract(DateTime.Now).Seconds == secondsRemaining)
        {
            Thread.Sleep(10);
            continue;
        }
        itemsLoggedThisSecond = 0;
        while (itemsLoggedThisSecond < RequestsPerSecond)
        {
            int innerItemsLoggedThisSecond = itemsLoggedThisSecond;
            Parallel.ForEach(Sessions, (session, state) =>
            {
                if (innerItemsLoggedThisSecond == RequestsPerSecond)
                {
                    state.Break();
                }
                var item = MakeRandomLogItem();
                ActivityLogger.Instance.Log(item, session);
                if (!session.Activities.Contains(item))
                {
                    session.Activities.Add(item);
                }
                innerItemsLoggedThisSecond++;
            });
            itemsLoggedThisSecond = innerItemsLoggedThisSecond;
        }
        secondsRemaining = (int) endTime.Subtract(DateTime.Now).TotalSeconds;
        Console.Write("\r" + secondsRemaining + " seconds remaining   ");
    }
}

如果有帮助,这里是个例外。

  System.InvalidOperationException was unhandled by user code
  HResult=-2146233079
  Message=Collection was modified; enumeration operation may not execute.
  Source=mscorlib
  StackTrace:
       at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
       at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
       at System.Collections.Generic.List`1.Enumerator.MoveNext()
       at Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IWrappedCollection values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalWriter.cs:line 524
       at Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalWriter.cs:line 129
       at Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalWriter.cs:line 383
       at Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalWriter.cs:line 124
       at Raven.Imports.Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalWriter.cs:line 62
       at Raven.Imports.Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\JsonSerializer.cs:line 627
       at Raven.Imports.Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value) in c:\Builds\RavenDB-Stable\Imports\Newtonsoft.Json\Src\Newtonsoft.Json\JsonSerializer.cs:line 599
       at Raven.Json.Linq.RavenJToken.FromObjectInternal(Object o, JsonSerializer jsonSerializer) in c:\Builds\RavenDB-Stable\Raven.Abstractions\Json\Linq\RavenJToken.cs:line 83
       at Raven.Json.Linq.RavenJObject.FromObject(Object o, JsonSerializer jsonSerializer) in c:\Builds\RavenDB-Stable\Raven.Abstractions\Json\Linq\RavenJObject.cs:line 159
       at Raven.Client.Document.EntityToJson.GetObjectAsJson(Object entity) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\EntityToJson.cs:line 74
       at Raven.Client.Document.EntityToJson.ConvertEntityToJson(String key, Object entity, RavenJObject metadata) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\EntityToJson.cs:line 41
       at Raven.Client.Document.InMemoryDocumentSessionOperations.EntityChanged(Object entity, DocumentMetadata documentMetadata) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:line 1033
       at Raven.Client.Document.InMemoryDocumentSessionOperations.<PrepareForEntitiesPuts>b__14(KeyValuePair`2 pair) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:line 908
       at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
       at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
       at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
       at Raven.Client.Document.InMemoryDocumentSessionOperations.PrepareForEntitiesPuts(SaveChangesData result) in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:line 908
       at Raven.Client.Document.InMemoryDocumentSessionOperations.PrepareForSaveChanges() in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\InMemoryDocumentSessionOperations.cs:line 901
       at Raven.Client.Document.DocumentSession.SaveChanges() in c:\Builds\RavenDB-Stable\Raven.Client.Lightweight\Document\DocumentSession.cs:line 694
       at Pendragon.Infrastructure.ActivityLogger.<>c__DisplayClass2.<Log>b__1() in c:\Users\me\repos\project\project\Infrastructure\Logger.cs:line 103
       at System.Threading.Tasks.Task.InnerInvoke()
       at System.Threading.Tasks.Task.Execute()

我在这里没有任何线索,因此非常感谢任何帮助。

【问题讨论】:

  • 您没有同步对可变共享变量的访问(至少 userSession 受到影响)。就那么简单。典型的比赛条件。

标签: c# ravendb


【解决方案1】:

在某个级别上,一个线程正在枚举一个集合,然后另一个线程更改该集合。简而言之,ActivityLogger.Instance 不是线程安全的。

我至少每个线程都有一个乌鸦连接。您可以考虑根本不共享连接,并且每个请求都有一个连接,特别是因为在托管时您将获得每个请求一个 MVCController,因此这将提供更准确的压力测试。

简单的解决方案可能是松开单例ActivityLogger.Instance 并在Parallel.ForEach 中创建一个新的。即

ActivityLogger.Instance.Log(item, session);

变成:

new ActivityLogger().Log(item, session);

(假设没有其他单身人士在玩?)

【讨论】:

  • +1 用于放弃单例模式。单例有一种快速变得线程不安全的方法。我认为将 ActivityLogger 分成具有非共享数据的多个实例将有助于避免线程问题(至少在这种特定情况下)。
  • 这似乎确实解决了问题。我为此选择了单例而不是静态,但似乎应该有不同的实例。解决此问题的其他方法是将 Raven DocumentStore 移动到 global.asax MvcApplication 类的公共属性。感谢您的帮助!
  • 很酷,顺便说一下,单例和static 意味着同样的事情 - 全局 1 个实例。实际上,大多数单例实现都是通过static 实现的(staticActivityLogger.Instance 必须的)。因此,选择一个优先于另一个没有区别。
  • 我选择单例模式而不是静态类的主要原因通常是为了进行单元测试,以便可以模拟对象以及由此带来的各种好处。但我确实同意你的观点:)
猜你喜欢
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 2015-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多