• 服务网关实践


    概述

    微服务架构由很多个微小的服务(微服务)组成系统,每个微服务有自己的主机和端口号。如果直接让客户端与各个微服务通信,带来的问题有:

    • 客户端需要维护多个请求地址(主机和端口号)
    • 每个微服务都需要独立认证
    • 某些场景存在跨域问题

    在客户端和各个微服务之间添加微服务网关可以解决如上问题,客户端的请求都先经过微服务网关,客户端就只需要知道网关的请求地址即可,添加网关的优点有:

    • 统一入口,只需要维护一个请求地址(主机和端口号)
    • 易于监控
    • 统一认证

    本文介绍的微服务网关技术是Spring Cloud GateWay。该项目提供了一个用于在 Spring WebFlux 或 Spring WebMVC 之上构建 API 网关的库。 Spring Cloud Gateway 旨在提供一种简单而有效的方法来路由到 API 并为其提供横切关注点,例如:安全性、监控。

    本文的操作是在服务熔断保护实践--Sentinal的基础上进行。

    环境说明

    jdk1.8

    maven3.6.3

    mysql8

    spring cloud2021.0.8

    spring boot2.7.12

    idea2022

    步骤

    创建子模块

    在父工程下,创建子模块:api_gateway_server

    添加依赖

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-gateway</artifactId>
    5. </dependency>
    6. </dependencies>

    启动类

    1. package org.example.gateway;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. @SpringBootApplication
    5. public class GatewayServerApplication {
    6. public static void main(String[] args) {
    7. SpringApplication.run(GatewayServerApplication.class, args);
    8. }
    9. }

    路由配置

    配置文件application.yml

    1. server:
    2. port: 8080
    3. spring:
    4. application:
    5. name: api-gateway-server
    6. cloud:
    7. gateway:
    8. routes:
    9. - id: product-service
    10. uri: http://127.0.0.1:9001
    11. predicates:
    12. - Path=/product/**

    配置的作用:通过网关访问路径符合/product/开头时,网关会将请求转发到uri(http://127.0.0.1:9001)的对应的路径下。例如:访问localhost:8080/product/1,网关会将请求转发到http://127.0.0.1:9001/product/1,其中127.0.0.1localhost可以互相替换。

    启动网关服务报错如下

    Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway.

    Action:

    Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.

    启动报错原因:

    SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容,所以gateway不需要引入web依赖,否则造成依赖冲突。

    解决启动报错:

    父工程不全局引入web依赖,需要的子工程单独引入web依赖,不需要则不引入(例如:gateway工程不需要则不引入)

    父工程,删除web依赖

    在需要的子工程(order-service、order-service-feign_hystrix、product-service)里添加Web依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-webartifactId>
    4. dependency>

    刷新依赖

    测试

    启动eureka服务、启动product服务(确认product服务的端口号为9001)

    浏览器访问

    直接访问微服务

    http://localhost:9001/product/1

     通过网关访问

    http://localhost:8080/product/1

    可以看到通过网关访问和直接访问微服务效果一样。

    路由规则

    Spring Cloud Gateway 的功能很强大,前面只是使用了 Predicates(断言) 进行了简单的条件匹配,其实Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。在 Spring Cloud Gateway 中 Spring 利用Predicate 的特性实现了各种路由匹配规则,如:通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。

    路由匹配示例

    1. spring:
    2. cloud:
    3. gateway:
    4. routes:
    5. - id: product-service
    6. uri: https://xxxx.com
    7. predicates:
    8. # 路由断言之前匹配
    9. - Before=xxx
    10. # 路由断言之后匹配
    11. - After=xxx
    12. # 路由断言之间匹配
    13. - Between=xxx,xxx
    14. # 路由断言Cookie匹配,此predicate匹配给定名称(chocolate)和正则表达式(ch.p)
    15. - Cookie=chocolate, ch.p
    16. # 路由断言Header匹配,header名称匹配X-Request-Id,且正则表达式匹配\d+
    17. - Header=X-Request-Id, \d+
    18. # 路由断言匹配Host匹配,匹配下面Host主机列表,**代表可变参数
    19. - Host=**.somehost.org, **.anotherhost.org
    20. # 路由断言Method匹配,匹配的是请求的HTTP方法
    21. - Methos=GET
    22. # 路由断言访问路径匹配,{segment}为可变参数
    23. - Path=/foo/{segment},/bar/{segment}
    24. # 路由断言请求参数匹配,将请求的参数param(baz)进行匹配
    25. - Query=baz
    26. # 路由断言请求参数匹配,可以进行regexp正则表达式匹配 (参数包含foo,并且foo的值匹配ba.)
    27. - Query=foo,ba.
    28. # 路由断言RemoteAddr匹配,将匹配192.168.1.1~192.168.1.254之间的ip地址,其中24为子网掩码位 数即255.255.255.0
    29. - RemoteAddr=192.168.1.1/24

    动态路由(面向服务的路由)

    之前我们配置路由uri是直接配置了微服务的主机和端口号:uri: http://127.0.0.1:9001

    如果微服务地址有变化,需要修改网关配置uri,耦合度高。

    Spring Cloud GateWay支持动态路由:即自动的从注册中心中获取服务列表并访问。

    api_gateway_server服务(网关服务)引入eureka依赖

    1. <dependency>
    2. <groupId>org.springframework.cloudgroupId>
    3. <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
    4. dependency>

    修改网关服务配置文件application.yml

    • 路由uri的值改为 lb://service-product

           lb代表从注册中心获取服务,同时具备赋值均衡功能,//后面接的是服务名称

    • 添加配置eureka的注册中心相关信息
    1. server:
    2. port: 8080
    3. spring:
    4. application:
    5. name: api-gateway-server
    6. cloud:
    7. gateway:
    8. routes:
    9. - id: product-service
    10. uri: lb://service-product
    11. predicates:
    12. - Path=/product/**
    13. # 配置eureka
    14. eureka:
    15. client:
    16. service-url:
    17. defaultZone: http://localhost:9000/eureka/
    18. instance:
    19. prefer-ip-address: true

    配置的作用:通过网关访问,如果路径包含/product开头,会转发到uri(lb://service-product) 对应的微服务接口。例如:访问localhost:8080/product/1符合断言路径的条件,网关将请求会转发到service-product微服务,微服务名称映射的主机和端口号从eureka注册中心得到,转发对应的接口为localhost:9001/product/1

    测试

    重启gateway服务

    查看eureka服务

    访问正常

    http://localhost:8080/product/1

    过滤器重写请求路径

    为了让更好识别访问路径将转发到哪个服务,在网关访问路径中带有具有识别作用的特定路径,例如:用带有product-service的路径表示请求将转发到product服务。

    完整的访问路径如下

    http://localhost:8080/product-service/product/1

    响应错误页面

    要访问成功,可以使用过滤器重写请求路径,

    application.yml配置如下:

    1. predicates:
    2. #- Path=/product/**
    3. - Path=/product-service/**
    4. filters:
    5. - RewritePath=/product-service/(?<segment>),/$\{segment} #路径重写的过滤器,把/product-service/xxx的路径重写为/xxx

    重启网关服务

    再次访问

    http://localhost:8080/product-service/product/1

    成功访问到数据

    过滤器

    过滤器类型:局部过滤器(GatewayFilter)和全局过滤器(GlobalFilter)。

    局部过滤器(GatewayFilter):应用到单个路由或者一个分组的路由上

    全局过滤器(GlobalFilter):应用到所有路由上。

    局部过滤器

    部分局部过滤器如下

    过滤器工厂作用参数
    AddRequestHeader为原始请求添加HeaderHeader的名称及值
    AddRequestParameter为原始请求添加请求参数参数名称及值
    AddResponseHeader为原始响应添加HeaderHeader的名称及值
    DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
    Hystrix为路由引入Hystrix的断路器保护HystrixCommand 的名称
    FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
    PrefixPath为原始请求路径添加前缀前缀路径
    PreserveHostHeader为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
    RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
    RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
    RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用,可以通过配置指定仅删除哪些Header
    RemoveRequestHeader为原始请求删除某个HeaderHeader名称
    RemoveResponseHeader为原始响应删除某个HeaderHeader名称
    RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
    RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正则表达式,重写后的值
    SaveSession在转发请求之前,强制执行WebSession::save 操作
    secureHeaders为原始响应添加一系列起安全作用的响应头无,支持修改这些安全响应头的值
    SetPath修改原始的请求路径修改后的路径
    SetResponseHeader修改原始响应中某个Header的值Header名称,修改后的值
    SetStatus修改原始响应的状态码HTTP 状态码,可以是数字,也可以是字符串
    StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量
    Retry针对不同的响应进行重试retries、statuses、methods、series
    RequestSize设置允许接收最大请求包的大 小。如果请求包大小超过设置的值,则返回 413 Payload TooLarge请求包大小,单位为字节,默认值为5M
    ModifyRequestBody在转发请求之前修改原始请求体内容修改后的请求体内容
    ModifyResponseBody修改原始响应体的内容修改后的响应体内容

    每个过滤器工厂都对应一个实现类,并且这些类的名称必须以GatewayFilterFactory 结尾,这是Spring Cloud Gateway的一个约定,例如 AddRequestHeader 对应的实现类为AddRequestHeaderGatewayFilterFactory

    全局过滤器

    全局过滤器(GlobalFilter)作用于所有路由。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。

    GloabalFilter接口源码如下:

    过滤器应用
    网关统一鉴权

    编写一个全局过滤器

    1. package org.example.gateway.filter;
    2. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    3. import org.springframework.cloud.gateway.filter.GlobalFilter;
    4. import org.springframework.core.Ordered;
    5. import org.springframework.stereotype.Component;
    6. import org.springframework.web.server.ServerWebExchange;
    7. import reactor.core.publisher.Mono;
    8. /**
    9. * 自定义全局过滤器
    10. */
    11. @Component
    12. public class LoginFilter implements GlobalFilter, Ordered {
    13. /**
    14. * 过滤器逻辑
    15. */
    16. @Override
    17. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    18. System.out.println("执行了自定义的全局过滤器");
    19. return chain.filter(exchange);//放行,继续向下执行
    20. }
    21. /**
    22. * 指定过滤器执行顺序
    23. * 返回值越小,优先级越高
    24. */
    25. @Override
    26. public int getOrder() {
    27. return 0;
    28. }
    29. }

    重启网关服务

     访问

    http://localhost:8080/product-service/product/1

    看到数据如下

    查看网关服务IDEA控制台的输出,看到执行了自定义的全局过滤器。

    说明过滤器正常工作了。

    鉴权逻辑:

    • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)

    • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证

    • 以后每次请求,客户端都携带认证的token

    • 服务端对token进行解密,判断是否有效

    修改LoginFilter的filter方法

    1. /**
    2. * 过滤器逻辑
    3. * 对请求参数中的access-token进行判断
    4. * 如果存在此参数:代表已经认证成功
    5. * 如果不存在此参数:代表认证失败
    6. * ServerWebExchange:相当于请求和响应的上下文(类似于zuul中的RequestContext)
    7. */
    8. @Override
    9. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    10. System.out.println("执行了自定义的全局过滤器");
    11. //1.获取请求参数,例如:access-token
    12. String token = exchange.getRequest().getQueryParams().getFirst("access-token");
    13. //2.判断是否存在,不存在则认证失败,存在则放行
    14. if(token == null){
    15. System.out.println("没有登录");
    16. exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
    17. return exchange.getResponse().setComplete();//请求结束
    18. }
    19. return chain.filter(exchange);//放行,继续向下执行
    20. }

    重启网关服务

    测试

    • 未携带access-token测试

      http://localhost:8080/product-service/product/1

    IDEA控制台提示没有登录  

    • 携带access-token测试

      http://localhost:8080/product-service/product/1?access-token=1

    网关限流

    可以在网关阶段对服务调用进行限流,达到保护服务的作用。

    限流算法

    常见的限流算法有:计数器限流算法、滑动窗口算法、漏桶算法、令牌桶算法。

    每种算法原理、优缺点、使用场景也各不一样,具体可参考 常见限流算法

    限流后的处理方式
    • 拒绝服务

    • 排队等待

    • 服务降级

    基于filter的限流

    Spring Cloud Gateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过redis和lua脚本结合的方式进行流量控制。

    安装好redis

    启动redis服务端

    启动redis客户端,使用monitor命令监控redis数据变化信息

    127.0.0.1:6379> monitor

    在api_gateway_server服务中引入redis相关依赖(基于reactive的redis依赖)

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-actuatorartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-data-redis-reactiveartifactId>
    8. dependency>

     修改application.yml,配置redis和网关过滤器

    1. server:
    2. port: 8080 #端口
    3. spring:
    4. application:
    5. name: api-gateway-server #服务名称
    6. redis:
    7. host: localhost
    8. port: 6379
    9. database: 0
    10. cloud: #配置SpringCloudGateway的路由
    11. gateway:
    12. routes:
    13. - id: product-service
    14. uri: lb://service-product
    15. predicates:
    16. - Path=/product-service/**
    17. filters:
    18. - name: RequestRateLimiter
    19. args:
    20. # 使用SpEL从bean容器中获取对象
    21. key-resolver: '#{@pathKeyResolver}'
    22. # 令牌桶每秒填充平均速率
    23. redis-rate-limiter.replenishRate: 1
    24. # 令牌桶的上限
    25. redis-rate-limiter.burstCapacity: 3
    26. - RewritePath=/product-service/(?<segment>.*), /$\{segment}
    27. #eureka注册中心
    28. eureka:
    29. client:
    30. service-url:
    31. defaultZone: http://localhost:9000/eureka/
    32. instance:
    33. prefer-ip-address: true #使用ip地址注册

    虽然args相关配置存在报错如下,但是经验证,不影响运行。

    org.example.gateway包下,新建config包,并在config包下编写KeyResolverConfiguration配置

    1. package org.example.gateway.config;
    2. import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import reactor.core.publisher.Mono;
    6. @Configuration
    7. public class KeyResolverConfiguration {
    8. /**
    9. * 基于请求路径的限流
    10. */
    11. @Bean
    12. public KeyResolver pathKeyResolver() {
    13. return exchange -> Mono.just(
    14. exchange.getRequest().getPath().toString()
    15. );
    16. }
    17. }

    启动eureka、product、gateway服务

    浏览器访问

    http://localhost:8080/product-service/product/1?access-token=1

    连续多次访问,每秒超过3次访问后,响应如下:

    查看redis客户端监控的输出如下:

    以上实现了基于请求路径的限流,其他方式限流,参考代码如下:

    1. package org.example.gateway.config;
    2. import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import reactor.core.publisher.Mono;
    6. @Configuration
    7. public class KeyResolverConfiguration {
    8. /**
    9. * 基于请求路径的限流
    10. * 方法名称与yml中key-resolver的值对应
    11. */
    12. @Bean
    13. public KeyResolver pathKeyResolver() {
    14. return exchange -> Mono.just(
    15. exchange.getRequest().getPath().toString()
    16. );
    17. }
    18. // 如果是其他的方式限流,代码如下,同时注意修改yml的key-resolver的值。 注意只能有一个@Bean生效,否则启动出错
    19. /**
    20. * 基于请求ip地址的限流
    21. */
    22. // @Bean
    23. // public KeyResolver ipKeyResolver() {
    24. // return exchange -> Mono.just(
    25. // exchange.getRequest().getHeaders().getFirst("X-Forwarded-For")
    26. // );
    27. // }
    28. /**
    29. * 基于用户的限流(参数)
    30. */
    31. // @Bean
    32. // public KeyResolver userKeyResolver() {
    33. // return exchange -> Mono.just(
    34. // exchange.getRequest().getQueryParams().getFirst("user")
    35. // );
    36. // }
    37. }

    测试其他的限流,例如:基于用户参数的限流

    注释其他Bean代码,让如下Bean生效

    1. /**
    2. * 基于请求参数的限流
    3. * 请求 ?userId=
    4. * @return
    5. */
    6. @Bean
    7. public KeyResolver userKeyResolver(){
    8. return exchange -> Mono.just(
    9. exchange.getRequest().getQueryParams().getFirst("userId")
    10. );
    11. }

    修改application.yml

                    key-resolver: '#{@userKeyResolver}'

    测试

    http://localhost:8080/product-service/product/1?userId=1&access-token=1

    访问达不到限流条件时

    访问达到限流条件时

    基于sentinel的限流

    api_gateway_server服务添加如下依赖

    1. <dependency>
    2. <groupId>com.alibaba.cspgroupId>
    3. <artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
    4. dependency>

    添加配置类

    1. package org.example.gateway.config;
    2. import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
    3. import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
    4. import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
    5. import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
    6. import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
    7. import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
    8. import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
    9. import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
    10. import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
    11. import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
    12. import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
    13. import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
    14. import org.springframework.beans.factory.ObjectProvider;
    15. import org.springframework.cloud.gateway.filter.GlobalFilter;
    16. import org.springframework.context.annotation.Bean;
    17. import org.springframework.context.annotation.Configuration;
    18. import org.springframework.core.Ordered;
    19. import org.springframework.core.annotation.Order;
    20. import org.springframework.core.io.buffer.DataBuffer;
    21. import org.springframework.http.HttpStatus;
    22. import org.springframework.http.MediaType;
    23. import org.springframework.http.codec.ServerCodecConfigurer;
    24. import org.springframework.http.server.reactive.ServerHttpResponse;
    25. import org.springframework.web.reactive.function.BodyInserters;
    26. import org.springframework.web.reactive.function.server.ServerResponse;
    27. import org.springframework.web.reactive.result.view.ViewResolver;
    28. import org.springframework.web.server.ServerWebExchange;
    29. import reactor.core.publisher.Mono;
    30. import javax.annotation.PostConstruct;
    31. import java.nio.charset.StandardCharsets;
    32. import java.util.*;
    33. @Configuration
    34. public class GatewayConfiguration {
    35. private final List viewResolvers;
    36. private final ServerCodecConfigurer serverCodecConfigurer;
    37. public GatewayConfiguration(ObjectProvider> viewResolversProvider,
    38. ServerCodecConfigurer serverCodecConfigurer) {
    39. this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
    40. this.serverCodecConfigurer = serverCodecConfigurer;
    41. }
    42. /**
    43. * 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
    44. */
    45. @Bean
    46. @Order(Ordered.HIGHEST_PRECEDENCE)
    47. public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
    48. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    49. }
    50. /**
    51. * 配置限流过滤器
    52. */
    53. @Bean
    54. @Order(Ordered.HIGHEST_PRECEDENCE)
    55. public GlobalFilter sentinelGatewayFilter() {
    56. return new SentinelGatewayFilter();
    57. }
    58. /**
    59. * 配置初始化的限流参数
    60. */
    61. @PostConstruct
    62. public void initGatewayRules() {
    63. Set rules = new HashSet<>();
    64. // rules.add(
    65. // new GatewayFlowRule("order-service") //资源名称
    66. // .setCount(1) // 限流阈值
    67. // .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
    68. // );
    69. // rules.add(new GatewayFlowRule("order-service")
    70. // .setCount(1)
    71. // .setIntervalSec(1)
    72. // .setParamItem(new GatewayParamFlowItem()
    73. // .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM).setFieldName("id")
    74. // )
    75. // );
    76. //限流规则对访问路径匹配product-service开头的路径生效
    77. rules.add(new GatewayFlowRule("product-service")
    78. .setCount(1)
    79. .setIntervalSec(1)
    80. );
    81. // rules.add(new GatewayFlowRule("product_api")
    82. // .setCount(1)
    83. // .setIntervalSec(1)
    84. // );
    85. // rules.add(new GatewayFlowRule("order_api")
    86. // .setCount(1)
    87. // .setIntervalSec(1)
    88. // );
    89. GatewayRuleManager.loadRules(rules);
    90. }
    91. // @PostConstruct
    92. // public void initBlockHandlers() {
    93. // BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
    94. // public Mono handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
    95. // Map map = new HashMap<>();
    96. // map.put("code", 001);
    97. // map.put("message", "对不起,接口限流了");
    98. // return ServerResponse.status(HttpStatus.OK).
    99. // contentType(MediaType.APPLICATION_JSON_UTF8).
    100. // body(BodyInserters.fromObject(map));
    101. // }
    102. // };
    103. // GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    104. // }
    105. // @PostConstruct
    106. // private void initCustomizedApis() {
    107. // Set definitions = new HashSet<>();
    108. // ApiDefinition api1 = new ApiDefinition("product_api")
    109. // .setPredicateItems(new HashSet() {{
    110. // add(new ApiPathPredicateItem().setPattern("/product-service/product/**").
    111. // setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
    112. // }});
    113. // ApiDefinition api2 = new ApiDefinition("order_api")
    114. // .setPredicateItems(new HashSet() {{
    115. // add(new ApiPathPredicateItem().setPattern("/order-service/order"));
    116. // }});
    117. // definitions.add(api1);
    118. // definitions.add(api2);
    119. // GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    120. // }
    121. }

    以上配置,根据路由(访问路径)进行限流,限流规则对访问路径匹配product-service开头的路径生效。

    修改application.yml

    • 注释RequestRateLimiter filter相关配置
    1. filters:
    2. # - name: RequestRateLimiter
    3. # args:
    4. # # 以下上个配置项目报错,不能解析,但不影响使用
    5. # # 使用SpEL从容器中获取对象
    6. # #key-resolver: '#{@pathKeyResolver}'
    7. # key-resolver: '#{@userKeyResolver}'
    8. # # 令牌桶每秒填充平均速率
    9. # redis-rate-limiter.replenishRate: 1
    10. # # 令牌桶的上限
    11. # redis-rate-limiter.burstCapacity: 3
    12. - RewritePath=/product-service/(?.*), /$\{segment}
    •  注释掉redis相关配置
    1. # redis:
    2. # host: localhost
    3. # port: 6379
    4. # database: 0

    为了防止启动服务报redis连接拒绝,注释掉redis依赖,同时注意刷新依赖生效

    测试

    启动eureka、product、gateway服务

    访问

    http://localhost:8080/product-service/product/1?access-token=1

    访问超过阈值后,响应

    Blocked by Sentinel: ParamFlowException

    因为路径中包含product-service开头,符合限流规则,所以当访问超出哦阈值后,执行限流操作,看到如上默认的响应信息。

    为了更加友好的限流提示,可以自定义响应信息,如下:

    取消initBlockHandlers方法的注释

    1. /**
    2. * 自定义限流提示
    3. */
    4. @PostConstruct
    5. public void initBlockHandlers() {
    6. BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
    7. public Mono handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
    8. Map map = new HashMap<>();
    9. map.put("code", 001);
    10. map.put("message", "对不起,接口限流了");
    11. return ServerResponse.status(HttpStatus.OK).
    12. contentType(MediaType.APPLICATION_JSON_UTF8).
    13. body(BodyInserters.fromObject(map));
    14. }
    15. };
    16. GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    17. }

    重启gateway服务

    测试,访问超过限流阈值时,响应如下

    以上是基于路由的限流规则,这个路由下的所有资源都受到这个限流规则限制,sentinel也支持自定义API限流规则,去掉initCustomizedApis()方法注释

    1. /**
    2. * 自定义API限流规则
    3. * 1.定义分组,例如:product_api和order_api
    4. * 2.在initGatewayRules方法对小组配置限流规则
    5. */
    6. @PostConstruct
    7. private void initCustomizedApis() {
    8. Set definitions = new HashSet<>();
    9. ApiDefinition api1 = new ApiDefinition("product_api")
    10. .setPredicateItems(new HashSet() {{
    11. add(new ApiPathPredicateItem().setPattern("/product-service/product/**").
    12. setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
    13. }});
    14. ApiDefinition api2 = new ApiDefinition("order_api")
    15. .setPredicateItems(new HashSet() {{
    16. add(new ApiPathPredicateItem().setPattern("/order-service/order"));
    17. }});
    18. definitions.add(api1);
    19. definitions.add(api2);
    20. GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    21. }

    在initGatewayRules方法对小组配置限流规则

    1. @PostConstruct
    2. public void initGatewayRules() {
    3. Set rules = new HashSet<>();
    4. rules.add(new GatewayFlowRule("product_api")
    5. .setCount(1)
    6. .setIntervalSec(1)
    7. );
    8. // rules.add(new GatewayFlowRule("order_api")
    9. // .setCount(1)
    10. // .setIntervalSec(1)
    11. // );
    12. GatewayRuleManager.loadRules(rules);
    13. }

    重启gateway服务

    测试

    访问超过阈值后,同样能够起到限流效果

    网关高可用

    所有请求都需要先经过网关才到具体的微服务,网关只有一个节点会存在单点故障,解决办法是使用多个网关服务构成网关集群,实现网关高可用,当一个网关服务不可用,还有其他网关服务可用。实现架构如下图所示:

    为了方便测试,取消网关限流配置,注释@Configuration注解

    1. //@Configuration
    2. public class GatewayConfiguration {

    重启网关服务(8080端口)

    复制配置得到另一个网关服务

    修改api_gateway_server网关服务的配置文件 application.yml,将端口号改为8081,通过选中运行中的GatewayServerApplication,右键-->Copy Configuration复制配置得到另一个网关服务运行实例

    运行复制得到的网关服务(8081端口)

    访问8080和8081均能访问到服务

    http://localhost:8080/product-service/product/1?access-token=1

    http://localhost:8081/product-service/product/1?access-token=1

    官网下载并解压nginx安装包

    修改nginx配置,进入conf目录,修改nginx.conf文件

    注释掉原来location /的配置,添加如下配置

    1. http {
    2. upstream gateway{
    3. server 127.0.0.1:8080;
    4. server 127.0.0.1:8081;
    5. }
    6. server {
    7. ...
    8. location / {
    9. proxy_pass http://gateway;
    10. }
    11. ...
    12. }
    13. }

    配置后,如图 

    启动nginx

    浏览器访问

    http://localhost/product-service/product/1?access-token=1

    如果需要改策略,可以修改nginx配置,配置案例如下

    IP哈希(IP Hash)策略

    upstream gateway {
            ip_hash;
            server 127.0.0.1:8080;
            server 127.0.0.1:8081;
        }

    重启nginx,同一机器访问服务三次,均是8080网关提供服务

    最少连接(Least Connections)策略

    upstream gateway {
            least_conn;
            server 127.0.0.1:8080;
            server 127.0.0.1:8081;
        }

    加权轮询(Weighted Round Robin)策略

    upstream gateway {
            server 127.0.0.1:8080 weight=3;
            server 127.0.0.1:8081 weight=1;
        }

    模拟故障,停掉一个网关服务

    依然能访问,实现了网关的高可用。

    完成!enjoy it!

  • 相关阅读:
    实验2 朴素贝叶斯和SVM 实操项目1 肿瘤分类与预测(朴素贝叶斯)
    解决Node服务启动失败的问题
    【考研复试】计算机专业考研复试英语常见问题四(优缺点/观点/观念篇)
    C语言游戏实战(4):人生重开模拟器
    JVM 运行期优化 & 反射优化
    linux内核分析:内存映射,文件系统
    鸿蒙开发通信与连接:【@ohos.rpc (RPC通信)】
    python-爬虫-requests
    开关电源环路稳定性分析(01)-Buck变换器
    微信小程序自定义导航
  • 原文地址:https://blog.csdn.net/qq_42881421/article/details/134301176