高并发场景设计(三)——服务限流

为什么要服务限流

对于分布式高并发场景下,通过应用缓存和服务降级,能够应对很大部分并发量突增的情景,为什么还要服务限流呢?对于一些稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页)等,缓存和降级并不能完全解决,需有一种手段来限制这些场景的并发/请求量,即限流。

限流的目的是通过对并发访问/请求进行限速,或者一个时间窗口内的的请求进行限速,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)来保护系统。

限流方式

  • 总并发控制:限制总并发数(比如数据库连接池、线程池)
  • 瞬时并发控制:限制瞬时并发数,如nginx的limit_conn模块,用来限制瞬时并发连接数
  • 窗口流量控制:限制时间窗口内的平均速率,如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率;其他还有如限制远程接口调用速率、限制MQ的消费速率。
  • 硬件资源限流:通过网络流量、CPU或内存负载等来限流。

限流算法

1、计数器

计数器是最简单粗暴的算法,直接通过控制时间段内的请求次数。

2、滑动窗口算法

比如某个服务最多只能每秒钟处理100个请求,我们可以设置一个1秒钟的滑动窗口,窗口中有10个格子,每个格子100毫秒,每100毫秒移动一次,每次移动都需要记录当前服务请求的次数。内存中需要保存10次的次数。可以用数据结构LinkedList来实现。格子每次移动的时候判断一次,当前访问次数和LinkedList中最后一个相差是否超过100,如果超过就需要限流了。

3、令牌桶算法

令牌桶算法的描述如下:首先基于一个队列,请求放到队列里面。但除了队列以外,还要设置一个令牌桶,另外有一个脚本以持续恒定的速度往令牌桶里面放令牌,后端处理程序每处理一个请求就必须从桶里拿出一个令牌,如果令牌拿完了,那就不能处理请求了。

  • 令牌将按照固定的速率被放入令牌桶中
  • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝。
  • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上。
  • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

4、漏桶算法 

漏桶算法即leaky bucket是一种非常常用的限流算法,可以用来实现流量整形(Traffic Shaping)和流量控制(Traffic Policing)。漏桶算法的主要概念如下:

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;

  • 如果桶是空的,则不需流出水滴;

  • 可以以任意速率流入水滴到漏桶;

  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

image

 令牌桶和漏桶对比:

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;

  • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;

  • 令牌桶限制的是平均流入速率,并允许一定程度突发流量;

  • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;

  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;

  • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

服务应用级限流

限流总并发/连接/请求数

一个应用系统一定会有极限并发/请求数,即PS/QPS阀值,如果超了阀值则系统就会不响应用户请求或响应的非常慢,因此最好进行过载保护,防止大量请求涌入击垮系统。比如Tomcat的并发请求限流,其Connector 其中一种配置有如下几个参数:

  • acceptCount:如果Tomcat的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接;
  • maxConnections: 瞬时最大连接数,超出的会排队等待;
  • maxThreads:Tomcat能启动用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数则可能会僵死。

类似的还有Mysql(如max_connections)、Redis(如tcp-backlog)配置。

限流总资源数

如果资源是稀缺资源(如数据库连接、线程),而且可能有多个系统都会去使用它,可以使用池化技术来限制总资源数:连接池、线程池。比如分配给每个应用的数据库连接是100,那么本应用最多可以使用100个资源,超出了可以等待或者抛异常。

限流某个接口的总并发/请求数

如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,如抢购业务;这个时候就需要限制这个接口的总并发/请求数总请求数了;因为粒度比较细,可以为每个接口都设置相应的阀值。可以使用Java中的AtomicLong进行限流:

try {
    if(atomic.incrementAndGet() > 限流数) {
        //拒绝请求
   }
    //处理请求
} finally {
    atomic.decrementAndGet();
}

限流某个接口的时间窗请求数

即一个时间窗口内的请求数,如想限制某个接口/服务每秒/每分钟/每天的请求数/调用量。一种实现方式如下所示:

// Goova 缓存
LoadingCache<Long, AtomicLong> counter = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .build(new CacheLoader<Long, AtomicLong>() {
                    @Override
                    public AtomicLong load(Long seconds) throws Exception {
                        return new AtomicLong(0);
                    }
                });

// 限定时间窗口1秒钟
long limit = 1000;

while(true) {
    //得到当前秒
    long currentSeconds = System.currentTimeMillis() / 1000;
    if(counter.get(currentSeconds).incrementAndGet() > limit) {
        System.out.println("限流了:" + currentSeconds);
        continue;
    }
    //业务处理
}

平滑限流某个接口的请求数

可以采用Goova提供的令牌桶工具类,Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。RateLimiter允许将一段时间内没有消费的令牌暂存到令牌桶中,留待未来使用,并允许未来请求的这种突发。

// 设置令牌桶的速率是1
private RateLimiter limiter = RateLimiter.create(1.0);

@GetMapping("/indexLimiter")
public String indexLimiter() {
    // 如果用户在500毫秒内没有获取到令牌,就直接放弃获取进行服务降级处理
    boolean tryAcquire = limiter.tryAcquire(500, TimeUnit.MILLISECONDS);
    if (!tryAcquire) {
        log.info("Error ---时间:{},获取令牌失败.");
        return "系统繁忙,请稍后再试.";
    }
    
    log.info("Success ---时间:{},获取令牌成功.");
    return "success";
}

分布式限流

布式限流最关键的是要将限流服务做成原子化,而解决方案可以使使用redis+lua或者nginx+lua技术进行实现,通过这两种技术可以实现的高并发和高性能。

接入层限流

接入层通常指请求流量的入口,该层的主要目的有:负载均衡、非法请求过滤、请求聚合、缓存、降级、限流、A/B测试、服务质量监控等等。对于Nginx接入层限流可以使用Nginx自带了两个模块:连接数限流模块ngx_http_limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module。

  • ngx_http_limit_conn_module

limit_conn是对某个KEY对应的总的网络连接数进行限流。可以按照IP来限制IP维度的总连接数,或者按照服务域名来限制某个域名的总连接数。但是记住不是每一个请求连接都会被计数器统计,只有那些被Nginx处理的且已经读取了整个请求头的请求连接才会被计数器统计。

  • ngx_http_limit_req_module

limit_req是漏桶算法实现,用于对指定KEY对应的请求进行限流,比如按照IP维度限制请求速率。

参考资料:

服务化体系之—限流

高并发---限流

服务限流 -- 自定义注解基于RateLimiter实现接口限流

谈谈高并发系统的限流

架构设计之「服务限流」

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值