您当前的位置:首页 > 电脑百科 > 站长技术 > 服务器

访问量一大服务器就挂?那是你不懂限流

时间:2019-11-04 10:59:27  来源:  作者:
作者丨	Gray.z	,
来源:https://blog.csdn.net/zrg523/article/details/82185088

前言

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流

缓存

缓存的目的是提升系统访问速度和增大系统处理容量

降级

降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开限流限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理

问题描述 某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。如何应对这种情况呢?生活给了我们答案:比如老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的接口也需要安装上“保险丝”,以防止非预期的请求对系统压力过大而引起的系统瘫痪,当流量过大时,可以采取拒绝或者引流等机制。

缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流

系统在设计之初就会有一个预估容量,长时间超过系统能承受的TPS/QPS阈值,系统可能会被压垮,最终导致整个服务不够用。为了避免这种情况,我们就需要对接口请求进行限流。

限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待。

一般开发高并发系统常见的限流模式有控制并发和控制速率一个是限制并发的总数量(比如数据库连接池、线程池),一个是限制并发访问的速率(如Nginx的limitconn模块,用来限制瞬时并发连接数),另外还可以限制单位时间窗口内的请求数量(如Guava的RateLimiter、nginx的limitreq模块,限制每秒的平均速率)。其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。

相关概念:

PV:page view 页面总访问量,每刷新一次记录一次。

UV:unique view 客户端主机访问,指一天内相同IP的访问记为1次。

QPS:query per second,即每秒访问量。qps很大程度上代表了系统的繁忙度,没次请求可能存在多次的磁盘io,网络请求,多个cpu时间片,一旦qps超过了预先设置的阀值,可以考量扩容增加服务器,避免访问量过大导致的宕机。

RT:response time,每次请求的响应时间,直接决定用户体验性。

本文主要介绍应用级限流方法,分布式限流、流量入口限流(接入层如NGINX limitconn和limitreq 模块)。

应用级限流

一、控制并发数量

属于一种较常见的限流手段,在实际应用中可以通过信号量机制(如JAVA中的Semaphore)来实现。 操作系统的信号量是个很重要的概念,Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

举个例子,我们对外提供一个服务接口,允许最大并发数为10,代码实现如下:

访问量一大服务器就挂?那是你不懂限流

 

在以上代码中,虽然有30个线程在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证,还可以用tryAcquire()方法尝试获取许可证,信号量的本质是控制某个资源可被同时访问的个数,在一定程度上可以控制某资源的访问频率,但不能精确控制,控制访问频率的模式见下文描述。

二、控制访问速率

在工程实践中,常见的是使用令牌桶算法来实现这种模式,常用的限流算法有两种:漏桶算法和令牌桶算法

漏桶算法

漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率

 

访问量一大服务器就挂?那是你不懂限流

 

 

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。

令牌桶算法

如下图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务,令牌桶算法通过发放令牌,根据令牌的rate频率做请求频率限制,容量限制等。

 

访问量一大服务器就挂?那是你不懂限流

 

 

访问量一大服务器就挂?那是你不懂限流

 

 

在Wikipedia上,令牌桶算法是这么描述的:

每过1/r秒桶中增加一个令牌。桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃。当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包。如果桶中可用令牌小于n,则该数据包将被缓存或丢弃。

令牌桶控制的是一个时间窗口内通过的数据量,在API层面我们常说的QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在1s罢了。以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。令牌桶的另外一个好处是可以方便的改变速度,一旦需要提高速率,则按需提高放入桶中的令牌的速率。

在我们的工程实践中,通常使用google开源工具包Guava提供的限流工具类RateLimiter来实现控制速率,该类基于令牌桶算法来完成限流,非常易于使用,而且非常高效。如我们不希望每秒的任务提交超过1个

访问量一大服务器就挂?那是你不懂限流

 

首先通过RateLimiter.create(1.0);创建一个限流器,参数代表每秒生成的令牌数,通过limiter.acquire(i);来以阻塞的方式获取令牌,令牌桶算法允许一定程度的突发(允许消费未来的令牌),所以可以一次性消费i个令牌;当然也可以通过tryAcquire(int permits, long timeout, TimeUnit unit)来设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回,支持阻塞或可超时的令牌消费。

从输出来看,RateLimiter支持预消费,比如在acquire(5)时,等待时间是4秒,是上一个获取令牌时预消费了3个两排,固需要等待3*1秒,然后又预消费了5个令牌,以此类推。

