什么是高可用?
高可用是指不管什么条件下,比如硬件故障或系统升级,系统都是可以使用的。
用很多个9来衡量。如99.99%,表示总的运行时间只有0.01%的时间不可用。
限流
Spring Cloud Gateway 自带的单机限流的早期是基于 Bucket4j 实现的。后来,替换成了 Resilience4j
Resilience4j 不仅能限流,还能熔断、负载保护、自动重试等保障高可用开箱即用的功能。Resilience4j 的生态也更好,很多网关都使用 Resilience4j 来做限流熔断的
单体限流和分布式限流的区别是什么?
流量来了 ,全在一台服务器上服务,和 要分成很多份,分到不同服务器服务。那么,怎么把一大堆流量分成很多份呢?
分布式限流常见的方案:
-
借助中间件限流:借助 Sentinel 或 Redis 自己实现对应限流逻辑。
-
网关层限流 :比较常用,直接在网关层限流。通常网关层限流也需要借助中间件/框架。如 Spring Cloud Gateway 的分布式限流实现
RedisRateLimiter
是基于 Redis+Lua 实现的,Spring Cloud Gateway 也可整合 Sentinel 限流。
基于 Redis 手动实现限流,建议配合 Lua 脚本。 原因:
-
减少网络开销:利用 Lua 脚本可以批量执行多条 Redis 命令,这些 命令会被提交到 Redis 服务器一次性执行完成,大幅减小网络开销。
-
原子性:一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。
网上现成的优秀限流脚本有 Apache 网关项目 ShenYu 的 RateLimiter 限流插件:基于 Redis + Lua 实现了令牌桶算法/并发令牌桶算法、漏桶算法、滑动窗口算法。
如果不想自己写 Lua 脚本,可直接利用 Redisson 中的 RRateLimiter
实现分布式限流,其底层实现基于 Lua 代码。
超时
由于网络问题、系统或者服务内部的 Bug、服务器宕机、操作系统崩溃等问题,系统或服务不可能永远是可用状态。
超时机制是当一个请求超过指定的时间(比如 1s)没被处理,此请求会直接被取消并抛出指定的异常或者错误(比如 504 Gateway Timeout
)。
分类:
- 连接超时(ConnectTimeout):客户端与服务端建立连接的最长等待时间。
- 读取超时(ReadTimeout):客户端和服务端已建立连接,客户端等待服务端处理完请求的最长时间。实际项目中关注较多是读取超时。
如未设置超时,可能会导致服务端连接数爆炸和大量请求堆积。
这些堆积的连接和请求会消耗系统资源,影响新收到的请求的处理。严重的情况下,甚至会拖垮整个服务。
超时时间设置是个难题!超时值设置太高或者太低都有风险。设置太高,会降低超时机制的有效性,如设置超时为 10s,那设置超时就没啥意义了,系统依然会出现大量慢请求堆积问题。设置太低就可能会导致服务在某些处理请求速度变慢的情况下(比如请求突然增多),大量请求重试(超时通常会结合重试)继续加重服务的压力,进而导致整个服务被拖垮。
通常建议读取超时设置为 1500ms ,比较普适。如果服务对于延迟比较敏感,读取超时可在 1500ms 上进行适当缩短。反之,读取超时值也可比 1500ms 长,不过,尽量不要超过 1500ms 。连接超时可适当设置长一些,建议 1000ms ~ 5000ms 之内。具体根据项目,调整优化得到。
固定值不好用,改成动态调整的。如Java线程池实现原理及其在美团业务中的实践 - 美团技术团队
如将超时的值放在配置中心,可随时根据情况更改。
重试
重试配合超时一起使用,指多次发送相同请求来避免瞬态故障和偶然性故障。
瞬态故障可以理解为某一瞬间系统偶然出现的故障,不会持久。偶然性故障可理解为在某些情况下偶尔出现的故障,频率通常较低。
重试核心思想是通过消耗服务器资源尽可能获得请求更大概率被成功处理。瞬态故障和偶然性故障很少发生,因此,重试对于服务器的资源消耗几乎可被忽略。
常见策略:
**固定间隔时间重试:**每隔固定时间,重试一次
梯度间隔重试:根据重试次数的增加延长下次重试时间,比如第一次重试间隔为 1 秒,第二次为 2 秒,第三次为 4 秒,以此类推
重试次数建议3次,太多给系统负载带来较大压力,且采用梯度间隔重试。
超时和重试在实际项目中使用,需注意保证同一个请求没被多次执行。如客户端等待服务端完成请求,完成超时但此时服务端已经执行了请求,但由于短暂的网络波动导致响应延迟发送给客户端。
举例:用户支付购买课程,结果用户支付的请求由于重试的问题,导致用户购买同一门课程支付了两次。对于这种情况,在执行用户购买课程的请求时,需判断用户是否已经购买过,以解决因重试问题导致重复购买。
手动编写代码实现重试,可通过循环(例如 while 或 for 循环)或者递归实现。不建议自己动手实现,有很多第三方开源库提供了更完善的重试机制实现,例如 Spring Retry、Resilience4j、Guava Retrying