问题是在服务器发送状态 200 响应之后但客户端收到响应之前连接中断。客户未将订单标记为同步。下次客户端再次发送此订单,服务器再次将其写入数据库,因此我们有两次相同的订单。
欢迎来到不可靠消息传递的世界。
处理这类问题有什么好的做法?
您应该查看“无人需要可靠消息传递”,Marc de Graauw (2010)。
可靠消息传递的基石是idempotent 请求处理。幂等语义就是这样描述的
如果使用该方法的多个相同请求对服务器的预期效果与单个此类请求的效果相同,则该请求方法被认为是“幂等的”。
然而,仅仅对请求方法大惊小怪并不能得到任何东西。首先,消息中的other语义可能与幂等请求方法不一致,其次,服务器需要知道如何实现效果按预期。
幂等请求处理有两种基本模式。其中更简单的是set,意思是“用我提供的那个覆盖当前的表示”。
// X == 6
server.setX(7)
// X == 7
server.setX(7) <- a second, identical request, but the _effect_ is the same.
// X == 7
替代方案是test and set(有时称为compare and swap);在这种模式中,请求有两部分 - 一个用于确定某个条件是否成立的谓词,以及如果该条件成立则应用的更改。
// X == 6
server.testAndSetX(6,7)
// X == 7
server.testAndSetX(6,7) <- this is a no op, because 7 != 6
// X == 7
这是核心思想。
根据您的描述,您正在做的是操纵一系列订单。
相同的基本思想在那里有效。如果您可以根据请求中的信息计算出唯一标识符,那么您可以将您的集合视为集合/键值存储。
// collection.get(Id.of(7)) == Nothing
collection.put(Id.of(7), 7)
// collection.get(Id.of(7)) == Just(7)
collection.put(Id.of(7), 7) <- a second, identical request, but the _effect_ is the same.
// collection.get(Id.of(7)) == Just(7)
如果这不是一个选项,那么您需要集合的某些属性,这些属性会在您进行编辑时更改,并编码到请求中
if (collection.size() == 3) {
collection.append(7)
}
管理此类事情的通用方法是考虑版本号——每次进行更改时,版本号都会作为同一事务的一部分递增
// begin transaction
if (resource.version.get() == expectedVersion) {
resource.version.set(1 + expectedVersion)
resource.applyChange(request)
}
// end transaction
对于现实世界的示例,请考虑 JSON Patch,其中包含一个 test operation,可用作防止“并发”修改文档的条件。
我们在所有这些test and set 场景中描述的是conditional request 的概念
条件请求是 HTTP 请求 [RFC7231],包括一个或多个标头字段,指示在将方法语义应用于目标资源之前要测试的先决条件。
条件请求规范为您提供的是一种通用方式来描述您的请求和响应的元数据中的条件,以便 通用 http 组件可以做出有用的贡献。
请注意:这样做的结果并不能保证服务器会按照客户端的要求进行操作。相反,它的弱点是:客户端可以安全地重复请求,直到它收到来自服务器的确认。