繁体   English   中英

如何加快从 Python-Flask 应用程序返回 20MB Json 文件的速度?

[英]How to speed up returning a 20MB Json file from a Python-Flask application?

我正在尝试调用 API 进而触发我们的 sqlserver 数据库中的存储过程。 我就是这样编码的。

class Api_Name(Resource):

    def __init__(self):
        pass

    @classmethod
    def get(self):
        try:
            engine = database_engine
            connection = engine.connect()
            sql = "DECLARE @return_value int EXEC @return_value = [dbname].[dbo].[proc_name])
            return call_proc(sql, apiname, starttime, connection)
        except Exception as e:
            return {'message': 'Proc execution failed with error => {error}'.format(error=e)}, 400
        pass

call_proc是我从数据库返回 JSON 的方法。

def call_proc(sql: str, connection):
    try:
        json_data = []
        rv = connection.execute(sql)
        for result in rv:
            json_data.append(dict(zip(result.keys(), result)))
        return Response(json.dumps(json_data), status=200)
    except Exception as e:
        return {'message': '{error}'.format(error=e)}, 400
    finally:
        connection.close()

output 的问题在于返回 JSON 的方式及其大小。 起初 API 需要 1 分 30 秒:当返回语句是这样的:

case1: return Response(json.dumps(json_data), status=200, mimetype='application/json')

上网查了一下,发现上面的说法是想美化JSON。 所以我从响应中删除了mimetype并将其设置为

case2: return Response(json.dumps(json_data), status=200)

API 运行 30 秒,尽管 JSON output 没有正确对齐,但它仍然是 Z0ECD18ZA48。 我看到从 API 返回的 JSON 的 output 大小接近 20MB。 我在 postman 响应中观察到这一点:

Status: 200 OK    Time: 29s    Size: 19MB

Json output的区别:

情况1:

[   {
        "col1":"val1",
        "col2":"val2"
    },
    {
        "col1":"val1",
        "col2":"val2"
    }
]

案例2:

[{"col1":"val1","col2":"val2"},{"col1":"val1","col2":"val2"}]

output与上述两种情况的区别会不同吗? 如果是这样,我该如何解决这个问题? 如果没有区别,有什么办法可以进一步加快速度并进一步减少运行时间,比如压缩我要返回的 JSON?

您可以使用gzip压缩来使纯文本的重量从兆字节到千字节。 甚至为此使用烧瓶压缩库。
另外我建议使用ujson来使dump()调用更快。

import gzip

from flask import make_response
import ujson as json


@app.route('/data.json')
def compress():
    compression_level = 5  # of 9 max
    data = [
        {"col1": "val1", "col2": "val2"},
        {"col1": "val1", "col2": "val2"}
    ]
    content = gzip.compress(json.dumps(data).encode('utf8'), compression_level)
    response = make_response(content)
    response.headers['Content-length'] = len(content)
    response.headers['Content-Encoding'] = 'gzip'
    return response

文档:

