我必须调用一些需要传递令牌的api,并且需要刷新此令牌,而主要问题是-如何以及在服务器中的何处存储令牌?互联网上的一些解决方案告诉我如何做那样(代码可能不起作用,但是想法很明显):
let myToken = { ... }
class MyService {
myFunc() {
makeHttpCall(myToken).catch(async err => {
if (err.httpCode == 401) {
let netToken = await refreshToken()
myToken = netToken;
return myFunc();
}
})
}
}
module.exports = MyService
他们告诉nodejs是单线程应用程序-因此调用之间的同步没有任何问题。我同意,
但是,如果令牌已过时并且随后有两个makeHttpCall
函数调用怎么办?是的,他们将一一失败
401
,并说这些呼叫有延迟,因此顺序可能如下:
First invocation
|
\|/
makeHttpCall Second invocation
| |
\|/ \|/
401 makeHttpCall
| |
\|/ \|/
refresh (unexpected delay here) 401
| |
| \|/
| refresh
\|/ |
writeNewToken |
| |
\|/ |
makeHttpCall |
| |
\|/ \|/
401 writeNewToken
(because token refreshed twice)
因此,想法是-第二个makeHttpCall
在第二次调用收到新令牌后立即发出请求。
如何同步呢?如何刷新令牌并确保其未被覆盖?
请注意,该问题与任何用户会话或oauth令牌无关。使用会话或对oauth签名的解决方案不是答案
答案 0 :(得分:0)
我也遇到过类似的情况,而找到的最理想的解决方法是-
将所有内容包装到一个主函数中,该主函数负责刷新令牌并调度请求。
请求仅应执行获取数据的任务,并且如果被拒绝,则请求将优雅地失败,让主函数知道令牌不再有效,而不会尝试刷新令牌本身。
存储刷新令牌,说明当前是否正在刷新以及已调度的请求。在原型/小型系统中,您可以将其存储在内存中,但我强烈建议您使用持久性数据存储-sqlite,postgresql,mysql或redis启用持久性。
该请求永远不要尝试刷新令牌本身,因为在高吞吐量环境中,您将很快陷入每次请求分派都会刷新令牌的状态。而且,如果新令牌立即使之前的令牌无效,您会看到请求成功或被随机拒绝
以编程方式(伪代码)-
let refreshToken = 'asfasfjhskajfaskf',
isTokenBeingRefreshed = 0, //1 if it is indeed being refreshed
pendingRequests = {
"<request id>":"data"
}
function dispatchRequest(params) {
if(isTokenBeingRefreshed) {
//push requests into pendingRequests and wait for token to be refreshed
if(!isTokenBeingRefreshed) {
//dispatch request
}
}
一旦收到发送请求,请检查令牌是否正在刷新。如果是,请保留该请求。如果不是,则调度请求。如果由于身份验证错误而导致请求失败,则将所有其他请求搁置(如果已经分派,则它们将失败,但由于一个请求已经在进行中,因此不请求另一个令牌,只需静默确认刷新令牌的请求即可),等待令牌刷新。请区分请求是否被拒绝,由于操作错误,由于网络错误等导致失败。
您可能必须使用setInterval之类的方法来分派保留的请求。
如果您将其作为项目的一部分进行编写(这将是长期的并且可能具有较高的吞吐量),那么您应该使用消息代理或队列进行评估。要考虑的选项是Kafka,ZeroMQ,RabbitMQ。即使是使用Redis的基本本地滚动队列也可以完成相当大的负载。检查节点是否为bull。
注意-在未决请求中,最好存储所有未决,已完成和失败的请求及其数据。最好为每个请求分配一个唯一的ID,这样,如果由于身份验证错误或网络错误而导致请求失败,您可以重试。