『超级架构师』服务限流的思路与手段

680 阅读6分钟

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」。PS:已经更文多少天,N就写几。一定要写对文案,否则文章不计入在内。

前言

本文已收录到 Github-java3c ,里面有我的系列文章,欢迎大家Star。

Hello 大家好,我是l拉不拉米,在我的『超级架构师』专栏前两篇文章《『超级架构师』图码实战限流算法(一)》《『超级架构师』图码实战限流算法(二)》中,以伪代码实例讲解限流的几种主流算法,这一篇主要通过理论 + 图解 + 代码的形式讲解系统限流的几种手段思路。

容器限流

Tomcat 限流

tomcat限流的方式就是通过tomcat的配置文件,设置最大线程数,当请求的并发大于最大线程数时,请求就会排队执行,相当于实现了限流的目的。

在tomcat目录文件conf/server.xml下配置:

<Connector port="8080" protocol="HTTP/1.1"
          connectionTimeout="20000"
          maxThreads="150"
          redirectPort="8443" />

这里的maxThreads即是最大并发线程数。

Nginx 限流

nginx的限流与tomcat的限流类似,也是通过配置文件实现。

主要是两种方式:

  • 控制速率
  • 控制并发连接数

控制速率

在 nginx.conf 配置文件的 http 模块中添加限流配置:

格式:limit_req_zone key zone rate

  • key :定义限流对象,binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
  • zone:定义共享内存区来存储访问信息, myRateLimit:10m 表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M 可以存储16W IP地址访问信息。
  • rate: 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一 个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求。
http {
    limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=10r/s;
}

配置 server,使用 limit_req 指令应用限流。

server {
    location / {
        limit_req zone=myRateLimit;
        proxy_pass http://my_upstream;
    }
}

控制并发连接数

利用 limit_conn_zone 和 limit_conn 两个指令即可。

Nginx 官方例子:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

limit_conn perip 10 作用的 key 是  $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。

limit_conn perserver 100 作用的 key 是  $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。

服务端限流

限流算法

服务端限流常用的限流算法有4种:

  • 固定窗口算法
  • 滑动窗口算法
  • 漏桶算法
  • 令牌桶算法

在前两篇文章《『超级架构师』图码实战限流算法(一)》《『超级架构师』图码实战限流算法(二)》中有图码详解。

具体的实现

Google Guava 类库的 RateLimiter

基于令牌桶算法实现,适用于单体架构。

// 每秒2个的速率提交任务
final RateLimiter rateLimiter = RateLimiter.create(2.0);
void submitTasks(List tasks, Executor executor) {
    for (Runnable task : tasks) {
        rateLimiter.acquire(); // may wait
        executor.execute(task);
    }
}
redis + Lua

redis提供分布式K-V存储,lua脚本保证原子性。

Lua脚本大致逻辑如下:

-- 获取调用脚本时传入的第一个key值(用作限流的 key)
local key = KEYS[1]
-- 获取调用脚本时传入的第一个参数值(限流大小)
local limit = tonumber(ARGV[1])

-- 获取当前流量大小
local curentLimit = tonumber(redis.call('get', key) or "0")

-- 是否超出限流
if curentLimit + 1 > limit then
-- 返回(拒绝)
return 0
else
-- 没有超出 value + 1
redis.call("INCRBY", key, 1)
-- 设置过期时间
redis.call("EXPIRE", key, 2)
-- 返回(放行)
return 1
end
网关层限流:

比如Zuul、Spring Cloud Gateway等,而像spring cloud - gateway网关限流底层实现原理,就是基于Redis + Lua,通过内置Lua限流脚本的方式。

image

阿里巴巴sentinel

启动sentinel务端,在sentinel的控制台针对你的接口资源配置相应的规则即可,非常简单。

image

服务熔断

系统在设计之初就把熔断措施考虑进去。当系统出现问题时,如果短时间内无法修复,系统要自动做出判断,开启熔断开关,拒绝流量访问,避免大流量对后端的过载请求。

系统也应该能够动态监测后端程序的修复情况,当程序已恢复稳定时,可以关闭熔断开关,恢复正常服务。常见的熔断组件有Hystrix以及阿里的Sentinel,两种互有优缺点,可以根据业务的实际情况进行选择。

sentinel

服务降级

将系统的所有功能服务进行一个分级,当系统出现问题需要紧急限流时,可将不是那么重要的功能进行降级处理,停止服务,这样可以释放出更多的资源供给核心功能的去用。

例如在电商平台中,如果突发流量激增,可临时将商品评论、积分等非核心功能进行降级,停止这些服务,释放出机器和CPU等资源来保障用户正常下单,而这些降级的功能服务可以等整个系统恢复正常后,再来启动,进行补单/补偿处理。除了功能降级以外,还可以采用不直接操作数据库,而全部读缓存、写缓存的方式作为临时降级方案。

延迟处理

这个模式需要在系统的前端设置一个流量缓冲池,将所有的请求全部缓冲进这个池子,不立即处理。然后后端真正的业务处理程序从这个池子中取出请求依次处理,常见的可以用队列模式来实现。这就相当于用异步的方式去减少了后端的处理压力,但是当流量较大时,后端的处理能力有限,缓冲池里的请求可能处理不及时,会有一定程度延迟。漏桶算法以及令牌桶算法就是这个思路。

特权处理

这个模式需要将用户进行分类,通过预设的分类,让系统优先处理需要高保障的用户群体,其它用户群的请求就会延迟处理或者直接不处理。

缓存、降级、限流的区别

缓存

缓存,是用来增加系统吞吐量,提升访问速度提供高并发。

降级

降级,是在系统某些服务组件不可用的时候、流量暴增、资源耗尽等情况下,暂时屏蔽掉出问题的服务,继续提供降级服务,给用户尽可能的友好提示,返回兜底数据,不会影响整体业务流程,待问题解决再重新上线服务。

限流

限流,是指在使用缓存和降级无效的场景。比如当达到阈值后限制接口调用频率,访问次数,库存个数等,在出现服务不可用之前,提前把服务降级。只服务好一部分用户。

最后

创作不易,感谢您的点赞!!🙏🙏