我写了一个应用程序,其目的是从一个大表(9000万)中读取日志,并将它们处理成易于理解的统计数据,多少,多长时间等。
首次运行耗时7.5小时,只需要处理9000万中的27小时。我想加快速度。所以我试图并行运行查询。但是当我运行下面的代码时,我会在几分钟内因内存不足而崩溃。
环境:
同步
测试:26个应用程序,1500万个日志,500万个检索,< 20mb,需要20秒
生产:56个应用程序,9000万个日志,2700万个检索,< 30mb,需要7.5小时
异步
测试:26个应用程序,1500万个日志,500万个检索,< 20mb,需要3秒
生产:56个应用程序,9000万个日志,2700万个检索,内存异常
public void Run()
{
List<Application> apps;
//Query for apps
using (var ctx = new MyContext())
{
apps = ctx.Applications.Where(x => x.Type == "TypeIWant").ToList();
}
var tasks = new Task[apps.Count];
for (int i = 0; i < apps.Count; i++)
{
var app = apps[i];
tasks[i] = Task.Run(() => Process(app));
}
//try catch
Task.WaitAll(tasks);
}
public void Process(Application app)
{
//Query for logs for time period
using (var ctx = new MyContext())
{
var logs = ctx.Logs.Where(l => l.Id == app.Id).AsNoTracking();
foreach (var log in logs)
{
Interlocked.Increment(ref _totalLogsRead);
var l = log;
Task.Run(() => ProcessLog(l, app.Id));
}
}
}
建议创建56个上下文是不明智的吗?
在检索到一定数量的日志后,是否需要处理和重新创建上下文?
也许我误解了IQueryable是如何工作的?&lt; - 我的猜测
我的理解是它会根据需要检索日志,我想这对循环来说就像收益一样?或者是我的问题56&#39;线程&#39;调用数据库,我在内存中存储了2700万条日志?
附带问题
结果并没有真正地扩展到一起。根据测试环境结果,我预计生产只需几分钟。我假设增加与表中的记录数直接相关。
答案 0 :(得分:0)
有2700万行,问题是流处理,而不是并行执行。您需要像使用SQL Server的SSIS或任何其他ETL工具一样解决问题:每个处理步骤都是一个转换处理步骤,它处理输入并将其输出发送到下一步。
通过使用单独的线程来运行每个步骤来实现并行处理。某些步骤还可以使用多个线程来处理多个输入,直至达到限制。为每个步骤的线程数和输入缓冲区设置限制可确保您可以实现最大吞吐量,而不会使机器因等待任务而充斥。
.NET的TPL数据流正好解决了这种情况。它提供了从输入到输出的转换(TransformBlock),将集合拆分为单个消息(TransformManyBlock),不转换执行操作(ActionBlock),批量组合数据(BatchBlock)等。
您还可以为每个步骤指定最大并行度,例如,每次只执行1个日志查询,但使用10个任务进行日志处理。
在您的情况下,您可以:
步骤#3可以分解为许多其他步骤。例如,如果不需要一起处理所有应用程序日志条目,则可以使用步骤处理单个条目。或者你可以先按日期对它们进行分组。
另一种选择是创建一个自定义块,使用DbDataReader从数据库中读取数据,并立即将每个条目发布到下一步,而不是等待所有行返回。这将允许您在到达时处理每个条目,而不是等待接收所有条目。
如果每个应用程序日志包含许多条目,这可能是一个巨大的内存和节省时间