我正在尝试获得我的两个Golang端点来支持幂等键。我的服务将存储和读取来自Mongo的密钥(因为我已经将其用于其他数据)作为它自己的Collection中的唯一索引。
我正在考虑两种解决方案,但每一种都有各自的弱点。我知道还有更复杂的事情,比如保存请求和响应,以及制造逻辑酸。但是,对于我的第一个端点,only-once logic (端点的代码需要幂等)调用发送电子邮件的服务,因此不能回滚。我的第二个端点在Mongo中执行多个插入,这似乎可以被回滚,但我不确定如何以及是否有另一个解决方案也可以解决第一个端点。
解决方案1
func MyEndpoint(request Request) (Response, error) {
doesExist, err := doesIdemKeyExist(request.IdemKey)
if err != nil {
return nil, status.Error(codes.Internal, "Failed to check idem key.")
}
if doesExist {
return Response{}, nil
}
// < only-once logic >
err := insertIdemKey(request.IdemKey)
if err != nil {
if mongo.IsDuplicateKeyError(err) {
return Response{}, nil
}
return nil, status.Error(codes.Internal, "Failed to insert idem key.")
}
return Response{}, nil
}这里的缺点是客户端可以向我的端点发送第一个请求,然后失去连接,然后用第二个请求重试。第一次请求可以处理,但不能到达insertIdemKey,第二次请求也会处理,这违反了幂等性。
解决方案2
func MyEndpoint(request Request) (Response, error) {
err := insertIdemKey(request.IdemKey)
if err != nil {
if mongo.IsDuplicateKeyError(err) {
return Response{}, nil
}
return nil, status.Error(codes.Internal, "Failed to insert idem key.")
}
// < only-once logic >
return Response{}, nil
}这里的缺点是,only-once logic可能会出现间歇性故障,例如依赖关系。受影响的重试请求将被忽略。
这里最好的解决方案是什么?我是否应该妥协,用这些不完美的解决方案之一呢?
发布于 2022-02-25 08:08:46
您应该在MongoDB中使用带有状态属性的文档,其中包含可能的值processing和done。
当请求传入时,尝试使用给定的idemKey和state=processing将文档插入数据库。如果由于键已经存在而失败,则报告成功(如果状态为done)或报告仍在处理(如果状态为processing)。或者等待它完成,然后报告成功。
如果插入文档成功,继续执行“只执行一次逻辑”。
一旦完成了“仅一次逻辑”,将文档的状态更新为state=done。如果执行逻辑失败,您可以从数据库中删除文档,以便后续请求可以尝试再次执行它。
为了防止服务器在执行逻辑期间发生故障,或者防止删除文档失败,您还应该记录开始/创建时间戳,并定义过期时间。假设一个新的请求传入,并且文档存在于processing sate中,但是文档超过30秒,您可以假设它永远不会完成,并且继续下去,就像文档在数据库中不存在一样:将其创建时间戳设置为当前时间并执行逻辑,然后如果逻辑执行成功,则将状态更新为done。MongoDB也支持自动移除过期文件,但请注意删除是不精确的时间。
请注意,这个解决方案也不是完美的:如果执行逻辑成功,但不能在之后将文档的状态更新为done,则过期后可能会重复执行。您需要的是逻辑和MongoDB操作的原子/事务执行,这是不可能的。
如果您的“仅一次逻辑”包含多个插入,则如果执行失败且必须重复,您可以使用insertOrUpdate()不复制记录,或者您可以插入包含idemKey的文档,以便识别先前插入的文档(可以首先删除它们,或者跳过它们,然后插入其余的文档)。
还请注意,从MongoDB 5.0开始,即支持事务,因此您可以在单个事务中执行多个插入。
https://stackoverflow.com/questions/71262703
复制相似问题