【问题标题】:Multithreading with cursors in Google DatastoreGoogle Datastore 中的游标多线程
【发布时间】:2016-04-06 12:36:34
【问题描述】:

我想从 Google 数据存储区加载大量数据。

所以,第 1 步:我运行查询(使用 keysOnly=true)并遍历游标,这样每个游标都指向包含 600 个对象的页面的开头。我将游标存储在局部变量中。

第 2 步:我为每个游标分离一个线程,在每个线程中加载和处理 600 个对象。

这不是使用游标的常用方式。

但是,我觉得它是正确的。步骤 1 和步骤 2 中的实际查询字符串是相同的。这类似于通常的无状态 Web 用例,其中用户可能会请求 Next、Back,然后重新加载前一页;游标不需要直接来自前一个游标查询的结果。

我不想按顺序遍历游标,然后分离线程以并行处理在给定游标查询中加载的对象,因为我想并行化来自数据库的实际 IO 密集型查询。

我的结果有些不一致,似乎涉及丢失页面和重复加载对象。这是多线程从 Google 数据存储区加载大量数据的正确方法吗?如果不是,那是什么?

【问题讨论】:

  • 你解决了吗?我有一个类似的问题。
  • LocationsClo​​udToHub 一种方法是按顺序从 Darattiore 加载每个实体,然后在新线程中为每个实体分拆初始化过程,或者使用队列。见@Andrei Volgin 回答

标签: multithreading google-app-engine google-cloud-datastore cursors


【解决方案1】:

我会推荐一种不同的方法。只运行一个循环遍历所有实体的查询。它发生得非常快(不要忘记将批量大小设置为 500,默认只有 10)。如果查询量很大,您可能仍需要使用游标。

对于每个实体,使用 Task API 创建一个任务并将其添加到任务队列中。这些任务可以并行执行。您可以设置队列中的所有参数。

使用这种方法,您不必担心线程,您可以将任务设置为在失败时自动重试等。我发现它是 App Engine 吸引力的一个非常重要的部分 - 只编写您自己的逻辑,并让 App Engine 担心执行部分。

【讨论】:

  • 谢谢,这可以工作。但缓慢的部分是实际查询,而不是每个实体的后处理。你说它发生得很快,但瓶颈是 IO。
  • 您只需要一个键来创建一个任务,然后get一个任务内的实体。使用仅键查询,它会快得多。
  • 谢谢。您是说我应该使用仅键查询顺序遍历所有对象(必要时使用游标)吗?随后,是否并行执行 get 操作(使用任务)?这确实意味着每个实体都要访问数据库两次,对吧?
  • 1.是的,这是我推荐的。 2. 键和索引属性存储在索引中。实体单独存储。仅键查询仅从索引中读取。常规查询执行相同的操作,then 检索并返回实体。通过将get 移动到任务中,您可以并行检索许多实体(因为任务是并行执行的),而不受查询循环的限制。
  • 另外,必须存储一个任务(它由 App Engine 自动完成)。如果您在查询中检索实体,然后将其存储在任务中,那么您实际上读取了两次实体并存储了一次。但是如果你只存储一个密钥,你就只能读取实体一次。这大大提高了整体性能。
【解决方案2】:

根据您的工作,您有几个选择:

  1. 如果您有大量数据 - 使用带有任务队列的“扇出”模型。在此模型中,任务队列作业加载一段数据,对其进行处理并存储结果,并可能触发更多的处理作业。任务队列限制允许您控制吞吐量/持续时间/成本并处理故障/重试。此模型的一个优点是您可以通过手动戳 URL 来测试和重新运行分段,并在管理面板中查看进度。

  2. 使用 GAE MapReduce - https://cloud.google.com/appengine/docs/java/dataprocessing/

  3. 如果您有少量数据,则在单个进程中。缺点是请求期限(60s、10m 或 24hrs - 取决于服务器和请求的类型)。回想一下,数据存储操作是异步的,因此您可以在单个线程中并行运行请求,这可以简化您的代码。在它们被阻塞之前有多少(我相信)由您的 appengine-web.xml 或 app.yaml 中的max-concurrent-requests 控制。如果您的请求可能失败并且不是可恢复的,这可能会非常昂贵。

【讨论】:

  • 谢谢。我只是将大量对象加载到内存中,每个对象的后处理最少。有许多选项可以并行化负载,但真正的问题是:我如何做游标?我尝试遍历所有对象以收集游标字符串(划分“页面”,例如 1000 个对象),然后跨线程分配游标,每个线程加载一个游标值的对象(或者我可以使用任务、MapReduce、异步数据库调用等)。但这种设计需要两次迭代,而且无论如何都会导致结果出现奇怪的不一致。
  • 您还没有告诉我们您想对这些数据做什么,因此很难为您提供具体的帮助。如果您尝试使用最终一致的查询(游标或其他方式)获取数据存储的完整快照,则会出现“丢失”或重复对象等症状,除非您的数据非常静态。游标在一般意义上工作(尽管不一定在开发服务器上)
  • 是的,我正在尝试使用最终一致的查询将给定表的完整快照加载到内存中。有数千个对象,所以我一次使用 800 个对象的游标。为什么会导致丢失或重复的对象?我知道如果在收集光标后添加或删除对象,我可能看不到,但其他一切不应该没问题吗?发生这种情况时,我的数据库实际上是静态的:我什至在使用这种方法(收集游标并为每个线程加载页面的对象价值)的只读模式下经历了丢失和重复的对象。
【解决方案3】:

Ed Davisson,一位从事 Google Datastore Client API 工作的 Google 工程师,answered this。他提供了问题的根本原因和推荐的解决方案。

他说:

"一个查询返回的游标只在同一个查询中有效。当你从keys-only查询[In my Step 1, JF]切换到non-keys-only查询[In my Step 2 , JF],游标不再适用....

“如果您的目标是将结果集拆分为类似大小的块,您可能需要查看QuerySplitter [现在是版本 1beta3,JF]。”

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    • 2018-07-18
    • 2021-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多