【问题标题】:What are the things I should avoid in my python Cloud Function to avoid memory leak?为了避免内存泄漏,我应该在我的 python 云函数中避免哪些事情?
【发布时间】:2019-01-20 22:24:59
【问题描述】:

一般问题

我的 python 云函数每秒引发大约 0.05 个内存错误 - 它每秒被调用大约 150 次。我感觉我的函数会留下内存残留,这会导致它的实例在处理了许多请求后崩溃。您应该做或不做哪些事情,以使您的函数实例在每次调用时都不会吃掉“更多分配的内存”?我被指向文档以了解I should delete all temporary files,因为这是写在内存中的,但我认为我没有写过任何内容。

更多上下文

我的函数的代码可以总结如下。

  • 全局上下文:在 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': '*'})

【问题讨论】:

  • 您是否因为崩溃而面临任何问题? Cloud Functions 应该会自动生成新实例。
  • 另外,你是如何抓取文件并打开它的?你要关闭文件吗?
  • 没有看到所有相关的代码,真的不可能说什么具体的。可以提供的只是删除临时文件,并且不将任何内容存储在全局内存中,任何地方,除非它在大小方面完全由您控制。
  • 您在使用 tempfile.mkstemp 吗?如果是这样,请看一下:logilab.org/blogentry/17873。另外,看看这些最佳实践:cloud.google.com/functions/docs/bestpractices/tips
  • 刚刚添加了代码。 @Kannappan由于文件是从全局上下文中的连接中获取的,我认为我不必关闭任何东西吗?我并没有因此而真正遇到“问题”,但我很好奇根本没有内存错误的可能性,而且我不喜欢我的某些事件可能不会得到处理的想法。

标签: python google-cloud-functions


【解决方案1】:

您可以尝试一些事情(理论上的答案,我还没有玩过 CF):

  • 显式删除您在机器人处理路径上分配的临时变量,这些临时变量可能相互引用,从而阻止内存垃圾收集器释放它们(请参阅https://stackoverflow.com/a/33091796/4495081):nb_prodsunwantedformnew_formfriendly_names,例如。

  • 如果 unwanted 始终相同,则改为全局。

  • 删除form,然后将其重新分配给new_form(旧的form 对象仍然存在);也删除new_form 实际上不会节省太多,因为该对象仍然由form 引用。 IE。改变:

        form = new_form
        del new_form
    

    进入

        del form
        form = new_form
    
  • 在发布主题后和返回之前显式调用内存垃圾收集器。我不确定这是否适用于 CF,或者调用是否立即生效(例如在 GAE 中它不是,请参阅When will memory get freed after completing the request on App Engine Backend Instances?)。这也可能是矫枉过正,可能会损害您 CF 的性能,看看它是否/如何适合您。

    gc.collect()
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-08
    • 2013-06-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多