Java并发访问重复请求过滤是一个常见的问题。在高并发场景下,由于网络延迟、异步任务执行时间过长等原因,客户端容易发起重复请求,导致服务端资源浪费或数据异常。因此,需要一种机制来过滤掉重复请求。
一、方案选择
解决这个问题的方案有很多,这里介绍两种比较常见的方案:
- 使用Token机制
Token机制的原理是:客户端发送一个请求时,服务端在响应中返回一个Token(如UUID),客户端在下次发送请求时,将Token携带在请求头中,服务端在处理请求时,如果发现Token已被使用过,则认为是重复请求,直接返回之前处理的结果;否则,将Token保存在服务端缓存中,等待客户端下次请求时进行校验。
- 使用Set集合
Set集合的原理是:服务端在处理每个请求时,将请求的关键信息(如请求URL、参数等)作为唯一标识,添加到Set集合中;当下次有相同关键信息的请求到来时,直接过滤掉。
二、实现示例
下面通过示例代码演示以上两种方案的实现:
- 使用Token机制
public class TokenFilter {
private static final String TOKEN_KEY = "TOKEN";
public static boolean isTokenValid(String token) {
if (StringUtils.isEmpty(token)) {
return false;
}
if (cache.getIfPresent(token) != null) {
return false;
}
cache.put(token, true);
return true;
}
public static void removeToken(String token) {
cache.invalidate(token);
}
// 使用guava的Cache作为缓存
private static final Cache<String, Boolean> cache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public static String getTokenHeader() {
return TOKEN_KEY + ":" + UUID.randomUUID().toString();
}
}
TokenFilter中的isTokenValid方法用于校验Token是否有效,removeToken方法用于清除Token。使用了Guava中的Cache集合作为缓存。
在Servlet中,可以这样使用TokenFilter:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String token = req.getHeader(TokenFilter.TOKEN_KEY);
if (!TokenFilter.isTokenValid(token)) {
resp.getWriter().println("重复请求");
return;
}
// 处理请求
TokenFilter.removeToken(token);
}
在HTTP响应头中返回Token:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String token = TokenFilter.getTokenHeader();
resp.setHeader(TokenFilter.TOKEN_KEY, token);
// 处理请求
}
- 使用Set集合
public class RequestFilter {
private static final Set<String> requestInfoSet = Collections.synchronizedSet(new HashSet<>());
public static boolean isDuplicateRequest(HttpServletRequest request) {
String requestInfo = getRequestInfo(request);
if (StringUtils.isEmpty(requestInfo)) {
return false;
}
// 加锁保证原子性
synchronized (requestInfoSet) {
if (requestInfoSet.contains(requestInfo)) {
return true;
}
requestInfoSet.add(requestInfo);
return false;
}
}
private static String getRequestInfo(HttpServletRequest request) {
String url = request.getRequestURI();
String queryString = request.getQueryString();
String method = request.getMethod();
if (StringUtils.isEmpty(url) || StringUtils.isEmpty(method)) {
return null;
}
if (queryString != null) {
url += "?" + queryString;
}
return url + ":" + method;
}
public static void clear() {
requestInfoSet.clear();
}
}
RequestFilter中的isDuplicateRequest方法使用了一个Set集合来保存已处理请求的关键信息,synchronizedSet保证了线程安全。getRequestInfo方法将请求URL和方法作为关键信息保存。
在Servlet中,可以这样使用RequestFilter:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (RequestFilter.isDuplicateRequest(req)) {
resp.getWriter().println("重复请求");
return;
}
// 处理请求
RequestFilter.clear();
}
注意,在处理请求后需要清空requestInfoSet,否则会导致后续请求无法通过检查。
三、总结
两种方案各有优缺点,Token机制需要服务端缓存来保存Token,增加了服务端的负担;Set集合方案简单,但需要注意清空集合。使用哪种方案具体要根据需求选择。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:java并发访问重复请求过滤问题 - Python技术站