由于业务系统服务接口被竞对高频爬取,我们需要上线一个防刷限流功能。场景要求如下:根据登陆ip,限制1分钟最多10次服务请求,须支持单机并发场景,请使用java代码原生实现(勿使用中间件redis、计数器之类方案)。
// 请完善如下模板代码
public class Solution {
//不限流返回ture,限流返回false
public boolean access(String ip) {
// @todo
return false;
}
}
废话不多说直接贴代码(注意!注意!注意!重要的事说三遍在线程超过150条的情况下,有几率会代码容错(超过10次正确请求,测了5都超不过11次),120条线程以下绝不会发生,所以此时就有了解决方法,采用信号量分批放线程,10条线程最安全,别问,问就是前10条全部成功,后面在规定时间内全部失败)
要是不想自己动手贴上gitee仓库地址自己克隆:
试题Java代码
https://gitee.com/jiuguanxiaojiu/java-written-test-questions
代码在bool里,如若不会克隆请移步
Git生成ssh公私钥,连接getee仓库克隆代码
https://blog.youkuaiyun.com/m0_59849460/article/details/123819669
http测试类
package com.it.juxiang.demo.bool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 模拟同一用户快速多次登录(请求),通过(UserLoginLimiter)限制
*
*/
@Controller
public class UserLoginTimeController {
@Autowired
private UserLoginLimiter userLoginLimiter;
@PostMapping("/bool")
@ResponseBody
public String test() {
String ip="192.168.198.10";
if (userLoginLimiter.check(ip)) {
System.out.println(ip + ":访问成功:true...........!");
return (ip + ":访问成功:true!");
} else {
System.out.println(ip +":访问失败:false!");
return (ip + ":访问失败:false!");
}
}
}
核心代码(答案)
package com.it.juxiang.demo.bool;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
/**
* 用户登录记录限制类
* 限制在一定时长(thresholdInMillisecond)内最多登录(maxLoginTimes)次
*/
@Component
public class UserLoginLimiter {
//默认限制器,限制在60秒之内最多访问10次
private int maxLoginTimes=10; //一定时间(thresholdInMillisecond值)内的最多登录次数
private int thresholdInMillisecond=60000; //时长
private LinkedList<LoginRecord> loginRecordList=new LinkedList<LoginRecord>(); //最近的登录队列
/**
* 检查登录记录,
* 对在(thresholdInMillisecond)时长内超过(maxloginTimes)次的登录记录返回false,否则返回true;
* @param ip
* @return
*/
public boolean check(String ip) {
//检查设置的值,对0及负数表示不限制登录
if (maxLoginTimes <= 0 || thresholdInMillisecond <= 0) return true;
LoginRecord loginRecord=new LoginRecord(ip,System.currentTimeMillis());
//队列长度未到maxLoginTimes的不用限制
if (loginRecordList.size() < maxLoginTimes) {
loginRecordList.addLast(loginRecord);
return true;
}
//队列长度达到maxLoginTimes的,清理和当前记录比较超过thresholdInMillisecond值的记录
LoginRecord firstLR = loginRecordList.getFirst();
while (loginRecordList.size() > 0 && firstLR.getTime() + thresholdInMillisecond < loginRecord.getTime()) {
loginRecordList.removeFirst();
if (loginRecordList.size() > 0) {
firstLR = loginRecordList.getFirst();
} else {
break;
}
}
//检查队列是否有空间添加新的记录
if (loginRecordList.size() < maxLoginTimes) {
loginRecordList.addLast(loginRecord);
return true;
} else {
return false; //队列中的记录与当前登录记录发生在thresholdInMillisecond内,拒绝
}
}
}
用户登录记录类
package com.it.juxiang.demo.bool;
/**
* 用户登录记录类
*/
public class LoginRecord {
private String ip;
private long time;
public LoginRecord(String ip, long time) {
this.ip = ip;
this.time = time;
}
public String getIp() {
return ip;
}
public long getTime() {
return time;
}
@Override
public String toString() {
return "LoginRecord{" +
"ip='" + ip + '\'' +
", time=" + time +
'}';
}
}
推荐一个软件,可以方便测试多线程下的运行:
Apache JMeter WEB