使用 Json.net 将大量数据流式传输为 JSON 格式

Posted

技术标签:

【中文标题】使用 Json.net 将大量数据流式传输为 JSON 格式【英文标题】:Streaming large list of data as JSON format using Json.net 【发布时间】:2014-12-03 20:29:02 【问题描述】:

使用 MVC 模型,我想编写一个 JsonResult,它将 Json 字符串流式传输到客户端,而不是一次将所有数据转换为 Json 字符串,然后将其流式传输回客户端。 我有一些动作需要在 Json 传输时发送非常大(超过 300,000 条记录),我认为基本的 JsonResult 实现是不可扩展的。

我正在使用 Json.net,我想知道是否有一种方法可以在转换 Json 字符串时对其进行流式传输。

//Current implementation:
response.Write(Newtonsoft.Json.JsonConvert.SerializeObject(Data, formatting));
response.End();

//I know I can use the JsonSerializer instead
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Serialize(textWriter, Data);

但是我不确定如何将块写入 textWriter 并写入响应并调用 reponse.Flush() 直到所有 300,000 条记录都转换为 Json。

这可能吗?

【问题讨论】:

【参考方案1】:

假设您的最终输出是一个 JSON 数组,并且每个“块”是该数组中的一项,您可以尝试类似下面的 JsonStreamingResult 类。它使用 JsonTextWriter 将 JSON 写入输出流,并使用 JObject 作为在将每个项目写入写入器之前单独序列化每个项目的方法。您可以传递 JsonStreamingResultIEnumerable 实现,它可以从您的数据源中单独读取项目,这样您就不会一次将它们全部存储在内存中。我没有对此进行广泛的测试,但它应该能让你朝着正确的方向前进。

public class JsonStreamingResult : ActionResult

    private IEnumerable itemsToSerialize;

    public JsonStreamingResult(IEnumerable itemsToSerialize)
    
        this.itemsToSerialize = itemsToSerialize;
    

    public override void ExecuteResult(ControllerContext context)
    
        var response = context.HttpContext.Response;
        response.ContentType = "application/json";
        response.ContentEncoding = Encoding.UTF8;

        JsonSerializer serializer = new JsonSerializer();

        using (StreamWriter sw = new StreamWriter(response.OutputStream))
        using (JsonTextWriter writer = new JsonTextWriter(sw))
        
            writer.WriteStartArray();
            foreach (object item in itemsToSerialize)
            
                JObject obj = JObject.FromObject(item, serializer);
                obj.WriteTo(writer);
                writer.Flush();
            
            writer.WriteEndArray();
        
    

【讨论】:

该解决方案有效,可以防止内存不足异常,这太棒了。但我认为,如果将成批的记录一起刷新而不是一个一个地刷新,它会更加优化。不确定最佳数字是多少! 是的,我也想知道。您可以轻松地向 JsonStreamingResult 添加一个计数器,使其等待刷新,直到从可枚举中读取了一些记录。如果数字因情况而异,您可以将其设置为参数,以便您可以针对每种不同的用途对其进行调整。此外,在 IEnumerable 方面,您还可以实现一种机制来批量查询数据源,以提高那里的效率。不过,您必须进行大量测量和测试才能看到最有效的方法。 另一个想法虽然可能不可能是测量缓冲区大小并以每 64KB 或类似的方式刷新。不确定我们是否可以检查 JsonTextWriter 中的数据大小 如果你想做类似的事情,你可以尝试用BufferedStream 包裹OutputStream。但是,this Q & A 似乎表明 .NET 中的大多数流在缓冲方面已经得到了很好的优化。如果是这种情况,也许最好不要调用Flush,而让流在其内部缓冲区已满时执行其操作。不过不确定;你必须测试它。 一些基准测试表明最有效的方法是使用 serializer.Serialize(writer, data);并一次将所有数据传递给它,因为上面的注释表明 Stream 本身在处理缓冲区方面做得很好,你的代码不需要做一个巨大的循环:)【参考方案2】:

将其留给 .NET 并等待缓冲区已满的问题还有其他问题。

例如: 如果你这样做,一些 json 的内容将被切断,从而导致前端的解析问题。

到目前为止,最好的方法是在您使用批次的情况下在每次迭代时刷新批次,或者如果您的设计是为了这样做,则按单个项目刷新它。

目前我使用 SSE 将数据推送到浏览器并使用分隔符消息“在消息结束时”向浏览器指示连接可以关闭,我知道 SSE 用例用于连续流,但我们也可以使用它帮助分块和批处理响应。

【讨论】:

以上是关于使用 Json.net 将大量数据流式传输为 JSON 格式的主要内容,如果未能解决你的问题,请参考以下文章

使用java每秒流式传输大量数据

Sails.js 将密钥和数据流式传输到 Angular

使用 Node.js HTTP API 或 Websockets 流式传输数据?

如何将视频数据流式传输到视频元素?

通过 Bookshelf.js 流式传输数据

使用管道将数据从 msssql 流式传输到节点