如何拒绝一个请求,如果另一个请求已经处理相同的user-id?



我正在尝试实现某种同步服务。具有不同用户代理的两个客户机可以同时使用相同的POST/PATCH/sync/user/{user_id}/resourceuser_idsync应该为数据库中id={user_id}的用户更新数据。

func (syncServer *SyncServer) Upload(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
userID := ps.ByName("user_id"))
if isAlreadyProcessedForUser(userID) {
w.WriteHeader(http.StatusConflict)
return
}
...
syncServer.db.Update(userID, data)
...
}

问题是我不知道如何正确地拒绝一个Upload时,另一个仍在处理请求相同的user_id。我认为使用mutex.Lock()的想法是不好的,因为我将使用许多pod来处理这个处理程序,如果Upload在不同的pod上被调用,它无论如何都不会帮助我。我可以使用什么同步方法来解决这个问题?我应该在数据库中使用一些额外的字段吗?我在请求你给我点主意!

在分布式系统中有很多方法可以做到这一点(分布式锁定),目前我能想到的一些方法:

  1. 使用redis(或任何其他类似的服务)锁。然后,您可以在收到第一个请求时锁定每个user_id,并拒绝针对同一user_id的其他请求,因为您无法锁定它。Redis锁通常有过期时间,所以你不会死锁它。裁判:https://redis.io/docs/reference/patterns/distributed-locks/
  2. 使用数据库锁您应该小心使用数据库锁,但是使用唯一索引是一种简单的方法:在上传之前使用unique(user_id)约束创建uploading记录,并在上传后删除它。有可能忘记/未能删除记录并导致死锁,因此您可能需要向记录添加另一个expired_at字段,检查&上传前请放下
  3. (特定于问题的场景)在(user_id, upload_status)上使用唯一的约束。这被称为部分索引,您只会在upload_stats = 'uploading'时检查这个唯一索引。然后,您可以在每个请求上创建uploading记录,并拒绝另一个请求。还需要过期,因此需要跟踪上传的start_time并清理长时间上传的记录。如果您不需要重新声明磁盘空间,您可以简单地将记录标记为failed,这样您还可以跟踪何时&

注意:

  1. 看起来你正在使用Kubernetes,所以任何非分布式锁都应该谨慎使用,这取决于你想要获得的一致性级别。pod是不稳定的,很难依赖本地信息并实现一致性,因为它们可能被复制/终止/重新调度到另一台机器上。这也适用于其他具有自动伸缩或调度机制的平台。
  2. 一个用户和服务器拥有的多个客户端之间的同步过程至少需要处理请求排序,请求重复删除和最终的一致性问题(例如Google Doc可以支持多人同时编辑)。有一些通用算法(如操作转换),但这取决于你的具体用例。