Spring Cloud

Starter

  1. 服务注册中心

    • Eureka (Netflix)
    • Zookeeper
    • Consul
    • Nacos (Alibaba)
  2. 服务配置

    • Config
    • Nacos (Alibaba)
  3. 服务总线

    • Bus
    • Nacos (Alibaba)
  4. 服务调用

    • Ribbon (Netflix)
    • LoadBalancer (SpringCloud)
    • Feign
    • OpenFeign (SpringCloud)
  5. 服务降级

    • Hystrix (Netflix)
    • Resilience4j
    • Sentinel (Alibaba)
  6. 服务网关

    • Zuul (Netflix)
    • Zuul2 (Netflix)
    • Gateway (SpringCloud)

服务注册&配置中心:Nacos (Spring Cloud Alibaba)

  • 服务中心 (同类实现: Eureka,Zookeeper,Consual)
  • 服务配置和总线 (同类实现:Config & Bus)

https://github.com/alibaba/nacos/releases https://nacos.io/zh-cn/docs/quick-start.html https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/en-us/index.html

# 安装
unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz
cd nacos/bin

# 打开
sh startup.sh -m standalone

# 关闭
sh shutdown.sh

Nacos Server

link:http://127.0.0.1:8848/nacos/ 默认账号和密码为:nacos/nacos

注意:Nacos Server 的数据源是用 Derby 还是 MySQL 完全是由其运行模式决定的

  • standalone 的话仅会使用 Derby,即使在 application.properties 里边配置 MySQL 也照样无视
  • cluster 模式会自动使用 MySQL,这时候如果没有 MySQL 的配置,是会报错的
  • 不支持 MySQL 8.0 版本

Nacos 服务注册

  1. parent pom

     <properties>
         <spring.cloud.alibaba>2.2.1.RELEASE</spring.cloud.alibaba>
     </properties>
    
      <dependencyManagement>
         <dependencies>
              <!-- Spring Cloud Alibaba -->
             <dependency>
                 <groupId>com.alibaba.cloud</groupId>
                 <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                 <version>${spring.cloud.alibaba}</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
         </dependencies>
     </dependencyManagement>
    
  2. provider/consumer client pom

     <!-- 服务发现 -->
     <dependency>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
     </dependency>
    
  3. provider

    • application.yml
        server:
            port: 7010
        spring:
          application:
            name: nacos-provider
          cloud:
            nacos:
              discovery:
                server-addr: localhost:8848 # ${NACOS_SERVER:8848}
      
    • main

        @SpringBootApplication
        @EnableDiscoveryClient
        public class NacosProviderApp {
      
            public static void main(String[] args){
                SpringApplication.run(NacosProviderApp.class,args);
            }
      
            @RestController
            public class HelloController {
                @GetMapping("/hi")
                public Object hello(){ return "Hello";}
            }
        }
      
  4. consumer

    • application.yml
        server:
            port: 7020
        spring:
          application:
            name: nacos-consumer
          cloud:
            nacos:
              discovery:
                server-addr: localhost:8848 # ${NACOS_SERVER:8848}
      
    • main

        @SpringBootApplication
        @EnableDiscoveryClient
        public class NacosConsumerApp {
            public static void main(String[] args){
                SpringApplication.run(NacosConsumerApp.class,args);
            }
      
            @Bean
            @LoadBalanced // 使用serviceId访问则一定要加上,否则找不到服务;不加此注解,则可通过普通的ip:host的URL来访问服务
            public RestTemplate restTemplate(){
                return new RestTemplate();
            }
      
            @RestController
            public class HelloController {
                private String remoteServiceId="nacos-provider";
                @Autowired
                private RestTemplate restTemplate;
      
                @GetMapping("/")
                public Object index(){
                    return "Index";
                }
      
                @GetMapping("/hi")
                public Object hello(){
                    String url = String.format("http://%s/hi",remoteServiceId);
                    String result = restTemplate.getForObject(url,String.class);
                    return "Say: "+result;
                }
            }
        }
      
  5. Test

