基于计数器的服务接口限流实例

2019-12-21  本文已影响0人  文景大大

计数器限流是服务接口限流策略中最为基本和简单的方式。本实例将实现不同接口设置不同的限流方案。

首先我们需要需要定义一个限流枚举类,用来存储各个接口需要设置的限流信息。

public enum LimitEnum {
    /**
     * 获取邮箱信息的接口限制,每秒最多被调用3次
     * */
    GET_MAIL_INFO_LIMIT("/getMailInfo", 3, 1),
    /**
     * 获取用户信息的接口限制,每2秒最多被调用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;
    }
}

然后,我们需要一个接口限流实体类,用来记录内存中的每个接口实时限流信息。

@Data
public class LimitDTO {
    /**
     * 单位时间内最大调用次数
     * */
    private Integer maxCount;
    /**
     * 自定义单位时间
     * */
    private Long expireTime;
    /**
     * 当前单位时间内的已调用次数
     * */
    private Integer currentCount;
}

下面,就是计数器限流的核心代码:

@Slf4j
public class CountLimiter {
    /**
     * 存放所有URL的计数器
     */
    private static Map<String, LimitDTO> limitMap = new ConcurrentHashMap<>();

    public static Boolean acquire(LimitEnum limitEnum) {
        LimitDTO limitDTO = limitMap.get(limitEnum.getUrlName());

        // 该URL首次使用限流或者限流信息已经过期,则需要初始化该URL的限流信息
        if (null == limitDTO || limitDTO.getExpireTime() < System.currentTimeMillis()) {
            limitMap.put(limitEnum.getUrlName(), resetLimitInfo(limitEnum, limitDTO));
            return true;
        }

        // 该URL非首次使用限流,且限流信息未过期
        if(limitDTO.getCurrentCount() >= limitEnum.getMaxCount()){
            log.warn("{}超出了调用限制,在{}秒内最多只能调用{}次,当前已经调用{}次!"
            , 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;
    }
}

最后,我们对外提供接口,在接口被调用的时候,我们需要使用同步的方式获取对计数器的读写权限。

@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 "调用太频繁啦,请稍后再试!";
        }
        return "OK";
    }

    @GetMapping("/getUserInfo")
    public String getUserInfo(){
        Boolean limitFlag = false;
        synchronized (LimitRest.class){
            limitFlag = CountLimiter.acquire(LimitEnum.GET_USER_INFO_LIMIT);
        }
        if(!limitFlag){
            return "调用太频繁啦,请稍后再试!";
        }
        return "OK";
    }
}

计数器限流方案的特点是,在单位时间内只允许固定数量的请求得到服务,其余的全部失败。但是这些固定数量的得到服务的请求可以是第一个毫秒就全部发生的,也可以是在单位时间内均匀发生的。

所以,我们无法控制请求的发生频率,造成的结果就是,在整个单位时间内,有的时间段服务器很忙,疲惫不堪;有的时间段服务器却很闲。

全文完。

上一篇 下一篇

猜你喜欢

热点阅读