分布式服务限流&微服务限流

文章内容输出来源:拉勾教育Java高薪训练营

1.什么是限流

限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。

2.常用的限流方法

(1)计数器法
(2)滑动窗口
(3)Leaky Bucket 漏桶
(4)Token Bucket 令牌桶


(1)计数器法
实现方式:控制单位时间内的请求数量

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    /**
     * 最大访问数量
     */
    private final int limit = 10;
    /**
     * 访问时间差
     */
    private final long timeout = 1000;
    /**
     * 请求时间
     */
    private long time;
    /**
     * 当前计数器
     */
    private AtomicInteger reqCount = new AtomicInteger(0);

    public boolean limit() {
        long now = System.currentTimeMillis();
        if (now < time + timeout) {
            // 单位时间内
            reqCount.addAndGet(1);
            return reqCount.get() <= limit;
        } else {
            // 超出单位时间
            time = now;
            reqCount = new AtomicInteger(0);
            return true;
        }
    }
}

缺点:控制粒度太粗,或控制不精确,假设在 00:01 时发生一个请求,在 00:01-00:58 之间不在发送请求,在 00:59 时发送剩下的所有请求 n-1 (n为限流请求数量),在下一分钟的 00:01 发送n个请求,这样在2秒钟内请求到达了 2n - 1 个。设每分钟请求数量为60个,每秒可以处理1个请求,用户在 00:59 发送 60 个请求,在 01:00 发送 60 个请求,此时2秒钟有120个请求(每秒60个请求),远远大于了每秒钟处理数量的阈值。

(2)滑动窗口

实现方式:滑动窗口是对计数器方式的改进, 增加一个时间粒度的度量单位,即把一分钟分成若干等分(6份,每份10秒), 在每一份上设置独立计数器,在 00:00-00:09 之间发生请求计数器累加1。当等分数量越大限流统计就越详细。

package com.example.demo1.service;

import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.IntStream;

public class TimeWindow {
    private ConcurrentLinkedQueue<Long> queue = new ConcurrentLinkedQueue<Long>();

    /**
     * 间隔秒数
     */
    private int seconds;

    /**
     * 最大限流
     */
    private int max;

