微服务怎么限流?算法+框架+实战!

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
云原生网关 MSE Higress,422元/月
简介: 背景随着微服务的流行,服务和服务之间的稳定性变得越来越重要。缓存、降级和限流是保护微服务系统运行稳定性的三大利器。缓存:提升系统访问速度和增大系统能处理的容量降级:当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉限流:解决服务雪崩,级联服务发生阻塞时,及时熔断,防止请求堆积消耗占用系统的线程、IO等资源,造成其他级联服务所在服务器的崩溃

背景

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。缓存、降级和限流是保护微服务系统运行稳定性的三大利器。


缓存:提升系统访问速度和增大系统能处理的容量

降级:当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉

限流:解决服务雪崩,级联服务发生阻塞时,及时熔断,防止请求堆积消耗占用系统的线程、IO等资源,造成其他级联服务所在服务器的崩溃

这里我们主要说一下限流,限流的目的应当是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率就可以拒绝服务、等待、降级。 首先,我们需要去了解最基本的两种限流算法。


限流算法

漏桶算法

令牌桶算法

计算器算法

限流框架

下面说一下现有流行的限流工具


guava

Google的Guava工具包中就提供了一个限流工具类——RateLimiter。


RateLimiter是基于“令牌通算法”来实现限流的。


hystrix

hystrix主要是通过资源池以及信号量来限流,暂时能支持简单的限流


sentinel

限流比较主流的三种算法:漏桶,令牌桶,滑动窗口。而Sentinel采用的是最后一种,滑动窗口来实现限流的。当然sentinel不仅仅局限于限流,它是一个面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。


限流实战

有很多应用都是可以直接在调用端、代理、网关等中间层进行限流,下面简单介绍下集中中间件限流方式


nginx限流

nginx限流方式有三种


limit_conn_zone

limit_req_zone

ngx_http_upstream_module

但是nginx限流不够灵活,不好动态配置。


zuul限流

除了zuul引入限流相关依赖

<dependency>
     <groupid>com.marcosbarbero.cloud</groupid>
     <artifactid>spring-cloud-zuul-ratelimit</artifactid>
     <version>2.0.0.RELEASE</version>
</dependency>

相关配置如下:

zuul:
    ratelimit:
        key-prefix: your-prefix  #对应用来标识请求的key的前缀
        enabled: true
        repository: REDIS  #对应存储类型(用来存储统计信息)默认是IN_MEMORY
        behind-proxy: true  #代理之后
        default-policy: #可选 - 针对所有的路由配置的策略,除非特别配置了policies
             limit: 10 #可选 - 每个刷新时间窗口对应的请求数量限制
             quota: 1000 #可选-  每个刷新时间窗口对应的请求时间限制(秒)
              refresh-interval: 60 # 刷新时间窗口的时间,默认值 (秒)
               type: #可选 限流方式
                    - user
                    - origin
                    - url
          policies:
                myServiceId: #特定的路由
                      limit: 10 #可选- 每个刷新时间窗口对应的请求数量限制
                      quota: 1000 #可选-  每个刷新时间窗口对应的请求时间限制(秒)
                      refresh-interval: 60 # 刷新时间窗口的时间,默认值 (秒)
                      type: #可选 限流方式
                          - user
                          - origin
                          - url

注意这里的仓库如果是针对全局限流,那么可以考虑存到redis中,这里的zuul.ratelimit.repository可以设置为redis,但是如果扩容后则需要动态调整,不过灵活,所以这里我建议还是选择本地内存(INM_MOMERY)或者不设置,这样伸缩容后可以自动扩展,不用变更配置,


如果需要动态更新,可以集成apollo配置进行动态更新,

