使用C#和SQL Server读取和聚合数千个文件

时间:2013-03-31 18:36:44

标签: c# sql-server sqlite tsql

我有很多文件在随机文件共享上。我必须将它们复制到我的SQL Server 2008数据库中并总结所有要点。将文件从网络复制到C#到数据库的开销使得这个过程变慢,我需要处理数千个非常大的文件。

文件1示例

Player | Points
---------------
Bean   | 10
Ender  | 15

文件2示例

Player | Points
---------------
Ender  | 20
Peter  | 5

结果

Player | Points
---------------
Bean   | 10
Ender  | 35
Peter  | 5

当前方法:使用C#,将每个文件读入数据库并合并到主表中。

MERGE INTO Points as Target
USING Source as Source
 ON Target.Player = Source.Player
WHEN MATCHED THEN
  UPDATE SET Target.Points = Target.Points + Source.Points
WHEN NOT MATCHED THEN 
  INSERT (Player, Points) VALUES (Source.Player, Source.Points);

这种方法很好,但我正在寻找改进的想法(有点慢)。

建议的解决方案:

将每个文件读入SQLite数据库(基于读取,这应该非常快),将整个数据库批量加载到我的SQL Server数据库中并在那里进行所有处理。我应该能够为每个玩家分配一个等级,从而加快分组速度,因为我没有基于文本列进行比较。建议的解决方案的失败是它不能在多个线程上工作。

将所有这些文件放入数据库并将它们聚合在一起的最快方法是什么?

编辑:我忘记提及的文件的更多背景

  • 这些文件位于多个服务器上
  • 我需要将此任务的“影响”保持在最低限度 - 因此不需要安装应用
  • 文件可以巨大 - 每个文件多达1GB,因此在内存中执行任何操作都不是一种选择
  • 有数千个要处理的文件

1 个答案:

答案 0 :(得分:1)

所以,如果你不能/不想运行代码来启动包含这些文件的各个服务器上的解析操作,并且传输它们的演出和演出很慢,那么这是否是多线程的可能是无关紧要的 - 您的流程中的性能瓶颈是文件传输。

所以做一些假设:

  1. 有一台主服务器,只有它可以正常工作。

  2. 它可以立即(如果慢)访问所有必需的文件共享,可以通过简单的路径访问,并且您知道这些路径。

  3. 主计数服务器上有一个本地数据库,用于存储玩家分数。

  4. 如果您可以像传输一个文件那样快速地传输多个文件,我会编写执行以下操作的代码:

    1. 收集需要聚合的文件列表 - 这至少应该是一个小而便宜的列表。将它们收集到ConcurrentBag

    2. 调整任务数量,因为机器上的带宽将允许您运行复制操作。您需要进行测试以确定这是什么。

    3. 每个任务都将ConcurrentBag作为参数。它从一个运行TryTake()的循环开始直到它成功 - 一旦它成功地从包中开始直接从文件位置读取文件路径并解析,将每个用户的分数添加到该用户的本地数据库中的任何内容。 / p>

    4. 一旦Task完成了一个文件的处理,它就会继续尝试从ConcurrentBag获取下一个文件路径,等等。

    5. 最终所有文件路径都已处理完毕,任务结束。

    6. 所以代码大概是:

      public void Start()
      {
          var bag = new ConcurrentBag<string>();
      
          for(var i = 0; i < COPY_OPERATIONS; i++)
          {
              Task.Factory.StartNew(() =>
              {
                  StartCopy(bag);
              });
          }
      }
      
      public void StartCopy(ConcurrentBag<string> bag)
      {
          while (true)
          {
              // Loop until the bag is available to hand us a path to work on
              string path = null;
              while (!bag.IsEmpty && !bag.TryTake(out path))
              {}
      
              // Access the file via a stream and begin parsing it, dumping scores to the db
          }
      }
      

      通过流式传输,您可以使复制操作保持完全倾斜(事实上,操作系统很可能会为您提前读取一些内容,以确保您最大限度地提高复制速度)并且仍然可以避免使用这些文件的大小来敲除内存。 / p>

      如果不使用多个中间步骤,您可以跳过转移和考虑所有数据的重复成本 - 这样您就可以只执行一次。

      通过使用上述方法,您可以轻松地考虑最佳复制操作数。

      你可以在这里进行一些优化,使其易于重启,就像所有线程都收到一个信号来停止他们正在做的事情,并在数据库中记录他们正在处理的文件,他们现在正在处理的文件,和他们离开的线。您可以让他们不断地将这些值写入数据库,但性能成本很低,以使其成为崩溃证明(通过使行号和分数写入单个事务的一部分)。


      原始答案

      你忘了在你的问题中指明这一点,但是这些分散的文件似乎记录了在网络服务器群集上玩游戏的玩家得分?

      这听起来像是一个令人尴尬的并行问题。而不是从每台机器上复制大量文件,为什么不编写一个可以在所有机器上运行并将其分发给它们的简单应用程序?它只是将机器上的点数相加,并通过网络向每个玩家发回一个号码和玩家ID,解决网络问题缓慢的问题。

      如果这是一项正在进行的任务,您可以为总和添加时间戳,这样您就不会计算两次相同的点,只是定期批量运行。

      我将网络服务器应用程序编写为一个简单的webapp,只响应一个IP(您最初将要执行所有操作的主计数服务器),并响应请求,在本地运行计数并响应和。这样主服务器就会将请求发送到所有分数服务器,并等待它们发回它们的总和。完成。

      您可以通过将和数据存储在内存中作为字典映射播放器ID到sum来保持客户端应用程序非常简单 - 无需SQL。

      计数软件也可以在RAM中完成所有操作,然后将其全部转储到SQL Server,总计完成以节省时间。

      有趣的问题。