RateLimiter通过限制后面请求的等待时间,来支持一定程度的突发请求(预消费),在使用过程中需要注意这一点,Guava有两种限流模式,一种为稳定模式(SmoothBursty:令牌生成速度恒定,平滑突发限流),一种为渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值,平滑预热限流)两种模式实现思路类似,主要区别在等待时间的计算上。

SmoothBursty 模式:RateLimiter limiter = RateLimiter.create(5);

RateLimiter.create(5)表示桶容量为5且每秒新增5个令牌,即每隔200毫秒新增一个令牌;limiter.acquire()表示消费一个令牌,如果当前桶中有足够令牌则成功(返回值为0),如果桶中没有令牌则暂停一段时间,比如发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌,这种实现将突发请求速率平均为了固定请求速率。

SmoothWarmingUp模式:

RateLimiter limiter = RateLimiter.create(5,1000, TimeUnit.MILLISECONDS);

创建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit),permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率。

放在Controller中用Jemter压测

访问量一大服务器就挂?那是你不懂限流

 

注:RateLimiter控制的是速率,Samephore控制的是并发量。

RateLimiter的原理就是令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑地分发,若请求超过permitsPerSecond则RateLimiter按照每秒 1/permitsPerSecond 的速率释放许可。注意:RateLimiter适用于单体应用,且RateLimiter不保证公平性访问。

使用上述方式使用RateLimiter的方式不够优雅,自定义注解+AOP的方式实现(适用于单体应用),详细见下面代码:

自定义注解:

访问量一大服务器就挂?那是你不懂限流

 

自定义切面类

import com.google.common.util.concurrent.RateLimiter;
import com.test.cn.springbootdemo.util.ResultUtil;
import net.sf.json.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
 
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@Component
@Scope
@Aspect
public class RateLimitAop {
 
 @Autowired
 private HttpServletResponse response;
 
 private RateLimiter rateLimiter = RateLimiter.create(5.0); //比如说,我这里设置"并发数"为5
 
 @Pointcut("@annotation(com.test.cn.springbootdemo.aspect.RateLimitAspect)")
 public void serviceLimit() {
 
 }
 
 @Around("serviceLimit()")
 public Object around(ProceedingJoinPoint joinPoint) {
 Boolean flag = rateLimiter.tryAcquire();
 Object obj = null;
 try {
 if (flag) {
 obj = joinPoint.proceed();
 }else{
 String result = JSONObject.fromObject(ResultUtil.success1(100, "failure")).toString();
 output(response, result);
 }
 } catch (Throwable e) {
 e.printStackTrace();
 }
 System.out.println("flag=" + flag + ",obj=" + obj);
 return obj;
 }
 
 public void output(HttpServletResponse response, String msg) throws IOException {
 response.setContentType("Application/json;charset=UTF-8");
 ServletOutputStream outputStream = null;
 try {
 outputStream = response.getOutputStream();
 outputStream.write(msg.getBytes("UTF-8"));
 } catch (IOException e) {
 e.printStackTrace();
 } finally {
 outputStream.flush();
 outputStream.close();
 }
 }
}

测试controller

import com.test.cn.springbootdemo.aspect.RateLimitAspect;
import com.test.cn.springbootdemo.util.ResultUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
 
@Controller
public class TestController {
 
 @ResponseBody
 @RateLimitAspect
 @RequestMapping("/test")
 public String test(){
 return ResultUtil.success1(1001, "success").toString();
 }

压测结果:

访问量一大服务器就挂?那是你不懂限流

 

三、控制单位时间窗口内请求数

某些场景下,我们想限制某个接口或服务 每秒/每分钟/每天 的请求次数或调用次数。例如限制服务每秒的调用次数为50,实现如下:

访问量一大服务器就挂?那是你不懂限流

 

到此应用级限流的一些方法就介绍完了。假设将应用部署到多台机器,应用级限流方式只是单应用内的请求限流,不能进行全局限流。因此我们需要分布式限流和接入层限流来解决这个问题。

分布式限流

自定义注解+拦截器+redis实现限流 (单体和分布式均适用,全局限流)

自定义注解:

访问量一大服务器就挂?那是你不懂限流

 

拦截器:

public class AccessLimitInterceptor implements HandlerInterceptor {
 