Nacos 服务配置

  1. provider/consumer client pom

     <!-- 服务配置 -->
     <dependency>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
     </dependency>
      <!-- spring cloud 2020,需加上这个才能读取bootstrap.yml,Nacos Config才能正常使用-->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-bootstrap</artifactId>
     </dependency>
    
  2. bootstrap.yml (或者 bootstrap.properties) 优先级高于application.yml,nacos相关配置需配在此处

     server:
       port: 7010
    
     spring:
       application:
         name: nacos-provider
       cloud:
         nacos:
           discovery:
             server-addr: localhost:8848 # ${NACOS_SERVER:8848} | 注意:必须有端口号
           config:
             server-addr: localhost:8848
             file-extension: yaml # 支持properties(默认),yaml | 注意:nacos识别yaml,yml会报错
             namespace: dear-v1 # 命名空间ID,不是命名空间名称 | 注意:只能配置一个命名空间
             group: NACOS_GROUP # 默认DEFAULT_GROUP | 注意:只能配置一个GROUP
             # => 加载的dataId为: ${prefix}-${spring.profiles.active}.${file-extension}
             # prefix 默认为 spring.application.name
             # spring.profiles.active为空时 => 对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
             ext-config: # 列表,优先级都低于上面正常group&namespace下的配置,列表的优先级是下标越大优先级越高
               + data-id: logback-spring.yaml
                 group: LOG_GROUP
                 refresh: true
               + data-id: external.yaml
                 group: EXTERNAL_GROUP
                 refresh: true
    
  3. application.yml (optional): spring.profiles.active 可在运行时指定

     spring:
       profiles:
         active: dev
    
  4. Controller

     @RestController
     @RefreshScope // 实现配置自动更新
     public class ConfigController{
    
         @Value("${provider.name:UnKnow}")
         private String providerName;
    
         @Value("${welcome:UnKnow}")
         private String welcome;
    
         @GetMapping("/config")
         public Object config(){
             return "ProviderName: "+providerName +"; Welcome: "+welcome;
         }
     }
    
  5. Nocos 配置界面:http://127.0.0.1:8848/nacos,添加配置文件

    • namesapce: dear-v1
    • group:
      • EXTERNAL_GROUP
        • external.yaml
            welcome: External Welcome V1
          
      • LOG_GROUP
        • logback-spring.yaml
            logging:
                config: classpath:logback-spring-${spring.profiles.active}.xml
                path: logs
                package: com.cj.dear
          
      • NACOS_GROUP
        • nacos-provider.yaml
            provider:
                name: Default Nacos Provider
          
        • nacos-provider-dev.yaml
            provider:
                name: Dev Nacos Provider
          
        • nacos-provider-prod.yaml
            provider:
                name: Prod Nacos Provider
          
  6. 启动运行,访问 http://localhost:7010/config

    • Run Configuration => program arguments --spring.profiles.active=prod
    • 使用命令行:java -jar nacos-provider.jar --spring.profiles.active=dev

链路追踪:Sleuth & Zipkin

Sleuth

  • trace: 整个调用链路,一系列span组成一个树状结构 -- tranceID
  • span: 每个最小的工作单元(一次微服务调用)-- spanID

Sleuth 帮助记录这些traceID,spanID Zipkin Twitter的一个开源项目,收集链路的跟踪数据,提供可插拔的数据存储方式,UI直观查看分析

Zipkin

https://zipkin.io/ https://github.com/openzipkin/openzipkin.github.io https://github.com/openzipkin/zipkin

核心组件:

  • Collector 收集链路信息
  • Storage 存储
  • Restful API
  • Web UI

