服务限流,是指通过控制请求的速率或次数来达到保护服务的目的,在微服务中,我们通常会将它和熔断、降级搭配在一起使用,来避免瞬时的大量请求对系统造成负荷,来达到保护服务平稳运行的目的。下面就来看一看常见的6种限流方式,以及它们的实现与使用。
固定窗口算法
固定窗口算法通过在单位时间内维护一个计数器,能够限制在每个固定的时间段内请求通过的次数,以达到限流的效果。
进行测试,允许在1000毫秒内通过5个请求:
void test() throws InterruptedException { FixedWindowRateLimiter fixedWindowRateLimiter = new FixedWindowRateLimiter(1000, 5); for (int i = 0; i < 10; i++) { if (fixedWindowRateLimiter.tryAcquire()) { System.out.println("执行任务"); }else{ System.out.println("被限流"); TimeUnit.MILLISECONDS.sleep(300); } }}复制代码
运行结果:
进行一下测试,对第一个例子中的规则进行修改,每1秒允许100个请求通过不变,在此基础上再把每1秒等分为10个0.1秒的窗口。
void test() throws InterruptedException { SlidingWindowRateLimiter slidingWindowRateLimiter = new SlidingWindowRateLimiter(1000, 10, 10); TimeUnit.MILLISECONDS.sleep(800); for (int i = 0; i < 15; i++) { boolean acquire = slidingWindowRateLimiter.tryAcquire(); if (acquire){ System.out.println("执行任务"); }else{ System.out.println("被限流"); } TimeUnit.MILLISECONDS.sleep(10); }}复制代码
查看运行结果:
进行一下测试,先初始化一个漏桶,设置桶的容量为3,每秒放行1个请求,在代码中每500毫秒尝试请求1次:
void test() throws InterruptedException { LeakyBucketRateLimiter leakyBucketRateLimiter=new LeakyBucketRateLimiter(3,1); for (int i = 0; i < 15; i++) { if (leakyBucketRateLimiter.tryAcquire()) { System.out.println("执行任务"); }else { System.out.println("被限流"); } TimeUnit.MILLISECONDS.sleep(500); }}复制代码
查看运行结果,按规则进行了放行:
进行测试,设置每秒产生5个令牌:
void acquireTest(){ RateLimiter rateLimiter=RateLimiter.create(5); for (int i = 0; i < 10; i++) { double time = rateLimiter.acquire(); log.info("等待时间:{}s",time); }}复制代码
运行结果:
运行结果:
查看运行结果:
配置限流规则,这里使用直接编码方式配置,指定QPS到达1时进行限流:
@Componentpublic class SentinelConfig { @PostConstruct private void init(){ List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(QueryService.KEY); rule.setCount(1); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); }}复制代码
在application.yml
中配置sentinel的端口及dashboard地址:
spring: application: name: sentinel-test cloud: sentinel: transport: port: 8719 dashboard: localhost:8088复制代码
启动项目后,启动sentinel-dashboard
:
java -Dserver.port=8088 -jar sentinel-dashboard-1.8.0.jar复制代码
在浏览器打开dashboard就可以看见我们设置的流控规则:
对gateway进行配置,主要就是配一下令牌的生成速率、令牌桶的存储量上限,以及用于限流的键的解析器。这里设置的桶上限为2,每秒填充1个令牌:
spring: application: name: gateway-test cloud: gateway: routes: - id: limit_route uri: lb://sentinel-test predicates: - Path=/sentinel-test/** filters: - name: RequestRateLimiter args: # 令牌桶每秒填充平均速率 redis-rate-limiter.replenishRate: 1 # 令牌桶上限 redis-rate-limiter.burstCapacity: 2 # 指定解析器,使用spEl表达式按beanName从spring容器中获取 key-resolver: "#{@pathKeyResolver}" - StripPrefix=1 redis: host: 127.0.0.1 port: 6379复制代码
我们使用请求的路径作为限流的键,编写对应的解析器:
@Slf4j@Componentpublic class PathKeyResolver implements KeyResolver { public Mono<String> resolve(ServerWebExchange exchange) { String path = exchange.getRequest().getPath().toString(); log.info("Request path: {}",path); return Mono.just(path); }}复制代码
启动gateway,使用jmeter进行测试,设置请求间隔为500ms,因为每秒生成一个令牌,所以后期达到了每两个请求放行1个的限流效果,在被限流的情况下,http请求会返回429状态码。
除了上面的根据请求路径限流外,我们还可以灵活设置各种限流的维度,例如根据请求header中携带的用户信息、或是携带的参数等等。当然,如果不想用gateway自带的这个Redis的限流器的话,我们也可以自己实现RateLimiter
接口来实现一个自己的限流工具。
gateway实现限流的关键是spring-cloud-gateway-core
包中的RedisRateLimiter
类,以及META-INF/scripts
中的request-rate-limiter.lua
这个脚本,如果有兴趣可以看一下具体是如何实现的。
总结
总的来说,要保证系统的抗压能力,限流是一个必不可少的环节,虽然可能会造成某些用户的请求被丢弃,但相比于突发流量造成的系统宕机来说,这些损失一般都在可以接受的范围之内。前面也说过,限流可以结合熔断、降级一起使用,多管齐下,保证服务的可用性与健壮性。
那么,这次的分享就到这里,我是Hydra,我们下篇再见。
文中的全部测试代码都传到了我的github,有需要可以自行领取:github.com/trunks2008/…
码字不易~ 欢迎大家给Hydra点个Star啊,谢谢~
原文地址:作者简介,
码农参上
,一个热爱分享的公众号,有趣、深入、直接,与你聊聊技术。欢迎关注、添加好友,进一步交流。