Google Cloud 上的变量生命周期 运行 Python 是什么?

What are variables life time running Python on Google Cloud?

当我在 Google Function 上 运行 一个 Python 程序时,我想每天 一次 从数据库中读取一些数组,并确保它始终可以在下一个 http requests.

中使用

目前 array 是硬编码的:

array = [a,b,c,d]

我想每天读一次,不是,每次 http request 都想读得更快:

 array = read daily from server

问题是 - 假设我今天读了它,我是否保证此变量将可用于所有接下来的 1000 个 http 请求?基本上永远?它的生命周期是多少? 在服务器 restart/built 上为每个请求再次执行 python 程序 就像在我的计算机上一样?

这可能是您想要的:

data = None

def get_data():
    global data
    if not data:
        data = get_data_from_somewhere_slow()
        return data

在两次调用之间,实例可能保持活动状态,然后将使用“热”缓存为下一个请求提供服务,其中全局数据保持填充状态。每个实例一个请求并不是严格意义上的,它只是足够真实以至于不值得花太多精力在调用之间持久化数据。

您可以像这样从本地文件(或实际上是数据库)读取然后缓存数据,因此您从中获取数据的“慢”位置是您的本地文件(或数据库)。但是你也可以直接把它放在源代码本身中,并在需要时从那里“读取”它。

您还会遇到这样的问题,即如果不进行部署(因此是数据库)就无法更新本地文件,因为您需要直接更新数据。

因此,如果您乐于进行部署以更新数据,则无需调用数据库即可从本地文件中以超快的速度读取数据。

然而,考虑到世界上几乎每个站点都连接到一个数据库,并且性能可以非常快。那么究竟需要多快才算快?

您要解决什么问题?一次读取某个值(为什么?)或服务速度超过某个速度限制?决定了,你会得到更好的答案。

将 Cloud Functions 实例想象成一个服务器。启动它时,您可以加载所需的全局变量并将该数据保存在内存中。只要您的服务器启动并且 运行,全局范围内的数据就会保留。如果你启动一个新服务器,你必须重新加载那个全局变量,因为它是一个新环境。

Cloud Funcitons 实例的工作方式完全相同。 1 个请求一次只能由 1 个实例处理(对于 v1)。我的意思是,如果您有 2 个并发请求,则会创建 2 个实例,并且每个请求都在其专用实例上处理。

因为您有很多并行实例,所以您可以多次从源加载数据。此外,Cloud Functions 在一段时间后没有任何请求处理(10 到 30 分钟)后被卸载。它可以缩放到 0。

相反,如果您在 Cloud Functions 上有持续的流量,您可以在几小时和几天内保持同一实例和 运行。没有过期,您必须自己管理缓存失效。

最后,因为你在全局范围内加载东西,在启动时,你会减慢冷启动,我的意思是你的云函数启动和服务请求所花费的时间。

听起来你想要的是将这个 1000 键字典硬编码到你的源代码中。

另一种方法是创建一个日常构建过程

  1. 从数据库中读取您的字典并将其直接写入您的 python 源文件(或您的脚本在启动时读取的附近文件)
  2. 打包 python 源并部署它替换当前的云函数

现在您的云函数不必担心连接到数据库或使缓存失效。

如果发现您需要更频繁地更新,请将构建从每天更改为每 12 小时或任何最佳时间。 乔什

在对这个问题的不同答案写了十几条评论后,我决定将我的意见统一在一个答案中。

免责声明:很遗憾,我没有工作,而且我从未为 Google 工作过,所以下面的文字是基于阅读文档、白皮书、博客, 和个人经验。我可能错了!!!

免责声明:我尽量简化并省略了很多细节。真正的实现和行为 - 复杂得多。

免责声明:我不知道原问题的具体上下文、范围和要求。因此,我必须应用大量假设。

答案由 3 部分组成 - 云函数状态机的一般描述;一些常见场景的描述;以及一些评论和建议 - 与原始问题相关。

云函数状态机

请参阅图表及其下方的说明。

我建议考虑 3 个状态 - 第一个状态是 'global'(或在多个 run-time containers/environments 之间共享);其他两个 - 每 运行 时间 container/environment - 不是一个理想的描述,但无论如何:

已部署(冷)

  • 这是第一个状态——带有各种参数的源代码被部署到GCP项目中。源码已解析,但没有run-time容器,云函数(实例)等待调用。从这个状态 - 云功能可以被初始化,或者完全删除。

已初始化(热)

  • 云函数的运行时间环境是在容器中准备的。一次初始化完成。源代码被加载到内存中。该函数已准备好(热)调用。如果初始化包括获取一些外部数据——该数据被复制到云函数实例内存中,因此如果原始数据源稍后被修改,云函数实例副本具有该数据的陈旧版本。请记住,对于给定的云函数(源代码),可能有(几乎)任意数量的已初始化容器。该数字由 Cloud Functions 服务(由 Google)控制,而不是由客户端控制。根据我的经验,云功能可以在没有 'movements' 的情况下保持这种状态很长一段时间 - 可能长达半小时。从此状态可以调用或释放(终止)云函数。

