我正在创建一个C#控制台应用程序,该应用程序将遍历给定的文件夹(和子文件夹)以加密所有文件(二进制或文本)并更新sqlserver数据库中的IsEncrypted
标志。客户端盒上将有数百万个文件需要加密。我们计划每天在下班时间按计划任务运行该应用程序(例如从晚上10点开始每天运行8个小时)。
我有两个选择:
选项1
使用Parallel.ForEach
进行文件处理。
public void Process(ProcessorOptions options, ProcessorParameter parameter)
{
int counter = 0;
CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.CancellationToken = cts.Token;
try
{
parallelOptions.MaxDegreeOfParallelism = Environment.ProcessorCount;
if (options.NumberOfThreads > 0)
{
parallelOptions.MaxDegreeOfParallelism = options.NumberOfThreads;
}
if (options.StopTime != 0)
{
Timer timer = new Timer(callback => { cts.Cancel(); }, null, options.StopTime * 60000, Timeout.Infinite);
}
List<string> storagePaths = parameter.StoragePaths;
Log("Process Started...");
foreach (var path in storagePaths)
{
Parallel.ForEach(TraverseDirectory(path, f => f.Extension != ".enc"), parallelOptions, file =>
{
if (file.Name.IndexOf("SRSCreate.dir") < 0)
{
ProcessFile(parameter, file.FullName, file.Directory.Name, file.Name);
counter++;
}
});
}
Log(string.Format("Process Files Ended... Total File Count = {0}", counter));
}
catch (OperationCanceledException ex)
{
log.WriteWarningEntry(string.Format("Reached stop time = {0} min, explicit cancellation triggered. Total number of files processed = {1}", options.StopTime, counter.ToString()), ex);
}
catch (Exception ex)
{
log.WriteErrorEntry(ex);
}
finally
{
cts.Dispose();
}
}
我对它进行了基准测试,发现处理2000个文件几乎需要7-8分钟。我可以做些什么来改善性能吗?另外,确定下一次运行(第二天)从哪里开始的最佳方法是什么?
选项2
使用RabbitMQ
的现有设计来推送带有文件路径的消息,以处理文件,以实现可伸缩性和维护列表。
public void Process(ProcessorOptions options, ProcessorParameter parameter)
{
try
{
using (IConnection connection = parameter.ConnectionFactory.CreateConnection())
{
using (IModel channel = connection.CreateModel())
{
var queueName = parameter.TopicSubscription.DeriveQueueName();
var queueDeclareResponse = channel.QueueDeclare(queueName, true, false, false, null);
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (o, e) =>
{
string messageContent = Encoding.UTF8.GetString(e.Body);
FileData message = JsonConvert.DeserializeObject(messageContent, typeof(FileData)) as FileData;
ProcessFile(parameter, message.EntityId, message.Attributes["Id"], message.Attributes["filename"]);
};
string consumerTag = channel.BasicConsume(queueName, true, consumer);
}
}
}
catch (Exception ex)
{
log.WriteErrorEntry(ex);
}
finally
{
Trace.Exit(method);
}
}
在配置了StopTime
之后,我仍然必须弄清楚如何停止阅读消息。性能不是很好,我看到它需要大约25-30分钟来处理2000个文件。我们认为我们可以在一台或多台计算机上运行应用程序的多个副本,以处理单个队列以进行扩展。您认为吗,我可以更改此代码以使其更优化?
最后一个问题:您是否认为还有其他选择比上述选择更有效,更可扩展?
注意:
1)方法ProcessFile
调用加密逻辑和用于更新数据库的逻辑。
2)我们遍历文件夹而不是从数据库开始,因为有可能文件系统中的文件在数据库中尚不存在。
答案 0 :(得分:0)
我不确定生产中将涉及多少个物理驱动器。但是如果需要,客户可以添加更多。未加密的文件将替换为同一台服务器上的已加密文件,由于未加密的文件存在安全风险,因此需要对100%的文件进行加密,并且每天的计数都会减少。是的,加密要求文件在内存中以运行算法。文件的平均大小约为3 mb。我知道文件大小没有限制,但是通常我们会得到巨大的图像文件,Word和Excel文档,然后是一些小文本文件。
我可以看到问题有很多未知数,这表明单个配置不能在所有情况下都能解决。所以我的建议是使系统灵活。我将从为所涉及的物理驱动器创建配置开始。每个物理驱动器应具有并发设置。一个SSD驱动器可以在同时读写2-3个线程的情况下达到最佳工作状态,而一个硬盘驱动器可能会遇到多个线程。下一个重要的设置是加密器线程的数量。理想情况下,当正在运行的线程数等于计算机的可用处理器/核心数时,系统应能最好地工作。执行流程应如下所示:
所有这些都可以通过线程或任务以及BlockingCollection
类来实现。无需Parallel.ForEach
或第三方库。
答案 1 :(得分:-1)
这涉及到绩效问题,因此我将首先链接绩效问题:https://ericlippert.com/2012/12/17/performance-rant/
从本质上讲,此操作应该是磁盘绑定的,而不是CPU绑定的。进程可以快速完成文件的速度以及它可以快速读取,加密和写入文件的速度-显然都与磁盘绑定。在磁盘上同时执行更多操作将使速度变慢而不是变快。除非您当然有一些极端的设置,例如SSD的Raid 0。
如果可以从多任务处理中受益,则应该是数据库访问。通常,这些操作会通过网络堆栈进行,尤其是如果数据库在另一台计算机上,则很有可能会比磁盘慢。同时,您不想向数据库发送查询垃圾邮件。所有查询都有开销,而1 200行查询比200 1行查询要快。因此,以某种形式的Enuemration或Streaming方法获取DB数据,然后在文件上执行操作。但最慢的情况取决于每次运行时有多少个新/未加密文件。
将整个内容移到数据库中是可行的。有两种方法可以将BLOBS与DB一起存储,听起来您正在使用“在磁盘上存储,仅在DB中链接”。如果是这样,Filestream之类的属性可能会帮助您:https://www.red-gate.com/simple-talk/sql/learn-sql-server/an-introduction-to-sql-server-filestream/
稍微偏离主题,但是我的Pet-Peeve是异常处理,您的示例代码有一个主要的缺点:
catch (Exception ex)
{
log.WriteErrorEntry(ex);
}
您捕获了Exception,但不让它继续,这意味着您在致命的异常之后继续。这只会给您带来更多-而又难以理解-跟进异常。因此,您绝对不要那样做。我有两篇关于异常处理的文章确实链接很多,我认为它们可以在这里为您提供帮助: