本文分享一下如何實現流量攔截
首先,流量攔截是要在某一段時間內控制訪問次數,如果訪問次數超過閾值,則拒絕訪問。
所以,要有兩個配置化信息,一個是流量監控緩存失效時間內可以調用的次數,一個是流量監控緩存失效時間
實現方式是使用Redis緩存記錄調用次數和攔截器用來攔截http請求調用。
@Service
public class RateLimitInterceptor extends HandlerInterceptorAdapter {//繼承攔截器的適配器類HandlerInterceptorAdapter
private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitInterceptor.class);
private String rateControlFlag = "0";//是否開啓流量控制
private Long lastLoadCacheTime = System.currentTimeMillis();
@Autowired
private RateLimitService rateLimitService;
/**
* 流量控制檢查入口
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//根據服務器狀態或請求用戶的限制策略來進行控制
// this is where magic happens
super.preHandle(request, response, handler);
//暫定userKey用請求方地址,可能對方是局域網。。
String userKey = request.getRemoteAddr();
Long thisTime = System.currentTimeMillis();
//配置的刷新時間
long configTime = 300000;
if(thisTime - lastLoadCacheTime >configTime){
lastLoadCacheTime = thisTime;//刷新上次加載緩存的時間
//是否開啓流量控制
rateControlFlag = SystemConfigUtils.getValueByCodeAndBranchChannel(SystemConfigEnum.RATE_CONTROL_SWITCH,"","");
}
if("1".equals(rateControlFlag)){//開啓流量控制
//獲取請求方在一定時間內 剩餘可以調用的次數
Long remainingCount = rateLimitService.decrease(userKey);
if(LOGGER.isDebugEnabled()){
LOGGER.debug(userKey+" remainingCount:"+ remainingCount);
}
// 小於0時表示該userKey的已經超過當前時間段能訪問的次數
if (remainingCount < 0) {//429表示你需要限制客戶端請求某個服務數量時,該狀態碼就很有用,也就是請求速度限制。
response.sendError(429, "Rate limit exceeded, wait for the next quarter minute");
return false;
}
response.addHeader("Remaining request count", remainingCount == null? null :remainingCount.toString());
}
return true;
}
}
下面看一下獲取剩餘可以調用的次數
/*
* 減少當前userKey的剩餘訪問次數,並返回增加後的次數
*/
@Override
public long decrease(String userKey) {
// 查詢緩存,如果沒有則初始化,並設置緩存失效時間
Long value = MyRedisCacheUtil.getCache(CacheEnum.RATE_LIMIT.getCode(), userKey);
if (value == null) {
this.doRefreshConfig();//刷新緩存配置
value = RATE_LIMIT;//初始化剩餘可以調用的次數爲配置化的閾值
MyRedisCacheUtil.putCache(CacheEnum.RATE_LIMIT.getCode(), userKey, value);
// 設置key的失效時間
MyRedisCacheUtil.expire(CacheEnum.RATE_LIMIT.getCode(), userKey, this.RATE_EXPIRES, TimeUnit.SECONDS);
} else {
value -= 1;//剩餘次數減一
MyRedisCacheUtil.putCache(CacheEnum.RATE_LIMIT.getCode(), userKey, value);//更新緩存
}
return value;
}
/**
* 刷新流量限額的配置信息
*/
public void doRefreshConfig() {
String rateExpires = SystemConfigUtils.getValueByCodeAndBranchChannel(SystemConfigEnum.RATE_EXPIRES,"","");
String rateLimitStr = SystemConfigUtils.getValueByCodeAndBranchChannel(SystemConfigEnum.RATE_LIMIT,"","");
if (StringUtils.hasText(rateLimitStr)) {
RATE_LIMIT = Long.parseLong(rateLimitStr);
}
if (StringUtils.hasText(rateExpires)) {
RATE_EXPIRES = Long.parseLong(rateExpires);
}
}