首先,配置文件:如果 90% 的时间用于通过网络传输,则优化处理速度不如优化传输速度有用(例如,按照wowkin 推荐的方式压缩响应(尽管 web 服务器可能配置为这会自动,如果您使用的是一个)

假设构建 JSON 很慢,如果您控制数据库代码,您可以使用它的JSON 功能来序列化数据,并避免在 ZA7F5F35426B9274173Z 层执行此操作。 例如,

SELECT col1, col2
FROM tbl
WHERE col3 > 42
FOR JSON AUTO

会给你

[
    {
        "col1": "foo",
        "col2": 1
    },
    {
        "col1": "bar",
        "col2": 2
    },
    ...
]

也可以创建嵌套结构,如文档中所述。

如果请求者只需要数据,请使用烧瓶的send_file功能将其作为下载返回,并避免构建 HTML 响应的成本:

from io import BytesIO
from flask import send_file

def call_proc(sql: str, connection):
    try:
        rv = connection.execute(sql)
        json_data = rv.fetchone()[0]
        # BytesIO expects encoded data; if you can get the server to encode 
        # the data instead it may be faster.
        encoded_json = json_data.encode('utf-8')
        buf = BytesIO(encoded_json)
        return send_file(buf, mimetype='application/json', as_attachment=True, conditional=True) 
    except Exception as e:
        return {'message': '{error}'.format(error=e)}, 400
    finally:
        connection.close()

您需要在 API 上实现分页。 19MB 大得离谱,会导致一些非常恼火的用户。

gzipJSON响应的聪明才智是不够的,你需要做更多的工作。

幸运的是,有很多分页问题和答案,而 Flasks 模块化的处理方式意味着有人可能编写了一个适用于您的问题的模块。 我将从使用 ORM 重新实现该方法开始。 听说sqlalchemy相当不错。

要回答您的问题:

1 - JSON 在语义上是相同的。 您可以使用http://www.jsondiff.com来比较两个 JSON。

2 - 我建议您制作数据块并通过网络发送。

这可能会有所帮助: https://masnun.com/2016/09/18/python-using-the-requests-module-to-download-large-files-efficiently.html

TL;博士; 尝试重组您的 JSON 有效负载(即更改架构)

我看到您正在您的一个 API 中构建 JSON 响应。 目前,您的 JSON 有效负载如下所示:

[
  {
    "col0": "val00",
    "col1": "val01"
  },
  {
    "col0": "val10",
    "col1": "val11"
  }
  ...
]

我建议您以 JSON 中的每个(第一级)键代表整个列的方式对其进行重组。 因此,对于上述情况,它将变为:

{
  "col0": ["val00", "val10", "val20", ...],
  "col1": ["val01", "val11", "val21", ...]
}

这是我执行的一些离线测试的结果。

实验变量:

  • NUMBER_OF_COLUMNS = 10
  • NUMBER_OF_ROWS = 100000
  • LENGTH_OF_STR_DATA = 5
#!/usr/bin/env python3

import json

NUMBER_OF_COLUMNS = 10
NUMBER_OF_ROWS = 100000
LENGTH_OF_STR_DATA = 5

def get_column_name(id_): 
    return 'col%d' % id_ 

def random_data(): 
    import string 
    import random 
    return ''.join(random.choices(string.ascii_letters, k=LENGTH_OF_STR_DATA))

def get_row(): 
    return { 
        get_column_name(i): random_data() 
        for i in range(NUMBER_OF_COLUMNS) 
    }

# data1 has same schema as your JSON
data1 = [ 
    get_row() for _ in range(NUMBER_OF_ROWS) 
]

with open("/var/tmp/1.json", "w") as f: 
    json.dump(data1, f) 

def get_column(): 
    return [random_data() for _ in range(NUMBER_OF_ROWS)] 

# data2 has the new proposed schema, to help you reduce the size
data2 = { 
    get_column_name(i): get_column() 
    for i in range(NUMBER_OF_COLUMNS) 
}

with open("/var/tmp/2.json", "w") as f: 
    json.dump(data2, f) 

比较两个 JSON 的大小:

$ du -h /var/tmp/1.json
17M

$ du -h /var/tmp/2.json
8.6M

在这种情况下,它几乎减少了一半。

我建议您执行以下操作:

  • 首先,分析您的代码以查看真正的罪魁祸首。 如果它确实是有效负载大小,请继续。
  • 尝试更改 JSON 的架构(如上所述)
  • 在发送之前压缩您的有效负载(从您的 Flask WSGI 应用程序层或您的网络服务器级别 - 如果您在一些生产级网络服务器后面运行 Flask 应用程序,例如 ZE9713AE04A02A810D6F33DD956F4279 或 4Nginx)

对于无法使用ndjson (或任何类型的分隔记录格式)之类的内容进行分页的大数据,可以真正减少所需的服务器资源,因为您会阻止在 ZCD69B4957F06CD818DZBF3E26 中保存 JSON object 您需要访问响应 stream 才能将每个对象/行写入响应。

响应

[   {
        "col1":"val1",
        "col2":"val2"
    },
    {
        "col1":"val1",
        "col2":"val2"
    }
]

最终看起来像

{"col1":"val1","col2":"val2"}
{"col1":"val1","col2":"val2"}

这对客户端也有好处,因为您也可以自己解析和处理每一行。

如果您不处理响应 CSV 的嵌套数据结构,那么它会更小。

我想指出,有一种标准方法可以在 JSON 中编写一系列单独的记录,并在RFC 7464中进行了描述。 对于每条记录:

  1. 写入记录分隔符字节 (0x1E)。
  2. 在 UTF-8 中写入 JSON 记录,这是一个常规的 JSON 文档,也可以包含内部换行符。
  3. 写入换行字节 (0x0A)。

(请注意,所谓的 JSON 文本序列格式使用更自由的语法来解析此类文本序列;有关详细信息,请参阅 RFC。)

在您的示例中,JSON 文本序列如下所示,其中\x1E\x0A分别是记录分隔符和换行字节:

 \x1E{"col1":"val1","col2":"val2"}\x0A\x1E{"col1":"val1","col2":"val2"}\x0A

由于 JSON 文本序列格式允许内部换行符,因此您可以按自然方式编写每个 JSON 记录,如下例所示:

 \x1E{
    "col1":"val1",
    "col2":"val2"}
 \x0A\x1E{
    "col1":"val1",
    "col2":"val2"
 }\x0A

请注意,JSON 文本序列的媒体类型不是application/json ,而是application/json-seq 请参阅 RFC。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM