高并发场景下的限流处理

108 篇文章 1 订阅
102 篇文章 0 订阅

对于业务系统来说高并发就是支撑「海量用户请求」,QPS 会是平时的几百倍甚至更高。
如果不考虑高并发的情况,即使业务系统平时运行得好好的,并发量一旦增加就会频繁出现各种诡异的业务问题,比如,在电商业务中,可能会出现用户订单丢失、库存扣减异常、超卖等问题。

限流是服务降级的一种手段,顾名思义,通过限制系统的流量,从而实现保护系统的目的。

合理的限流配置,需要了解系统的吞吐量,所以,限流一般需要结合容量规划和压测来进行。

当外部请求接近或者达到系统的最大阈值时,触发限流,采取其他的手段进行降级,保护系统不被压垮。常见的降级策略包括延迟处理、拒绝服务、随机拒绝等。

限流后的策略,其实和 Java 并发编程中的线程池非常类似,我们都知道,线程池在任务满的情况下,可以配置不同的拒绝策略,比如:

AbortPolicy,会丢弃任务并抛出异常
DiscardPolicy,丢弃任务,不抛出异常
DiscardOldestPolicy 等,当然也可以自己实现拒绝策略
Java 的线程池是开发中一个小的功能点,但是见微知著,也可以引申到系统的设计和架构上,将知识进行合理地迁移复用。

限流方案中有一点非常关键,那就是如何判断当前的流量已经达到我们设置的最大值,具体有不同的实现策略,下面进行简单分析。

  1. 计数器法
    一般来说,我们进行限流时使用的是单位时间内的请求数,也就是平常说的 QPS,统计 QPS 最直接的想法就是实现一个计数器。

计数器法是限流算法里最简单的一种算法,我们假设一个接口限制 100 秒内的访问次数不能超过 10000 次,维护一个计数器,每次有新的请求过来,计数器加 1。

这时候判断,

如果计数器的值小于限流值,并且与上一次请求的时间间隔还在 100 秒内,允许请求通过,否则拒绝请求
如果超出了时间间隔,要将计数器清零
下面的代码里使用 AtomicInteger 作为计数器,可以作为参考:

public class CounterLimiter { 
    //初始时间 
    private static long startTime = System.currentTimeMillis(); 
    //初始计数值 
    private static final AtomicInteger ZERO = new AtomicInteger(0); 
    //时间窗口限制 
    private static final int interval = 10000; 
    //限制通过请求 
    private static int limit = 100; 
    //请求计数 
    private AtomicInteger requestCount = ZERO; 
    //获取限流 
    public boolean tryAcquire() { 
        long now = System.currentTimeMillis(); 
        //在时间窗口内 
        if (now < startTime + interval) { 
            //判断是否超过最大请求 
            if (requestCount.get() < limit) { 
                requestCount.incrementAndGet(); 
                return true; 
            } 
            return false; 
        } else { 
            //超时重置 
            requestCount = ZERO; 
            startTime = now; 
            return true; 
        } 
    } 
} 
 

计数器策略进行限流,可以从单点扩展到集群,适合应用在分布式环境中。

单点限流使用内存即可,如果扩展到集群限流,可以用一个单独的存储节点,比如 Redis 或者 Memcached 来进行存储,在固定的时间间隔内设置过期时间,就可以统计集群流量,进行整体限流。

计数器策略有一个很大的缺点,对临界流量不友好,限流不够平滑。

假设这样一个场景,我们限制用户一分钟下单不超过 10 万次,现在在两个时间窗口的交汇点,前后一秒钟内,分别发送 10 万次请求。也就是说,窗口切换的这两秒钟内,系统接收了 20 万下单请求,这个峰值可能会超过系统阈值,影响服务稳定性。

对计数器算法的优化,就是避免出现两倍窗口限制的请求,可以使用滑动窗口算法实现,感兴趣的同学可以去了解一下。

  1. 漏桶和令牌桶算法
    漏桶算法和令牌桶算法,在实际应用中更加广泛,也经常被拿来对比。

漏桶算法可以用漏桶来对比,假设现在有一个固定容量的桶,底部钻一个小孔可以漏水,我们通过控制漏水的速度,来控制请求的处理,实现限流功能。

漏桶算法的拒绝策略很简单:如果外部请求超出当前阈值,则会在水桶里积蓄,一直到溢出,系统并不关心溢出的流量。

漏桶算法是从出口处限制请求速率,并不存在上面计数器法的临界问题,请求曲线始终是平滑的。

它的一个核心问题是对请求的过滤太精准了,我们常说“水至清则无鱼”,其实在限流里也是一样的,我们限制每秒下单 10 万次,那 10 万零 1 次请求呢?是不是必须拒绝掉呢?

大部分业务场景下这个答案是否定的,虽然限流了,但还是希望系统允许一定的突发流量,这时候就需要令牌桶算法。

在令牌桶算法中,假设我们有一个大小恒定的桶,这个桶的容量和设定的阈值有关,桶里放着很多令牌,通过一个固定的速率,往里边放入令牌,如果桶满了,就把令牌丢掉,最后桶中可以保存的最大令牌数永远不会超过桶的大小。当有请求进入时,就尝试从桶里取走一个令牌,如果桶里是空的,那么这个请求就会被拒绝。

不知道你有没有使用过 Google 的 Guava 开源工具包?在 Guava 中有限流策略的工具类 RateLimiter,RateLimiter 基于令牌桶算法实现流量限制,使用非常方便。

RateLimiter 会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,RateLimter 的 API 可以直接应用,主要方法是 acquire 和 tryAcquire。

acquire 会阻塞,tryAcquire 方法则是非阻塞的。

