python - 为了避免内存泄漏,我应该在我的 python 云函数中避免哪些事情?
问题描述
一般问题
我的 python 云函数每秒引发大约 0.05 个内存错误 - 它每秒被调用大约 150 次。我感觉我的函数会留下内存残留,这会导致它的实例在处理了许多请求后崩溃。您应该做或不做哪些事情,以使您的函数实例在每次调用时都不会吃掉“更多分配的内存”?我被指给文档了解我应该删除所有临时文件,因为这是写在内存中的,但我认为我没有写过任何文件。
更多上下文
我的函数的代码可以总结如下。
- 全局上下文:在 Google Cloud Storage 上抓取一个文件,其中包含已知的机器人用户代理列表。实例化一个错误报告客户端。
- 如果 User-Agent 识别出机器人,则返回 200 代码。否则解析请求的参数,重命名它们,格式化它们,为请求的接收加上时间戳。
- 将生成的消息以 JSON 字符串形式发送到 Pub/Sub。
- 返回一个 200 代码
我相信我的实例正在逐渐消耗所有可用内存,因为我在 Stackdriver 中完成了这张图:
这是我的云函数实例的内存使用热图,红色和黄色表示我的大多数函数实例都在消耗这个范围的内存。由于似乎出现了循环,我将其解释为实例内存的逐渐填满,直到它们崩溃并产生新实例。如果我提高分配给函数的内存,这个循环仍然存在,它只是提高了循环所遵循的内存使用上限。
编辑:代码摘录和更多上下文
这些请求包含有助于在电子商务网站上实现跟踪的参数。现在我复制了它,可能会有一个反模式,我form['products']
在迭代它时会修改它,但我认为这与内存浪费没有任何关系?
from json import dumps
from datetime import datetime
from pytz import timezone
from google.cloud import storage
from google.cloud import pubsub
from google.cloud import error_reporting
from unidecode import unidecode
# this is done in global context because I only want to load the BOTS_LIST at
# cold start
PROJECT_ID = '...'
TOPIC_NAME = '...'
BUCKET_NAME = '...'
BOTS_PATH = '.../bots.txt'
gcs_client = storage.Client()
cf_bucket = gcs_client.bucket(BUCKET_NAME)
bots_blob = cf_bucket.blob(BOTS_PATH)
BOTS_LIST = bots_blob.download_as_string().decode('utf-8').split('\r\n')
del cf_bucket
del gcs_client
del bots_blob
err_client = error_reporting.Client()
def detect_nb_products(parameters):
'''
Detects number of products in the fields of the request.
'''
# ...
def remove_accents(d):
'''
Takes a dictionary and recursively transforms its strings into ASCII
encodable ones
'''
# ...
def safe_float_int(x):
'''
Custom converter to float / int
'''
# ...
def build_hit_id(d):
'''concatenate specific parameters from a dictionary'''
# ...
def cloud_function(request):
"""Actual Cloud Function"""
try:
time_received = datetime.now().timestamp()
# filtering bots
user_agent = request.headers.get('User-Agent')
if all([bot not in user_agent for bot in BOTS_LIST]):
form = request.form.to_dict()
# setting the products field
nb_prods = detect_nb_products(form.keys())
if nb_prods:
form['products'] = [{'product_name': form['product_name%d' % i],
'product_price': form['product_price%d' % i],
'product_id': form['product_id%d' % i],
'product_quantity': form['product_quantity%d' % i]}
for i in range(1, nb_prods + 1)]
useful_fields = [] # list of keys I'll keep from the form
unwanted = set(form.keys()) - set(useful_fields)
for key in unwanted:
del form[key]
# float conversion
if nb_prods:
for prod in form['products']:
prod['product_price'] = safe_float_int(
prod['product_price'])
# adding timestamp/hour/minute, user agent and date to the hit
form['time'] = int(time_received)
form['user_agent'] = user_agent
dt = datetime.fromtimestamp(time_received)
form['date'] = dt.strftime('%Y-%m-%d')
remove_accents(form)
friendly_names = {} # dict to translate the keys I originally
# receive to human friendly ones
new_form = {}
for key in form.keys():
if key in friendly_names.keys():
new_form[friendly_names[key]] = form[key]
else:
new_form[key] = form[key]
form = new_form
del new_form
# logging
print(form)
# setting up Pub/Sub
publisher = pubsub.PublisherClient()
topic_path = publisher.topic_path(PROJECT_ID, TOPIC_NAME)
# sending
hit_id = build_hit_id(form)
message_future = publisher.publish(topic_path,
dumps(form).encode('utf-8'),
time=str(int(time_received * 1000)),
hit_id=hit_id)
print(message_future.result())
return ('OK',
200,
{'Access-Control-Allow-Origin': '*'})
else:
# do nothing for bots
return ('OK',
200,
{'Access-Control-Allow-Origin': '*'})
except KeyError:
err_client.report_exception()
return ('err',
200,
{'Access-Control-Allow-Origin': '*'})
解决方案
您可以尝试一些事情(理论上的答案,我还没有玩过 CF):
显式删除您在机器人处理路径上分配的临时变量,这些变量可能相互引用,从而阻止内存垃圾收集器释放它们(请参阅https://stackoverflow.com/a/33091796/4495081):
nb_prods
、、、、, , 例如。unwanted
form
new_form
friendly_names
如果
unwanted
总是相同,则改为全局。form
在将其重新分配给之前删除new_form
(旧form
对象仍然存在);由于new_form
对象仍然由form
. 即改变:form = new_form del new_form
进入
del form form = new_form
在发布主题后和返回之前显式调用内存垃圾收集器。我不确定这是否适用于 CF 或调用是否立即生效(例如在 GAE 中它不是,请参阅在 App Engine 后端实例上完成请求后何时释放内存?)。这也可能是矫枉过正,并可能会损害您 CF 的表现,看看它是否/如何为您工作。
gc.collect()
推荐阅读
- swift - Swift/URLSession 返回与 OpenSSL 不同的公钥
- twilio - 创建会议后立即拨打号码
- java - 数组快捷语法困境 | Java 编程语言
- node.js - 如何在 Typescript 中正确扩展 Request 的标头
- axapta - 如何在 d365 中动态更改 formcommandbuttonControl 的命令?
- python - 类型错误:“功能”缺少一个必需的位置参数:“自我”
- python - 使用 map() 的 multiprocessing.Pool 在大列表输入时表现得非常慢
- html - 特定 div 的 CSS 动画
- java - 在java中将列表的旧值更改为新值
- ios - 如何创建可更新的 CoreML 模型?