多平台统一管理软件接口,如何实现多平台统一管理软件接口
536
2023-01-11
本文目录一览:
@Retention :注解的作用域,包含RetentionPolicy参数
@Inherited :允许子类继承父类的注解
@Documented :此注解会包含在javadoc中
RedisTemplate对象的配置
切面类
获取web对象工具
测试接口
正常访问:
限流:
限流在日常生活中限流很常见,例如去有些景区玩,每天售卖的门票数是有限的,例如 2000 张,即每天最多只有 2000 个人能进去游玩。那在我们工程上限流是什么呢?限制的是 「流」,在不同场景下「流」的定义不同,可以是 每秒请求数、每秒事务处理数、网络流量 等等。通常意义我们说的限流指代的是限制到达系统的并发请求数,使得系统能够正常的处理部分用户的请求,来保证系统的稳定性。
日常的业务上有类似秒杀活动、双十一大促或者突发新闻等场景,用户的流量突增,后端服务的处理能力是有限的,如果不能处理好突发流量,后端服务很容易就被打垮。另外像爬虫之类的不正常流量,我们对外暴露的服务都要以最大恶意为前提去防备调用者。我们不清楚调用者会如何调用我们的服务,假设某个调用者开几十个线程一天二十四小时疯狂调用你的服务,如果不做啥处理咱服务基本也玩完了,更胜者还有ddos攻击。
对于很多第三方开放平台来说,不仅仅要防备不正常流量,还要保证资源的公平利用,一些接口资源不可能一直都被一个客户端占着,也需要保证其他客户端能正常调用。
计数器限流也就是最简单的限流算法就是计数限流了。例如系统能同时处理 100 个请求,保存一个计数器,处理了一个请求,计数器就加一,一个请求处理完毕之后计数器减一。每次请求来的时候看看计数器的值,如果超过阈值就拒绝。计数器的值要是存内存中就算单机限流算法,如果放在第三方存储里(例如Redis中)集群机器访问就算分布式限流算法。
一般的限流都是为了限制在指定时间间隔内的访问量,因此还有个算法叫固定窗口。
它相比于计数限流主要是多了个时间窗口的概念,计数器每过一个时间窗口就重置。规则如下:
这种方式也会面临一些问题,例如固定窗口临界问题:假设系统每秒允许 100 个请求,假设第一个时间窗口是 0-1s,在第 0.55s 处一下次涌入 100 个请求,过了 1 秒的时间窗口后计数清零,此时在 1.05 s 的时候又一下次涌入100个请求。虽然窗口内的计数没超过阈值,但是全局来看在 0.55s-1.05s 这 0.1 秒内涌入了 200 个请求,这其实对于阈值是 100/s 的系统来说是无法接受的。
为了解决这个问题,业界又提出另外一种限流算法,即滑动窗口限流。
滑动窗口限流解决固定窗口临界值的问题,可以保证在任意时间窗口内都不会超过阈值。相对于固定窗口,滑动窗口除了需要引入计数器之外还需要记录时间窗口内每个请求到达的时间点,因此对内存的占用会比较多。
规则如下,假设时间窗口为 1 秒:
但是滑动窗口和固定窗口都无法解决短时间之内集中流量的冲击问题。 我们所想的限流场景是: 每秒限制 100 个请求。希望请求每 10ms 来一个,这样我们的流量处理就很平滑,但是真实场景很难控制请求的频率,因为可能就算我们设置了1s内只能有100个请求,也可能存在 5ms 内就打满了阈值的情况。当然对于这种情况还是有变型处理的,例如设置多条限流规则。不仅限制每秒 100 个请求,再设置每 10ms 不超过 2 个,不过带来的就是比较差的用户体验。
而漏桶算法,可以解决时间窗口类的痛点,使得流量更加平滑。
如下图所示,水滴持续滴入漏桶中,底部定速流出。如果水滴滴入的速率大于流出的速率,当存水超过桶的大小的时候就会溢出。
规则如下:
水滴对应的就是请求。
与线程池实现的方式方式如出一辙。
面对突发请求,服务的处理速度和平时是一样的,这并非我们实际想要的。我们希望的是在突发流量时,在保证系统平稳的同时,也要尽可能提升用户体验,也就是能更快地处理并响应请求,而不是和正常流量一样循规蹈矩地处理。
而令牌桶在应对突击流量的时候,可以更加的“激进”。
令牌桶其实和漏桶的原理类似,只不过漏桶是定速地流出,而令牌桶是定速地往桶里塞入令牌,然后请求只有拿到了令牌才能通过,之后再被服务器处理。
当然令牌桶的大小也是有限制的,假设桶里的令牌满了之后,定速生成的令牌会丢弃。
规则:
令牌桶的原理与JUC的Semaphore 信号量很相似,信号量可控制某个资源被同时访问的个数,其实和拿令牌思想一样,不同的是一个是拿信号量,一个是拿令牌。信号量用完了返还,而令牌用了不归还,因为令牌会定时再填充。
对比漏桶算法可以看出 令牌桶更适合应对突发流量 ,假如桶内有 100 个令牌,那么这100个令牌可以马上被取走,而不像漏桶那样匀速的消费。不过上面批量获取令牌也会致使一些新的问题出现,比如导致一定范围内的限流误差,举个例子你取了 10 个此时不用,等下一秒再用,那同一时刻集群机器总处理量可能会超过阈值,所以现实中使用时,可能不会去考虑redis频繁读取问题,转而直接采用一次获取一个令牌的方式,具体采用哪种策略还是要根据真实场景而定。
1、计数器 VS 固定窗口 VS 滑动窗口
2、漏桶算法 VS 令牌桶算法
总的来说
单机限流和分布式限流本质上的区别在于 “阈值” 存放的位置,单机限流就是“阀值”存放在单机部署的服务/内存中,但我们的服务往往是集群部署的,因此需要多台机器协同提供限流功能。像上述的计数器或者时间窗口的算法,可以将计数器存放至 Redis 等分布式 K-V 存储中。又如滑动窗口的每个请求的时间记录可以利用 Redis 的 zset 存储,利用 ZREMRANGEBYSCORE 删除时间窗口之外的数据,再用 ZCARD 计数,
可以看到,每个限流都有个阈值,这个阈值如何定是个难点。定大了服务器可能顶不住,定小了就“误杀”了,没有资源利用最大化,对用户体验不好。一般的做法是限流上线之后先预估个大概的阈值,然后不执行真正的限流操作,而是采取日志记录方式,对日志进行分析查看限流的效果,然后调整阈值,推算出集群总的处理能力,和每台机子的处理能力(方便扩缩容)。然后将线上的流量进行重放,测试真正的限流效果,最终阈值确定,然后上线。
其实真实的业务场景很复杂,需要限流的条件和资源很多,每个资源限流要求还不一样。
一般而言,我们不需要自己实现限流算法来达到限流的目的,不管是接入层限流还是细粒度的接口限流,都有现成的轮子使用,其实现也是用了上述我们所说的限流算法。
具体的使用还是很简单的,有兴趣的同学可以自行搜索,对内部实现感兴趣的同学可以下个源码看看,学习下生产级别的限流是如何实现的。
限流具体应用到工程还是有很多点需要考虑的,并且限流只是保证系统稳定性中的一个环节,还需要配合降级、熔断等相关内容。
Nginx现在已经是最火的负载均衡之一,在流量陡增的互联网面前,接口限流也是很有必要的,尤其是针对高并发的场景。Nginx的限流主要是两种方式:限制访问频率和限制并发连接数。
Nginx中我们使用 ngx_http_limit_req_module 模块来限制请求的访问频率,基于漏桶算法原理实现。接下来我们使用 nginx limit_req_zone 和 limit_req 两个指令,限制单个IP的请求处理速率。
语法: limit_req_zone key zone rate
按上面的配置在流量突然增大时,超出的请求将被拒绝,无法处理突发流量,那么在处理突发流量的时候,该怎么处理呢?Nginx提供了 burst 参数来解决突发流量的问题,并结合 nodelay 参数一起使用。burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数。
burst=20 nodelay 表示这20个请求立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置这只能按 100ms一个来释放。这就达到了速率稳定,但突然流量也能正常处理的效果。
Nginx 的 ngx_http_limit_conn_module 模块提供了对资源连接数进行限制的功能,使用 limit_conn_zone 和 limit_conn 两个指令就可以了。
好了,以上几种限流方式,你都清楚了吗?
在前面的一篇中分析了Dubbo是如何降级的,除了降级,有时限流也是一种很有效的解决高并发的性能问题,那在本篇中开始分析Dubbo是如何限流的。我们知道限流主要是通过控制连接数来实现的,防止某一片段内请求处理过大,导致重要服务的失效。
服务端连接控制
限制当前提供者在使用dubbo协议最多接受10个消费者链接
或者
并发控制
限制 com.foo.BarService 的每个方法,服务端并发执行(或占用线程池线程数)不能超过10个:
限制 com.foo.BarService 的 sayHello 方法,服务器并发执行(或占用线程池线程数)不能超过10个。
actives限流
该限流方式与前两种不同,其可以设置在提供端,也可以设置在消费者端。可以设置为接口级别,也可以设置为方法级别。
根据消费者与提供者建立的连接类型,其意义也不同。
长连接 : 表示当前的长连接最多可以处理的请求个数。与长连接的数量没有问题。
短连接 :表示当前服务可以同时处理的短连接数量。
类级别
方法级别
connections限流
可以设置在提供端,也可以设置在消费者端。限定连接的个数。对于短连接,和actives相同。但对于长连接,表示长连接的个数。
一般情况下,会使connections与actives联用,让connections限制长连接的个数,让actives限制长连接中可以处理的请求个数。
限制客户端服务使用连接不能超过10个
或
如果 <dubbo:service 和 <dubbo:reference 都配置了connections, <dubbo:reference 优先。
延迟连接
延迟连接仅可以设置在消费者端,并且不能设置为方法级别。仅作用于Dubbo服务暴露协议。将长连接的建立推迟到消费者真正调用提供者时。 可以减少长连接的数量。
我们已经讲解了如何设置控制链接数的,那么它们底层是如何实现的呢?
实际上上面的逻辑都是一个个Filter,所有的Filter会连接成一个过滤器链,每次请求都会经过整个链路中的每一个Filter。那它是在什么时候构造成一个过滤器链的呢。
在服务暴露的时候会调用 buildInvokerChain , 将真正执行的 invoker 放到过滤链的尾部,再执行 protocol.expert(buildInvokerChain(invoker, ...)) 方法来进行服务暴露。
在服务引用的时候会调用 protocol.refer() 方法先生成 Invoker ,再调用 buildInvokerChain(protocol.refer(type, url), ...) 来生成消费类型的调用链。
ExecuteLimitFilter
它用于限制每个服务中每个方法的最大并发数,有接口级别和方法级别的配置方式。
其基本原理:在框架中使用一个ConcurrentMap缓存了并发数的计数器,为每个请求URL生成一个IdentityString,并以此为key;再将每个IdentityString生成一个RpcStatus对象,将此作为value。RpcStatus对象用于记录对应的并发数。在调用开始之前,会通过URL获得RpcStatus对象,把对象中的并发数计数器原子+1,在finally中再将原子减1。只要在计数器+1的时候,发现当前计数器比设置的并发数大时,就会抛出异常。
TpsLimitFilter
TpsLimitFilter的限流是基于令牌的,即一段时间内只分配N个令牌,每次请求都会消耗一个令牌,耗完为止,后面再来的请求都会被拒绝。
具体的逻辑是在 DefaultTPSLimiter#isAllowable ,会用这个方法判断是否触发限流。
在DefaultTPSLimiter内部用一个ConcurrentHashMap缓存每个接口的令牌数,key是interface+group+version,value是一个StatItem对象,它包装了令牌刷新时间间隔、每次发放的令牌数等。首先判断当前时间减去上次发放令牌的时间是否超过了时间间隔,超过了就重新发放令牌,之前剩余的令牌会被直接覆盖掉。然后,通过CAS的方式减去1令牌,减掉后小于0就会触发限流。
ActiveLimitFilter
和服务提供者的 ExecuteLimitFilter 相似,它是消费者端的过滤器,限制的是客户端的并发量。
但是它与 ExecuteLimitFilter 有所不同,它不会直接抛出异常。而是当到达阈值的时候,会先加锁抢占当前接口的RpcStatus对象,然后通过wait方法进行等待,等待是有时间的,因为请求是有 timeout 属性的。然后如果某个Invoker在调用结束后,并发把计数器减-1并触发一个notify,此时会有一个在wait状态的线程被唤醒并继续执行,判断现在是否超时,如果超时则抛出异常。如果当前并发数仍然超出阈值,则继续执行wait方法;如果没有超出阈值在,则跳出循环,CAS+1,并调用invoke方法,调用结束后CAS-1,最后通过notify唤醒另外一个线程。
参考文章:
Dubbo之限流TpsLimitFilter源码分析
Dubbo服务限流
Dubbo源码分析----过滤器之ActiveLimitFilter
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~