下面是一个简单的示例:

public class LimiterTest { 
    public static void main(String[] args) throws InterruptedException { 
        //允许10个,permitsPerSecond 
        RateLimiter limiter = RateLimiter.create(100); 
        for(int i=1;i<200;i++){ 
            if (limiter.tryAcquire(1)){ 
                System.out.println("第"+i+"次请求成功"); 
            }else{ 
                System.out.println("第"+i+"次请求拒绝"); 
            } 
        } 
    } 
} 
 

不同限流算法的比较
计数器算法实现比较简单,特别适合集群情况下使用,但是要考虑临界情况,可以应用滑动窗口策略进行优化,当然也是要看具体的限流场景。

漏桶算法和令牌桶算法,漏桶算法提供了比较严格的限流,令牌桶算法在限流之外,允许一定程度的突发流量。在实际开发中,我们并不需要这么精准地对流量进行控制,所以令牌桶算法的应用更多一些。

如果我们设置的流量峰值是 permitsPerSecond=N,也就是每秒钟的请求量,计数器算法会出现 2N 的流量,漏桶算法会始终限制 N 的流量,而令牌桶算法允许大于 N,但不会达到 2N 这么高的峰值。

参考:《2020最新Java基础精讲视频教程和学习路线!》

链接:https://segmentfault.com/a/1190000038949009

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在高并发场景下,需要注意以下几个方面: 1. 高可用性:高并发场景下系统出现故障的风险增加,因此需要保证系统的高可用性。为此可以采用负载均衡、多副本部署、容错机制等方式。 2. 性能优化:高并发场景下,系统的性能压力非常大,需要优化系统的性能。例如,通过优化数据库查询语句、使用缓存、减少IO操作等方式提高系统的响应速度。 3. 分布式系统:在高并发场景下,单机系统可能无法满足需求,需要采用分布式系统来承载更大的负载。分布式系统需要考虑数据一致性、负载均衡、容错机制等问题。 4. 安全性:高并发场景下,系统容易成为攻击的目标,需要采用安全策略来保护系统的安全性。例如,使用防火墙、反DDoS攻击等方式。 5. 监控和诊断:高并发场景下,系统问题可能随时出现,需要及时发现和诊断问题。为此,需要采用监控和诊断工具来实时监控系统的运行状态,并及时发现和解决问题。 综上所述,高并发场景下需要注意系统的高可用性、性能优化、分布式系统、安全性以及监控和诊断等问题。 ### 回答2: 在高并发场景下,我们需要注意以下几点: 1. 资源管理:高并发场景下,服务器的资源容易被耗尽,如CPU、内存、网络带宽等。因此,我们需要对资源进行合理的管理和分配,避免出现系统崩溃或响应延迟过高的情况。 2. 数据库优化:高并发场景下,数据库可能成为性能瓶颈。因此,我们需要对数据库进行优化,使用索引、分表、缓存等技术手段来提高数据库的查询速度和并发处理能力。 3. 缓存技术:高并发场景下,使用缓存可以减轻数据库的压力,提高系统的响应速度。我们可以使用分布式缓存、页面缓存等技术来实现缓存,加快数据的访问速度。 4. 消息队列:高并发场景下,使用消息队列可以实现异步处理,将请求解耦合,并发处理请求。通过消息队列,可以保证系统的高可用性和可伸缩性。 5. 负载均衡:高并发场景下,多个请求可能会集中在某个服务器上,导致该服务器无法承受压力而崩溃。通过负载均衡技术,可以将请求均匀地分发到不同的服务器上,提高系统的稳定性和并发处理能力。 6. 限流措施:为了保护系统免受恶意请求的攻击,我们需要实施限流措施,防止恶意请求过多导致系统资源被耗尽,如设置请求频率限制、IP访问限制等。 7. 异常处理高并发场景下,可能会出现各种异常情况,如请求超时、服务器异常等。我们需要及时捕获和处理异常,保证系统的稳定运行。 8. 测试和监控:在高并发场景下,我们需要进行压力测试,模拟大量并发请求,评估和优化系统的性能。同时,建立监控系统,实时监测系统的运行状态,及时发现并解决潜在的问题。 ### 回答3: 在高并发场景下,我们需要注意以下几点: 1. 性能优化:高并发环境下系统的性能是关键,需要对系统进行性能优化,包括对代码进行优化、服务器和数据库的配置优化等,以提高系统的响应速度和吞吐量。 2. 资源管理:高并发场景下,系统需要处理大量的请求,对于服务器、网络带宽、数据库等资源的使用要进行合理的管理和分配,避免资源瓶颈导致系统崩溃。 3. 并发控制:高并发情况下,可能会出现资源竞争的问题,如数据库的并发读写、缓存的并发访问等。需要通过锁机制、缓存策略、队列等方式进行并发控制,保证数据的一致性和系统的稳定性。 4. 缓存策略:在高并发场景下,合理使用缓存可以大幅度提高系统的性能。可以使用分布式缓存、页面静态化等方式来减少对数据库的访问压力,提高系统的响应速度。 5. 限流和熔断:在高并发场景下,如果不进行限流和熔断处理,系统可能会因为请求过多而崩溃。可以使用限流算法、负载均衡等方式来控制请求的流量,同时使用断路器等机制来保护系统免受超负荷的攻击。 6. 日志和监控:在高并发场景下,日志和监控是必不可少的工具。通过对系统的日志进行分析和监控,可以及时发现问题和瓶颈,做出相应的优化调整,保障系统的稳定性和可靠性。 总之,高并发场景下需要综合考虑性能、资源、并发控制、缓存、限流和熔断、日志和监控等方面的问题,以保证系统的稳定性、可扩展性和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值