如何解决api接⼝的并发问题?
上周公司⾥发⽣了⼀件怪事,就是我们⾃⼰系统的注册接⼝被⼈恶意频繁访问最后导致该服务不可⽤,该注册接⼝是输⼊电话号码然后获取验证码注册,有⼈⽤遍历的⽅法⽆限重试验证码,最终服务没抗住挂掉了。更怪的是查到这个⼈的ip竟然是⾃⼰内部的公⽹ip,⼤概是有⼈闲的⽆聊了在搞怪,没办法,⼜不能封了ip,那样⼤家都访问不了了。
So,今天有空研究了⼀下关于如何解决api接⼝⾼并发的问题,在此记录⼀下。
1、通过控制并发数量来实现
信号量:这应该是⼤学操作系统课本⾥的概念,它是⽤在多进程和多任务之间的同步的,它就像⼗字路⼝的红绿灯,保证这个路⼝四条路的畅通⾏驶。
java中也有这个概念,Java并发库的Semaphore可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过acquire() 获取⼀个许可,如果没有就等待,⽽ release() 释放⼀个许可。
写⼀个测试接⼝如下:
private final Semaphore permit = new Semaphore(10, true);
@PostMapping("/test")
public String test(){
try {
permit.acquire();
log.info("处理请求===============>");
Thread.sleep(2000);
}catch (Exception e){
<("error");
}finally {
}
return "success";
}
以上是我写了⼀个测试接⼝,这个接⼝最多允许同时10个并发量,超过10个则等待。
接下来我们⽤压⼒测试⼯具测⼀下这个接⼝,这个是我⽤的测试⼯具:
下载解压后,在⼯具上输⼊url和并发量
这⾥设置并发量为50,也就是同时50个客户端访问该接⼝,点击开始测试,查看⽇志
我们可以看到,每次只有10个请求进⼊接⼝,然后过2秒后再来10个请求,效果很明显,我们想增⼤访问量只需要修改初始的信号量的数量即可。
2、通过控制访问速率来实现
这种⽅式采⽤令牌桶算法来实现,我们以⼀个恒定的速率向⼀个桶内放令牌,每次请求来的时候去桶⾥拿令牌,如果拿到了就继续后⾯的操作,如果没有拿到则等待。
在我们的⼯程实践中,通常使⽤Google开源⼯具包Guava提供的限流⼯具类RateLimiter来实现控制速率,该类基于令牌桶算法来完成限流,⾮常易于使⽤,⽽且⾮常⾼效。如我们不希望每秒的任务提交超过1个。
public static void main(String[] args) {
String start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
RateLimiter limiter = ate(1.0); // 这⾥的1表⽰每秒允许处理的量为1个
for (int i = 1; i <= 10; i++) {
double waitTime = limiter.acquire(i);// 请求RateLimiter, 超过permits会被阻塞
System.out.println("cutTime=" + System.currentTimeMillis() + " call execute:" + i + " waitTime:" + waitTime);
}
String end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("start time:" + start);
System.out.println("end time:" + end);
}
RateLimiter limiter = ate(1.0) 创建⼀个限流器,每秒⽣成1个令牌;
limiter.acquire(i) 以阻塞的⽅式获取令牌,随着i的增加,需要的令牌数增多,则需要等待的时间也增加。
查看输出结果:
可以看出,当 i=6时,等待时间差不多为i-1=5秒,在这⾥是因为RateLimiter⽀持预消费,来⽀持⼀定程度的突发情况。
开可以⽤tryAcquire(int permits, long timeout, TimeUnit unit)来设置等待超时时间的⽅式获取令牌,当等待超过了超时时长则⽴马返回。
令牌的⽣成策略有两种,⼀种是稳定模式(SmoothBursty 模式),⼀种为渐进模式(SmoothWarmingUp模式)。
SmoothBursty 模式:RateLimiter limiter = ate(5);
()表⽰消费⼀个令牌,如果当前桶中有⾜够令牌则成功(返回值为0),如果桶中没有令牌则暂停⼀段时间,⽐如发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌,这种实现将突发请求速率平均为了固定请求速率。
SmoothWarmingUp模式:RateLimiter limiter = ate(5,1000, TimeUnit.MILLISECONDS);
创建⽅式:ate(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit),permitsPerSecond表⽰每秒新增的令牌数,warmupPeriod表⽰在从冷启动速率过渡到平均速率的时间间隔。速率是梯形上升速率的,也就是说冷启动时会以⼀个⽐较⼤的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现⼀开始就是平滑固定速率。
写⼀个测试接⼝测试
private final RateLimiter limiter = ate(5.0);
@PostMapping("/test2")
public String test2(){
try {
boolean flag = Acquire(1,3, TimeUnit.SECONDS);
百度api接口if (flag){
log.info("处理请求===============>");
Thread.sleep(1000);
}else {
return "系统繁忙!";
}
}catch (Exception e){
<("error");
}
return "success";
}
每秒产⽣5个令牌,请求进来每次获取⼀个,然后超时时长是3秒,还是设置50的并发量:
查看输出结果:
可以看到,⼀开始预消费进来了8个请求,随后的时间⾥每次进来5个请求,在这之间有超时3秒没有获取到令牌的都返回超时了。返回结果如下:
⾄此,我所了解的解决接⼝并发调⽤的⽅法就是这些,欢迎⼤佬指出错误和不⾜。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论