如何解决使用 WCF 通过流返回文件时如何减少内存使用量?
我每天有 1 个大文件和许多小文件被发送到服务器。服务器在收到这些时解析并创建/重新创建/更新一个 sqlite 数据库。客户端机器也需要这个数据库,并且可以请求它或请求更新。一切都通过局域网连接。
客户端机器需要数据库,因为它们没有可靠的互联网访问,因此不能选择使用云数据库。服务器也可能已关闭,因此向服务器询问单个查询是不可靠的。
大文件更新涉及数据库中的每一行,因为增量中可能遗漏了某些信息。因此,我们无法将大增量发送给客户端,我认为在客户端上重新创建它们更有意义。
由于客户端机器很差,查询服务器的行并在这些机器上进行大量增量非常耗时,可能需要 2 个多小时。由于这种情况每天都会发生,因此无法选择 24 小时内有 2 小时的陈旧数据。
我们决定让客户端请求整个数据库,当发生这种情况时,服务器会压缩并发送数据库,这只需几分钟。
为此,我将服务器设置为压缩数据库,然后返回 MemoryStream
。
var dbcopyPath = ".\\db_copy.db";
using (var readFileStream = new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.Read))
{
Log("Compressing db copy...");
using (var writeFileStream = new FileStream(dbcopyPath,FileMode.OpenorCreate,FileAccess.Write,FileShare.Read))
{
using (var gzipStream = new GZipStream(writeFileStream,CompressionLevel.Optimal))
{
readFileStream.copyTo(gzipStream);
}
}
}
return new MemoryStream(File.ReadAllBytes(dbcopyPath));
我尝试了其他一些方法,例如将 FileStream
写入 GZipStream(new MemoryStream())
并返回 GZipStream.ToArray()
,或者直接从文件中返回内存流。
我尝试过的所有选项的问题是它们都保留了大量内存(或者根本不起作用)。当我压缩后只有 200mb 的文件时,我已经看到该进程在运行时始终保留 600mb 的内存。如果进来的文件太大,这最终会开始给我内存不足的异常。在客户端,我可以像这样读取流:
var dbStream = client.OpenRead(downloadUrl);
这使得下载数据时客户端的内存使用量根本不会激增。
我的理想解决方案是将数据直接从文件通过服务器传输到客户端。我不确定这是否可能,因为我已经尝试了许多不同的流组合,但是如果有某种方法可以使用惰性流,例如服务器不会加载流的一部分,直到客户端需要它们进行写入这将是理想的,但我再次不确定这是否可能,甚至完全有意义。
我已尽力避免 XY 问题,因此,如果我遗漏了任何内容,请告诉我,感谢您对此提供的帮助。谢谢
解决方法
由于我不知道您如何传输数据(NetworkStream byte[] 等),您也可以将压缩数据库直接作为 FileStream 返回,因此无需使用 MemoryStream:
private static Stream GetCompressedDbStream(string path)
{
var tempFileStream = new TemporaryFileStream();
try
{
using (var readFileStream = new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.Read))
{
using (var gzipStream = new GZipStream(tempFileStream,CompressionLevel.Optimal,true))
{
readFileStream.CopyTo(gzipStream);
}
}
tempFileStream.Seek(0,SeekOrigin.Begin);
return tempFileStream;
}
catch (Exception)
{
// Log to console or alert user.
tempFileStream.Dispose();
throw;
}
}
为了正确管理临时文件的范围,我在这里实现了一个“TemporaryFileStream”类。这将在处理流后立即删除临时文件:
public class TemporaryFileStream : Stream,IDisposable
{
private readonly FileStream _fileStream;
private bool _disposedValue;
public override bool CanRead => _fileStream.CanRead;
public override bool CanSeek => _fileStream.CanSeek;
public override bool CanWrite => _fileStream.CanWrite;
public override long Length => _fileStream.Length;
public override long Position
{
get => _fileStream.Position;
set => _fileStream.Position = value;
}
public TemporaryFileStream()
{
_fileStream = new FileStream(Path.GetTempFileName(),FileAccess.ReadWrite);
new FileInfo(_fileStream.Name).Attributes = FileAttributes.Temporary;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_fileStream.Dispose();
File.Delete(_fileStream.Name);
}
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public override void Flush() => _fileStream.Flush();
public override int Read(byte[] buffer,int offset,int count) => _fileStream.Read(buffer,offset,count);
public override long Seek(long offset,SeekOrigin origin) => _fileStream.Seek(offset,origin);
public override void SetLength(long value) => _fileStream.SetLength(value);
public override void Write(byte[] buffer,int count) => _fileStream.Write(buffer,count);
}
然后您可以使用简单的 CopyTo 或 Read 来有效地传输数据:
using var stream = GetCompressedDbStream(@"DbPath");
// CopyTo ...
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。