    public TimeWindow(int max, int seconds) {
        this.seconds = seconds;
        this.max = max;

        /**
         * 永续线程执行清理queue 任务
         */
        new Thread(() -> {
            while (true) {
                try {
                    // 等待 间隔秒数-1 执行清理操作
                    Thread.sleep((seconds - 1) * 1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                clean();
            }
        }).start();

    }

    public static void main(String[] args) throws Exception {

        final TimeWindow timeWindow = new TimeWindow(10, 1);

        // 测试3个线程
        IntStream.range(0, 3).forEach((i) -> {
            new Thread(() -> {

                while (true) {

                    try {
                        Thread.sleep(new Random().nextInt(20) * 100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    timeWindow.take();
                }

            }).start();

        });

    }

    /**
     * 获取令牌,并且添加时间
     */
    public void take() {

        long start = System.currentTimeMillis();
        try {

            int size = sizeOfValid();
            if (size > max) {
                System.err.println("超限");

            }
            synchronized (queue) {
                if (sizeOfValid() > max) {
                    System.err.println("超限");
                    System.err.println("queue中有 " + queue.size() + " 最大数量 " + max);
                }
                this.queue.offer(System.currentTimeMillis());
            }
            System.out.println("queue中有 " + queue.size() + " 最大数量 " + max);

        }

    }

    public int sizeOfValid() {
        Iterator<Long> it = queue.iterator();
        Long ms = System.currentTimeMillis() - seconds * 1000;
        int count = 0;
        while (it.hasNext()) {
            long t = it.next();
            if (t > ms) {
                // 在当前的统计时间范围内
                count++;
            }
        }

        return count;
    }

    /**
     * 清理过期的时间
     */
    public void clean() {
        Long c = System.currentTimeMillis() - seconds * 1000;

        Long tl = null;
        while ((tl = queue.peek()) != null && tl < c) {
            System.out.println("清理数据");
            queue.poll();
        }
    }

}

(3)Leaky Bucket 漏桶

实现方式:规定固定容量的桶,有水进入,有水流出。对于流进的水我们无法估计进来的数量、速度,对于流出的水我们可以控制速度。

public class LeakBucket {
    /**
     * 时间
     */
    private long time;
    /**
     * 总量
     */
    private Double total;
    /**
     * 水流出去的速度
     */
    private Double rate;
    /**
     * 当前总量
     */
    private Double nowSize;

    public boolean limit() {
        long now = System.currentTimeMillis();
        nowSize = Math.max(0, (nowSize - (now - time) * rate));
        time = now;
        if ((nowSize + 1) < total) {
            nowSize++;
            return true;
        } else {
            return false;
        }

    }
}

(4)Token Bucket 令牌桶

实现方式:规定固定容量的桶,token 以固定速度往桶内填充,当桶满时 token 不会被继续放入,每过来一个请求把 token 从桶中移除,如果桶中没有 token 不能请求。

public class TokenBucket {
    /**
     * 时间
     */
    private long time;
    /**
     * 总量
     */
    private Double total;
    /**
     * token 放入速度
     */
    private Double rate;
    /**
     * 当前总量
     */
    private Double nowSize;

    public boolean limit() {
        long now = System.currentTimeMillis();
        nowSize = Math.min(total, nowSize + (now - time) * rate);
        time = now;
        if (nowSize < 1) {
            // 桶里没有token
            return false;
        } else {
            // 存在token
            nowSize -= 1;
            return true;
        }
    }
}

3.分布式服务限流(dubbo限流)

Dubbo服务限流方式:
为了防止某个消费者的QPS或是所有消费者的QPS总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。
Dubbo有多种限流方式,可以使用以下参数进行多维度的限流:
(1)accepts:服务端最大可接受连接数,可以理解为可以接受的最大消费者数;
(2)connections:每个Reference开启的连接数;
(3)actives:消费端控制每个接口的最大并发数;
(4)executes:服务端控制每个接口的最大并发数;

Dubbo服务限流方式具体使用:
(1)accepts的配置:
accepts配置数表示服务端配置最大可接受连接数,这是项目级别设置,它仅可设置在服务端。
比如一个Provider设置了accepts=2,该Provider3个消费者分别为C1、C2、C3。假如这3个消费者的启动顺序为C1、C2、C3,则C3会无法启动,因为服务已经达到了最大连接数限制;
基于XML配置如下:

<!--限制当前提供者在使用dubbo协议最多接受10个消费者链接-->
<dubbo:protocol name="dubbo" port="20880" accepts="10"/>

(2)connections的配置:
connections配置数表示每个Reference开启的长连接数,默认是0,表示所有的Reference共享同一条连接;如果大于0,则单独为此Reference设置connections条长连接。
比如同一个项目有3个reference:
@Reference(connections=3) HelloService helloService;
@Reference TestService testService;
@Reference FooService fooService;
则该项目会生成4条连接,其中helloService有3条,testService与fooService共用一条

connections可以设置在提供者端,也可以设置在消费者端。限定连接的个数。对于短连接,该属性效果与actives相同。但对于长连接,其限制的是长连接的个数。 一般情况下,会使connectons与actives联用,让connections限制长连接个数,让actives限制一个长连接中可以处理的请求个数。联用前提:使用默认的Dubbo服务暴露协议

服务提供端限流配置:
接口级别:

<!--限制当前接口中每个方法的链接数不能超过10-->
<dubbo:service interface="com.tdd.service.UserService" ref="userService" connections="10"/>

方法级别:

<!--限制当前接口中sayHello方法的链接数不能超过10-->
    <dubbo:service version="1.0" interface="com.tdd.service.UserService" ref="userService">
        <dubbo:method name="sayHello" connections="10"/>
    </dubbo:service>

服务消费端限流配置:
接口级别:

<!--设置当前消费者对指定接口的每一个方法的链接数不能超过10-->
<dubbo:reference interface="com.tdd.service.UserService" id="userService" connections="10"/>

方法级别:

<!--设置当前消费者对指定接口的sayHello方法的链接数不能超过10-->
<dubbo:reference interface="com.tdd.service.UserService" id="userService" connections="10">
        <dubbo:method name="sayHello" connections="10"/>
</dubbo:reference>

(3)actives的配置:
actives配置数表示服务消费端每个接口的最大并发数,默认是0,如果是0则没有限制。该限流方式可以设置在服务供者端,也可以设置在服务消费端。可以设置为接口级别,也可以设置为方法级别。
服务提供端限流配置:
接口级别:

<!--设置当前服务提供端对指定接口的每一个方法的并发连接数不能超过10-->
<dubbo:service interface="com.tdd.service.UserService" ref="userService" actives="10"/>

方法级别:

<!--限制当前接口中sayHello方法的并发链接数不能超过10-->
    <dubbo:service interface="com.tdd.service.UserService" ref="userService">
        <dubbo:method name="sayHello" actives="10"/>
    </dubbo:service>

服务消费端限流配置:
接口级别:

<!--设置当前消费者对指定接口的每一个方法的并发链接数不能超过10-->
<dubbo:reference interface="com.tdd.service.UserService" id="userService" actives="10"/>

方法级别:

<!--设置当前消费者对指定接口的sayHello方法的并发链接数不能超过10-->
    <dubbo:reference interface="com.tdd.service.UserService" id="userService" connections="10">
        <dubbo:method name="sayHello" actives="10"/>
    </dubbo:reference>

(4)executes的配置:
executes的配置数表示服务提供端每个接口的最大并发数,默认是0,如果是0则没有限制。它仅可设置在服务端。
基于XML配置如下:
接口级别:

<!--服务器端并发执行(或占用线程池线程数)不能超过 10-->
<dubbo:service interface="com.tdd.service.UserService" ref="userService" executes="10"/>

方法级别:

<!--服务器端并发执行(或占用线程池线程数)不能超过 10-->
<dubbo:service interface="com.tdd.service.UserService" ref="userService">
	<dubbo:method name="sayHello" executes="10"/>
</dubbo:service>

4.微服务限流(springcloud gateway和sentinel)

(1)springcloud gateway

springcloud gateway 默认使用redis进行限流,一般只要修改参数属性即可使用。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
spring:
  cloud:
    gateway:
      routes:

      - id: requestratelimiter_route

        uri: lb://pigx-upms
        order: 10000
        predicates:

        - Path=/admin/**

       filters:
       - name: RequestRateLimiter
         args:
           redis-rate-limiter.replenishRate: 1  # 令牌桶的容积
           redis-rate-limiter.burstCapacity: 3  # 流速 每秒
           key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean
       - StripPrefix=1
@Bean
KeyResolver remoteAddrKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}

(2)sentinel

对于使用springcloud alibaba的项目可以使用sentinel来实现流控。即通过配置来控制每个url的流量。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8720
      datasource:
        ds:
          nacos:
            server-addr: localhost:8848
            dataId: spring-cloud-sentinel-nacos
            groupId: DEFAULT_GROUP
            rule-type: flow
            namespace: xxxxxxxx

在sentinel控制台进行流控设置:

sentinel控制台

sentinel流控规则设置

  • 资源名(resource):限流规则的作用对象。
    -针对来源(limitApp):流控针对的调用来源,若为 default 则不区分调用来源。
  • 阈值类型(grade):限流阈值类型,QPS 或线程数模式,0代表根据并发数量来限流,1代表根据QPS来进行流量控制。
  • 单机阈值(count):限流阈值
  • 资源类型(strategy):判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口
  • 流控效果(controlBehavior):流控效果包括直接拒绝、排队等待、慢启动模式;
  • 是否集群(clusterMode):是否为集群模式

5.服务限流总结

若服务中使用了dubbo,则可以通过配置dubbo的服务提供者和服务消费者的处理请求数、发送请求数来进行服务限流;
spring cloud gateway和sentinel作为限流框架来使用都很好,若项目中使用了springcloud netflix 的一套组件,这里就可以选用spring cloud gateway来实现限流控制,若项目中接入了spring-cloud-alibaba的一套组件,比如nacos等,这样使用setinel进行流量控制会有更加好的体验。


文章内容输出来源:拉勾教育Java高薪训练营
若有错误之处,欢迎留言指正~~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 是的,我知道一些常用的分布式限流工具。以下是一些比较流行的限流工具: 1. Redis: 使用 Redis 可以简单地实现限流,特别是滑动窗口限流。 2. Guava RateLimiter: Guava 是 Google 的 Java 工具包,其中 RateLimiter 可以提供高效的令牌桶限流。 3. Bucket4J: Bucket4J 是一个 Java 库,可以提供功能强大的令牌桶和漏桶限流。 4. Netflix Zuul: Netflix Zuul 是一个微服务网关,内置了对限流的支持,可以很方便地在应用程序的边界处实现限流。 这些工具都有不同的优势和特性,请根据您的具体需求和环境选择适合您的工具。 ### 回答2: 有一些好用的分布式限流工具可以帮助我们实现系统的流量控制和限流,以下是其中几个常用的: 1. Alibaba Sentinel(阿里巴巴开源):一个功能强大的分布式系统流量防卫士,支持实时的流量控制、熔断降级、系统保护等功能,提供了丰富的配置和管理方式。 2. Spring Cloud Gateway:Spring Cloud生态系统中的一个网关工具,可以通过使用过滤器和限流机制来实现分布式限流,支持基于QPS、令牌桶等算法进行限流控制。 3. Nacos(阿里巴巴开源):一个用于动态服务发现、配置管理和服务治理的平台,其中包含了限流的功能,可以通过配置限流规则来实现请求的限制。 4. Redis+Lua脚本:通过在Redis中使用Lua脚本来实现限流功能。可以利用Redis的高性能和原子操作特性,结合令牌桶、漏桶等算法来实现流量控制。 5. ZooKeeper:一个分布式协调服务,可以用于实现分布式限流。可以利用ZooKeeper的有序节点特性和Watch机制来控制请求的并发量。 这些工具各有特点,具体选择取决于应用场景和需求。在实际使用时,需要根据系统的规模、性能需求和业务特点等因素,综合考虑选择合适的分布式限流工具。 ### 回答3: 当今的分布式系统越来越复杂和庞大,限流是保证系统稳定性和高可用性的重要策略之一。以下是我所知的几个好用的分布式限流工具: 1. Redis:Redis是一个高性能的内存数据存储系统,通过其提供的分布式缓存和限流功能可以实现简单而高效的限流逻辑。需要利用Redis的计数器或令牌桶等数据结构,将请求和访问进行计数,在达到限流阈值时进行拒绝或延迟处理。 2. Sentinel:Sentinel是阿里巴巴开源的一款分布式流量控制组件,它提供了流量控制、熔断降级、系统负载等功能。通过在每个服务节点上配置规则,可以统一限制请求的数量,避免系统被过多的请求压垮。 3. Nginx:Nginx是一款高性能的开源Web服务器,也可以用作分布式限流工具。通过配置Nginx反向代理服务器的限流策略,可以限制请求的并发数、连接数等,而且能够根据不同的URL或IP设置不同的限流策略。 4. Alibaba Yet Another Distributed Rate Limiter (Sentinel):Sentinel是一个用于流量控制的分布式限流组件,由Alibaba开源。它具有动态规则的特性,可以基于各种参数和维度(如QPS、线程数、CPU负载等)对请求进行限流,以保护系统免受过载。 以上是一些我所知道的分布式限流工具,每个工具都有其独特的特点和适用场景。根据具体的需求和系统架构,选择适合的工具进行分布式限流是非常重要的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值