我正在构建一个 API,它将与 Java Spark 中的 Spotify API 进行交互。我使用授权代码流程进行 token 管理 - 这意味着 token 将在一小时内有效(对于给定用户),然后需要刷新。
对于连接 Spotify 帐户的每个用户,我创建一个计时器,用于在 50 分钟后检查用户是否处于 Activity 状态:
如果是 -> 我刷新用户的 token 。 如果否 -> 我会删除该用户以及用户的 token ,这意味着如果他们想使用我的服务(用于存储目的),则必须再次登录。
我还保留一个带有用户对象的 HashMap,其中包含每个用户的各种信息,例如他们的个人资料名称、图像、播放列表等。如果计时器的检查证明该用户处于非 Activity 状态,这也会从 HashMap 中删除。
问题: 每个计时器对象都会创建一个新线程。理论上,如果有数千个用户使用我的服务,就会有数千个线程......我的直觉告诉我,这是 Not Acceptable 。我似乎无法理解这一点。我应该如何跟踪每个用户 50 分钟的时间,同时保持尽可能少的线程而不是“过度使用”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();
}
}
}
}
我为每个新用户创建该类的一个新实例,并调用 startAutomaticProcess() 方法。
最佳答案
Will creating a thread for every user of my API scale well?
显然不是。
每个线程都有一个线程堆栈,该堆栈使用至少 64K 字节,默认情况下为 1MB;请参阅:
因此,如果用户数量增加,您将耗尽内存。这是不可扩展的。
此外,每次执行刷新时,每个线程都需要唤醒。这需要 2 次上下文切换和相关的开销。
建议:
- 创建
UserToken
代表每个用户 token 的类,并包含上次检查 token 的时间戳。 - 创建
PriorityQueue<UserToken>
这是根据 token 的时间戳排序的。 - 使用
TimerTask
删除UserToken
优先级队列中需要检查的对象。 - 当检查成功时(即用户仍处于 Activity 状态),更新时间戳并重新添加
UserToken
到队列中。
这种方法需要更好的规模。假设N
是经过身份验证的用户数量:
- 只有一个线程,而不是
N
线程和TimerTask
对象。 - 线程需要每隔
M
唤醒一次分钟,而不是N
所有线程每隔M2
唤醒一次分钟。 - 每个活跃用户需要少于 500 字节1,而不是 64K(最小值)。
- 优先级队列插入/重新插入成本低廉,并且可缩放为
O(logN)
.
1 - 该空间由 UserToken
组成对象及其附属对象,以及优先级队列中的内部“节点”。 100 到 200 字节是一个更好的估计,尽管这将取决于具体的实现。
关于java - 为我的 API 的每个用户创建一个线程是否可以很好地扩展?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59696682/