保护非幂等 post 操作不被快速调用 Node.js?
Defending a non-idempotent post operation against being rapidly called in Node.js?
简短的问题:假设一个非幂等的 post 操作,您如何保护 node.js 中的 post 请求处理程序在响应之前被多次调用,因此导致数据损坏?
具体情况:我有一个匹配的 API,它需要大约 2-3 秒才能达到 return(因为必须 运行 通过大量用户群)。有许多操作,用户可以在同一秒内简单地两次调用它(这是一个错误,但不在我的控制之下,因此回答这部分并不构成对根本问题的回答)。在这些条件下,为用户选择了多个匹配项,这是不可取的。理想的结果是所有这些快速请求都具有相同的最终结果。
具体限制:
node.js/表达/续集。
如果我们添加一个队列,每个用户的请求都将位于所有其他用户的请求之上,这在交通繁忙时可能会产生严重影响。
尝试使用 transaction。
如果您将所有 SQL 命令放入一个事务中,我认为这些请求将被分开。
您可以将所有请求推送到 queue。在这种情况下,您的所有回复都必须等待前面的回复完成。
另一种解决方案是使用 sequelize 事务,但这会导致数据库中 lock_wait_timeout
错误。
在我的 API 下,我对所有 REST 身份验证使用 express-jwt 和基于令牌的身份验证。我让这些令牌仅对一个请求有效。一旦使用令牌将被列入黑名单。
因此,即使您的客户发出多个请求,也只会接受第一个请求。其他人可以抛出错误 409 Conflict
。一旦处理了第一个请求,新的 API 令牌将与响应一起发回,可能在 headers.
中
您的客户端必须不断更新每个响应的令牌。如果您在客户端使用 AngularJS 那使用 interceptors
非常容易
我最终确定的解决方案是@Festo 的一般方法的变体,如下所示:
- 为匹配项添加唯一键约束
- 每个并行请求都尝试创建一个新的匹配项;由于约束
,除了第一个以外的所有其他人都将无法这样做
- 如果约束插入失败,应用程序只会提取数据库中已有的匹配项,将其添加到其余匹配项中,然后return将其
这使得无法通过快速调用 API 来垃圾邮件创建新的匹配项。但是,我对此并不满意,因为这种方法没有推广到例如:非确定性幂等操作(例如,如果匹配是随机生成的,随后的调用将 return 不同的匹配,因此是一个简单的约束检查是不够的)。
我所有的互联网点都不需要客户端票证管理(@Sushant),也不需要队列,并且可以处理不确定的幂等函数。
我提出了一个解决方案,其中服务器优雅地响应 same*
请求,因此不需要对客户端进行任何更改。
*
首先我们需要确定什么构成了被视为 "same" 的请求。当您计划这种优雅的同步响应时,您会在客户端请求中放置一个递增的计数器,这个计数器是将请求定义为 same
的唯一属性。但是由于您可能无法访问客户端并且没有这样的计数器,您可以将请求定义为相同,如果它们的 post
-body + url 是相同的(并且可以抛出用户也需要相同)。
因此,对于每个用户,当请求到达您的服务器时,您会立即保存请求。一种简单的方法是散列 url + post
-body,比如 SHA-256
并将其保存在这样的对象中:
requests[user][hashOfRequest] = null
一旦您的服务器计算出来,null
将被替换为响应对象。现在您处理请求。
如果客户端再次发送 same*
请求,您可以通过检查您的 requests[user][hashOfRequest]
轻松发现。
如果服务器已完成处理,它将包含一个响应对象,您只需将该对象发送回客户端。如果它仍然是空的,您需要等待服务器(第一个请求)的处理完成。也许使用事件侦听器或其他任务同步模式。
一旦服务器完成第一个请求,它将生成 response
并将其保存在 requests[user][hashOfRequest]=response
中并发出事件,以便潜在的等待客户端也能得到响应。
此模式也不再处理来自客户端的双重处理和连接断开(响应未到达客户端)。
当然,您应该在适合您的(客户端)场景的时间后清理响应哈希 table。可能在请求被放入哈希 table.
后 10 分钟
简短的问题:假设一个非幂等的 post 操作,您如何保护 node.js 中的 post 请求处理程序在响应之前被多次调用,因此导致数据损坏?
具体情况:我有一个匹配的 API,它需要大约 2-3 秒才能达到 return(因为必须 运行 通过大量用户群)。有许多操作,用户可以在同一秒内简单地两次调用它(这是一个错误,但不在我的控制之下,因此回答这部分并不构成对根本问题的回答)。在这些条件下,为用户选择了多个匹配项,这是不可取的。理想的结果是所有这些快速请求都具有相同的最终结果。
具体限制:
node.js/表达/续集。
如果我们添加一个队列,每个用户的请求都将位于所有其他用户的请求之上,这在交通繁忙时可能会产生严重影响。
尝试使用 transaction。 如果您将所有 SQL 命令放入一个事务中,我认为这些请求将被分开。
您可以将所有请求推送到 queue。在这种情况下,您的所有回复都必须等待前面的回复完成。
另一种解决方案是使用 sequelize 事务,但这会导致数据库中 lock_wait_timeout
错误。
在我的 API 下,我对所有 REST 身份验证使用 express-jwt 和基于令牌的身份验证。我让这些令牌仅对一个请求有效。一旦使用令牌将被列入黑名单。
因此,即使您的客户发出多个请求,也只会接受第一个请求。其他人可以抛出错误 409 Conflict
。一旦处理了第一个请求,新的 API 令牌将与响应一起发回,可能在 headers.
您的客户端必须不断更新每个响应的令牌。如果您在客户端使用 AngularJS 那使用 interceptors
非常容易我最终确定的解决方案是@Festo 的一般方法的变体,如下所示:
- 为匹配项添加唯一键约束
- 每个并行请求都尝试创建一个新的匹配项;由于约束 ,除了第一个以外的所有其他人都将无法这样做
- 如果约束插入失败,应用程序只会提取数据库中已有的匹配项,将其添加到其余匹配项中,然后return将其
这使得无法通过快速调用 API 来垃圾邮件创建新的匹配项。但是,我对此并不满意,因为这种方法没有推广到例如:非确定性幂等操作(例如,如果匹配是随机生成的,随后的调用将 return 不同的匹配,因此是一个简单的约束检查是不够的)。
我所有的互联网点都不需要客户端票证管理(@Sushant),也不需要队列,并且可以处理不确定的幂等函数。
我提出了一个解决方案,其中服务器优雅地响应 same*
请求,因此不需要对客户端进行任何更改。
*
首先我们需要确定什么构成了被视为 "same" 的请求。当您计划这种优雅的同步响应时,您会在客户端请求中放置一个递增的计数器,这个计数器是将请求定义为 same
的唯一属性。但是由于您可能无法访问客户端并且没有这样的计数器,您可以将请求定义为相同,如果它们的 post
-body + url 是相同的(并且可以抛出用户也需要相同)。
因此,对于每个用户,当请求到达您的服务器时,您会立即保存请求。一种简单的方法是散列 url + post
-body,比如 SHA-256
并将其保存在这样的对象中:
requests[user][hashOfRequest] = null
一旦您的服务器计算出来,null
将被替换为响应对象。现在您处理请求。
如果客户端再次发送 same*
请求,您可以通过检查您的 requests[user][hashOfRequest]
轻松发现。
如果服务器已完成处理,它将包含一个响应对象,您只需将该对象发送回客户端。如果它仍然是空的,您需要等待服务器(第一个请求)的处理完成。也许使用事件侦听器或其他任务同步模式。
一旦服务器完成第一个请求,它将生成 response
并将其保存在 requests[user][hashOfRequest]=response
中并发出事件,以便潜在的等待客户端也能得到响应。
此模式也不再处理来自客户端的双重处理和连接断开(响应未到达客户端)。
当然,您应该在适合您的(客户端)场景的时间后清理响应哈希 table。可能在请求被放入哈希 table.
后 10 分钟