为我的 API 的每个用户创建一个线程是否可以扩展?
Will creating a thread for every user of my API scale well?
我正在构建一个 API,它将与 Java Spark 中的 Spotify API 交互。我使用授权代码流进行令牌管理 - 这意味着令牌将在一个小时内有效(对于给定用户),然后需要刷新。
对于每个连接他们的 Spotify 帐户的用户,我创建了一个计时器,它将在 50 分钟后检查用户是否处于活动状态:
如果是 -> 我刷新用户的令牌。
如果否 -> 我将用户连同用户的令牌一起删除,这意味着如果他们想使用我的服务(出于存储目的),他们将不得不重新登录。
我还保留了一个带有用户对象的 HashMap,其中包含来自每个用户的各种信息,例如他们的个人资料名称、图像、播放列表等。如果计时器的检查证明该用户是不活跃。
问题:
每个定时器对象创建一个新的线程。如果理论上有成千上万的用户使用我的服务,就会有成千上万的线程……我的直觉告诉我,这是不可接受的。我似乎无法解决这个问题。我应该如何跟踪每个用户何时过了 50 分钟,同时保持尽可能少的线程而不是 "over-powering" 而不是 API?如有任何提示,我们将不胜感激!
代码:
package Authentication;
import Spotify.Users.UserSessions;
import java.util.Date;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
public class RefreshTokens extends TimerTask {
private UserSessions userSessions;
private Authentication authentication;
private String currentUserSession;
private Timer timer = new Timer(true);
public RefreshTokens(UserSessions userSessions, Authentication authentication, String currentUserSession) {
this.userSessions = userSessions;
this.authentication = authentication;
this.currentUserSession = currentUserSession;
}
public void startAutomaticProcess() {
timer.schedule(this, 20000, 20000); //runs every 20 seconds for testing purposes
}
@Override
public void run() {
System.out.println("Automatic process started: " + new Date());
refresh();
}
private void refresh() {
if (userSessions.contains(currentUserSession)) {
if (userSessions.get(currentUserSession).isActive()) {
authentication.refreshToken(userSessions.get(currentUserSession));
} else {
System.out.println("User was not active enough and has been removed from the server.");
System.out.println("----------");
System.out.println("Size of HashMap before: " + userSessions.getHashMap().size());
userSessions.getHashMap().remove(currentUserSession);
System.out.println("Size of HashMap after: " + userSessions.getHashMap().size());
timer.cancel();
timer.purge();
}
}
}
}
我为每个新用户创建这个 class 的新实例并调用 startAutomaticProcess() 方法。
Will creating a thread for every user of my API scale well?
显然没有。
每个线程都有一个线程堆栈,它使用至少 64K 字节,默认为 1MB;见:
因此,如果用户数量增加,您将 运行 内存不足。这是不可扩展的。
此外,每次执行刷新时,每个线程都需要唤醒。这需要 2 次上下文切换和相关的开销。
建议:
- 创建一个
UserToken
class 代表每个用户令牌,并包含上次检查令牌的时间戳。
- 创建
PriorityQueue<UserToken>
以令牌的时间戳为准。
- 使用
TimerTask
从需要检查的优先级队列中删除 UserToken
个对象。
- 当检查成功时(即用户仍然活跃),更新时间戳并将
UserToken
重新添加到队列中。
这种方法需要更好的尺度。假设N
是认证用户数:
- 只有一个线程,而不是
N
个线程和 TimerTask
个对象。
- 线程需要每
M
分钟唤醒一次,而不是 N
个线程每 M2
分钟全部唤醒一次。
- 每个活动用户需要少于 500 字节1,而不是 64K(最小值)。
- 优先队列 insertion/re-insertion 很便宜,并且扩展为
O(logN)
。
1 - space 由 UserToken
对象及其附属对象,以及优先级队列中的内部 "node" 组成。 100 到 200 字节是一个更好的估计,尽管这将是特定于实现的。
我正在构建一个 API,它将与 Java Spark 中的 Spotify API 交互。我使用授权代码流进行令牌管理 - 这意味着令牌将在一个小时内有效(对于给定用户),然后需要刷新。
对于每个连接他们的 Spotify 帐户的用户,我创建了一个计时器,它将在 50 分钟后检查用户是否处于活动状态:
如果是 -> 我刷新用户的令牌。 如果否 -> 我将用户连同用户的令牌一起删除,这意味着如果他们想使用我的服务(出于存储目的),他们将不得不重新登录。
我还保留了一个带有用户对象的 HashMap,其中包含来自每个用户的各种信息,例如他们的个人资料名称、图像、播放列表等。如果计时器的检查证明该用户是不活跃。
问题: 每个定时器对象创建一个新的线程。如果理论上有成千上万的用户使用我的服务,就会有成千上万的线程……我的直觉告诉我,这是不可接受的。我似乎无法解决这个问题。我应该如何跟踪每个用户何时过了 50 分钟,同时保持尽可能少的线程而不是 "over-powering" 而不是 API?如有任何提示,我们将不胜感激!
代码:
package Authentication;
import Spotify.Users.UserSessions;
import java.util.Date;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
public class RefreshTokens extends TimerTask {
private UserSessions userSessions;
private Authentication authentication;
private String currentUserSession;
private Timer timer = new Timer(true);
public RefreshTokens(UserSessions userSessions, Authentication authentication, String currentUserSession) {
this.userSessions = userSessions;
this.authentication = authentication;
this.currentUserSession = currentUserSession;
}
public void startAutomaticProcess() {
timer.schedule(this, 20000, 20000); //runs every 20 seconds for testing purposes
}
@Override
public void run() {
System.out.println("Automatic process started: " + new Date());
refresh();
}
private void refresh() {
if (userSessions.contains(currentUserSession)) {
if (userSessions.get(currentUserSession).isActive()) {
authentication.refreshToken(userSessions.get(currentUserSession));
} else {
System.out.println("User was not active enough and has been removed from the server.");
System.out.println("----------");
System.out.println("Size of HashMap before: " + userSessions.getHashMap().size());
userSessions.getHashMap().remove(currentUserSession);
System.out.println("Size of HashMap after: " + userSessions.getHashMap().size());
timer.cancel();
timer.purge();
}
}
}
}
我为每个新用户创建这个 class 的新实例并调用 startAutomaticProcess() 方法。
Will creating a thread for every user of my API scale well?
显然没有。
每个线程都有一个线程堆栈,它使用至少 64K 字节,默认为 1MB;见:
因此,如果用户数量增加,您将 运行 内存不足。这是不可扩展的。
此外,每次执行刷新时,每个线程都需要唤醒。这需要 2 次上下文切换和相关的开销。
建议:
- 创建一个
UserToken
class 代表每个用户令牌,并包含上次检查令牌的时间戳。 - 创建
PriorityQueue<UserToken>
以令牌的时间戳为准。 - 使用
TimerTask
从需要检查的优先级队列中删除UserToken
个对象。 - 当检查成功时(即用户仍然活跃),更新时间戳并将
UserToken
重新添加到队列中。
这种方法需要更好的尺度。假设N
是认证用户数:
- 只有一个线程,而不是
N
个线程和TimerTask
个对象。 - 线程需要每
M
分钟唤醒一次,而不是N
个线程每M2
分钟全部唤醒一次。 - 每个活动用户需要少于 500 字节1,而不是 64K(最小值)。
- 优先队列 insertion/re-insertion 很便宜,并且扩展为
O(logN)
。
1 - space 由 UserToken
对象及其附属对象,以及优先级队列中的内部 "node" 组成。 100 到 200 字节是一个更好的估计,尽管这将是特定于实现的。