Python Flask。异步上传至AWS S3
使用线程和Celery将文件上传到AWS S3时实现亚秒级响应
有一段时间,你的客户要求你找到一种方法来上传一个相对较大的文件,但仍然比较快,尽管你不能立即知道上传过程的结果。
寻找另一种情况,可能有一次你需要预处理你的文件或存储在CSV或Excel文件中的数据,在它到达你的数据湖或数据库之前有几个步骤需要完成。
通常情况下,上传(或另外处理)数据需要时间,我很肯定,这对用户来说,等待过程完成是很奇怪的。如果是这样的话,那么拥有一个异步上传将给你带来很多好处。
在这篇文章中,我们将看一下同步和异步上传文件的几种方法。让我们直接进入主题!
准备工作
在进一步编写一些代码之前,让我们先准备好开始项目所需的所有东西。
首先,确保你有AWS访问密钥和秘密访问密钥。因为这对上传我们的文件到S3桶是必不可少的。
之后,我们需要创建一个S3桶。进入你的AWS控制台,搜索S3,然后点击 "创建水桶"。现在,你可以填写桶的名称(必须是一个独特的名称)和区域(保持与你和你的用户接近),为了这个教程,我们将授予公众对我们的S3桶的读取权限。
接下来,进入你新创建的水桶,进入权限选项卡。然后,用这个JSON添加/编辑水桶策略。
记得把BUCKET_NAME 改为你自己的水桶名称。
为了存储敏感数据,我们将使用.env 。所以,在你项目的根目录下创建.env 。
把你的AWS访问密钥和秘密访问密钥放在那里,还有S3桶的名称和S3桶的基本URL。通常情况下,你的S3桶的基本URL将是这样的格式。https://<BUCKET_NAME>.s3.<REGION>.amazonaws.com,例如 [https://mybucket.s3.ap-southeast-1.amazonaws.com](https://flaskasyncbucket.s3.ap-southeast-1.amazonaws.com/).
最后,为了确保我们能在本地机器上运行Celery,我们需要Docker和docker-compose。如果你没有安装这些,那么你可以按照这个文档docs.docker.com/engine/inst…。
编码时间
在上一节中,我们创建了我们的S3桶,并授予公共读取权限。还确保我们安装了Docker引擎和docker-compose,并拥有AWS访问密钥和秘密访问密钥。
在本节中,我们将开始建立我们的项目,首先创建一个虚拟环境。运行这个程序来创建一个虚拟环境。
python -m venv venv
然后通过运行激活虚拟环境。
# Mac OS / Linuxsource venv/bin/activate
现在,安装所需的依赖项。
pip install flask Flask-SQLAlchemy boto3 celery
对于配置,让我们创建一个名为config.py 的文件,并把这些代码放在那里。
在这里,我们创建了一个配置类来存储所有需要的信息(包括敏感数据),并创建了一个Celery实例来定义Celery以后的任务。
由于我们实际上是将文件上传到S3桶,而且我们需要知道这些文件的存储位置,那么我们需要一个数据库来存储目标文件的URL。要做到这一点,请用这个内容创建一个名为models.py 的文件。
在这里,我们定义了一个名为 "文件 "的模型实体,其字段为id,name,url, 和upload_status 。对于上传状态,它是一个枚举,所以它只能在四个值之间,PENDING,PROCESSING,COMPLETE, 和ERROR 。这个状态很重要,因为我们将以异步方式上传文件,所以用户不需要等到上传过程结束,但他/她可以立即检查上传状态。
现在是上传功能。创建file.py ,把这个放在那里。
上述代码片断的解释。
- 我们总共有四个函数,与上传文件有关。第一个是
rename_file,就像函数名称所暗示的那样,这个函数将通过结合原始文件名和当前的时间戳来重命名我们要上传的文件,同时也确保用户首先提供正确的文件名。 - 对于
process_file_to_stream,我们需要这个函数将文件类型对象转换为字节流,因为我们不能直接将文件类型对象的参数给线程或Celery任务中的可调用函数。实际上,还有另一种方法可以做到这一点,即暂时存储该文件,然后将其上传到S3桶中。还注意到有一个参数to_utf8,这是在我们想用Celery上传文件时使用的,因为不可能向Celery的任务发送字节(基本上是因为所有发送到Celery任务的数据必须是可序列化的JSON)。 upload_file是向S3桶上传文件的正常同步方式。我们不需要将其转换为字节流,我们可以直接使用 中的 函数将其上传。boto3upload_fileobj- 在
upload_file_from_stream,我们将字节或utf8字符串转换为文件类型对象。然后我们就可以正常使用upload_fileobj。
在开始测试我们的项目之前,我们需要通过端点打开所有定义的函数。让我们创建一个名为routes.py 的Python文件。
对上述代码片断的解释。
index: 这很好理解,对吧? 😊normal_upload: 这个端点使用了一个同步上传的功能。所以用户需要等待,直到上传过程完成。async_upload: 这个端点使用线程来处理上传的过程。因此这个函数会立即返回,而不需要等待过程完成。注意,在第51行,我们创建了一个Thread实例,并为它提供了一个可调用的函数,以及运行该可调用函数所需的参数。在 定义里面,我们使用了应用程序的上下文,因此我们可以与应用程序或数据库进行交互。__async_uploadcelery_upload: 这个端点需要Celery的任务被定义。我们现在就来做这个。
让我们来定义Celery的任务,在celery_upload 。
很简单吧?首先,使用file_id 获得一个文件实体,然后使用upload_file_from_stream 上传文件(以文件字典的形式,包含字节流、文件名、内容类型等)。然后,如果一切工作正常,则提交完成状态,如果发生异常,则提交错误状态。
最后,将所有这些代码合并到app.py 。这就是我们项目的入口。
一切准备就绪后,现在我们可以测试我们的项目了。但首先,如果我们想测试celery的上传端点,我们需要Dockerfile和docker-compose。
创建Dockerfile 和docker-compose.yaml 。
理性检查
让我们通过running来运行我们的项目。
docker-compose up
这个命令将创建三个容器,应用程序、Redis实例和celery工作者。
转到http://localhost:5000/normal_upload,测试正常的上传。提供表单数据,其中包括键file 和要上传的文件的值。
你可以对/async_upload 和/celery_upload 做同样的事情。
完成上传请求需要多长时间?在我的例子中,如果没有非常快的网络连接,使用普通上传需要5秒钟来上传一个10MB的PDF文件,使用异步上传需要不到100毫秒,使用芹菜上传需要100-200毫秒。你的情况如何?
免责声明,上述数字当然不是基准测试的具体证据。因此,要提出这样的主张,最好是做负载测试。如果你对负载测试感到好奇,我有好的资源给你。
使用Locust.io进行负载测试
有一段时间,在我们的应用程序或服务运行后,我们想知道性能和负载,可以...
用k6进行负载测试
只用一个工具就能实现一堆负载测试功能
负载测试
(levelup.gitconnected.com/load-testin…)
结论
我们已经看到了如何使用几种方式将文件上传到AWS S3桶,正常上传、使用线程上传和使用Celery上传。一般来说,我们可以根据自己的需要,悬浮使用线程和作业队列,在这种情况下,当用户上传文件时,可以实现亚秒级的响应。
如果你想对上传的文件进行预处理,你也可以使用这个方案,例如为图片制作不同大小的副本,以及为你的数据科学项目清理数据点。
以下是本文的编码材料。
flask-workbook/async-upload-to-s3 at master - agusrichard/flask-workbook
用Flask学习Web开发项目。贡献于agusrichard/flask-workbook的开发,通过创建一个...
谢谢您的阅读,祝您编码愉快!