经典笔试题:设计一个限流系统
笔试题:
登陆安全的题目,如果你的系统登陆接口在被刷。我们要建立一个防刷系统。
根据登陆ip,30分钟之内,只能请求30次登陆请求,如果超过这个限制,则整个ip限制登陆请求30分钟
设计数据结构和实现代码模拟分布式限流,多线程问题。不允许使用redis等。
设计思路:
这道题主要是设计两个Map,
第一个Map,记录每个IP及其登录的时间,题目要求,30分钟之内只能登录30次,
所以Map的key为IP,value可以设计一个队列,队列长度30,队列元素为每次的登录时间。
第二个Map,记录禁止登录的IP及禁止开始时间,Map的key为IP,value为时间。
实现代码如下:
/** * 限流器实现代码 * 单例模式是为了保证一个JVM中只有一个限流器 */ public class LimitCache { private static LimitCache instance; // 记录登录的ip地址及每次登录时间 private static HashMap<String, LinkedList<LocalDateTime>> loginMap = new HashMap<String, LinkedList<LocalDateTime>>(); // 记录禁止登录的ip地址及禁止开始时间 private static HashMap<String, LocalDateTime> forbiddenMap = new HashMap<String, LocalDateTime>(); // 私有化构造方法 private LimitCache() { } // 单例:双重检查模式 (DCL) public static synchronized LimitCache getInstance() { if (instance == null) { synchronized (LimitCache.class) { if (instance == null) { instance = new LimitCache(); } } } return instance; } public static HashMap<String, LinkedList<LocalDateTime>> getLoginMap() { return loginMap; } public static HashMap<String, LocalDateTime> getForbiddenMap() { return forbiddenMap; } }
/** * 模拟登陆 * */ @RestController public class LoginController { @GetMapping("/login") public String login(String ip) { String result = ""; LimitCache limitCache = LimitCache.getInstance(); LinkedList<LocalDateTime> queue = null; // 先判断ip地址是否禁止登录 LocalDateTime forbiddenTime = limitCache.getForbiddenMap().get(ip); if (forbiddenTime != null) { Long after = ChronoUnit.MINUTES.between(forbiddenTime, LocalDateTime.now()); if (after <= 30) { result = "当前时间=" + LocalDateTime.now() + " 上次禁止登录时间= " + forbiddenTime + " 距上次被禁时间没有超过30分钟"; return result; } else { limitCache.getForbiddenMap().clear(); } } // 如果是首次登录,则创建队列 if (limitCache.getLoginMap().get(ip) == null) { queue = new LinkedList<LocalDateTime>(); } else { queue = limitCache.getLoginMap().get(ip); } // 登录次数达到登录次数上限 if (queue.size() == 30) { // 当前时间和队列中最早的登录时间比较 是否小于30分钟 LocalDateTime now = LocalDateTime.now(); LocalDateTime firstLoginTime = queue.poll(); Long duration = ChronoUnit.MINUTES.between(firstLoginTime, now); if (duration <= 30) { result = "30分钟内登录超过30次,不允许登录,30分钟后再登录"; // 禁止该IP登录 limitCache.getLoginMap().clear(); limitCache.getForbiddenMap().put(ip, now); return result; } }
queue.offer(LocalDateTime.now()); limitCache.getLoginMap().put(ip, queue); result = ip + " 登录时间=" + queue.getLast() + " 队列长度=" + queue.size(); return result; } }