Ruby on rails 将复杂哈希传递给Sidekiq作业

Ruby on rails 将复杂哈希传递给Sidekiq作业,ruby-on-rails,ruby,sidekiq,Ruby On Rails,Ruby,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

从开始到使用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)
...
}
然后像这样保存对象:

MyObject.new(my_object_hash).save
我的问题是,这安全吗?尽管我将一个“简单”数据类型传递给Sidekiq,但它实际上包含复杂的对象。我会失去准确性吗


谢谢大家!

您链接的最佳实践中最重要的部分是

复杂的Ruby对象不会转换为JSON

因此,不应该将模型的实例传递给辅助对象。 如果您使用的是Sidekiq workers,那么您应该遵守这条语句,并且您传递的哈希值应该很好。我并不完全确定TimeWithZone对象,但您可以尝试将其转换为JSON或字符串,就像在最佳实践指南中所做的那样

但是,如果您使用的是ActiveJob而不是Sidekiq workers(您的作业是从
ApplicationJob
继承的还是
包含Sidekiq::Worker
?),那么您就不会有这个问题,因为ActiveJob使用全局ID将对象转换为字符串。然后在执行作业之前再次反序列化对象。这意味着你可以将一个对象传递给你的工作

my_object = MyObject.find(1)
my_object.to_global_id #=> #<GlobalID:0x000045432da2344 [...] gid://your_app_name/MyObject/1>>
serialized_my_object = my_object.to_global_id.to_s

my_object = GlobalID.find(serialized_my_object)
my_object=MyObject.find(1)
my_object.to_global_id#=>#>
序列化的\u我的\u对象=我的\u对象到\u全局\u id到\u s
my_object=GlobalID.find(序列化的_my_对象)
你可以在这里找到更多信息

在我的工作中对时间对象进行了一些实验后,我发现在工作的另一端,我正在失去纳秒精度

my_object.start_time
=> Mon, 21 Dec 2020 11:35:50 PST -08:00
my_object.strftime('%Y-%m-%d %H:%M:%S.%N')
=> "2020-12-21 11:35:50.151893000"
你可以在这里看到,我们有精度,包括小数点后的6位数字。 (有关“strftime”的更多信息,请参阅)

一旦我们在对象上调用JSON方法:

generated = JSON.generate(my_object.attributes))
=> \"start_time\":\"2020-12-21T11:35:50.151-08:00\"
你可以看到这里我们在小数点后的精度降到了3位。剩下的3位数字在此点丢失

parsed = JSON.parse(generated)
parsed[‘start_time’] = "2020-12-21T11:35:50.151-08:00"
它出现在最基本的层次上,JSON库递归地对散列中的每个键值对调用
as_JSON
。因此,这实际上取决于您的特定对象如何将
实现为\u json

这个问题导致测试失败,涉及查询数据库中持久化对象(初始化为类似于,
start\u time=time.zone.now
(!)的内容),这些对象在时间上与我们的
MyObject
类完全重叠。一旦半生不熟的
my_object
blueprints通过Sidekiq,它们就失去了一点精度,导致了轻微的错位

解决这个问题的一种方法是通过

在我们的例子中,一个更好的解决方案是相反的方向,在我们的测试中不要使用太多的精度。示例中的
my_对象
是人类用户将在其日历上显示的内容;在生产过程中,我们从未从客户那里获得如此高的精度。因此,我们通过指示一些测试对象使用诸如
Time.zone.now.start\u of_minute
,而不是
Time.zone.now
,来修复我们的测试。为了解决这个问题,我们故意取消了精确性,同时更接近于现实。

这听起来像是一个“波塔托,波塔托”的解决方案。您没有使用Sidekiq的序列化,而是自己将其序列化

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

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

我想补充第三点:

序列化状态使得无法区分持久化数据和以太(内存中的、已记忆的、延迟加载的等)数据。例如,a
def发送的邮件@已发送邮件| |=Mail.for(用户id:id);结束
现在被序列化:您想要吗

sidekiq也提供了解决方案:

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

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

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

延迟 你的存储速度慢吗?这难道不是一种验证,一种串行化,一种缓慢的存储的副作用吗

您可以通过将其分为两步来改进这一点:插入状态并稍后异步更新/充实/验证它吗?如果您使用的是Rails,它在这里对您没有帮助,甚至可能对您不利,但一个常见的模型是将对象存储在一个特殊的“队列”表或事件队列中;e、 卡夫卡就是因为这个而出名的

例如,当存储发生在一个慢速网络上,而API速度较慢时,这可能是无法解决的,但当存储发生在本地数据库中时,您可以使用几十年的解决方案来提高写入性能。无论是在您的数据库中,还是使用特定的状态存储队列(sidekiq不是特定的存储队列),都取决于用于存储的技术。例如,Linux将允许您通过内存进行存储,使写入磁盘的速度非常快,但取消了对写入磁盘的保证

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

例如,在一个写得很重的GIS系统中,我们会存储obje
parsed = JSON.parse(generated)
parsed[‘start_time’] = "2020-12-21T11:35:50.151-08:00"