 @Autowired
 private RedisTemplate<String, Integer> redisTemplate; //使用RedisTemplate操作redis
 
 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 if (handler instanceof HandlerMethod) {
 HandlerMethod handlerMethod = (HandlerMethod) handler;
 Method method = handlerMethod.getMethod();
 if (!method.isAnnotationPresent(AccessLimit.class)) {
 return true;
 }
 AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
 if (accessLimit == null) {
 return true;
 }
 int limit = accessLimit.limit();
 int sec = accessLimit.sec();
 String key = IPUtil.getIpAddr(request) + request.getRequestURI();
 Integer maxLimit = redisTemplate.opsForValue().get(key);
 if (maxLimit == null) {
 redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS); //set时一定要加过期时间
 } else if (maxLimit < limit) {
 redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);
 } else {
 output(response, "请求太频繁!");
 return false;
 }
 }
 return true;
 }
 
 public void output(HttpServletResponse response, String msg) throws IOException {
 response.setContentType("application/json;charset=UTF-8");
 ServletOutputStream outputStream = null;
 try {
 outputStream = response.getOutputStream();
 outputStream.write(msg.getBytes("UTF-8"));
 } catch (IOException e) {
 e.printStackTrace();
 } finally {
 outputStream.flush();
 outputStream.close();
 }
 }
 
 @Override
 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
 
 }
 
 @Override
 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
 
 }
}
controller:
@Controller
@RequestMapping("/activity")
public class AopController {
 @ResponseBody
 @RequestMapping("/seckill")
 @AccessLimit(limit = 4,sec = 10) //加上自定义注解即可
 public String test (HttpServletRequest request,@RequestParam(value = "username",required = false) String userName){
 //TODO somethings……
 return "hello world !";
 }
}

配置文件:

/*springmvc的配置文件中加入自定义拦截器*/
<mvc:interceptors>
 <mvc:interceptor>
 <mvc:mapping path="/**"/>
 <bean class="com.pptv.activityapi.controller.pointsmall.AccessLimitInterceptor"/>
 </mvc:interceptor>
</mvc:interceptors>

访问效果如下,10s内访问接口超过4次以上就过滤请求,原理和计数器算法类似:

访问量一大服务器就挂?那是你不懂限流

 

接入层限流

主要介绍nginx 限流,采用漏桶算法。

限制原理:可一句话概括为:“根据客户端特征,限制其访问频率”,客户端特征主要指IP、UserAgent等。使用IP比UserAgent更可靠,因为IP无法造假,UserAgent可随意伪造。

用limitreq模块来限制基于IP请求的访问频率:http://nginx.org/en/docs/http/ngxhttplimitreqmodule.html也可以用tengine中的增强版:http://tengine.taobao.org/documentcn/httplimitreq_cn.html

1.并发数和连接数控制的配置:

访问量一大服务器就挂?那是你不懂限流

 

2. ngxhttplimitconnmodule 可以用来限制单个IP的连接数:

ngxhttplimitconnmodule模块可以按照定义的键限定每个键值的连接数。可以设定单一 IP 来源的连接数。

并不是所有的连接都会被模块计数;只有那些正在被处理的请求(这些请求的头信息已被完全读入)所在的连接才会被计数。

访问量一大服务器就挂?那是你不懂限流

 



