ZeroMQ对数据库事务来说太快了
在一个网页应用程序(Pyramid)中,我在处理POST
请求时会创建一些对象,这些对象需要进行一些操作(主要是从网上获取一些东西)。这些对象会通过SQLAlchemy保存到PostgreSQL数据库中。由于这些操作可能需要一些时间,所以我并不在请求处理程序中完成这些工作,而是把它们交给一个在不同主机上的守护进程来处理。当对象被创建时,我会获取它的ID
(这是客户端生成的UUID
),然后通过ZeroMQ发送给守护进程。守护进程接收到ID
后,会从数据库中获取对象,进行处理,然后把结果写入数据库。
问题:
守护进程可能会在对象创建的事务还没有提交之前就接收到ID
。因为我们使用了pyramid_tm
,所有的数据库事务会在请求处理程序返回且没有错误时提交,我希望保持这种方式。在我的开发系统中,一切都在同一台机器上运行,所以ZeroMQ的速度非常快。在生产系统中,这通常不是问题,因为网页应用和守护进程运行在不同的主机上,但我不想完全依赖这个。
这个问题最近才出现,因为我们之前使用的是MongoDB,write_convern
设置为2。由于只有两个数据库服务器,实体的write
操作总是会阻塞网页请求,直到实体被保存(显然这不是个好主意)。
- 有没有人遇到过类似的问题?
- 你是如何解决的?
我看到几种可能的解决方案,但大多数都让我不太满意:
- 在触发ZMQ消息之前手动刷新事务。不过,我现在使用SQLAlchemy的
after_created
事件来触发这个操作,这样做非常好,因为它完全解耦了这个过程,从而消除了“忘记”告诉守护进程工作的风险。我还认为我在守护进程那边仍然需要READ UNCOMMITTED
的隔离级别,这样理解对吗? - 给ZMQ消息添加一个时间戳,让接收到消息的工作线程在处理对象之前等待。这显然会限制处理速度。
- 完全放弃ZMQ,直接轮询数据库。绝对不行!
2 个回答
这跟你的第二个解决方案很接近:
你可以创建一个缓冲区,把你的zeromq消息中的id放进去,然后让你的工作程序定期检查这个id池。如果从数据库中获取这个id对应的对象失败,就让这个id在池子里等到下次检查;如果成功了,就把这个id从池子里移除。
你必须以某种方式处理你系统的异步行为。当这些id不断到达,而对象还没存入数据库时,是否把id放在池子里(并且重新检查同一个id)对处理速度影响不大,因为瓶颈出现在更早的阶段。
一个好处是,你可以在这个前面运行多个前端。
我建议直接使用PostgreSQL的LISTEN
和NOTIFY
功能。工作程序可以连接到SQL服务器(这本来就是它需要做的),然后发出相应的LISTEN
命令。这样,PostgreSQL就会在相关的事务完成时通知它。你在SQL服务器上触发生成通知时,甚至可以把整行数据都包含在通知中,这样工作程序就不需要再请求任何信息了:
CREATE OR REPLACE FUNCTION magic_notifier() RETURNS trigger AS $$
BEGIN
PERFORM pg_notify('stuffdone', row_to_json(new)::text);
RETURN new;
END;
$$ LANGUAGE plpgsql;
这样一来,一旦它知道有工作要做,就能立即获取到必要的信息,从而可以开始工作,而不需要再进行一次请求。