分两端:

  • 服务端
    • Zipkin Server 下载 https://zipkin.io/pages/quickstart
    • 启动 java -jar zipkin.jar 或使用docker docker run -d -p 9411:9411 openzipkin/zipkin
    • visit http://localhost:9411
  • 客户端 发送链路信息给服务端
    • HTTP报文方式发送
    • 消息总线方式(如RabbitMQ)

Sample

  1. pom.xml (所有微服务)

     <!-- 链路追踪 Sleuth-->
     <!--<dependency>-->
         <!--<groupId>org.springframework.cloud</groupId>-->
         <!--<artifactId>spring-cloud-starter-sleuth</artifactId>-->
     <!--</dependency>-->
     <!-- Zipkin Client(dependence 已包含了sleuth相关依赖) 收集链路信息,发送给Zipkin Server -->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-zipkin</artifactId>
     </dependency>
    
  2. application.xml (所有微服务)

     spring:
       zipkin:
         base-url: http://localhost:9411 # Zipkin Server端地址
         sender:
           type: web                     # 数据用HTTP方式传送给Zipkin Server端的方式
       sleuth:
         sampler:
           probability: 0.5              # 数据采样比(0~1,默认0.1)
    
  3. Test (多请求一些链接,然后去 http://localhost:9411 查看可视化界面)

优化:链路数据持久化(默认存储在内存)

https://github.com/openzipkin/zipkin/tree/master/zipkin-server

Sample: 持久化到MySQL

  1. 准备数据库表

  2. 重启 Zipkin Server, 启动时传递相关配置参数

优化:链路数据传输方式(默认Http方式发送给Zipkin Server)

Sample: 使用消息中间件

  1. 准备RabbitMQ服务器 http://localhost:15672

  2. 修改Zipkin Server,从RabbitMQ中拉取信息

  3. 修改微服务Zipkin client 相关配置,将采集信息发送RabbitMQ

服务调用:OpenFeign (Spring Cloud)

服务调用(同类实现:Ribbon,LoadBalancer,Feign)

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

  • 依赖注册中心
  • 使用本地负载均衡器
    • 常见算法:轮询,随机,固定IP,权重,Hash一致性,...
  1. pom.xml

     <!-- 服务发现 Nacos-->
     <dependency>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
         <!-- 排除ribbon, 使用Springcloud loadbalancer (f否则启动报错)-->
         <exclusions> 
             <exclusion>
                 <groupId>org.springframework.cloud</groupId>
                 <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
             </exclusion>
         </exclusions>
     </dependency>
    
     <!-- Spring Cloud OpenFeign -->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-openfeign</artifactId>
     </dependency>
    
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-loadbalancer</artifactId>
     </dependency>
    
  2. application.yml

     server:
       port: 7020
    
     spring:
       application:
         name: nacos-consumer
       cloud:
         nacos:
           discovery:
             server-addr: localhost:8848 # ${NACOS_SERVER:8848}
             namespace: dear-v1
         loadbalancer:
           ribbon:
             enabled: false
    
     # config feign -- optional
     feign:
       client:
         config:
           nacos-provider: #  FeignClient名 或使用 default
             connectTimeout: 5000
             readTimeout: 5000
             loggerLevel: FULL
    
     logging:
       level:
         com.cj.dear.nacos: debug
       config: classpath:logback-spring-dev.xml
       path: logs
    
  3. 激活Feign @EnableFeignClients

     @SpringBootApplication
     @EnableDiscoveryClient
     @EnableFeignClients
     public class NacosConsumerApp {
         public static void main(String[] args){
             SpringApplication.run(NacosConsumerApp.class,args);
         }
     }
    
  4. FeignClient (注意:feign要求指明确定返回值类,才能正确解析方法返回值,即方法返回值不能使用Object!)

     @FeignClient("nacos-provider")
     public interface ProviderFeignClient{
    
         @GetMapping("/hi")
         String hello();
    
         @GetMapping("/config")
         String config();
     }
    
  5. Usage

     @RestController
     public class HelloController{
         @Autowired
         private ProviderFeignClient providerFeignClient;
    
         @GetMapping("callProvider")
         public Object callProvider(@RequestParam(required = false,
                 name="func",defaultValue = "hi")String func){
             System.out.println("func:"+func);
             if(func.equals("hi"))
                 return "Call "+func+" Result: "+providerFeignClient.hello();
             return "Call "+func+" Result: "+providerFeignClient.config();
         }
     }
    
  6. Test (启动N个nacos-provider)

服务降级:Sentinel(Spring Cloud Alibaba)

服务降级(同类实现:Hystrix,Resilience4j)

https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/en-us/index.html#_spring_cloud_alibaba_sentinel

https://github.com/alibaba/Sentinel/

https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

高并发问题 由于请求积压,造成服务瘫痪,服务与服务之间存在依赖性,故障传播,连锁反应,造成整个系统崩溃 (雪崩)=》 处理方案:

  • 服务隔离
    • 线程池隔离(每个服务接口有自己独立的线程池,互不影响)
    • 信号量隔离(计数器,最大阈值,超过则拒绝)
  • 服务熔断,降级
  • 服务限流

Sentinel 控制台

https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0

  1. 启动Sentinel控制台

     java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
    
    • 启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口。
    • 若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx 指定客户端监控 API 的端口(默认是 8719)
  2. visit: http://localhost:8080/

    • username/password: sentinel/sentinel

Sample

  1. pom.xml

     <!-- Spring Cloud Aalibaba Sentinel -->
     <dependency>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
     </dependency>
    
  2. application.yml

     spring:
       cloud:
         sentinel:
           transport:
             dashboard: localhost:8080
           eager: true # 默认false,懒加载
    
  3. java

     @GetMapping("callProvider")
     @SentinelResource(value="callProvider",
             blockHandler="callProviderBlockHandler",fallback="callProviderFallbackHandler")
     public Object callProvider(@RequestParam(required = false,
             name="func",defaultValue = "hi")String func){
         System.out.println("func:"+func);
         if(func.equals("hi"))
             return "Call "+func+" Result: "+providerFeignClient.hello();
         return "Call "+func+" Result: "+providerFeignClient.config();
     }
    
     // sentinel
     // 1. 限流熔断 blockHandler(捕获BlockException) 注意:参数(最后Optional: BlockException),返回值类型需与原方法一致
     // 2. 异常降级 fallback(捕获其他异常) 注意:参数,返回值类型需与原方法一致(Optional: 可加Throwable)
    
     public Object callProviderBlockHandler(String func,BlockException ex){
         return "CallProvider:"+func+"; BlockHandler";
     }
    
     public Object callProviderFallbackHandler(String func){
         return "CallProvider:"+func+"; FallBack";
     }
    
    • 注意:blockHandler和fallback都配置了,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑
  4. 启动服务

  5. 访问Sentinel控制台,配置限流规则,测试

限流规则持久化

一条限流规则的组成:

  • resource : 资源名(限流规则的作用对象),唯一,默认为请求路径
  • limitApp : 流控针对的调用来源(Sentinel可针对调用者进行限流,填写微服务名,默认default,即不区分调用来源)
  • grade : 阈值类型
    • 0: 线程数
    • 1: QPS
  • count : 单机限流阈值
  • strategy : 流控模式
    • 0: 直接,API达到限流条件时,直接限流
    • 1: 关联,关联资源达到阈值时,限流自己
    • 2: 链路,只记录指定链路上的流量(指定资源从入口资源进来的流量,达到阈值则进行限流,API级别的针对来源)
  • controlBehavior : 流控效果
    • 0: 快速失败,直接拒绝,抛异常
    • 1: Warm up,根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS值
    • 2: 排队等待,匀速排队,让请求匀速通过(阈值类型必须为QPS才可)
  • clusterMode : 是否集群

Sample: 本地文件加载限流规则

  1. resources/flowRule.json

     [
         {
             "resource": "callProvider",
             "limitApp": "default",
             "grade": 0,
             "count": 2,
             "strategy": 0,
             "controlBehavior": 0
         }
     ]
    
  2. resources/application.yml

     spring:
       application:
         name: nacos-consumer
       cloud:
         sentinel:
           transport:
             dashboard: localhost:8080
           eager: true # 默认false,懒加载
           datasource:
             ds1:
               file:  # 配置从本地文件读取限流规则
                 dataType: json
                 ruleType: flow
                 file: classpath:flowRule.json
    
  3. 启动服务

  4. visit sentinel控制台查看规则 http://localhost:8080/

Sample: 从nacos加载限流规则

  1. visit nacos控制台,配置 nacos-consumer-sentinel.json

  2. pom.xml: 增加 sentinel-datasource-nacos

     <!-- Spring Cloud Aalibaba Sentinel -->
     <dependency>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
     </dependency>
     <!-- Sentinel从nacos获取限流规则-->
     <dependency>
         <groupId>com.alibaba.csp</groupId>
         <artifactId>sentinel-datasource-nacos</artifactId>
     </dependency>
    
  3. application.yml

     spring:
       application:
         name: nacos-consumer
       cloud:
         sentinel:
           transport:
             dashboard: localhost:8080
           eager: true # 默认false,懒加载
           datasource:
             ds1:
     #          file: # 配置从本地文件读取限流规则
     #            dataType: json
     #            ruleType: flow
     #            file: classpath:flowRule.json
               nacos: # 配置从nacos中获取
                 dataType: json
                 ruleType: flow
                 serverAddr: localhost:8848
                 namespace: dear-v1
                 dataId: nacos-consumer-sentinel.json
                 groupId: DEFAULT_GROUP
    
  4. 启动服务

  5. visit sentinel控制台查看规则 http://localhost:8080/

服务网关:Gateway(Spring Cloud)

服务网关(同类实现:Zuul1,Zuul2)

  • Netflix Zuul1: 阻塞Servlet
  • Netflix Zuul2: 基于Netty,非阻塞,支持长链接
  • SpringCloud Gateway: 基于Netty,Project Reactor响应式非阻塞,支持长链接,使用WebFlux,Filter链方式

可应用解决问题:

  • 统一微服务登录认证问题,权限控制,减少代码冗余
  • 跨域问题
  • 保护服务,限流,黑白名单等
  • 统一日志

核心概念:

  • 路由 routes: 一个route由id,uri,predicates,filters组成
  • 断言 predicates : 定义匹配来自HttpRequest的任何信息(断言函数输入类型是Spring5.0的ServerWebExchange)
  • 过滤器 filters : 两种类型 GatewayFilter / GlobalFilter

Sample

  1. pom.xml

     <!-- Spring Cloud Gateway | 注意:依赖WebFlux,所以不要引入spring-boot-starter-web,,不然会与SpringMVC冲突,启动报错-->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-gateway</artifactId>
     </dependency>
      <!--<dependency>-->
         <!--<groupId>org.springframework.boot</groupId>-->
         <!--<artifactId>spring-boot-starter-web</artifactId>-->
     <!--</dependency>-->
    
     <!--
     Spring Cloud 2x 有这个问题
     解决报错:Parameter 0 of method loadBalancerWebClientBuilderBeanPostProcessor
     in org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration
     required a bean of type 'org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction'
     that could not be found.
     -->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-loadbalancer</artifactId>
     </dependency>
    
  2. application.yml

     spring:
       profiles:
         active: dev
       cloud:
         gateway:
           discovery:                        # 简化路由配置,自动根据serviceId进行路由转发 eg: http://localhost:81/nacos-consumer/hi 会自动转发到 http://nacos-consumer/hi
             locator:
               enabled: true                 # 开启根据serviceId自动转发|打开后,可使用serviceId,负载均衡定位访问服务
               lower-case-service-id: true   # 微服务名称以小写形式呈现
           routes:
             + id: baidu
               uri: https://www.baidu.com
               filters:
                 + SetPath=/?{args}
               predicates:
                 + Path=/baidu/{args}
             + id: nacos-provider          # 路由的ID,没有固定规则但要求唯一
               uri: lb://nacos-provider/   # 匹配后提供服务的路由地址(lb:// 根据serviceId,负载均衡策略确定具体服务地址)
               filters:
                 + StripPrefix=1           # 过滤器,不加时定位服务为:lb://nacos-provider/provider/**;设置去掉第一段,则变成lb://nacos-provider/**
               predicates:
                 + Path=/provider/**       # 断言,路径相匹配的进行路由
    
  3. 自定义GlobalFilter

     @Component
     public class GatewayTokenFilter implements GlobalFilter {
    
         @Override
         public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
             String token = exchange.getRequest().getQueryParams().getFirst("token");
             if(StringUtils.isEmpty(token)){
                 ServerHttpResponse response = exchange.getResponse();
                 response.setStatusCode(HttpStatus.FORBIDDEN);
                 String msg = "Gateway:Forbidden to visit";
                 DataBuffer df = response.bufferFactory().wrap(msg.getBytes());
                 return response.writeWith(Mono.just(df));
             }
    
             return chain.filter(exchange);
         }
     }
    
  4. Test

  5. 结合Nacos

     server:
       port: 81
    
     spring:
       application:
         name: cloud-gateway
       cloud:
         nacos:
           discovery:
             server-addr: localhost:8848
             namespace: dear-v1
           config:
             server-addr: localhost:8848
             file-extension: yaml
             namespace: dear-v1
             group: CLOUD_GROUP
             ext-config:
               + data-id: logback-spring.yaml
                 group: LOG_GROUP
                 refresh: true
               + data-id: external.yaml
                 group: EXTERNAL_GROUP
                 refresh: true
    
  6. main

     @SpringBootApplication
     @EnableDiscoveryClient
     public class CloudGatewayApp {
         public static void main(String[] args){
             SpringApplication.run(CloudGatewayApp.class,args);
         }
    
         @Value("${provider.name:UnKnow}")
         private String providerName;
    
         @RestController
         @RefreshScope
         public class HelloController{
    
             @Value("${provider.name:UnKnow}")
             private String providerName;
    
             @GetMapping("/hi")  // http://localhost:81/hi => Gateway: Hello | ProviderName: Default Cloud Gateway V5 dev
             public String Hello() {
                 return "Gateway: Hello | ProviderName: "+providerName;
             }
         }
    
     }
    
  7. Test (需启动 Nacos Server,nacos-provider工程)

predicates 断言

RoutePredicateFactory

  • datetime 请求时间校验: After,Before,Between
  • Cookie 请求Cookie校验
  • Header 请求头校验
  • Host 请求Host校验
  • Method 请求方法校验
  • Path 请求路径校验
  • Queryparam 请求参数校验
  • RemoteAddr 请求远程地址校验

开启微服务名称转发

spring.cloud.gateway.discovery.locator.enable=true

spring:
  cloud:
    gateway:
      discovery:                        # 简化路由配置,自动根据serviceId进行路由转发
        locator:
          enabled: true                 # 开启根据serviceId自动转发|打开后,可使用serviceId,负载均衡定位访问服务
          lower-case-service-id: true   # 微服务名称以小写形式呈现

开启后,无需配置routes,即可实现 visit http://localhost:81/nacos-consumer/hi 自动转发到 http://nacos-consumer/hi

Test: http://localhost:81/nacos-consumer/hi?token=11 =》 Say: Hello

过滤器

  • 生命周期:
    • pre
    • post
  • 类型:
    • GatewayFilter 局部过滤器,应用到单一路由,或一个分组路由上
      • Gateway中已内置了很多不同类型的局部过滤器,可直接使用 (实现类以GatewawyFilterFactory结尾)
    • GlobalFilter 全局过滤器,应用到所有路由上,eg 内置的一些全局过滤器
      • LoadBalancer 负载均衡相关过滤器: LoadBalancerClientFilter
      • HttpClient http客户短相关:NettyRoutingFilter
      • Websocket :WebsocketRoutingFilter
      • ForwardPath 路径转发:ForwardPathFilter
      • RouteToRequestUrl 路由Url相关:RouteToRequestUrlFilter
      • WebClient :WebClientHttpRoutingFilter,WebClientWriteResponseFilter

自定义GlobalFilter: 实现GlobalFilter, Ordered(Optional,越小优先级越高)

网关限流

常见限流算法:

  • 计数器
    • 每个单位时间内 => 累计统计单位时间内请求数量 => 是否超过单位时间内最大的请求数量(阈值)=> 一个单位时间结束,重置0重新累计
    • 缺点:单位时间内不平滑
  • 漏桶算法
    • 内部维护一个队列(桶容量)=> 按一定频率输出(rate)=> 超出桶容量的丢弃/拒绝
    • 都按一定频率输出,自己可能积压太多请求,自身压力大,对后面微服务是种浪费
  • 令牌算法(漏桶算法改进版,允许一定程度的突发调用)
    • 内部维护桶(可负载最大容量)=> 按一定速率往桶里存放令牌(满了则不生成令牌) => 请求从桶里获取令牌成功则可进行下步操作 => 请求无法获取令牌则等待或失败
    • 可快速转发为后面的微服务,不用积压太多请求,保护自己

Spring Cloud Gateway:

  • 基于Filter:官方提供了基于令牌桶的限流支持
    • 基于内置的局部过滤器工厂RequestRateLimiterGatewayFilterFactory实现(通过Redis和lu脚本结合的方式实现)
  • 基于Sentinel的限流

网关限流:基于Filter

  1. pom.xml

     <!-- 网关限流: 基于Filter 实现 -->
     <!-- 监控依赖 -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</artifactId>
     </dependency>
     <!-- Reids 相关依赖(基于reactive的redis依赖)) -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
     </dependency>
    
  2. application.yml 添加redis & 限流配置

     spring:
       redis:
         host: localhost
         port: 6379
         database: 1
         password: 123456
       cloud:
         gateway:
           routes:
             + id: nacos-provider
               uri: lb://nacos-provider/
               filters:
                 + name: RequestRateLimiter # 使用限流过滤器
                   args:
                     key-resolver: '#{@queryParamKeyResolver}'   # 限流Key解析器(即基于什么限流),使用SpEL表达式@xxx从Spring容器中获取name为xxx的Bean对象, 查看自定义的RateLimiteConfig中注入的Bean
                     redis-rate-limiter.replenishRate: 1  # 向令牌桶中填充token的速率
                     redis-rate-limiter.burstCapacity: 3  # 令牌桶容量
                     redis-rate-limiter.requestedTokens: 1 # 一个请求消耗几个令牌
                 + StripPrefix=1
               predicates:
                 + Path=/provider/**
    
  3. 编写KeyResolver

     @Configuration
     public class KeyResolverConfig {
    
         // 基于请求路径限流
         @Bean
         @Primary
         public KeyResolver pathKeyResolver(){
             return new KeyResolver(){
                 @Override
                 public Mono<String> resolve(ServerWebExchange exchange) {
                     return Mono.just(exchange.getRequest().getPath().toString());
                 }
             };
         }
    
         // 基于请求参数限流
         @Bean
         public KeyResolver queryParamKeyResolver(){
             return exchange ->{
                 String token = exchange.getRequest().getQueryParams().getFirst("token");
                 return Mono.just(token!=null?token:exchange.getRequest().getPath().toString());
             };
         }
     }
    
  4. Test

网关限流:基于Sentinel

https://www.cnblogs.com/yinjihuan/p/10772558.html

https://github.com/alibaba/Sentinel/wiki/API-Gateway-Flow-Control

  1. pom.xml

     <!-- 网关限流: 基于Sentinel 实现 -->
     <dependency>
         <groupId>com.alibaba.csp</groupId>
         <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
     </dependency>
    
  2. application.yml

     spring:
       cloud:
         gateway:
           discovery:
             locator:
               enabled: true
               lower-case-service-id: true
           routes:
             + id: baidu
               uri: https://www.baidu.com
               filters:
                 + SetPath=/?{args}
               predicates:
                 + Path=/baidu/{args}
             + id: nacos-provider
               uri: lb://nacos-provider/
               predicates:
                 + Path=/provider/**
               filters:
                 + StripPrefix=1
    
  3. Configuration

     @Configuration
     public class SentinelGatewayConfig {
    
         private final List<ViewResolver> viewResolvers;
         private final ServerCodecConfigurer serverCodecConfigurer;
    
         public SentinelGatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                     ServerCodecConfigurer serverCodecConfigurer) {
             this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
             this.serverCodecConfigurer = serverCodecConfigurer;
         }
    
         // 配置限流异常处理器
         @Bean
         @Order(-1)
         public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
             // Register the block exception handler for Spring Cloud Gateway.
             return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
         }
    
         // 配置限流过滤器
         @Bean
         @Order(Ordered.HIGHEST_PRECEDENCE)
         public GlobalFilter sentinelGatewayFilter() {
             // By default the order is HIGHEST_PRECEDENCE
             return new SentinelGatewayFilter();
         }
    
         // Optinal: 配置一些初始化的限流规则
         // Test: http://localhost:81/provider/hi?token=22
         @PostConstruct
         public void initGatewayRules(){
             Set<GatewayFlowRule> rules = new HashSet<>();
    
             // 路由ID,单位时间限流阈值,单位时间
             // rules.add(new GatewayFlowRule("nacos-provider").setCount(2).setIntervalSec(1));
    
             // 限流分组名,单位时间限流阈值,单位时间
             rules.add(new GatewayFlowRule("providerApis").setCount(2).setIntervalSec(1));
    
             GatewayRuleManager.loadRules(rules);
         }
    
         // Optinal: 自定义限流分组
         @PostConstruct
         public void initCustomizedApis(){
             Set<ApiDefinition> definitions = new HashSet<>();
    
             Set<ApiPredicateItem> predicateItems = new HashSet<>();
             predicateItems.add(new ApiPathPredicateItem()
                     .setPattern("/provider/**")
                     .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)
             );
             ApiDefinition def = new ApiDefinition("providerApis")
                     .setPredicateItems(predicateItems);
    
             definitions.add(def);
             GatewayApiDefinitionManager.loadApiDefinitions(definitions);
         }
    
         // Optinal: 自定义限流处理器
         @PostConstruct
         public void initBlockHandlers(){
             BlockRequestHandler handler = new BlockRequestHandler() {
                 @Override
                 public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                     Map map = new HashMap();
                     map.put("code","001");
                     map.put("message","不好意思,限流了哦");
                     return ServerResponse.status(HttpStatus.OK)
                             .contentType(MediaType.APPLICATION_JSON)
                             .body(BodyInserters.fromValue(map));
                 }
             };
             GatewayCallbackManager.setBlockHandler(handler);
         }
     }
    
  4. Test: http://localhost:81/provider/hi?token=22

网关高可用

Ngnix + 网关集群

  1. Ngnix 配置

     // gateway集群
     upstream gateway{
         server 127.0.0.1:81;
         server 127.0.0.1:82;
     }
    
     Server{
         listen 80;
         server_name localhost;
         # 路由
         location / {
             proxy_pass http://gateway;
         }
     }
    
  2. 启动两个网关实例,端口分别为81,82

  3. Test: http://localhost/provider/hi?token=aa