SpringCloudGateway限流原理与实践
<p>本文已收录在Github,<strong>关注我,紧跟本系列专栏文章,咱们下篇再续!</strong></p><ul>
<li>🚀 魔都架构师 | 全网30W技术追随者</li>
<li>🔧 大厂分布式系统/数据中台实战专家</li>
<li>🏆 主导交易系统百万级流量调优 & 车联网平台架构</li>
<li>🧠 AIGC应用开发先行者 | 区块链落地实践者</li>
<li>🌍 以技术驱动创新,我们的征途是改变世界!</li>
<li>👉 实战干货:编程严选网</li>
</ul>
<h2 id="0-高并发三板斧">0 高并发三板斧</h2>
<p>高服务保护:</p>
<ul>
<li>缓存,提升系统访问速度和增大系统能处理的容量,抗高并发流量的银弹</li>
<li>熔断、降级,当服务异常或影响核心流程,需暂时屏蔽,待高峰或问题解决后打开。服务降级,对不怎么重要的服务进行低优先级处理。尽可能把系统资源让给优先级高的服务。资源有限,而请求无限</li>
<li>有时不能缓存、降级解决,如稀缺资源、写服务、频繁复杂查询,需限制这些场景的并发/请求量,即限流。对并发访问/请求进行限速或对一个时间窗口内的请求进行限速。一旦达限制速率,则拒绝服务、排队或等待、降级。Guava RateLimiter、lua+Redis、Sentinel方案。</li>
</ul>
<h3 id="高并发系统限流">高并发系统限流</h3>
<p>限制总并发数、限制瞬时并发数、限制时间窗口内的平均速率、限制远程接口的调用速率、限制MQ的消费速率,或根据网络连接数、网络流量、CPU或内存负载等来限流。</p>
<p>本文讨论分布式限流方案Spring Cloud Gateway限流原理。</p>
<h3 id="限流关键">限流关键</h3>
<p>将限流服务做成原子化,常见限流算法:令牌桶、漏桶等。Spring Cloud Gateway用Redis+Lua实现高并发和高性能限流。</p>
<h2 id="1-令牌桶算法">1 令牌桶算法</h2>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/other/1097393/202505/1097393-20250505212139401-1388855927.png" class="lazyload"></p>
<p>存放固定容量令牌的桶,按固定速率往桶里添加令牌。</p>
<h3 id="11-算法描述">1.1 算法描述</h3>
<ul>
<li>假如用户配置的平均速率为r,则每隔1/r秒一个令牌被加入到桶</li>
<li>假设桶最多可存b个令牌。若令牌到达时,令牌桶已满,则该令牌丢弃</li>
<li>当一个n字节的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上</li>
<li>若令牌桶中少于n个令牌,则不会删除令牌,并且认为这个数据包在流量限制之外<br>
算法允许最长b个字节的突发,但长期运行结果看,数据包的速率被限制成常量r。</li>
</ul>
<p>流量限制外的数据包可以不同方式处理:</p>
<ul>
<li>被丢弃</li>
<li>排放在队列中,以便当令牌桶中累积足够多的令牌时再传输</li>
<li>继续发送,但要特殊标记,网络过载时将这些特殊标记的包丢弃</li>
</ul>
<h2 id="2-漏桶算法">2 漏桶算法</h2>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/other/1097393/202505/1097393-20250505212140511-552083752.png" class="lazyload"></p>
<p>漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可用于:</p>
<ul>
<li>流量整形(Traffic Shaping)</li>
<li>流量控制(Traffic Policing)</li>
</ul>
<h3 id="21-算法描述">2.1 算法描述</h3>
<p>一个固定容量的漏桶,按固定速率流出水滴。</p>
<p>若桶空,则不需流出水滴;</p>
<p>可以任意速率流入水滴到漏桶;</p>
<p>若流入水滴超出桶容量,则流入的水滴溢出(被丢弃),而漏桶容量不变。</p>
<h2 id="3-实践">3 实践</h2>
<p>SCG默认实现 Redis 限流,如扩展只需实现Ratelimter接口,同时也可通过自定义KeyResolver指定限流K,如需根据用户、IP、URI做限流等,通过exchange对象可获取到请求信息,如:</p>
<h3 id="31-用户限流">3.1 用户限流</h3>
<pre><code class="language-java">@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
</code></pre>
<p>SCG默认提供的 RedisRateLimiter 的核心逻辑为判断是否取到令牌的实现,调用</p>
<p>META-INF/scripts/request_rate_limiter.lua</p>
<p>脚本实现基于令牌桶算法限流:</p>
<pre><code class="language-lua">1: local tokens_key = KEYS1
2: local timestamp_key = KEYS2
3:
4: local rate = tonumber(ARGV1)
5: local capacity = tonumber(ARGV2)
6: local now = tonumber(ARGV3)
7: local requested = tonumber(ARGV4)
8:
9: local fill_time = capacity/rate
10: local ttl = math.floor(fill_time*2)
11:
12: local last_tokens = tonumber(redis.call(“get”, tokens_key))
13: if last_tokens == nil then
14: last_tokens = capacity
15: end
16:
17: local last_refreshed = tonumber(redis.call(“get”, timestamp_key))
18: if last_refreshed == nil then
19: last_refreshed = 0
20: end
21:
22: local delta = math.max(0, now-last_refreshed)
23: local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
24: local allowed = filled_tokens >= requested
25: local new_tokens = filled_tokens
26: local allowed_num = 0
27: if allowed then
28: new_tokens = filled_tokens - requested
29: allowed_num = 1
30: end
31:
32: redis.call(“setex”, tokens_key, ttl, new_tokens)
33: redis.call(“setex”, timestamp_key, ttl, now)
34:
35: return { allowed_num, new_tokens }
</code></pre>
<h4 id="keyresolver">KeyResolver</h4>
<p>KeyResolver从传入请求中提取路由键(route key)值,以路由到正确服务实例。KeyResolver根据请求中不同属性提取路由键,如请求头、请求参数、请求路径等。</p>
<p>常与Predicate和Filter协作:</p>
<ul>
<li>Predicate匹配请求</li>
<li>Filter处理请求</li>
</ul>
<p>用KeyResolver,可根据请求的不同属性将请求路由到不同的服务实例,实现LB和高可用性。</p>
<blockquote>
<p>本文由博客一文多发平台 OpenWrite 发布!</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/JavaEdge/p/18860472
頁:
[1]