如何选型架构师必备的限流方案?

摘要:大家好,我是Java烘焙师。为了避免突增流量引起服务雪崩,需要对接口、存储资源做限流保护,根据系统负载情况设置合适的限流值。下面结合笔者的经验和思考,对主要限流方案的选型做一下总结,本篇先看如何使用,下一篇再看背后的原理。 下面介绍几种常见
大家好,我是Java烘焙师。为了避免突增流量引起服务雪崩,需要对接口、存储资源做限流保护,根据系统负载情况设置合适的限流值。下面结合笔者的经验和思考,对主要限流方案的选型做一下总结,本篇先看如何使用,下一篇再看背后的原理。 下面介绍几种常见限流方案的使用方法、优缺点: 单机限流:Guava RateLimiter 同时支持单机限流、集群限流:Sentinel 分布式限流:Redisson RateLimiter 1. 单机限流:Guava RateLimiter 使用 官网API文档:https://guava.dev/releases/snapshot-jre/api/docs/com/google/common/util/concurrent/RateLimiter.html Guava是Google开源的Java类库,提供了很多实用的工具,包括集合、本地缓存、并发编程工具等。Guava RateLimiter就是其中的单机限流工具,核心概念如下: 每秒允许通过的最大permits: 当请求获取1个permit时,就相当于是qps限流 当请求获取多个permits时,代表该请求需要消耗多少资源,比如想限定更新DB的SQL qps为1000,则需获取1000 permits 支持预热:在预热期内逐步支持到最大permits,适用于需要预热缓存资源的场景 允许突发流量:每次请求是否被限流,不取决于该次请求permits的多少,而是取决于前一次请求,前一次请求的permits越多,则后续请求需等待的时间越长 比如有一个空闲的RateLimiter,第一次请求无论是获取1 permit、还是10000 permits,都能立刻成功,但是会计算出下一次permits可用的时间。那么第一次请求是10000 permits时,第二次请求即使只需10 permits也可能要长时间等待 如下分别是阻塞、非阻塞方式,使用Guava RateLimiter的示例。 // 创建一个每秒允许10个permits的限流器 RateLimiter rateLimiter = RateLimiter.create(10.0); // 1. 阻塞的方式 long startTime = System.currentTimeMillis(); // 获取5 permits;如果充足,则获取成功,否则阻塞等待 rateLimiter.aquire(5); long endTime = System.currentTimeMillis(); // 打印阻塞等待的时间 System.out.println("Processing business logic. Waiting time ms: " + (endTime - startTime)); // 2. 非阻塞的方式 // 尝试获取1 permit if (rateLimiter.tryAcquire()) { // 如果能获取到令牌,则继续处理业务逻辑 System.out.println("Processing business logic..."); } else { // 否则快速失败,返回友好提示或执行降级逻辑 System.out.println("Too many requests."); } 优点 性能极高:纯计算,无网络开销 实现简单 缺点 无法跨实例协同,从整体层面控制限流 2. 同时支持单机限流、集群限流:Sentinel 使用 官网文档:https://sentinelguard.io/zh-cn/docs/introduction.html Sentinel是阿里开源的流量治理组件,在国内使用较广泛。核心概念如下: 资源:可以是接口(调入、调出均可),也可以是任何一段代码逻辑。可通过sentinel注解、或者sentinel API包装成受保护的资源 规则:围绕资源的实时状态设定的规则,包括流量控制规则、熔断降级规则以及系统保护规则,所有规则都可以动态实时调整。 常见的流量控制规则: 限流阈值类型:支持QPS、线程数模式,默认是按QPS来限流 流控效果:即超过限流阈值时的行为,支持直接拒绝、排队等待、慢启动模式,默认是直接拒绝 常见的熔断降级规则: 熔断策略:即在什么情况下熔断降级,支持慢调用比例、异常比例、异常数策略,默认是慢调用比例 熔断阈值:慢调用比例模式下为慢调用的RT平均响应时间,异常比例/异常数模式下为对应的阈值 集群限流:是为了解决流量在集群内各实例之间不均匀、导致触发单机限流,而达不到总QPS预期的问题。集群限流有2种模式:单机均摊、全局阈值,为了避免token server成为瓶颈,比较典型的是单机均摊。 单机均摊:先设置一个集群限流阈值,然后再根据机器实例数均摊到单机上,即单机限流值=集群限流值/机器实例数 全局阈值:集群流控中共有两种身份 Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流 Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过) 如下分别是通过sentinel注解、sentinel API方式的示例。 sentinel注解一般加在受保护的接口、访问存储资源的方法上 sentinel API更加灵活,可包住受保护的业务代码片段;更进一步,结合业务入参,可实现热点参数限流 // 1. sentinel注解:在sentinel控制台上可针对定义的资源名配置限流规则 @Service @SentinelResource(value = "protectedMethod") public String protectedMethod(Request request) { } // 2. sentinel API // 2.1 抛异常方式定义资源 try (Entry entry = SphU.entry("protectedResource1")) { // 被保护的逻辑 System.out.println("protected resource 1"); } catch (BlockException ex) { // 处理被流控的逻辑 System.out.println("blocked!"); } // 2.2 返回布尔值方式定义资源 if (SphO.entry("protectedResource2")) { // 务必保证finally会被执行 try { // 被保护的业务逻辑 } finally { SphO.exit(); } } else { // 资源访问阻止,被限流或被降级 // 进行相应的处理操作 } // 2.3 热点参数限流 try (Entry entry = SphU.entry("hotspotResource", EntryType.IN, 1, paramA, paramB)) { // 易出现热点参数的业务逻辑 } catch (BlockException ex) { // 热点参数限流 } finally { if (entry != null) { entry.exit(1, paramA, paramB); } } 优点 易于与Java框架集成,提高应用开发效率:如Spring Cloud、Dubbo等 功能强大:在控制台页面,可动态配置限流规则、熔断降级规则 缺点 sentinel的集群限流,在单机均摊模式下最终会换算为单机限流,当集群限流值较低、机器实例数较多时,计算出的单机限流值可能不准,无法精准控制总的集群限流 例如:有100台机器,按30 qps来限流;则计算出的单机均摊qps不足1、按1处理,这样总的集群限流就变成了100 qps,大于预期的30 qps 3. 分布式限流:Redisson RateLimiter 使用 官网API文档:https://redisson.pro/docs/data-and-services/objects/#ratelimiter Redisson是一个高性能的Redis客户端框架,提供了基于redis实现的分布式工具,如分布式集合、分布式锁、布隆过滤器等,借助Redisson RateLimiter可以实现分布式限流。核心概念如下: 允许在一段时间间隔内,最多有n个permits通过 通过redis lua脚本实现限流逻辑、以及原子操作 如下是使用Redisson RateLimiter的示例。 // 使用Redisson的分布式限流器 RRateLimiter limiter = redisson.getRateLimiter("myLimiter"); // 限定每60秒100个permits limiter.trySetRate(RateType.OVERALL, 100, 60, RateIntervalUnit.SECONDS); // 阻塞式获取5 permits limiter.acquire(5); // 非阻塞式获取1 permit if (limiter.tryAcquire(1)) { // 获取到permit,执行业务逻辑 } else { // 被限流 } 优点 基于redis实现了分布式限流,能较精准地控制低qps场景的限流 与特定框架/限流组件无关 缺点 引入了外部依赖,有网络开销,系统稳定性易受影响 其它分布式限流方案 笔者还了解到有一个redis模块 redis-cell 可做分布式限流。不过有以下问题,不是很建议使用: 需要额外运维部署,成本较高 项目由个人维护,目前已基本停滞 结论 Java应用开发,一般情况下,可引入Sentinel做接口、存储资源的限流控制 单机任务,如手动执行或定期执行的task,可引入Guava RateLimiter做简易的限流控制 低qps场景的限流、或不想接入特定框架/限流组件的服务,可引入Redisson RateLimiter来实现较精准的分布式限流控制