我正在使用排队系统 (Sidekiq) 并希望迁移到 ActiveJob 以获得性能优势,即每次将 ActiveRecord 对象传递给工作人员时都不必查询数据库。我想询问并确认,因为我不是 100% 确定,但我的理解是,当 ActiveJob 使用 GlobalID 传递所有在内存中完成的 ActiveRecord 对象并且没有完成对数据库的单独查询时,对吗?
2 回答
这是不正确的。
如果您使用 ActiveJob,它会将任何 ActiveRecord 对象序列化为 global_id 字符串以保存到您的队列中。然后在作业开始时从该字符串中再次查找它。默认情况下,该字符串仅包含应用程序名称、类名和 id,它将使用您的数据库来加载模型。
"gid://app/User/1"
DelayedJob 会将您提供的任何对象序列化为 yaml 字符串并对其进行反序列化,而不会在加载作业之外访问数据库。您也可以使用 Sidekiq 来执行此操作,而不是点击 Redis 来加载作业而不接触主数据库。
user = User.find(1)
MyJob.perform_later(user.to_yaml)
# Load the user object from the yaml
YAML::load(user.to_yaml) == user # true
您无需前往数据库即可获得您的对象。但是,YAML 会很大,并且使用 Redis 获得的性能损失可能不值得。
还有一些你应该注意的问题。该对象可能在数据和结构方面都已过时。如果您更改代码,序列化对象可能由于结构更改而无法再次加载。如果您在序列化对象后更新数据库,当您加载它时,您将在不知不觉中使用旧数据。
希望能帮助您了解 ActiveJob 和 GlobalId 提供的内容。
无论如何都会执行数据库查询,但要透明。以下面的代码为例,说明 ActiveJob 内部的作用:
gid = User.find(1).to_global_id
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
=> #<GlobalID:0x00007f86f76f46d8 @uri=#<URI::GID gid://app/User/1>>
然后,当作业执行时,ActiveJob 在内部运行以下代码,无论如何都会查询数据库:
GlobalID::Locator.locate(gid)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, ... >
使用 GlobalIDs 的一个问题是,如果在作业入队之后但在#perform
调用方法之前删除了传递的记录,则 Active Job 将引发ActiveJob::DeserializationError
异常。
表现
根据 Sidekiq 的作者 Mike Perham 的说法,基准测试表明 ActiveJob 将作业推送到 Redis 的速度要慢 2-20 倍,并且处理开销大约是 3 倍(https://github.com/mperham/sidekiq/wiki/Active-Job#表现)。
附加信息
有关 Sidekiq、ActiveJob 和 GlobalID 的所有信息都可以在这里找到:https ://github.com/mperham/sidekiq/wiki/Active-Job#using-global-id