目录
生产中,我们经常会把接口api 提供给第三方的使用者,但是 如果当调用者出现故障疯狂的调用我们的接口,或者 同一个请求发了多次,第一种 情况 会造成 服务器大量的链接被占用,造成服务挂掉,正常的服务无法提供给其他人, 第二种情况会造成 业务上很大的困扰,比如 减库存或者转账多加多减得情况,
解决调用频率太高的问题
我使用的是 redis 作为解决调用频率的问题
首先 我们将 需要 进行 访问频率的url列出来,给他们限制住 访问的间隔时间,然后在 拦截器中 使用redis 去判断每一次进来的请求 是否 符合 间隔时间 的要求,
Integer milliSecond = Integer.parseInt(repeatMap.get(currUrl));
logger.info("当前url={}进行防重校验,{}毫秒禁止重复请求!", currUrl, milliSecond);
//用户ID
String tUserId = userInfo.getUserId();
Map<String, String> currThreadMap = new HashMap<String, String>();
// 系统当前毫秒数
Long currTime = System.currentTimeMillis();
Long expireTime = currTime + new Long(milliSecond);
Long count = jedisTemplate.STRINGS.setnx(Constants.INSTALLMENT_KEY + Constants.REPEAT_KEY + tUserId, expireTime.toString());
if (count == 1L) {
logger.info("防重校验通过,不是重复提交!");
currThreadMap.put(LOCK, tUserId);
} else {
logger.info("防重校验失败,是重复提交,需校验超时时间!");
String cacheExpTime = jedisTemplate.STRINGS.get(Constants.INSTALLMENT_KEY + Constants.REPEAT_KEY + tUserId);
if (StringUtils.isNotEmpty(cacheExpTime)) {
if (currTime > Long.valueOf(cacheExpTime)) {
logger.info("redis锁已超时,当前请求可以重新获取锁,currTime={}, cacheExpTime={}", currTime, cacheExpTime);
Long newExpTime = System.currentTimeMillis() + new Long(milliSecond);
String oldExpTime = jedisTemplate.STRINGS.getSet(Constants.INSTALLMENT_KEY + Constants.REPEAT_KEY + tUserId,
newExpTime.toString());
if (!cacheExpTime.equals(oldExpTime)) {
logger.info("锁已经被其他线程获取,当前线程拒绝执行请求");
setResponse(response, ResCode.REQUEST_PROCESS);
return false;
}
logger.info("当前线程已经获得锁,可以执行请求");
currThreadMap.put(LOCK, tUserId);
} else {
logger.info("redis锁未超时,当前请求拒绝执行,currTime={}, cacheExpTime={}", currTime, cacheExpTime);
setResponse(response, ResCode.REQUEST_PROCESS);
return false;
}
} else {
logger.info("redis缓存时间为空,锁已解除,重新获取锁");
expireTime = currTime + new Long(milliSecond);
count = jedisTemplate.STRINGS.setnx(Constants.INSTALLMENT_KEY + Constants.REPEAT_KEY + tUserId, expireTime.toString());
if (count == 0L) {
logger.info("锁已经被其他线程获取,当前线程拒绝执行请求");
setResponse(response, ResCode.REQUEST_PROCESS);
return false;
}
logger.info("当前线程已经获得锁,可以执行请求");
currThreadMap.put(LOCK, tUserId);
}
}
threadMap.set(currThreadMap);
threadMap 是 一个 ThreadLocal,用于最后将 失效的 redis key清除
解决同一个请求的问题
我们使用是 zookpeer 的临时节点
String path = publicKey[1] + "_" + params.get("platform") + "_" + params.get("outUserId");
boolean lock = zkClient.getEphemeralLock(path);
if (!lock) {
logger.info("当前存在并发问题,获取锁失败,请求打回,path={}", publicKey[1]);
throw new RequestException(OutApiResCode.REQUEST_CONCURRENT_WAINING.getCode(), "请求提交频率过高!");
}
这一步可以在 验证参数的时候 使用,如果是 同一个 outUserId 那么就会有拦截的功能