public class ZuulPropertiesRefresher implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    @Autowired
    private RouteLocator routeLocator;
    @ApolloConfigChangeListener(interestedKeyPrefixes = "zuul.",value="zuul.yml")
    public void onChange(ConfigChangeEvent changeEvent) {
        refreshZuulProperties(changeEvent);
    }
    private void refreshZuulProperties(ConfigChangeEvent changeEvent) {
        log.info("Refreshing zuul properties!");
        /**
         * rebind configuration beans, e.g. ZuulProperties
         * @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
         */
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
        /**
         * refresh routes
         * @see org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration.ZuulRefreshListener#onApplicationEvent
         */
        this.applicationContext.publishEvent(new RoutesRefreshedEvent(routeLocator));
        log.info("Zuul properties refreshed!");
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

springcloud gateway限流

在Spring Cloud Gateway中,有Filter过滤器,因此可以在“pre”类型的Filter中自行实现上述三种过滤器。


但是限流作为网关最基本的功能,Spring Cloud Gateway官方就提供了RequestRateLimiterGatewayFilterFactory这个类,适用Redis和lua脚本实现了令牌桶的方式。


具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中,lua脚本在如下图所示的文件夹中:


image.png


具体源码不打算在这里讲述,读者可以自行查看,代码量较少,先以案例的形式来讲解如何在Spring Cloud Gateway中使用内置的限流过滤器工厂来实现限流。


首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:

 <dependency>
    <groupid>org.springframework.cloud</groupid>
    <artifactid>spring-cloud-starter-gateway</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifatid>spring-boot-starter-data-redis-reactive
</artifatid></dependency>

复制代码在配置文件中做以下的配置:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
  cloud:
    gateway:
      routes:
      - id: limit_route
        uri: http://httpbin.org:80/get
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: '#{@hostAddrKeyResolver}'
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 3

配置了 redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:


burstCapacity,令牌桶总容量。

replenishRate,令牌桶每秒填充平均速率。

key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

可以通过KeyResolver来指定限流的Key,比如我们需要根据用户来做限流,IP来做限流等等。


1)IP限流

@Bean
public KeyResolver ipKeyResolver() {
    return exchange -&gt; Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}

2)用户限流

@Bean
KeyResolver userKeyResolver() {
    return exchange -&gt; Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}

3)接口限流

@Bean
KeyResolver apiKeyResolver() {
    return exchange -&gt; Mono.just(exchange.getRequest().getPath().value());
}

这里只是针对单节点限流,如果需要可以自定义全局限流


sentinel 限流

sentinel限流这里不做详细描述,大家想了解可以参考下面文档:https://mp.weixin.qq.com/s/4LjnzDg9uNQIJML6MIriEg


应用限流

这里springboot应用服务需要限流的话,这里给的方案是集成google的guava类库,大家在网上能搜索到很多demo,我这里不做详细描述,主要是下面api的使用:

 RateLimiter.create(callerRate);

现在容器比较火,现在如果部署在容器或者虚拟机上,我们需要动态调整资源数后,那么限流也会跟着变化,这里说一下如何实现动态限流。第一步肯定是集成配置中心实现配置动态更新,至于说生效方式有几种 方案一: 增加监听器,当配置变动时重新创建限流对象


方案二: 限流对象定时创建,这里引入了应用缓存框架,下面给个demo

import com.ctrip.framework.apollo.Config;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
@Slf4j
public class RateLimitInterceptor implements HandlerInterceptor {
    private Config config;
    private static final String RATE_TYPE_GLOBAL = "global";
    private static final String RATE_TYPE_URL = "url";
    //全局限流
    public RateLimitInterceptor(Config config) {
        this.config = config;
    }
    Cache<object, ratelimiter> rateLimiterCache = Caffeine.newBuilder()
            .initialCapacity20
            .expireAfterWrite(2, TimeUnit.MINUTES)
            .maximumSize100
            .softValues()
            .recordStats()
            .build();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (StringUtils.isBlank(request.getRequestURI()) || request.getRequestURI().startsWith("/actuator/")
                || request.getRequestURI().startsWith("/srch-recommend/fault-tolerant/health")||request.getRequestURI().startsWith("/health")) {
            return true;
        }
         try {
            boolean rateLimitEnabled=config.getBooleanProperty("ratelimit.enabled", false);
            if(!rateLimitEnabled){
                return true;
            }
            if (!do(RATE_TYPE_GLOBAL, StringUtils.EMPTY, "ratelimit.global")) {
                return false;
            }
            String url = request.getRequestURI();
            if (StringUtils.isNotBlank(url)) {
                return do(RATE_TYPE_URL, url, "ratelimit.url.");
            }
            return true;
        } catch (Exception e) {
            log.warn("RateLimitInterceptor error message:{}", e.getMessage(), e);
            return true;
        }
    }
    private boolean doRateLimiter(String rateType, String key, String configPrefix) {
        String cacheKey = rateType + "-" + key;
        RateLimiter rateLimiter = rateLimiterCache.getIfPresent(cacheKey);
        if (rateLimiter == null) {
            int callerRate = config.getIntProperty(configPrefix + uniqueKey, 0);
            if (callerRate &gt; 0) {
                rateLimiter = RateLimiter.create(callerRate);
                rateLimiterCache.put(cacheKey, rateLimiter);
            }
        }
        return rateLimiter == null || rateLimiter.tryAcquire();
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    }
}

