单机,分布式,微服务下应用限流学习记录

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

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

什么是限流

通过对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机,限流的根本目的就是为了保障服务的高可用

常见的应用限流有两种算法

  • 漏桶算法
  • 令牌桶算法

漏桶算法比较简单,就是将流量放入桶中,漏桶同时也按照一定的速率流出,如果流量过快的话就会溢出(漏桶并不会提高流出速率)。溢出的流量则直接丢弃。

如图所示


漏桶算法虽说简单,但却不能应对实际场景,比如突然暴增的流量。

令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,而如果请求需要被处理时则取走一个或多个令牌。当桶中没有令牌则将当前请求丢弃或阻塞,则拒绝服务。

操作系统的信号量是个很重要的概念,Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

信号量的本质是控制某个资源可被同时访问的个数,在一定程度上可以控制某资源的访问频率,但不能精确控制。

Semaphore又称信号量,是操作系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量,

一般来说,在网关系统中,还有一个参数叫并发控制,就是某一个资源可以被同时访问的个数。这种情况下,我们可以使用Semaphore来控制。

Semaphore不同于互斥锁。互斥锁是某个资源只能支持同时一个访问,而Semaphore可以支持多个访问,但是加上了总数的控制。



相比之下令牌桶可以应对一定的突发流量.

Guava中开源出来一个令牌桶算法的工具类RateLimiter,可以轻松实现限流的工作,RateLimiter 对简单的令牌桶算法做了一些工程上的优化,具体的实现是 SmoothBursty。需要注意的是,RateLimiter 的另一个实现 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也许是出于简单起见,RateLimiter 中的时间窗口能且仅能为 1s,如果想搞其他时间单位的限流,只能另外造轮子。

RateLimiter 有一个有趣的特性是「前人挖坑后人跳」,也就是说 RateLimiter 允许某次请求拿走超出剩余令牌数的令牌,但是下一次请求将为此付出代价,一直等到令牌亏空补上,并且桶中有足够本次请求使用的令牌为止。这里面就涉及到一个权衡,是让前一次请求干等到令牌够用才走掉呢,还是让它先走掉后面的请求等一等呢?Guava 的设计者选择的是后者,先把眼前的活干了,后面的事后面再说。



  1. Guava RateLimiter只能应用于单进程,多进程间协同控制便无能为力
  2. Guava RateLimiter能够处理突发请求(预消费),这里rest接口调用频率限制是固定的,不需要更不能使用预消费能力,否则将会导致接口调用失败

 微服务限流

spring-cloud-zuul-ratelimit是和zuul整合提供分布式限流策略的扩展,只需在yaml中配置几行配置,就可使应用支持限流  

添加依赖

<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
   <version>1.7.1.RELEASE</version>
</dependency
支持的限流粒度

  • 服务粒度 (默认配置,当前服务模块的限流控制)
  • 用户粒度 (详细说明,见文末总结)
  • ORIGIN粒度 (用户请求的origin作为粒度控制)
  • 接口粒度 (请求接口的地址作为粒度控制)
  • 以上粒度自由组合,又可以支持多种情况。
  • 如果还不够,自定义RateLimitKeyGenerator实现
zuul:
  ratelimit:
    key-prefix: your-prefix
    enabled: true
    repository: REDIS
    behind-proxy: true
    default-policy: #deprecated - please use "default-policy-list"
      limit: 10 #optional - request number limit per refresh interval window
      quota: 1000 #optional - request time limit per refresh interval window (in seconds)
      refresh-interval: 60 #default value (in seconds)
      type: #optional
        - user
        - origin
        - url
    default-policy-list: #optional - will apply unless specific policy exists
      - limit: 10 #optional - request number limit per refresh interval window
        quota: 1000 #optional - request time limit per refresh interval window (in seconds)
        refresh-interval: 60 #default value (in seconds)
        type: #optional
          - user
          - origin
          - url
    policies: #deprecated - please use "policy-list"
      myServiceId:
        limit: 10 #optional - request number limit per refresh interval window
        quota: 1000 #optional - request time limit per refresh interval window (in seconds)
        refresh-interval: 60 #default value (in seconds)
        type: #optional
          - user
          - origin
          - url
    policy-list:
      myServiceId:
        - limit: 10 #optional - request number limit per refresh interval window
          quota: 1000 #optional - request time limit per refresh interval window (in seconds)
          refresh-interval: 60 #default value (in seconds)
          type: #optional
            - user
            - origin
            - url
        - type: #optional value for each type
            - user=anonymous
            - origin=somemachine.com
            - url=/api #url prefix

#限流
#对应用来标识请求的key的前缀
zuul.ratelimit.key-prefix=your-prefix
zuul.ratelimit.enabled=true
#对应存储类型(用来存储统计信息)
zuul.ratelimit.repository=redis
#开启代理
zuul.ratelimit.behind-proxy=true
#deprecated - please use "default-policy-list"
#可选 - 每个刷新时间窗口对应的请求数量限制
zuul.ratelimit.policy-list.snjx-api.limit=10
# 刷新时间窗口的时间,默认值 (秒)
zuul.ratelimit.policy-list.snjx-api.refreshInterval=60
#可选-  每个刷新时间窗口对应的请求时间限制(秒)
zuul.ratelimit.policy-list.snjx-api.quota=1000
#可选 限流类型
zuul.ratelimit.policy-list.snjx-api.type[1]=user
zuul.ratelimit.policy-list.snjx-api.type[2]=origin
zuul.ratelimit.policy-list.snjx-api.type[3]=url
#user是通过授权用户进行区分,也包括匿名用户
zuul.ratelimit.policy-list.snjx-api.type[user]=anonymous
#origin是通过客户端IP地址区分
zuul.ratelimit.policy-list.snjx-api.type[origin]=somemachine.com
#url类型的限流就是通过请求路径区分
zuul.ratelimit.policy-list.snjx-api.type[url]=/snjx-api/**
#url prefix

以上配置意思是:60秒内允许10个访问,并且要求总请求时间小于1000秒


可以使用Spring Boot Actuator 提供的服务状态,动态设置限流开关

用户限流的实现:如果你的项目整合 Shiro 或者 Spring Security 安全框架,那么会自动维护request域UserPrincipal,如果是自己的框架,请登录成功后维护request域UserPrincipal,才能使用用户粒度的限流。未登录默认是:anonymous

猜你喜欢

转载自blog.csdn.net/sinat_24798023/article/details/80363273