并发编程-25 高并发处理手段之消息队列思路 + 应用拆分思路 + 应用限流思路

2021-08-17 16:00:11 浏览数 (1)

文章目录

  • 概述
  • 消息队列
    • 消息队列特性
    • 为什么需要消息队列
    • 消息队列的好处
    • 消息队列举例
  • 应用拆分
    • 应用拆分的原则
    • 应用拆分的思考
    • 应用拆分常用的组件
      • Dubbo
      • Spring Cloud
  • 应用限流
    • 限流算法 -- 计数器法 ,简单但是有临界问题
    • 限流算法 -- 滑动窗口 (Rolling Window),划分多个时间窗口解决临界问题
    • 限流算法 -- 漏桶(Leaky Bucket)
    • 限流算法 -- 令牌桶(Token Bucket)
    • 总结
      • 计数器 VS 滑动窗口
      • 漏桶算法 VS 令牌桶算法

概述

这里只是讲通用的思路,实际高并发的场景需要根据实际情况来决定方案。


消息队列

消息队列特性

  • 业务无关: 只做消息分发
  • FIFO : 先投递先到达
  • 容灾:节点的动态增删和消息的持久化
  • 性能: 吞吐量提升,系统内部通信效率提高

为什么需要消息队列

  • 【生产】和【消费】的速度或稳定性等因素不一致

消息队列的好处

  • 业务解耦
  • 最终一致性(要么都成功,要么都失败)
  • 广播,接入新的系统,只要需要确保把消息推送到消息队列即可,新系统从消息队列订阅即可
  • 错峰与流控

消息队列举例

Kafka

RabbitMQ


应用拆分

应用拆分的原则

  • 业务优先
  • 循序渐进
  • 兼顾技术:重构、分层
  • 可靠测试

应用拆分的思考

  • 应用之间的通信: RPC(Dubbo等)、消息队列
  • 应用之间的数据库设计:每个应用应该有独立的数据库
  • 避免事务操作跨应用

应用拆分常用的组件

Dubbo

Spring Cloud


应用限流

如果有大量的数据,在同一时间内直接写入数据库,势必对系统造成很大的压力。如果通过特定的方式采用限流的方式以很定的速率来写入数据库,那数据库压力就会小很多。


以下算法是说的我们在业务代码中的逻辑限流

限流算法 – 计数器法 ,简单但是有临界问题

假设有个接口A,规定1分钟的访问次数不能超过100次。通常的做法:设置一个计数器counter,每当一个请求过来的时候,counter就加1,

如果counter>100并且该请求与第一个请求的间隔时间还在1分钟之内,那么说明请求数过多,触发限流

如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置counter


优点: 计数器法是限流算法里最简单也是最容易实现的一种算法。


缺点 :临界问题

假设有一个恶意用户,在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求,可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

刚才的问题其实是因为我们统计的精度太低。那么如何很好地处理这个问题呢?或者说,如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法


限流算法 – 滑动窗口 (Rolling Window),划分多个时间窗口解决临界问题

在上图中,整个红色的矩形框表示一个时间窗口,一个时间窗口就是一分钟。

然后我们将时间窗口进行划分,如上图中,我们就将滑动窗口划成了6格,所以每格代表的是10秒钟。

每过10秒钟,我们的时间窗口就会往右滑动一格。

每一个格子都有自己独立的计数器counter,比如当一个请求在0:35秒的时候到达,那么0:30~0:39对应的counter就会加1。

那么滑动窗口怎么解决刚才的临界问题的呢?我们可以看上图,0:59到达的100个请求会落在灰色的格子中,而1:00到达的请求会落在橘黄色的格子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触发了限流。

再来看一下刚才的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。


限流算法 – 漏桶(Leaky Bucket)

首先,有一个固定容量的桶,有水流进来,也有水流出去。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。

我们将算法中的水换成实际应用中的请求,我们可以看到漏桶算法天生就限制了请求的速度。当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法天生不会出现临界问题


限流算法 – 令牌桶(Token Bucket)

首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的,token以一个固定的速率r往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。


总结

计数器 VS 滑动窗口

计数器算法是最简单的算法,可以看成是滑动窗口的低精度实现。滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存储空间。也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。

漏桶算法 VS 令牌桶算法

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。

令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。


0 人点赞