計(jì)數(shù)器限流是服務(wù)接口限流策略中最為基本和簡(jiǎn)單的方式。本實(shí)例將實(shí)現(xiàn)不同接口設(shè)置不同的限流方案。
首先我們需要需要定義一個(gè)限流枚舉類或悲,用來(lái)存儲(chǔ)各個(gè)接口需要設(shè)置的限流信息。
public enum LimitEnum {
/**
* 獲取郵箱信息的接口限制堪唐,每秒最多被調(diào)用3次
* */
GET_MAIL_INFO_LIMIT("/getMailInfo", 3, 1),
/**
* 獲取用戶信息的接口限制巡语,每2秒最多被調(diào)用5次
* */
GET_USER_INFO_LIMIT("/getUserInfo", 5, 2);
private String urlName;
private Integer maxCount;
private Integer timePeriod;
LimitEnum(String urlName, Integer maxCount, Integer timePeriod) {
this.urlName = urlName;
this.maxCount = maxCount;
this.timePeriod = timePeriod;
}
public String getUrlName() {
return urlName;
}
public Integer getMaxCount() {
return maxCount;
}
public Integer getTimePeriod() {
return timePeriod;
}
}
然后,我們需要一個(gè)接口限流實(shí)體類淮菠,用來(lái)記錄內(nèi)存中的每個(gè)接口實(shí)時(shí)限流信息男公。
@Data
public class LimitDTO {
/**
* 單位時(shí)間內(nèi)最大調(diào)用次數(shù)
* */
private Integer maxCount;
/**
* 自定義單位時(shí)間
* */
private Long expireTime;
/**
* 當(dāng)前單位時(shí)間內(nèi)的已調(diào)用次數(shù)
* */
private Integer currentCount;
}
下面,就是計(jì)數(shù)器限流的核心代碼:
@Slf4j
public class CountLimiter {
/**
* 存放所有URL的計(jì)數(shù)器
*/
private static Map<String, LimitDTO> limitMap = new ConcurrentHashMap<>();
public static Boolean acquire(LimitEnum limitEnum) {
LimitDTO limitDTO = limitMap.get(limitEnum.getUrlName());
// 該URL首次使用限流或者限流信息已經(jīng)過(guò)期合陵,則需要初始化該URL的限流信息
if (null == limitDTO || limitDTO.getExpireTime() < System.currentTimeMillis()) {
limitMap.put(limitEnum.getUrlName(), resetLimitInfo(limitEnum, limitDTO));
return true;
}
// 該URL非首次使用限流枢赔,且限流信息未過(guò)期
if(limitDTO.getCurrentCount() >= limitEnum.getMaxCount()){
log.warn("{}超出了調(diào)用限制澄阳,在{}秒內(nèi)最多只能調(diào)用{}次,當(dāng)前已經(jīng)調(diào)用{}次踏拜!"
, limitEnum.getUrlName(), limitEnum.getTimePeriod()
, limitEnum.getMaxCount(), limitDTO.getCurrentCount());
return false;
}
limitDTO.setCurrentCount(limitDTO.getCurrentCount() + 1);
limitMap.put(limitEnum.getUrlName(), limitDTO);
return true;
}
private static LimitDTO resetLimitInfo(LimitEnum limitEnum, LimitDTO limitDTO) {
if (null == limitDTO) {
limitDTO = new LimitDTO();
}
limitDTO.setMaxCount(limitEnum.getMaxCount());
limitDTO.setExpireTime(limitEnum.getTimePeriod() * 1000 + System.currentTimeMillis());
limitDTO.setCurrentCount(1);
return limitDTO;
}
}
最后碎赢,我們對(duì)外提供接口,在接口被調(diào)用的時(shí)候速梗,我們需要使用同步的方式獲取對(duì)計(jì)數(shù)器的讀寫權(quán)限肮塞。
@Slf4j
@RestController("/limit")
public class LimitRest {
@GetMapping("/getMailInfo")
public String getMailInfo(){
Boolean limitFlag = false;
synchronized (LimitRest.class){
limitFlag = CountLimiter.acquire(LimitEnum.GET_MAIL_INFO_LIMIT);
}
if(!limitFlag){
return "調(diào)用太頻繁啦,請(qǐng)稍后再試姻锁!";
}
return "OK";
}
@GetMapping("/getUserInfo")
public String getUserInfo(){
Boolean limitFlag = false;
synchronized (LimitRest.class){
limitFlag = CountLimiter.acquire(LimitEnum.GET_USER_INFO_LIMIT);
}
if(!limitFlag){
return "調(diào)用太頻繁啦峦嗤,請(qǐng)稍后再試!";
}
return "OK";
}
}
計(jì)數(shù)器限流方案的特點(diǎn)是屋摔,在單位時(shí)間內(nèi)只允許固定數(shù)量的請(qǐng)求得到服務(wù)烁设,其余的全部失敗。但是這些固定數(shù)量的得到服務(wù)的請(qǐng)求可以是第一個(gè)毫秒就全部發(fā)生的钓试,也可以是在單位時(shí)間內(nèi)均勻發(fā)生的装黑。
所以,我們無(wú)法控制請(qǐng)求的發(fā)生頻率弓熏,造成的結(jié)果就是恋谭,在整個(gè)單位時(shí)間內(nèi),有的時(shí)間段服務(wù)器很忙挽鞠,疲憊不堪疚颊;有的時(shí)間段服務(wù)器卻很閑。
全文完信认。