在这种情况下,最佳重试策略是什么:
Database
成功创建数据条目,但响应时间太长,无法达到Application
。因此,为了执行工作,Application
重试创建,当然Database
会返回“已存在”错误。所以最后从Application
的角度来看,创作似乎失败了,而实际上它成功了。更糟糕的是,如果这是在一系列步骤的中间,那么Application
无法决定是否在前面的步骤中触发回滚。
在Application
上增加超时长度并不是一个可以接受的解决方案,因为IP网络永远不可能100%可靠,而且网络中的响应很可能会丢失。
在创建之前添加<data>
的存在检查可行。但这只有在考虑并发时才会出现。在我的情况下,Database
可能有多个客户,我不确定竞争条件的可能性。
+-------------+ +-----------+
| Application | | Database |
+-------------+ +-----------+
| |
| CREATE <data> |
|--------------------------------------------------------->|
| |
| | creating
| |---------
| | |
| |<--------
| -------------------------------\ |
|-| timeout waiting for response | |
| |------------------------------| |
| |
| SUCCESS |
|<---------------------------------------------------------|
| -----------------------------------------------\ |
|-| response from a timed out session is ignored | |
| |----------------------------------------------| |
| |
| retry CREATE <data> |
|--------------------------------------------------------->|
| |
| ERROR: <data> ALREADY EXISTS |
|<---------------------------------------------------------|
| ---------------------------------------------------\ |
|-| no idea whether the creation actually took place | |
| |--------------------------------------------------| |
| |
答案 0 :(得分:0)
大多数现代数据库提供了一些编写“upsert”语句的方法,如果数据不存在则会自动插入数据,如果数据已经存在则更新(或不执行任何操作)。这样,您的应用程序可以安全地重试,如果数据已经创建,则不会出错,从而使您的数据创建idempotent。
一些流行数据库的示例:
-- Do nothing if data exists
INSERT IGNORE ...
-- Update if data exists
INSERT ... ON DUPLICATE KEY UPDATE ...
-- Do nothing if data exists
INSERT ... ON CONFLICT DO NOTHING
-- Update if data exists
INSERT ... ON CONFLICT ... DO NOTHING
-- Do nothing if data exists
MERGE INTO ... USING ...
WHEN NOT MATCHED THEN INSERT ...
-- Update if data exists
MERGE INTO ... USING ...
WHEN NOT MATCHED THEN INSERT ...
WHEN MATCHED THEN UPDATE ...
如果原子操作或事务不是一个选项,您可以编写数据库操作,以便重试无害,并在循环中执行每个操作,首先检查数据库是否已处于所需状态,然后尝试如果不是,则进行操作,并在失败时重试。换句话说,像(伪代码):
max_retries = n
retries = 0
WHILE NOT database_in_desired_state
IF retries < max_retries THEN
perform_database_operation
retries = retries + 1
ELSE
fail
您可以通过使操作成为条件(例如UPDATE some_table SET field = value, version = version + 1 WHERE version = expected_version
或添加唯一约束等等来禁止重复操作来使重试无害。如果您提供有关您正在使用的数据库的更多详细信息,我可能会能够提供更具体的建议。
如果您正在多个远程系统上执行一系列操作,如果发生故障并且无法将所有交互包装在单个(分布式)事务中,则应回滚整个操作,您将需要编写补偿事务,手动回滚到目前为止完成的错误工作。当然,补偿交易也可能失败,您需要考虑应该如何处理。一种方法是定期清理任务,查找失败的事务或不一致的状态。
答案 1 :(得分:0)
这完全取决于具体情况。
是的,网络连接可能会失败 - 但您必须确定这有多大的风险。如果您使用专业的主机设置,使用企业级设备,这将会发生 - 好吧,几乎从不。在这种情况下,我不会在应用程序中构建许多额外的逻辑来处理网络问题;您应该依靠数据库的事务管理功能来确保数据处于一致状态。 一旦您的应用程序捕获到网络异常,您就可以向用户显示错误,并要求他们重新开始。
如果您的环境本质上不可靠 - 例如,您通过公共互联网连接 - 常见的架构模式是使用消息总线,而不是同步操作。
编写同步代码来处理不可靠的网络条件并非易事;你会从伪代码@markusk开始,但我会添加到关闭并重新打开数据库连接。