Tags:限流   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除,谢谢。
▌相关推荐
What is 限流?限流顾名思义,限制流量或者说叫流量管制。很形象的比喻如老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。W...【详细内容】
2021-06-28  Tags: 限流  点击:(10)  评论:(0)  加入收藏
引言 在web开发中功能是基石,除了功能以外运维和防护就是重头戏了。因为在网站运行期间可能会因为突然的访问量导致业务异常、也有可能遭受别人恶意攻击 所以我们的接口需要...【详细内容】
2021-05-31  Tags: 限流  点击:(34)  评论:(0)  加入收藏
Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面。在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数;或者按照某种规...【详细内容】
2021-04-30  Tags: 限流  点击:(103)  评论:(0)  加入收藏
目前,Spring Cloud Gateway是仅次于Spring Cloud Netflix的第二个最受欢迎的Spring Cloud项目(就GitHub上的星级而言)。它是作为Spring Cloud系列中Zuul代理的继任者而创建的。...【详细内容】
2021-04-21  Tags: 限流  点击:(165)  评论:(0)  加入收藏
前言在一个高并发系统中对流量的把控是非常重要的,当巨大的流量直接请求到我们的服务器上没多久就可能造成接口不可用,不处理的话甚至会造成整个应用不可用。那么何为限流呢?顾...【详细内容】
2020-12-15  Tags: 限流  点击:(58)  评论:(0)  加入收藏
今天准备谈下基于API网关来实现微服务治理管控中的服务限流,熔断和降级方面的内容。在前面谈微服务架构的时候也谈到过类似通过Hystrix,Sentinel来是服务限流熔断。包括也不...【详细内容】
2020-11-19  Tags: 限流  点击:(59)  评论:(0)  加入收藏
作为热点频出的电商系统,经常遇到高并发,热点秒杀的场景。我们在开发设计高并发海量业务请求的系统时,通常利用三板斧:缓存、降级和限流来保障系统稳定性。...【详细内容】
2020-09-27  Tags: 限流  点击:(42)  评论:(0)  加入收藏
限流又称为流量控制(流控),通常是指限制到达系统的并发请求数。我们生活中也会经常遇到限流的场景,比如:某景区限制每日进入景区的游客数量为 8 万人;沙河地铁站早高峰通过站外排队逐一放行的方式限制同一时间进入车站的旅...【详细内容】
2020-09-17  Tags: 限流  点击:(36)  评论:(0)  加入收藏
有的主播勤勤恳恳开播,战战兢兢做数据,生怕一个手抖,被官方限流。怎么直播才能不被限流?如何查看自己有没有被限流?本期为主播大大们一一解析!1.如何检测账号是否被限流?打开抖音ap...【详细内容】
2020-09-01  Tags: 限流  点击:(72)  评论:(0)  加入收藏
一个基于Redis实现的接口限流方案,先说要实现的功能 可以限制指定的接口,在一定时间内,只能被请求N次,超过次数就返回异常信息 可以通过配置文件,或者管理后台,动态的修改限流配置...【详细内容】
2020-08-10  Tags: 限流  点击:(34)  评论:(0)  加入收藏
▌简易百科推荐
最近发现点好玩的工具,迫不及待地想跟大家分享一下。大家平时都怎么查Linux日志呢?像我平时会用tail、head、cat、sed、more、less这些经典系统命令,或者awk这类三方数据过滤工...【详细内容】
2021-07-16  程序猿阿嘴  公众号  Tags:Linux日志   点击:(2)  评论:(0)  加入收藏
nginx配置文件详解#启动子进程程序默认用户#user nobody;#一个主进程和多个工作进程。工作进程是单进程的,且不需要特殊授权即可运行;这里定义的是工作进程数量worker_proce...【详细内容】
2021-07-16  请叫我老菜鸟    Tags:nginx   点击:(1)  评论:(0)  加入收藏
上云在今天几乎已经成了很多企业的标配,云服务器因为不需要运维人员去机房维护,也不需要企业去建设机房等,大大降低了企业的IT资源门槛,可以帮助用户快速实现业务部署。云服务器...【详细内容】
2021-07-14  云服务批发部    Tags:云服务器   点击:(4)  评论:(0)  加入收藏
做运维的都知道,服务器出点问题,就够我们受的,轻则被骂,重则丢饭碗,所以,每逢节假日,我们恨不得在机房点上三柱香、贴上几张符,给服务器和网络设备跪拜一番,求它们别出问题,也让我们过...【详细内容】
2021-07-13  IT狂人日志    Tags:服务器   点击:(5)  评论:(0)  加入收藏
Nginx是现在最火的服务器web和反向代理服务器,反向代理是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给inte...【详细内容】
2021-07-09  数通畅联    Tags:Nginx配置   点击:(4)  评论:(0)  加入收藏
1. 实验说明:前面的章节我们已经简单学习了windows server2019上如何搭建web服务器的,有的同学可能在上网的过程中会发现有些域名是使用的http访问而有的域名则是使用https加...【详细内容】
2021-07-07  Kali与编程    Tags:https   点击:(5)  评论:(0)  加入收藏
作者:vbirdbestblog.csdn.net/vbirdbest/article/details/80913319一、HTTP服务器Nginx本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用Nginx来做服务器,如果一...【详细内容】
2021-07-06  vbirdbest  CSDN  Tags:Nginx   点击:(16)  评论:(0)  加入收藏
1.实验说明:Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以处理浏览器等Web客户端的请求并返回相应响应,也可以放置网站文件,让全世界浏览;可以放置数据...【详细内容】
2021-07-06  Kali与编程    Tags:web服务器   点击:(16)  评论:(0)  加入收藏
一、前言server.xml 配置,是 Tomcat启动配置,从配置结构可以看出 Tomcat 的整体架构。如果能够了解其常用配置项,对 Tomcat有一个高屋建瓴的把握,然后再庖丁解牛,一步步深入源码...【详细内容】
2021-07-04  徐同学呀  CSDN  Tags:Tomcat   点击:(10)  评论:(0)  加入收藏
我们常常会听到这样一句话:“为了让研发只关心业务开发,我们做了某某某!”做了啥呢,做了让你不用关心,系统搭建、技术框架、核心组件、通用模块以及上线应用时也只是点点点就可以...【详细内容】
2021-07-04  IT技术管理那些事儿    Tags:服务器   点击:(9)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条