6

我有一个 1M 项目商店,List<Person>我在其中进行序列化以便插入到 Redis。(2.8)

我将工作划分为10 Tasks<>每个部分都有自己的部分(List<>对于只读线程是安全的在 List 上执行多个读取操作是安全的

简化:

例子:

对于 ITEMS=100, THREADS=10, 每个Task都会​​捕获自己的 PAGE 并处理相关范围。

例如:

void Main()
{
    var ITEMS=100;
    var THREADS=10;
    var PAGE=4;

    List<int> lst = Enumerable.Range(0,ITEMS).ToList();

    for (int i=0;i< ITEMS/THREADS ;i++)
    {
      lst[PAGE*(ITEMS/THREADS)+i].Dump();
    }
}
  • PAGE=0将处理:0,1,2,3,4,5,6,7,8,9
  • PAGE=4将处理:40,41,42,43,44,45,46,47,48,49

一切都好。

现在回到 SE.redis。

我想实现这种模式,所以我做到了:(使用ITEMS=1,000,000

在此处输入图像描述

我的测试:

(这里是dbsize每秒检查一次):

在此处输入图像描述

如您所见,通过 10 个线程添加了 1M 条记录。

现在,我不知道它是否很快,但是,当我将 ITEMS 从更改1M10M-- 事情变得非常缓慢并且我得到异常:

例外是在for循环中。

未处理的异常:System.AggregateException:发生一个或多个错误。---

System.TimeoutException:在 StackExchange.Redis 执行 SET urn:user>288257,inst:1,queu e:11,qu=0,qs=11,qc=0,wr=0/0,in=0/0 时超时。 ConnectionMultiplexer.ExecuteSyncImpl[T](消息消息,ResultProcessor 1 processor, ServerEndPoint server) in c:\TeamCity\buildAgen t\work\58bc9a6df18a3782\StackExchange.Redis\StackExchange\Redis\ConnectionMultip lexer.cs:line 1722 at StackExchange.Redis.RedisBase.ExecuteSync[T](Message message, ResultProces sor1 处理器,ServerEndPoint 服务器)在 c:\TeamCity\buildAgent\work\58bc9a6df 18a3782\StackExchange.Redis\StackExchange\Redis\RedisBase.cs:line 79 ... .. 。 按任意键继续 。. .

问题:

  • 我的分工方式是正确的方式吗(最快)
  • 我怎样才能更快地得到东西(示例代码将不胜感激)
  • 如何解决此异常?

相关资料:

<gcAllowVeryLargeObjects enabled="true" />存在于 App.config 中(否则我会出现 outOfmemoryException ),还 - 为 x64 位构建,我有 16GB ,,ssd 驱动器,i7 cpu)。

4

1 回答 1

16

目前,您的代码正在使用同步 API ( StringSet),并由 10 个线程同时加载。这不会对 SE.Redis 带来明显的挑战——它在这里工作得很好。我怀疑这确实是一个超时,服务器花费的时间比您想要处理一些数据的时间长,很可能也与服务器的分配器有关。那么,一种选择是简单地增加超时时间。不是很多...尝试 5 秒而不是默认的 1 秒。很可能,无论如何,大多数操作都运行得非常快。

关于加快速度:这里的一个选择是不要等待- 即保持流水线数据。如果您满足于不检查每条消息的错误状态,那么执行此操作的一种简单方法是在通话, flags: CommandFlags.FireAndForget结束时添加。StringSet在我的本地测试中,这将 1M 示例加快了 25%(我怀疑其余的大部分时间实际上都花在了字符串序列化上)。

我在使用 10M 示例时遇到的最大问题仅仅是使用 10M 示例的开销——尤其是因为这需要大量内存用于redis-server和应用程序,它们(模拟您的设置)在同一台机器上。这会在托管代码中产生竞争性内存压力,以及 GC 暂停等。但也许更重要的是:开始做任何事情都需要很长时间。因此,我重构了代码以使用并行yield return生成器而不是单个列表。例如:

    static IEnumerable<Person> InventPeople(int seed, int count)
    {
        for(int i = 0; i < count; i++)
        {
            int f = 1 + seed + i;
            var item = new Person
            {
                Id = f,
                Name = Path.GetRandomFileName().Replace(".", "").Substring(0, appRandom.Value.Next(3, 6)) + " " + Path.GetRandomFileName().Replace(".", "").Substring(0, new Random(Guid.NewGuid().GetHashCode()).Next(3, 6)),
                Age = f % 90,
                Friends = ParallelEnumerable.Range(0, 100).Select(n => appRandom.Value.Next(1, f)).ToArray()
            };
            yield return item;
        }
    }

    static IEnumerable<T> Batchify<T>(this IEnumerable<T> source, int count)
    {
        var list = new List<T>(count);
        foreach(var item in source)
        {
            list.Add(item);
            if(list.Count == count)
            {
                foreach (var x in list) yield return x;
                list.Clear();
            }
        }
        foreach (var item in list) yield return item;
    }

和:

foreach (var element in InventPeople(PER_THREAD * counter1, PER_THREAD).Batchify(1000))

在这里,目的Batchify是通过在每次操作之间花费可观的时间来确保我们不会对服务器提供太多帮助 - 数据以 1000 个为一组创建,并且每批都非常快速地可用。

我也担心 JSON 的性能,所以我切换到 JIL:

    public static string ToJSON<T>(this T obj)
    {
        return Jil.JSON.Serialize<T>(obj);
    }

然后只是为了好玩,我将 JSON 工作移到批处理中(以便实际处理循环:

 foreach (var element in InventPeople(PER_THREAD * counter1, PER_THREAD)
     .Select(x => new { x.Id, Json = x.ToJSON() }).Batchify(1000))

这进一步缩短了时间,因此我可以在 3 分 57 秒内加载 10M,速度为 42,194 rops。大部分时间实际上是应用程序内部的本地处理。如果我更改它以便每个线程加载相同的项目ITEMS / THREADS时间,那么这将更改为 1 分 48 秒 - 92,592 rops 的速率。

我不确定我是否真的回答了任何问题,但简短的版本可能只是“尝试更长的超时时间;考虑使用即发即弃)。

于 2014-05-12T10:35:24.967 回答