首页 > 解决方案 > 将复杂哈希传递给 Sidekiq 作业

问题描述

从使用 Sidekiq 的最佳实践指南中,我了解到最好将“字符串、整数、浮点数、布尔值、null(nil)、数组和哈希”作为参数传递给作业。

我经常只是将持久对象的 id 传递给我的作业,但由于延迟限制,我需要在运行作业后保存对象。

我正在使用的非持久对象包含多种数据类型:

#MyObject<00x000>{
id: nil
start_time: Fri, 11 Dec 2020 08:45:00 PST -08:00 (*this is a TimeWithZone object)
rate: 18.0 (*this is a BigDecimal object)
...
}

我计划通过首先将其转换为哈希来将此对象传递给我的工作:

MyJob.perform_async(my_object.attributes)

然后像这样持久化对象:

MyObject.new(my_object_hash).save

我的问题是,这安全吗?即使我将“简单”数据类型传递给 Sidekiq,它实际上也包含复杂对象。我会失去精度吗?

谢谢!

标签: ruby-on-railsrubysidekiq

解决方案


这听起来像是一个“potayto,potahto”的解决方案。你不是在使用 Sidekiq 的序列化,而是自己序列化它。

我们来看看为什么sidekiq有这个规则:

即使他们确实正确地序列化了,如果您的队列备份并且同时引用对象发生变化会发生什么?[...] 不要传递符号、命名参数、关键字参数或复杂的 Ruby 对象(如日期或时间!),因为它们无法正确地在转储/加载往返过程中幸存下来。

我想添加第三个:

序列化状态使得无法区分持久数据和空灵(内存中、记忆化、延迟加载等)数据。例如 a def sent_mails; @sent_mails ||= Mail.for(user_id: id); endnow 被序列化:你想要那个吗?

sidekiq也提供了解决方案:

不要将状态保存到 Sidekiq,保存简单的标识符。一旦您在 perform 方法中真正需要它们,就查找这些对象。

这里的XY问题

真正的问题不是在哪里或如何序列化状态。因为 sidekiq 警告不要序列化状态,无论您在哪里以及如何执行此操作。

您需要解决的问题是如何将状态存储在可以正确存储的地方。或者完全避免存储状态:不在 redis/sidekiq 中,也不在给你带来问题的存储中。

潜伏

你的存储速度慢吗?这不是验证、序列化、存储的一些副作用吗?

您可以通过两步改进这一点:插入状态并稍后异步更新/丰富/验证它吗?如果您使用的是 Rails,它在这里对您没有帮助,甚至可能对您不利,但一个常见的模型是将对象存储在特殊的“队列”表或事件队列中;例如,kafka 以此而闻名。

例如,当存储发生在慢速网络到慢速 API 时,这可能是无法解决的,但是当存储发生在本地数据库中时,您可以使用数十年的解决方案来提高写入性能。无论是在您的数据库内部,还是使用一些专门的状态存储队列(sidekiq 并不是这样一个专门的存储队列),具体取决于用于存储的技术。例如,Linux 将允许您通过内存进行存储,从而非常快速地写入磁盘,但消除了它确实写入磁盘的保证。

例如,在一个簿记 api 中,我们会将经过验证的对象存储在 PostgreSQL 中,然后让异步作业稍后为此添加昂贵的属性(例如,必须从遗留 API 或通过复杂计算检索的状态)。

例如,在写入繁重的 GIS 系统中,我们会将对象存储到“to_process_places”表中,该表由处理 Places 的工具监控。这完全取决于您的领域和要求。

不使用状态。

一个常见的解决方案是不制作对象,而是使用客户的实际有效负载。只需发送 HTTP 有效负载(在 rails 中,the params)并保留它。也许合并到一个标头(如请求日期)或过滤掉一些数据(标头令牌或 cookie)。

如果您的控制器可以使用此数据进行操作,那么延迟作业也可以。与其在控制器中构建对象,不如将其留给延迟的工作。这甚至可以产生非常简洁和精益的控制器:他们所做的只是(一些身份验证和授权,然后)调用正确的工作并将其传递给 sanitized params.

显然,这需要权衡,例如无法同步验证,而是通过电子邮件、推送通知或延迟响应提供此类信息,具体取决于您的要求(例如,大型 CSV 导入可能只是通过电子邮件发送任何验证问题,但是如果登录无效,登录请求可能需要立即得到响应)。

它还需要一些思考:您可能不想将 Base64 编码的 CSV 发送到 sidekiq,而是将文件写入(临时)存储并传递文件名/url。这听起来很明显,因为它是:文件上传本质上是前面提到的“临时状态存储”的实现:您不会将整个 PDF/high-res-header-image/CSV 传递给 sidekiq,而是将其存储某个地方,以便 sidekiq 稍后可以将其拾取以进行处理。如果将其他属性传递给 sidekiq 是有问题的,为什么其他属性不采用相同的模式?


推荐阅读