当然这里如果有业务相关的限流可以根据参考上面的demo自己来实现限流。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
打赏
0
0
0
0
310
分享
相关文章
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
微服务——MongoDB实战演练——MongoTemplate实现评论点赞
本节介绍如何使用MongoTemplate实现评论点赞功能。传统方法通过查询整个文档并更新所有字段,效率较低。为优化性能,采用MongoTemplate对特定字段直接操作。代码中展示了如何利用`Query`和`Update`对象构建更新逻辑,通过`update.inc(&quot;likenum&quot;)`实现点赞数递增。测试用例验证了功能的正确性,确保点赞数成功加1。
94 0
微服务——MongoDB实战演练——根据上级ID查询文章评论的分页列表
本节介绍如何根据上级ID查询文章评论的分页列表,主要包括以下内容:(1)在CommentRepository中新增`findByParentid`方法,用于按父ID查询子评论分页列表;(2)在CommentService中新增`findCommentListPageByParentid`方法,封装分页逻辑;(3)提供JUnit测试用例,验证功能正确性;(4)使用Compass插入测试数据并执行测试,展示查询结果。通过这些步骤,实现对评论的高效分页查询。
65 0
微服务——MongoDB实战演练——文章评论的基本增删改查
本节介绍了文章评论的基本增删改查功能实现。首先,在`cn.itcast.article.dao`包下创建数据访问接口`CommentRepository`,继承`MongoRepository`以支持MongoDB操作。接着,在`cn.itcast.article.service`包下创建业务逻辑类`CommentService`,通过注入`CommentRepository`实现保存、更新、删除及查询评论的功能。最后,新建Junit测试类`CommentServiceTest`,对保存和查询功能进行测试,并展示测试结果截图,验证功能的正确性。
80 2
微服务架构下的电商API接口设计:策略、方法与实战案例
本文探讨了微服务架构下的电商API接口设计,旨在打造高效、灵活与可扩展的电商系统。通过服务拆分(如商品、订单、支付等模块)和标准化设计(RESTful或GraphQL风格),确保接口一致性与易用性。同时,采用缓存策略、负载均衡及限流技术优化性能,并借助Prometheus等工具实现监控与日志管理。微服务架构的优势在于支持敏捷开发、高并发处理和独立部署,满足电商业务快速迭代需求。未来,电商API设计将向智能化与安全化方向发展。
2025 年最新 Java 面试从基础到微服务实战指南全解析
《Java面试实战指南:高并发与微服务架构解析》 本文针对Java开发者提供2025版面试技术要点,涵盖高并发电商系统设计、微服务架构实现及性能优化方案。核心内容包括:1)基于Spring Cloud和云原生技术的系统架构设计;2)JWT认证、Seata分布式事务等核心模块代码实现;3)数据库查询优化与高并发处理方案,响应时间从500ms优化至80ms;4)微服务调用可靠性保障方案。文章通过实战案例展现Java最新技术栈(Java 17/Spring Boot 3.2)的应用.
118 9
颠覆开发效率!国内首个微服务编排框架Juggle开源啦!
Juggle是国内首个开源的微服务编排框架,专注于解决企业微服务进程中接口重复开发、系统对接复杂等问题。它提供零代码、低代码和AI增强功能,通过可视化拖拽快速组装简单API为复杂接口,支持多协议、多语言脚本和流程多版本管理。相比国外框架如Conductor,Juggle更贴合国内需求,具备高效开发、企业级可靠性及信创适配等优势,助力企业实现敏捷创新与数字化转型。
颠覆开发效率!国内首个微服务编排框架Juggle开源啦!
Go实现常见的限流算法
本文介绍了五种常见的限流算法:固定窗口、滑动窗口、漏桶算法、令牌桶和滑动日志。固定窗口简单高效,但可能产生两倍突发流量;滑动窗口可避免突发问题,但可能掐断流量;漏桶算法搭配生产者消费者模式实现平滑流量;令牌桶允许一定突发流量;滑动日志适用于多级限流场景。每种算法通过Go语言实现并附有代码解读,帮助理解其工作原理与适用场景。
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
420 6
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
201 1
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问