springcloud怎么限流面试题?springcloud实现限流实操

Spring Cloud 是分布式微服务架构的一站式解决方案,它提供了一套简单易用的编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务系统的构建。正因为这样,我们在进行java面试的时候肯定会遇到springcloud的问题,那springcloud怎么限流面试题?下面来我们就来给大家讲解一下。

springcloud怎么限流面试题.png

一、实战基于 Spring cloud Gateway 的限流

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>

其基础是基于redis,所以:

spring:
    application:
    name: gateway - service
redis: #redis相关配置
database: 8
host: 10.12 .15 .5
port: 6379
password: 123456# 有密码时设置
jedis:
    pool:
    max - active: 8
max - idle: 8
min - idle: 0
timeout: 10000 ms

接下来需要注入限流策略的 bean:

@Primary
@Bean(value = "ipKeyResolver")
KeyResolver ipKeyResolver()
{
    return exchange - > Mono.just(exchange.getRequest()
        .getRemoteAddress()
        .getHostName());
    //return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    //return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
/**
 * API限流
 * @return
 * @author Damon 
 * @date 2020年3月18日
 *
 */
@Bean(value = "apiKeyResolver")
KeyResolver apiKeyResolver()
{
    return exchange - > Mono.just(exchange.getRequest()
        .getPath()
        .value());
}
/**
 * 请求路径中必须携带userId参数
 * 用户限流
 * @return
 * @author Damon 
 * @date 2020年3月18日
 *
 */
@Bean(value = "userKeyResolver")
KeyResolver userKeyResolver()
{
    return exchange - > Mono.just(exchange.getRequest()
        .getQueryParams()
        .getFirst("userId"));
}

这里引入ipKeyResolver、apiKeyResolver、userKeyResolver三种策略

,可以利用注解 @Primary 来决定其中一个被使用。

注入bean后,需要在配置中备用:

spring:
    application:
    name: gateway - service
redis: #redis相关配置
database: 8
host: 10.12 .15 .5
port: 6379
password: 123456# 有密码时设置
jedis:
    pool:
    max - active: 8
max - idle: 8
min - idle: 0
timeout: 10000 ms
cloud:
    kubernetes:
    discovery:
    all - namespaces: true
gateway:
    discovery:
    locator:
    enabled: true
lowerCaseServiceId: true
routes: #路由配置: 参数为一个List -
    id: cas - server# 唯一标识
uri: lb: //cas-server-service #转发的地址,写服务名称
    order: -1
predicates:
    -Path = /cas-server/ ** #判断匹配条件, 即地址带有 / ribbon
/**的请求,会转发至lb:cas-server-service
        filters:
        - StripPrefix=1 #去掉Path前缀,参数为1代表去掉/ribbon
        - name: RequestRateLimiter #基于redis的Gateway的自身限流
          args:
            redis-rate-limiter.replenishRate: 1  # 允许用户每秒处理多少个请求
            redis-rate-limiter.burstCapacity: 3  # 令牌桶的容量,允许在一秒钟内完成的最大请求数
            key-resolver: "#{@ipKeyResolver}" #SPEL表达式取的对应的bean
      - id: admin-web
        uri: lb://admin-web-service
        order: -1
        predicates:
        - Path=/admin-web/**
        filters:
        - StripPrefix=1
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 1  # 允许用户每秒处理多少个请求
            redis-rate-limiter.burstCapacity: 3  # 令牌桶的容量,允许在一秒钟内完成的最大请求数
            key-resolver: "#{@ipKeyResolver}" #SPEL表达式取的对应的bean
      - id: order-service
        uri: lb://order-service-service
        order: -1
        predicates:
        - Path=/order-service/**
        filters:
        - StripPrefix=1
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 1  # 允许用户每秒处理多少个请求
            redis-rate-limiter.burstCapacity: 3  # 令牌桶的容量,允许在一秒钟内完成的最大请求数
            key-resolver: "#{@ipKeyResolver}" #SPEL表达式取的对应的bean
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true
  mvc:
    throw-exception-if-no-handler-found: true
  main:
    allow-bean-definition-overriding: true # 当遇到同样名称时,是否允许覆盖注册

这里是在原有的路由基础上加入 RequestRateLimiter限流过滤器,包括三个参

数:

-name: RequestRateLimiter# 基于redis的Gateway的自身限流
args:
    redis - rate - limiter.replenishRate: 3# 允许用户每秒处理多少个请求
redis - rate - limiter.burstCapacity: 5# 令牌桶的容量, 允许在一秒钟内完成的最大请求数
key - resolver: "#{@ipKeyResolver}"#
SPEL表达式取的对应的bean

其中 replenishRate,其含义表示允许每秒处理请求数;

burstCapacity 表示允许在一秒内处理的最大请求数;

key-resolver 这里采用请求 IP 限流,利用SPEL 表达式取对应的 bean

写一个小脚本来压测一下:

for i in $(seq 1 30000);
do echo $(expr $i\\ * 3 + 1);
curl - i - H "Accept: application/json" - H "Authorization:bearer b064d95b-af3f-4053-a980-377c63ab3413" - X GET http: //10.10.15.5:5556/order-service/api/order/getUserInfo;done
    for i in $(seq 1 30000);
do echo $(expr $i\\ * 3 + 1);
curl - i - H "Accept: application/json" - H "Authorization:bearer b064d95b-af3f-4053-a980-377c63ab3413" - X GET http: //10.10.15.5:5556/admin-web/api/user/getCurrentUser;done

上面两个脚本分别对2个服务进行压测,打印结果:

HTTP / 1.1 200 OK
transfer - encoding: chunked
X - RateLimit - Remaining: 2
X - RateLimit - Burst - Capacity: 3
X - RateLimit - Replenish - Rate: 1
Expires: 0
Cache - Control: no - cache, no - store, max - age = 0, must - revalidate
Set - Cookie: ORDER - SERVICE - SESSIONID = R99Ljit9XvfCapyUJDWL8I0rZqxReoY6HwcQV2n2;
path = /
X - XSS - Protection: 1;
mode = block
Pragma: no - cache
X - Frame - Options: DENY
Date: Thu, 19 Mar 2020 06: 32: 27 GMT
X - Content - Type - Options: nosniff
Content - Type: application / json;
charset = UTF - 8
{
    "message":
    {
        "status": 200
        , "code": 0
        , "message": "success"
    }
    , "data": "{\"message\":{\"status\":200,\"code\":0,\"message\":\"get user success\"},\"data\":{\"id\":23,\"isAdmin\":1,\"userId\":\"fbb18810-e980-428c-932f-848f3b9e7c84\",\"userType\":\"super_admin\",\"username\":\"admin\",\"realName\":\"super_admin\",\"password\":\"$2a$10$89AqlYKlnsTpNmWcCMvgluRFQ/6MLK1k/nkBpz.Lw6Exh.WMQFH6W\",\"phone\":null,\"email\":null,\"createBy\":\"admin\",\"createTime\":1573119753172,\"updateBy\":\"admin\",\"updateTime\":1573119753172,\"loginTime\":null,\"expireTime\":null,\"remarks\":\"super_admin\",\"delFlag\":0,\"loginType\":null}}"
}
ex
同一秒内多次后:
HTTP / 1.1 429 Too Many Requests
X - RateLimit - Remaining: 0
X - RateLimit - Burst - Capacity: 3
X - RateLimit - Replenish - Rate: 1
content - length: 0
expr: syntax error
HTTP / 1.1 429 Too Many Requests
X - RateLimit - Remaining: 0
X - RateLimit - Burst - Capacity: 3
X - RateLimit - Replenish - Rate: 1
content - length: 0
expr: syntax error

从上面可以看到,执行后,会出现调用失败的情况,状态变为429 (Too Many Requests) 。

二、基于阿里开源限流神器:Sentinel

首先引入依赖:

<!--基于 阿里的sentinel作限流 -->
    <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>

在配置文件 application.yaml 文件中配置,需要新增2个配置:

spring:
    application:
    name: admin - web
cloud:
    kubernetes:
    discovery:
    all - namespaces: true
sentinel:
    eager: true# 取消Sentinel控制台的懒加载
transport:
    dashboard: 10.12 .15 .2: 8080# sentinel的Dashboard地址
port: 8719# 是sentinel应用端和控制台通信端口
heartbeat - interval - ms: 500# 心跳时间
scg:
    fallback: #scg.fallback为sentinel限流后的响应配置
mode: response
response - status: 455
response - body: 已被限流

其中,这里面配置了一个服务:spring.cloud.sentinel.transport.dashboard

,配置的是 sentinel 的 Dashboard 地址。同时 spring.cloud.sentinel.transport

.port 这个端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。

Sentinel 默认为所有的 HTTP 服务提供限流埋点,上面配置完成后自动完成所有埋点,只需要控制配置限流规则即可。

这里我们讲下通过注解来给指定接口函数加上限流埋点,写一个RestController,在接口函数上加上注解 @SentinelResource:

@GetMapping(value = "/getToken")
@SentinelResource("getToken")
public Response < Object > getToken(Authentication authentication)
{
    //Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    authentication.getCredentials();
    OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
    String token = details.getTokenValue();
    return Response.ok(200, 0, "get token success", token);
}

以上代码部分完成了,接下来先安装SentinelDashBoard,Sentinel DashBoard下载地址:https://github.com/alibaba/Sentinel/releases 。

下载完成后,命令启动:

java -jar sentinel-dashboard-1.6.2.jar

默认启动端口为8080,访问 IP:8080,就可以显示 Sentinel 的登录界面,用户名与密码均为sentinel。登录 Dashboard 成功后,多次访问接口"/getToken",可以在 Dashboard 看到相应数据,这里不展示了。接下来可以设置接口的限流功能,在 “+流控” 按钮点击打开设置界面,设置阈值类型为 qps,单机阈值为5。

浏览器重复请求 http://10.10.15.5:5556/admin-web/api/user/getToken 如果超过阀值就会出现如下界面信息:

Blocked by Sentinel (flow limiting)

此时,就看到Sentinel 限流起作用了,可以加上 spring.cloud.sentinel.scg.

fallback 为sentinel 限流后的响应配置,亦可自定义限流异常信息:

@GetMapping(value = "/getToken")
@SentinelResource(value = "getToken", blockHandler = "handleSentinelException", blockHandlerClass = {
MySentinelException.class
}))
public Response < Object > getToken(Authentication authentication)
{
    //Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    authentication.getCredentials();
    OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
    String token = details.getTokenValue();
    return Response.ok(200, 0, "get token success", token);
}
public class MySentinelException
{
    public static Response < Object > handleSentinelException(BlockException e)
    {
        Map < String, Object > map = new HashMap < > ();
        logger.info("Oops: " + ex.getClass()
            .getCanonicalName());
        return Response.ok(200, -8, "通过注解 @SentinelResource 配置限流埋点并自定义限流后的处理逻辑", null);
    }
}

这里讲下注解 @SentinelResource 包含以下属性:

value:资源名称,必需项;

entryType:入口类型,可选项(默认为 EntryType.OUT);

blockHandler:blockHandlerClass中对应的异常处理方法名,参数类型和返回值必须和原方法一致;

blockHandlerClass:自定义限流逻辑处理类

Sentinel 限流逻辑处理完毕了,但每次服务重启后,之前配置的限流规则就会被清空。因为是内存形式的规则对象。所以下面就讲下用 Sentinel 的一个特性 ReadableDataSource 获取文件、数据库或者配置中心设置限流规则,目前支持 Apollo、Nacos、ZK 配置来管理。

首先回忆一下,一条限流规则主要由下面几个因素组成:

resource:资源名,即限流规则的作用对象,即为注解 @SentinelResource 的value;

count:限流阈值;

grade:限流阈值类型(QPS 或并发线程数);

limitApp:流控针对的调用来源,若为 default 则不区分调用来源;

strategy:基于调用关系的限流策略;

controlBehavior:流量控制效果(直接拒绝、排队等待、匀速器模式)

理解了意思,接下来通过文件来配置:

#通过文件读取限流规则
spring.cloud.sentinel.datasource.ds1.file.file=classpath:flowrule.json
spring.cloud.sentinel.datasource.ds1.file.data-type=json
spring.cloud.sentinel.datasource.ds1.file.rule-type=flow

在resources新建一个文件,比如 flowrule.json 添加限流规则:

[
    {
        "resource": "getToken"
        , "count": 1
        , "controlBehavior": 0
        , "grade": 1
        , "limitApp": "default"
        , "strategy": 0
  }
    , {
        "resource": "resource"
        , "count": 1
        , "controlBehavior": 0
        , "grade": 1
        , "limitApp": "default"
        , "strategy": 0
  }
]

重新启动项目,出现如下日志说明成功:

[Sentinel Starter] DataSource 
ds1-sentinel-file-datasource start to loadConfig
[Sentinel Starter] 
DataSource ds1-sentinel-file-datasource load 2 FlowRule

如果采用 Nacos 作为配置获取限流规则,可在文件中加如下配置:

spring:
    application:
    name: order - service
cloud:
    nacos:
    config:
    server - addr: 10.10 .15 .5: 8848
discovery:
    server - addr: 10.10 .15 .5: 8848
sentinel:
    eager: true
transport:
    dashboard: 10.10 .15 .5: 8080
datasource:
    ds1:
    nacos:
    server - addr: 10.10 .15 .5: 8848
dataId: $
{
    spring.application.name
} - flow - rules
data - type: json
rule - type: flow

我们可以使用以上的方法实现Spring Cloud限流,当然这也是在面试中常问的,所以建议大家多看一些Spring Cloud面试题哦!最后大家如果想要了解更多Java面试题知识,敬请关注奇Q工具网。

推荐阅读:

如何用java开发表白程序?java开发表白程序实例

idea怎么在项目下创建模块?Idea模块如何拆卸?

java初学者用什么软件?java初学者软件分享