运行

  • 在将请求绑定到具有初始化云函数的容器后调用云函数实例。云函数实例可以保持此状态,直到执行完成、超时或 run-time 崩溃发生。从这个状态,云功能可以完成并 return 进入初始化(热)状态,或者可以完全崩溃(我知道在这种情况下整个容器都被杀死了)。

可能会有一些状态转换:

1 - deploy - 第一个可能的状态转换发生在云功能部署过程中(即源代码、配置和环境解析和检查)。此状态转换导致已部署(冷)状态。

2 - initialize - 当 Cloud Function 服务 (Google) 决定使用所选的云函数初始化新容器时发生。初始化总是从最新的可用源代码开始(加载到容器内存中并在那里可用)。在此过程中,执行 'global'(在云函数入口点函数之外)代码 - 例如,在 Go 语言中执行 init 函数;以及各种全局变量的初始化。此状态转换导致初始化(热)状态。

3 - invoke - 当 Cloud Function 服务 (Google) 将传入请求与可用的(免费)初始化(热)云函数实例绑定并调用入口点函数时发生。此状态转换导致 运行 状态。

4 - finish - 在云函数完成执行时发生。此状态转换导致初始化(热)状态。

5 - crash - 当云函数代码内部出现完全错误时发生。我知道在这种情况下整个容器都被杀死了。此状态转换导致已部署(冷)状态。

6 - release (kill) - 当 Cloud Function 服务 (Google) 决定从 运行 时间 container/environment 释放内存时发生。此状态转换导致已部署(冷)状态。

7 - delete - 当客户端完全删除云功能时发生。

几个场景 - 非常简单的描述

云函数的部署。

我了解 - 最多发生 3 次状态转换 - 1 - deploy2 - initialize;如果没有立即调用云函数实例,则可选 6 - release (kill)。请记住,部署过程不会影响云 f已经在 运行 时间容器中的功能 - INITIALIZED (HOT) 和 运行 状态。他们可以在这些状态('invocation' 和 'finish' 状态转换)中继续上下波动一段时间 - 几分钟,几小时。但是,下一次初始化(运行 时间容器 - 'initialism' 状态转换)发生在已部署源代码的最新版本中。请记住 - 在 'hot' 容器中用新代码替换旧代码 - 需要时间 - 几分钟,甚至几小时。

调用云函数。

Cloud Function 服务 (Google) 检查是否有具有 INITIALIZED (HOT) 云函数的容器。如果存在这样的云函数实例 - 调用它。否则检查可能的云函数实例的最大数量。如果阈值未存档 - 'initialize' 来自最新可用的 DEPLOYED (COLD) 状态(最新部署的代码)的云函数,并在初始化时调用它。

云函数容器终止。

Cloud Function 服务 (Google) 检查是否有一个具有 INITIALIZED (HOT) 云函数的容器,该容器有一段时间未被调用(从几个 seconds/minutes 可能最多半小时)。并杀死它,释放所有内存。如果该容器云功能实例在其伪目录 temp 中有任何内容 - 所有这些也都消失了。

关于原题的几点评论

  • 云函数是无状态的。并且应该是幂等的。它们的行为完全依赖于外部数据(它要么随请求而来,要么在请求处理期间获取数据)。

  • 随意使用伪文件系统(tmp目录),但当云函数运行时,不要指望那里有任何东西(也不要使用在那里找到的任何数据)实例被调用。最佳实践 - 在完成云函数实例调用执行之前删除那里的所有内容。

  • 随时用源代码打包任何东西,并在 运行 时使用它。但请做好准备 - 该数据与您的 运行ning 代码同龄。最近部署的代码(及其辅助数据)可以比正在执行的代码提前几个版本。

  • 初始化越少 - 2 - initialize 状态转换完成得越快,云函数进入初始化(热)状态的速度就越快。只有不可变的东西值得初始化 - 全局常量,一些 API 客户端。普通初始化和“惰性”初始化之间没有 material 区别(在某些非常特殊的特殊情况下可能会有区别)。

  • 配置和缓存 - 是外部的,在云功能之外。您必须在每次调用时从它们那里获取数据。也许也更新它们。所有这些 - 就像流程的状态 - 应该在云功能之外。

  • 使用外部 API 是不可避免的。如果需要一些缓存并且延迟很关键 - 使用 Go 而不是 Python,找到一些延迟最小的存储(即 Firestore 可能是一个很好的选择),并将持久数据保存在那里。

一个云功能(或一个功能组件)- 一个服务帐户。具有最低 IAM 角色和权限。

等等……