Nacos,Sentine限流熔断,Gateway网关


本案例通过前端的一个按钮发送请求,经过网关调用服务的过程。
知识点:前端向网关发送请求需要解决跨域问题
一、网关访问其他服务

建立项目

新建空项目
打开Idea,创建一个空项目(Empty Project),项目名为GitCGB2108IVProjects,例如:
在这里插入图片描述
在这里插入图片描述
其中,这个空项目就类似磁盘中的一个空的文件夹,可以将此文件夹作为一个代码工作区。

项目初始化配置
第一步:配置maven环境(只要是新的工作区,都要重新配置),注意本地库选择新的位置不要与其它项目共用本地库,因为多个项目所需要依赖的版本不同时,可能会有一些依赖版本的冲突。.
在这里插入图片描述

说明,这里的本地库名字最要不要选择中文,单词之间也不要有空格。
第二步:配置JDK编译环境
聚合工程在编译时,需要对相关依赖的工程进行一起编译,所以需要做一些配置,例如:
在这里插入图片描述

指定一下当前工作区的jdk编译版本,例如:
在这里插入图片描述

第三步:配置工作区中项目编码方式
在这里插入图片描述

创建聚合父工程
我们后续在创建微服务工程进行学习时,相关服务依赖版本的管理,公共依赖,项目编译,打包设计等都可以放在此工程下,进行统一的配置,然后实现共享。

第一步:创建父工程模块,例如:
在这里插入图片描述

第二步:删除工程中的src目录(父工程不需要这个目录),例如:
在这里插入图片描述

第三步:修改项目pom.xml文件内容,例如:
1.spring boot,Spring Cloud微服务,Spring Cloud alibaba版本管理
2.子模块公共依赖lombok,springboot单元测试
3.定义当前工程模块及子工程的的统一编译和运行版本

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--当前工程的坐标-->
    <groupId>com.jt</groupId>
    <artifactId>01-sca</artifactId>

    <version>1.0-SNAPSHOT</version>

    <!--有的同学在创建maven工程时,可能会有如下有两句话,这两句话用于指定
    当前项目的jdk编译版本以及运行版本,也可以不指定,后续我们自己通过maven插件方式进行配置-->
    <!--
    <properties>
         <maven.compiler.source>8</maven.compiler.source>
         <maven.compiler.target>8</maven.compiler.target>
    </properties>
    -->
    <!--maven父工程的pom文件中一般要定义子模块,
    子工程中所需依赖版本的管理,公共依赖并且父工程的
    打包方式一般为pom方式-->

    <!--第一步: 定义子工程中核心依赖的版本管理(注意,只是版本管理)-->
    <dependencyManagement>
        <dependencies>
            <!--spring boot 核心依赖版本定义(spring官方定义)-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--Spring Cloud 微服务规范(由spring官方定义)-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type><!--假如scope是import,type必须为pom-->
                <scope>import</scope><!--引入三方依赖的版本设计-->
            </dependency>

            <!--Spring Cloud alibaba 依赖版本管理 (参考官方说明)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--第二步: 添加子工程的所需要的公共依赖-->
    <dependencies>
        <!--lombok 依赖,子工程中假如需要lombok,不需要再引入-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope><!--provided 表示此依赖仅在编译阶段有效-->
        </dependency>
        <!--单元测试依赖,子工程中需要单元测试时,不需要再次引入此依赖了-->
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope><!--test表示只能在test目录下使用此依赖-->
            <exclusions>
                <exclusion><!--排除一些不需要的依赖-->
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--其它依赖...-->
    </dependencies>
    <!--第三步: 定义当前工程模块及子工程的的统一编译和运行版本-->
    <build><!--项目构建配置,我们基于maven完成项目的编译,测试,打包等操作,
    都是基于pom.xml完成这一列的操作,但是编译和打包的配置都是要写到build元素
    内的,而具体的编译和打包配置,又需要plugin去实现,plugin元素不是必须的,maven
    有默认的plugin配置,常用插件可去本地库进行查看-->
        <plugins>
            <!--通过maven-compiler-plugin插件设置项目
            的统一的jdk编译和运行版本-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                 <!--假如本地库没有这个版本,这里会出现红色字体错误-->
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

其中,服务核心依赖版本可参考如下网址(涉及到一个兼容性问题,不能随意指定其版本):

https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

创建服务提供方模块
创建服务提供方工程,继承01-sca,例如:
在这里插入图片描述

创建服务消费方模块
创建服务消费方工程,继承01-sca,例如:
在这里插入图片描述

创建API网关服务模块
创建网关工程(这个工程后续会作为API服务访问入口),继承01-sca,例如:
在这里插入图片描述

服务关系以及调用关系设计
基于前面章节创建的项目,后续我们会讲解服务的注册,服务的配置,服务之间的调用,负载均衡,限流,熔断,网关等相关知识,现在先了解一个简易结构,例如:
在这里插入图片描述

我们最终会基于这个结构的设计,实现一个从网关到服务消费方,再从服务消费方到服务提供方的一个调用链路的业务及代码实践过程。

注册中心简介

背景分析

在微服务中,首先需要面对的问题就是如何查找服务(软件即服务),其次,就是如何在不同的服务之间进行通信?如何更好更方便的管理应用中的每一个服务,如何建立各个服务之间联系的纽带,由此注册中心诞生(例如淘宝网卖家提供服务,买家调用服务)。
市面上常用注册中心有Zookeeper(雅虎Apache),Eureka(Netfix),Nacos(Alibaba),Consul(Google),那他们分别都有什么特点,我们如何进行选型呢?我们主要从社区活跃度,稳定性,功能,性能等方面进行考虑即可.本次微服务的学习,我们选择Nacos,它很好的支持了阿里的双11活动,不仅可以做注册中心,还可以作为配置中心,稳定性和性能都很好。

Nacos概述

Nacos(DynamicNaming and Configuration Service)是一个应用于服务注册与发现、配置管理的平台。它孵化于阿里巴巴,成长于十年双十一的洪峰考验,沉淀了简单易用、稳定可靠、性能卓越的核心竞争力。其官网地址如下:

https://nacos.io/zh-cn/docs/quick-start.html

构建Nacos服务

准备工作

第一:确保你电脑已配置JAVA_HOME环境变量(Nacos启动时需要),例如:
在这里插入图片描述

第二:确保你的MySQL版本为5.7以上(MariaDB10.5以上),例如

下载与安装
第一步:Nacos下载,可在浏览器直接输入如下地址:

https://github.com/alibaba/nacos/releases

第二步:选择对应版本,直接下载,如图所示:

第三步:解压Nacos(最好不要解压到中文目录下),其目录结构如下:

初始化配置
第一步:打开操作系统命令行,登陆mysql,执行课前资料中的sql脚本,不是nacos/conf目录下自带的。
例如,我们可以使用mysql自带客户端,在命令行首先登录mysql,然后执行如下指令:

source d:/nacos-mysql.sql
1
执行成功以后,会创建一个nacos_config数据库,打开数据库会看到一些表,例如;

说明:在执行此文件时,要求mysql的版本大于5.7版本(MariaDB最好10.5.11),否则会出现如下错误:

第二步:打开/nacos/conf/application.properties里打开默认配置,并基于你当前环境配置要连接的数据库,连接数据库时使用的用户名和密码(假如前面有"#"要将其去掉):

If use MySQL as datasource:

spring.datasource.platform=mysql

Count of DB:

db.num=1

Connect URL of DB:

db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
1
2
3
4
5
6
7
8
9
10
服务启动与访问
第一步:启动Nacos服务(nacos的bin目录去通过指令启动)。

Linux/Unix/Mac启动命令(standalone代表着单机模式运行,非集群模式):

./startup.sh -m standalone
1
Windows启动命令(standalone代表着单机模式运行,非集群模式):

startup.cmd -m standalone
1
说明:
1)执行执行令时要么配置环境变量,要么直接在nacos/bin目录下去执行.
2)nacos启动时需要本地环境变量中配置了JAVA_HOME(对应jdk的安装目录),
3)一定要确保你连接的数据库(nacos_config)是存在的.
4)假如所有的配置都正确,还连不上,检查一下你有几个数据库(mysql,…)

第二步:访问Nacos服务。

打开浏览器,输入http://localhost:8848/nacos地址,出现如下登陆页面:

其中,默认账号密码为nacos/nacos.

Nacos服务调用(重点)

业务描述

创建两个项目Module分别为服务提供者和服务消费者(假如已有则无需创建),两者都要注册到NacosServer中(这个server本质上就是一个web服务,端口默认为8848),然后服务提供者可以为服务消费者提供远端调用服务(例如支付服务为服务提供方,订单服务为服务消费方),如图所示:

在这里插入图片描述

生产者服务创建及注册

第一步:创建服务提供者工程(module名为sca-provider,假如已有则无需创建),继承parent工程(01-sca),其pom.xml文件内容如下:
注入SpringBoot-Web,SpringCloudAlibaba-Nacos

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>01-sca</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>sca-provider</artifactId>
    <dependencies>
        <!--Web服务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--服务的注册和发现(我们要讲服务注册到nacos)-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
</project>

第二步:创建并修改配置文件application.yml(或者application.properties),实现服务注册,关键代码如下:
添加服务名和服务中心域名配置

server:
   port: 8081
spring:
  application:
    name: sca-provider #服务中心服务名,进行服务注册必须配置服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848#服务中心域名

注意:服务名不要使用下划线(“_”),应使用横杠(“-”),这是规则。
第三步:创建启动类(假如已有则无需定义),关键代码如下:

package com.jt;

@SpringBootApplication
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
  }

第四步:启动启动类,然后刷先nacos服务,检测是否服务注册成功,如图所示:
在这里插入图片描述

第五步:停掉sca-provider服务,然后不断刷新nacos服务列表,检查服务的健康状态。

消费者服务发现及调用
第一步: 在sca-provider项目中创建服务提供方对象,基于此对象对外提供服务,例如:

package com.jt.provider.controller;
 /**定义Controller对象(这个对象在spring mvc中给他的定义是handler),
   * 基于此对象处理客户端的请求*/
@RestController
public class ProviderController{
//@Value默认读取项目配置文件中配置的内容
//8080为没有读到server.port的值时,给定的默认值
@Value("${server.port:8080}")
private String server;
//http://localhost:8081/provider/echo/tedu
@GetMapping("/provider/echo/{msg}")
public String doRestEcho1(@PathVariable String msg){
     return server+" say hello "+msg;
     }
}

第二步:创建服务消费者工程(module名为sca-consumer,假如已有则无需创建),继承parent工程(01-sca),其pom.xml文件内容如下:
注入SpringBoot-Web,SpringCloudAlibaba-Nacos

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>01-sca</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>sca-consumer</artifactId>
    
   <dependencies>
    <!--Web服务-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--服务的注册和发现(我们要讲服务注册到nacos)-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    </dependencies>
</project>

第三步:创建sca-consumer服务中的配置文件application.yml,关键代码如下:
添加服务名和服务中心域名配置

server:
  port: 8090
spring:
  application:
    name: sca-consumer #服务注册时,服务名必须配置
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #从哪里去查找服务

第四步:创建消费端启动类并实现服务消费,关键代码如下:

package com.jt;
@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

第五步:在sca-consumer启动类中添加如下方法,用于创建RestTemplate对象.
RestTemplate用于在注册中心调用其他服务

@Bean
public RestTemplate restTemplate(){//基于此对象实现远端服务调用
    return new RestTemplate();
}

第六步:定义sca-consumer服务的消费端Controller,在此对象方法内部实现远端服务调用

package com.jt.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;


/**
 * 定义服务消费端Controller,在这个Controller对象
 * 的方法中实现对远端服务sca-provider的调用
 */
@RestController
public class ConsumerController {
    /**
     * 从spring容器获取一个RestTemplate对象,
     * 基于此对象实现远端服务调用
     */
    @Autowired
    private RestTemplate restTemplate;
    /**
     * 在此方法中通过一个RestTemplate对象调用远端sca-provider中的服务
     * @return
     * 访问此方法的url: http://localhost:8090/consumer/doRestEcho1
     */
    @GetMapping("/consumer/doRestEcho1")
    public String doRestEcho01(){
        //1.定义要调用的远端服务的url
        String url="http://localhost:8081/provider/echo/8090";
        //2.基于restTemplate对象中的相关方法进行服务调用
        return restTemplate.getForObject(url, String.class);
    }

}

第七步:启动消费者服务,并在浏览器输入http://localhost:8090/consumer/doRestEcho1地址进行访问,假如访问成功会出现,如图所示效果:
根本作用,访问消费端可以调用提供方功能
在这里插入图片描述

小节面试分析

为什么要将服务注册到nacos?(为了更好的查找这些服务)
在Nacos中服务提供者是如何向Nacos注册中心(Registry)续约的?(5秒心跳)
对于Nacos服务来讲它是如何判定服务实例的状态?(检测心跳包,15,30)
服务消费方是如何调用服务提供方的服务的?(RestTemplate)

服务负载均衡设计及实现(重点)

业务描述
一个服务实例可以处理请求是有限的,假如服务实例的并发访问比较大,我们会启动多个服务实例,让这些服务实例采用一定策略均衡(轮询,权重,随机,hash等)的处理并发请求,在Nacos中服务的负载均衡(Nacos客户端负载均衡)是如何应用的?

LoadBalancerClient应用
LoadBalancerClient对象可以从nacos中基于服务名获取服务实例,然后在工程中基于特点算法实现负载均衡方式的调用,案例实现如下:

第一步:修改ConsumerController类,注入LoadBalancerClient对象,并添加doRestEcho2方法,然后进行服务访问.

@Autowired
  private LoadBalancerClient loadBalancerClient;
  
  @Value("${spring.application.name:8090}")
  private String appName;
   
  @GetMapping("/consumer/doRestEcho02")
 public String doRestEcho02(){
     ServiceInstance serviceInstance = loadBalancerClient.choose("sca-provider");
     String url = String.format("http://%s:%s/provider/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName);
     System.out.println("request url:"+url);
     return restTemplate.getForObject(url, String.class);
     }
 }

第二步:打开Idea服务启动配置,如图所示:
在这里插入图片描述

第三步:修改并发运行选项(假如没有找到这个选项我们需要通过搜索引擎基于组合查询的方法,去找到对应的解决方案,例如搜索 idea allow parallel run),如图所示:
在这里插入图片描述

第四步:修改sca-provider的配置文件端口,分别以8081,8082端口方式进行启动。

server:
  port: 8082
spring:
  application:
    name: sca-provider
  cloud:
    nacos:
      server-addr: localhost:8848

第五步:启动成功以后,访问nacos的服务列表,检测服务是否成功注册,如图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第六步:启动sca-consumer项目模块,打开浏览器对consumer服务进行访问,访问时不断刷新,检测页面数据变化,例如:

说明,这里多个实例并发提供服务的方式为负载均衡,这里的负载均衡实现默认是因为Nacos集成了Ribbon来实现的,Ribbon配合RestTemplate,可以非常容易的实现服务之间的访问。Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是客户端的负载均衡(客户端可以采用一定算法,例如轮询访问,访问服务端实例信息),这个功能可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡方式的服务调用。

@LoadBalanced
当使用RestTemplate进行远程服务调用时,假如需要负载均衡,还可以在RestTemplate对象构建时,使用@LoadBalanced对构建RestTemplate的方法进行修饰,例如在ConsumerApplication中构建名字为loadBalancedRestTemplate的RestTemplate对象:

@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
    return new RestTemplate();
}

在需要RestTemplate实现负载均衡调用的地方进行依赖注入.例如在ConsumerController类中添加loadBalancedRestTemplate属性

@Autowired
private RestTemplate loadBalancedRestTemplate;

接下来,可以在对应的服务端调用方的方法内,基于RestTemplate借助服务名进行服务调用, 例如:

@GetMapping("/consumer/doRestEcho3")
public String doRestEcho03(){
    String url=String.format("http://%s/provider/echo/%s","sca-provider",appName);
    //向服务提供方发起http请求,获取响应数据
    return loadBalancedRestTemplate.getForObject(
            url,//要请求的服务的地址
            String.class);//String.class为请求服务的响应结果类型
}

RestTemplate在发送请求的时候会被LoadBalancerInterceptor拦截,它的作用就是用于RestTemplate的负载均衡,LoadBalancerInterceptor将负载均衡的核心逻辑交给了loadBalancer,核心代码如下所示(了解):

public ClientHttpResponse intercept(final HttpRequest request, 
    final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
	final URI originalUri = request.getURI();
	String serviceName = originalUri.getHost();
	return this.loadBalancer.execute(serviceName, 
    requestFactory.createRequest(request, body, execution));
}

@LoadBalanced注解是属于Spring,而不是Ribbon的,Spring在初始化容器的时候,如果检测到Bean被@LoadBalanced注解,Spring会为其设置LoadBalancerInterceptor的拦截器。

Ribbon负载均衡策略(了解)
基于Ribbon方式的负载均衡,Netflix默认提供了七种负载均衡策略,对于SpringCloud Alibaba解决方案中又提供了NacosRule策略,默认的负载均衡策略是轮训策略。如图所示:
在这里插入图片描述

当系统提供的负载均衡策略不能满足我们需求时,我们还可以基于IRule接口自己定义策略.

小节面试分析

  • @Bean注解的作用?(一般用于配置类内部,描述相关方法,用于告诉spring此方法的返回值要交给spring管理,bean的名字默认为方法名,假如需要指定名字可以@Bean(“bean的名字”),最多的应用场景是整合第三方的资源-对象)
  • @Autowired注解的作用?(此注解用于描述属性,构造方法,set方法等,用于告诉spring框架,按找一定的规则为属性进行DI操作,默认按属性,方法参数类型查找对应的对象,假如只找到一个,则直接注入,类型多个时还会按照属性名或方法参数名进行值的注入,假如名字也不同,就出报错.)
  • Nacos中的负责均衡底层是如何实现的?(通过Ribbon实现,Ribbon中定义了一些负载均衡算法,然后基于这些算法从服务实例中获取一个实例为消费方法提供服务)
  • Ribbon 是什么?(Netflix公司提供的负载均衡客户端,一般应用于服务的消费方法)
  • Ribbon 可以解决什么问题? (基于负载均衡策略进行服务调用, 所有策略都会实现IRule接口)
  • Ribbon 内置的负载策略都有哪些?(8种,可以通过查看IRule接口的实现类进行分析)
  • @LoadBalanced的作用是什么?(描述RestTemplate对象,用于告诉Spring框架,在使用RestTempalte进行服务调用时,这个调用过程会被一个拦截器进行拦截,然后在拦截器内部,启动负载均衡策略。)
  • 我们可以自己定义负载均衡策略吗?(可以,基于IRule接口进行策略定义,也可以参考NacosRule进行实现)

基于Feign的远程服务调用(重点)

背景分析
服务消费方基于rest方式请求服务提供方的服务时,一种直接的方式就是自己拼接url,拼接参数然后实现服务调用,但每次服务调用都需要这样拼接,代码量复杂且不易维护,此时Feign诞生。

Feign是什么
Feign 是一种声明式Web服务客户端,底层封装了对Rest技术的应用,通过Feign可以简化服务消费方对远程服务提供方法的调用实现。如图所示:
在这里插入图片描述

Feign 最早是由 Netflix 公司进行维护的,后来 Netflix 不再对其进行维护,最终 Feign 由一些社区进行维护,更名为 OpenFeign。

Feign应用实践(掌握)
第一步:在服务消费方,添加项目依赖(SpringCloud团队基于OpenFeign研发了starter),代码如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

第二步:在启动类上添加@EnableFeignClients注解,代码如下:

@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {}

第三步:定义Http请求API,基于此API借助OpenFeign访问远端服务,代码如下:
创建一个接口,使用@FeignClient注解,属性name为服务提供者名称,@GetMapping注解为提供者路径

package com.jt.consumer.service;
@FeignClient(name="sca-provider")//sca-provider为服务提供者名称
public interface RemoteProviderService{
    @GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
    public String echoMessage(@PathVariable("string") String string);
}

其中,@FeignClient描述的接口底层会为其创建实现类。

第四步:创建FeignConsumerController中并添加feign访问,代码如下:

package com.jt.consumer.controller;
@RestController
@RequestMapping("/consumer/ ")
public class FeignConsumerController {
    @Autowired
    private RemoteProviderService remoteProviderService;
    /**基于feign方式的服务调用*/
    @GetMapping("/echo/{msg}")
    public String doFeignEcho(@PathVariable  String msg){
        //基于feign方式进行远端服务调用(前提是服务必须存在)
        return remoteProviderService.echoMessage(msg);
    }
}

第五步:启动消费者服务,在浏览器中直接通过feign客户端进行访问,如图所示(反复刷新检测其响应结果):
在这里插入图片描述

说明,feign方式的远程服务调用,底层会自动基于Ribbon组件实现负载均衡。

Feign配置进阶实践

基于同一提供方写了多个接口

一个服务提供方通常会提供很多资源服务,服务消费方基于同一个服务提供方写了很多服务调用接口,此时假如没有指定contextId,服务
启动就会失败
,例如,假如在服务消费方再添加一个如下接口,消费方启动时就会启动失败,例如:

@FeignClient(name="sca-provider")
 public interface RemoteOtherService {
     @GetMapping("/doSomeThing")
     public String doSomeThing();
}

其启动异常如下:

The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.

此时我们需要为远程调用服务接口指定一个contextId,作为远程调用服务的唯一标识(这个标识是Bean对象的名字)即可,例如:

@FeignClient(name="sca-provider",contextId="remoteProviderService")//sca-provider为服务提供者名称
interface RemoteProviderService{
    @GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
    public String echoMessage(@PathVariable("string") String string);
}

服务不可用或调用超时解决方案

还有,当我们在进行远程服务调用时,假如调用的服务突然不可用了或者调用过程超时了,怎么办呢?一般服务消费端会给出具体的容错方案,例如,在Feign应用中通过FallbackFactory接口的实现类进行默认的相关处理,例如:

第一步:定义FallbackFactory接口的实现,代码如下:

package com.cy.service.factory;
/**
 * 基于此对象处理RemoteProviderService接口调用时出现的服务中断,超时等问题
 */
@Component
public class ProviderFallbackFactory implements FallbackFactory<RemoteProviderService> {
    /**
     * 此方法会在RemoteProviderService接口服务调用时,出现了异常后执行.
     * @param throwable 用于接收异常
     */
    @Override
    public RemoteProviderService create(Throwable throwable) {
        return (msg)->{
                return "服务维护中,稍等片刻再访问";
        };
    }
}

第二步:在Feign访问接口中应用FallbackFactory对象,例如:

@FeignClient(name = "sca-provider", contextId = "remoteProviderService",
             fallbackFactory = ProviderFallbackFactory.class)//sca-provider为nacos中的服务名
public interface RemoteProviderService {
    @GetMapping("/provider/echo/{msg}")
    public String echoMsg(@PathVariable String msg);
}

第三步:在配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制.

feign:  
  hystrix:
    enabled: true #默认值为false

第四步:在服务提供方对应的调用方法中添加Thread.sleep(5000)模拟耗时操作,然后启动服务进行访问测试.

Feign 调用过程分析(了解)

Feign应用过程分析(底层逻辑先了解):
1)通过 @EnableFeignCleints 注解告诉springcloud,启动 Feign Starter 组件。
2) Feign Starter 会在项目启动过程中注册全局配置,扫描包下所由@FeignClient注解描述的接口,然后由系统底层创建接口实现类(JDK代理类),并构建类的对象,然后交给spring管理(注册 IOC 容器)。
3) Feign接口被调用时,底层代理对象会将接口中的请求信息通过编码器创建 Request对象,基于此对象进行远程过程调用。
4) Feign客户端请求对象会经Ribbon进行负载均衡,挑选出一个健康的 Server 实例(instance)。
5) Feign客户端会携带 Request 调用远端服务并返回一个响应。
6) Feign客户端对象对Response信息进行解析然后返回客户端。

小节面试分析

为什么使用feign?(基于Feign可以更加友好的实现服务调用,简化服务消费方对服务提供方方法的调用)。
@FeignClient注解的作用是什么?(告诉Feign Starter,在项目启动时,为此注解描述的接口创建实现类-代理类)
Feign方式的调用,底层负载均衡是如何实现的?(Ribbon)
@EnableFeignCleints 注解的作用是什么?(描述配置类,例如启动类)

Nacos配置快速入门

业务描述
在sca-provider项目中添加一个Controller对象,例如ProviderLogController,基于此Controller中的方法演示日志级别的配置。

配置准备工作
第一步:创建ProviderLogController对象,例如:

package com.jt.provider.controller;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 基于此controller演示配置中心的作用.
 * 在这个controller中我们会基于日志对象
 * 进行日志输出测试.
 */
//@Slf4j
@RestController
public class ProviderLogController {
    //创建一个日志对象
    //org.slf4j.Logger (Java中的日志API规范,基于这个规范有Log4J,Logback等日志库)
    //org.slf4j.LoggerFactory
    //log对象在哪个类中创建,getLogger方法中的就传入哪个类的字节码对象
    //记住:以后只要Java中使用日志对象,你就采用下面之中方式创建即可.
    //假如在log对象所在的类上使用了@Slf4j注解,log不再需要我们手动创建,lombok会帮我们创建
   private static Logger log=
           LoggerFactory.getLogger(ProviderLogController.class);
    @GetMapping("/provider/log/doLog01")
    public String doLog01(){//trace<debug<info<warn<error
        System.out.println("==doLog01==");
        log.trace("===trace===");
        log.debug("===debug===");
        log.info("===info====");
        log.warn("===warn===");
        log.error("===error===");
        return "log config test";
    }
}

第二步:在已有的sca-provider项目中添加如配置依赖,例如:
注入SpringCloudAlibaba-NacosConfig

<dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>

第三步: 将项目sca-provider的application.yml的名字修改为bootstrap.yml(启动优先级最高),并添加配置中心配置,代码如下:
添加SpringCloudNacos配置中心配置

spring:
  application:
    name: sca-provider
  cloud:
    nacos:
      discovery: #配置服务注册,发现地址
        server-addr: 127.0.0.1:8848
      config: #配置服务配置中心地址
        server-addr: 127.0.0.1:8848#配置中心域名
        file-extension: yml # 配置内容的数据格式,默认为属性

新建Nacos配置
打开nacos配置中心,新建配置,如图所示:
在这里插入图片描述

其中,Data ID的值要与bootstrap.yml中定义的spring.application.name的值相同(服务名-假如有多个服务一般会创建多个配置实例,不同服务对应不同的配置实例)。配置发布以后,会在配置列表中,显示我们的配置,例如:
在这里插入图片描述

测试Nacos数据读取
配置创建好以后,启动sca-provider服务,然后打开浏览器,输入http://localhost:8081/provider/log/doLog01,检测idea控制台日志输出。然后再打开nacos控制台动态更新日志级别,再访问资源并检测后台日志输出.
配置中心设置显示日志级别为debug
在这里插入图片描述

然后,修改nacos配置中心的日志级别,再刷新浏览器,检测日志的输出,是否会发生变化.

@RefreshScope注解的应用

对于nacos配置中心而言,有系统内部对配置变化的感知,还有外部系统对配置的感知,假如我们系统在浏览器中能看到日志级别的变化,该如何实现呢?我们现在来实现一个案例.

第一步:在ProviderLogController类的上面添加一个@RefreshScope注解,例如:

@RefreshScope
@RestController
public class ProviderLogController{
  //.....
}

其中,@RefreshScope的作用是在配置中心的相关配置发生变化以后,能够及时看到类中属性值的更新(底层是通过重新创建Controller对象的方式,对属性进行了重新初始化)。

第二步:添加ProviderLogController中添加一个获取日志级别(debug<info<warn<error)的的属性和方法,代码如下:

@Value("${logging.level.com.jt:error}")
private String logLevel;
@GetMapping("/provider/log/doLog02")
public String doLog02(){
   log.info("log level is  {}",logLevel);
   return  "log level  is "+logLevel;
}

第三步:启动sca-provider服务,然后打开浏览器并输入http://localhost:8081/provider/log/doLog02进行访问测试。

说明,假如对配置的信息访问不到,请检测项目配置文件的名字是否为bootstrap.yml,检查配置文件中spring.application.name属性的值是否与配置中心的data-id名相同,还有你读取的配置信息缩进以及空格写的格式是否正确.

小节面试分析
配置中心一般都会配置什么内容?(可能会经常变化的配置信息,例如连接池,日志、线程池、限流熔断规则)
什么信息一般不会写到配置中心?(服务端口,服务名,服务的注册地址,配置中心)
项目中为什么要定义bootstrap.yml文件?(此文件被读取的优先级比较高,可以在服务启动时读取配置中心的数据)
Nacos配置中心宕机了,我们的服务还可以读取到配置信息吗?(可以从内存,客户端获取了配置中心的配置信息以后,会将配置信息在本地内存中存储一份.)
微服务应用中我们的客户端如何获取配置中心的信息?(我们的服务一般首先会从内存读取配置信息,同时我们的微服务还可以定时向nacos配置中心发请求拉取(pull)更新的配置信息)
微服务应用中客户端如何感知配置中心数据变化?(1.4.x版本的nacos客户端会基于长轮询机制从nacos获取配置信息,所谓的长轮询就是没有配置更新时,会在nacos服务端的队列进行等待.)
服务启动后没有从配置中心获取我们的配置数据是什么原因?(依赖,配置文件名字bootstrap.yml,配置中心的dataId名字是否正确,分组是否正确,配置的名字是否正确,缩进关系是否正确,假如是动态发布,类上是否有@RefreshScope注解)
你项目中使用的日志规范是什么?(SLF4J)
你了解项目中的日志级别吗?(debug,info,error,…,可以基于日志级别控制日志的输出)

Nacos配置管理模型

概述

Nacos 配置管理模型由三部分构成,如图所示:
在这里插入图片描述

其中:

  • Namespace:命名空间,对不同的环境进⾏隔离,⽐如隔离开发环境和⽣产环境。
  • Group:分组,将若⼲个服务或者若⼲个配置集归为⼀组。
  • Service/DataId:某⼀个服务或配置集,一般对应一个配置文件。

命名空间设计

Nacos中的命名空间一般用于配置隔离,这种命名空间的定义一般会按照环境(开发,生产等环境)进行设计和实现.我们默认创建的配置都存储到了public命名空间,如图所示:
在这里插入图片描述

创建新的开发环境并定义其配置,然后从开发环境的配置中读取配置信息,该如何实现呢?
第一步:创建新命名空间,如图所示:
在这里插入图片描述

命名空间成功创建以后,会在如下列表进行呈现。
在这里插入图片描述

在指定命名空间下添加配置,也可以直接取配置列表中克隆,例如:
在这里插入图片描述在这里插入图片描述

克隆成功以后,我们会发现在指定的命名空间中有了我们克隆的配置,如图所示:
在这里插入图片描述

此时我们修改dev命名空间中Data Id的sca-provider配置,如图所示:
在这里插入图片描述

修改项目module中的配置文件bootstrap.yml,添加如下配置,关键代码如下:

spring:
  cloud:
    nacos:
      config:
        namespace: 6058fd3f-0d4d-44f2-85d6-5fc7d2348046#命名空间ID
        ……

其中,namespace后面的字符串为命名空间的id,可直接从命名空间列表中进行拷贝.然后重启服务,继续刷新http://localhost:8081/provider/log/doLog02地址。检测输出,看看输出的内容是什么,是否为dev命名空间下配置的内容。

分组设计及实现

当我们在指定命名空间下,按环境或服务做好了配置以后,有时还需要基于服务做分组配置,例如,一个服务在不同时间节点(节假日,活动等)切换不同的配置,可以在新建配置时指定分组名称,如图所示:
在这里插入图片描述

其中,这里的useLocalCache为自己定义的配置值,表示是否使用本地缓存.
配置发布以后,修改boostrap.yml配置类,在其内部指定我们刚刚创建的分组,代码如下:
group分组,值为配置中心分组名

server:
  port: 8081
spring:
  application:
    name: sca-provider
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        group: DEFAULT_GROUP_51 # Group,默认为DEFAULT_GROUP
        file-extension: yml # 配置内容的数据格式,默认为属性
        namespace: 7da4aa75-f64c-43c6-b101-9d77ad96f1c0

在指定的Controller类中添加属性和方法用于获取和输出DEFAULT_GROUP_51中的useLocalCache的值,代码如下:

package com.jt.provider.controller;

@RefreshScope
@RestController
public class ProviderCacheController {
    @Value("${useLocalCache:false}")
    private boolean useLocalCache;

    @RequestMapping("/provider/cache01")
    public String doUseLocalCache01(){
        return "useLocalCache'value is   "+useLocalCache;
    }
}

然后重启服务,进行访问测试,检测内容输出。
默认为false,因为配置中心设置了useLocalCache为true
在这里插入图片描述

共享配置设计及读取

当同一个namespace的多个配置文件中都有相同配置时,可以对这些配置进行提取,然后存储到nacos配置中心的一个或多个指定配置文件,哪个微服务需要,就在服务的配置中设置读取即可。例如:

第一步:在nacos中创建一个共享配置文件,例如:
在这里插入图片描述

其中,这里的secret可以理解为一个密钥。

第二步:在指定的微服务配置文件(bootstrap.yml)中设置对共享配置文件的读取,例如:
添加共享机制,data-id值为共享配置文件名,开启refresh

spring:
  application:
    name: sca-provider
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        # 命名空间
        namespace: 83ed55a5-1dd9-4b84-a5fe-a734e4a6ec6d
        # 分组名
        # group: DEFAULT_GROUP
        # 配置中心文件扩展名
        file-extension: yml
        # 共享配置
        shared-configs[0]:
                data-id: app-public.yml
                refresh: true #默认false,共享配置更新,引用此配置的地方是否要更新

第三步:在指定的Controller类中读取和应用共享配置即可,例如:

package com.jt.provider.controller;

@RefreshScope
@RestController
public class ProviderSecretController {
    @Value("${app.secret:123456}")
    private String secret;
    @GetMapping("/provider/secret")
    public String doGetSecret(){
        //return String.format()
        return "The Secret is "+secret;
    }
}

第四步:启动服务,然后打开浏览器进行访问测试。
加入共享机制,可以共享其他配置文件内容
在这里插入图片描述

Sentinel简介

背景分析
在我们日常生活中,经常会在淘宝、天猫、京东、拼多多等平台上参与商品的秒杀、抢购以及一些优惠活动,也会在节假日使用12306 手机APP抢火车票、高铁票,甚至有时候还要帮助同事、朋友为他们家小孩拉投票、刷票,这些场景都无一例外的会引起服务器流量的暴涨,导致网页无法显示、APP反应慢、功能无法正常运转,甚至会引起整个网站的崩溃。
我们如何在这些业务流量变化无常的情况下,保证各种业务安全运营,系统在任何情况下都不会崩溃呢?我们可以在系统负载过高时,采用限流、降级和熔断,三种措施来保护系统,由此一些流量控制中间件诞生。例如Sentinel。

Sentinel概述
Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

Sentinel核心分为两个部分:

  • 核心库(Java 客户端):能够运行于所有 Java 运行时环境,同时对Dubbo /Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。
    安装Sentinel服务
    Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能,其控制台安装步骤如下:
    第一步:打开sentinel下载网址
https://github.com/alibaba/Sentinel/releases

第二步:下载Jar包(可以存储到一个sentinel目录),如图所示:
在这里插入图片描述

第三步:在sentinel对应目录,打开命令行(cmd),启动运行sentinel

java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar

检测启动过程,如图所示:
在这里插入图片描述

访问Sentinal服务
第一步:假如Sentinal启动ok,通过浏览器进行访问测试,如图所示:
在这里插入图片描述

第二步:登陆sentinel,默认用户和密码都是sentinel,登陆成功以后的界面如图所示:
在这里插入图片描述

Sentinel限流入门

概述

我们系统中的数据库连接池,线程池,nginx的瞬时并发等在使用时都会给定一个限定的值,这本身就是一种限流的设计。限流的目的防止恶意请求流量、恶意攻击,或者防止流量超过系统峰值。

准备工作

第一步:Sentinel 应用于服务提供方(sca-provider),在消费方添加依赖如下:
注入SpringCloudAlibaba-Sentinel

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

第二步:打开服务消费方配置文件bootstrap.yml,添加sentinel配置,代码如下:
指定Sentinel服务域名

spring:
  cloud:
    sentinel:
      transport:
         dashboard: localhost:8180 # 指定sentinel控制台地址。

第三步:创建一个用于演示限流操作的Controller对象,例如:

package com.jt.provider.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/provider")
public class ProviderSentinelController {
       @GetMapping("/sentinel01")
       public String doSentinel01(){
           return "sentinel 01 test  ...";
       }
}

第三步:启动sca-provider服务,然后对指定服务进行访问,如图所示:
在这里插入图片描述

第四步:刷新sentinel 控制台,实时监控信息,如图所示:
在这里插入图片描述

Sentinel的控制台其实就是一个SpringBoot编写的程序,我们需要将我们的服务注册到控制台上,即在微服务中指定控制台的地址,并且还要在消费端开启一个与sentinel控制台传递数据端的端口,控制台可以通过此端口调用微服务中的监控程序来获取各种信息。

Sentinel限流入门实践

我们设置一下指定接口的流控(流量控制),QPS(每秒请求次数)单机阈值为1,代表每秒请求不能超出1次,要不然就做限流处理,处理方式直接调用失败。

第一步:选择要限流的链路,如图所示:
在这里插入图片描述

第二步:设置限流策略,如图所示:
在这里插入图片描述

第三步:反复刷新访问消费端端服务,检测是否有限流信息输出,如图所示:
在这里插入图片描述

小节面试分析

Sentinel是什么?(阿里推出一个流量控制平台,防卫兵)
类似Sentinel的产品你知道有什么?(hystrix-一代微服务产品)
Sentinel是如何对请求进行限流的?(基于sentinel依赖提供的拦截器)
你了解哪些限流算法?(计数器、令牌桶、漏斗算法,滑动窗口算法,…)
Sentinel 默认的限流算法是什么?(滑动窗口算法)

Sentinel流控规则分析

阈值类型

  • QPS(Queries Per Second):当调用相关url对应的资源时,QPS达到单机阈值时,就会限流。
  • 线程数:当调用相关url对应的资源时,线程数达到单机阈值时,就会限流。
    设置限流模式
    Sentinel的流控模式代表的流控的方式,默认【直接】,还有关联,链路。
    关联是当关联的资源达到阈值,就限流自己。
    链路是多个链路访问一个资源,对部分链路限流

直接模式

Sentinel默认的流控处理就是【直接->快速失败】。
在这里插入图片描述

关联模式

当关联的资源达到阈值,就限流自己。例如设置了关联资源为/ur2时,假如关联资源/url2的qps阀值超过1时,就限流/url1接口(是不是感觉很霸道,关联资源达到阀值,是本资源接口被限流了)。这种关联模式有什么应用场景呢?我们举个例子,订单服务中会有2个重要的接口,一个是读取订单信息接口,一个是写入订单信息接口。在高并发业务场景中,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能。业务中如果我们希望写入订单比较重要,要优先考虑写入订单接口。那就可以利用关联模式;在关联资源上面设置写入接口,资源名设置读取接口就行了;这样就起到了优先写入,一旦写入请求多,就限制读的请求。例如

第一步:在ProviderSentinelController中添加一个方法,例如:

@GetMapping("/sentinel02")
   public String doSentinel02(){
     return "sentinel 02 test  ...";
   }

第二步:在sentinel中做限流设计,例如
在这里插入图片描述

第三步:打开两个测试窗口,对/provider/sentinel02进行访问,检查/provider/sentinel01的状态,例如:
在这里插入图片描述

链路模式

链路模式只记录指定链路入口的流量。也就是当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流。被调用的方法用@SentinelResource进行注解,然后分别用不同业务方法对此业务进行调用,假如A业务设置了链路模式的限流,在B业务中是不受影响的。现在对链路模式做一个实践,例如:

例如现在设计一个业务对象,代码如下(为了简单,可以直接写在启动类内部):

第一步:在指定包创建一个ResourceService类,代码如下:

package com.jt.provider.service;
@Service
public class ResourceService{
	/**
     * @SentinelResource 描述方法时,可以在sentinel控制台创建一个资源链路,
     * 这个链路的名称默认为value属性的值,当出现限流时,客户端默认看到的是一个
     * 500异常。假如希望对限流结果进 行自定义处理,可以考虑使用blockHandlerClass
     * 属性指定一个限流处理类,然后再通过blockHandler属性指定具体异常处理方法,
     * 这个异常处理方法必须与@SentinelResource注解描述的方法,返回值类型相同
     * ,同时必须是static方法,方法中参数可以是BlockException类型
     * @return
     */
    @SentinelResource("doGetResource")
    public String doGetResource(){
        return "doGetResource";
    }
}

第二步:在ProviderSentinelController中添加一个方法,例如:

			@Autowired
           private ResourceService resourceService;
           @GetMapping("/sentinel03")
           public String doSentinel03() throws InterruptedException {
               resourceService.doGetResource();
               return "sentinel 03 test";
           }

第三步:在sentinel中配置限流规则,例如:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

设置链路流控规则后,再频繁对限流链路进行访问,检测是否会出现500异常,例如
在这里插入图片描述

说明,流控模式为链路模式时,假如是sentinel 1.7.2以后版本,Sentinel Web过滤器默认会聚合所有URL的入口为sentinel_spring_web_context,因此单独对指定链路限流会不生效,需要在application.yml添加如下语句来关闭URL PATH聚合,例如:

sentinel:
     web-context-unify: false

还有,当我们也可以基于@SentinelResource注解描述的方法进行限流后的异常进行自定义处理,其步骤如下:

第一步:定义blockHandlerClass,例如:

package com.jt.provider.service;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ResourceBlockHandler {
    /**
     * 注意此方法中的异常类型必须为BlockException (
     * 它是所有限流,降级等异常的父类类型),方法的返回
     * 值类型为@SentinelResource注解描述的返回值类型,
     * 方法的其他参数为@SentinelResource注解描述的方法参数,
     * 并且此方法必须为静态方法
     * @param ex
     * @return
     */
    public static String call(BlockException ex){
        log.error("block exception {}", ex.getMessage());
        return "访问太频繁了,稍等片刻再访问";
    }
}

第二步:修改@SentinelResource注解中的属性定义,例如:
给资源类的@SentinelResource注解添加blockHandlerClass, blockHandler属性。blockHandlerClass为自定义处理类类型,blockHandler为自定义处理类名

@SentinelResource(value="doGetResource",
        blockHandlerClass = ResourceBlockHandler.class,
        blockHandler = "call")
public String doGetResource(){
    return "do get resource";
}

第三步:在controller方法中,调用@Sentinel注解描述的方法,例如:

/**
 * 演示链路限流
 * @return
 */
@GetMapping("/sentinel03")
public String doSentinel03(){
   return resourceService.doGetResource();
   //return "sentinel 03 test";
}

小节面试分析

你了解sentinel中的阈值应用类型吗?(两种-QPS,线程数)
Sentinel的限流规则中默认有哪些限流模式?(直连,关联,链路)
Sentinel的限流效果有哪些?(快速失败,预热,排队)

Sentinel降级应用实践

概述
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

慢调用比例:对资源限定,最大访问时间,超出时间比例,最低请求次数,统计时间。如果在统计时间内访问该资源超出时间比例超过约定,则会熔断一定时间。
准备工作
在ProviderController 类中添加doSentinel04方法,基于此方法演示慢调用过程下的限流,代码如下:

//AtomicLong 类支持线程安全的自增自减操作
           private AtomicLong atomicLong=new AtomicLong(1);
           @GetMapping("/sentinel04")
           public  String doSentinel04() throws InterruptedException {
               //获取自增对象的值,然后再加1
               long num=atomicLong.getAndIncrement();
               if(num%2==0){//模拟50%的慢调用比例
                  Thread.sleep(200);
               }
               return "sentinel 04 test";
           }

说明,我们在此方法中设置休眠,目的是为了演示慢调用(响应时间比较长).

Sentinel降级入门
接下来,我们基于一个请求链路,进行服务降级及应用实践,例如:
第一步:服务启动后,选择要降级的链路,如图所示:
在这里插入图片描述

第二步:选择要降级的链路,如图所示:
在这里插入图片描述

这里表示熔断策略选择"慢调用比例",表示请求数超过3时,假如平均响应时间超过200毫秒的有30%,则对请求进行熔断,熔断时长为10秒钟,10秒以后恢复正常。

第三步:对指定链路进行刷新,多次访问测试,检测页面上是否会出现 Blocked By Sentinel (flow Limiting)内容.

我们也可以进行断点调试,在DefaultBlockExceptionHandler中的handle方法内部加断点,分析异常类型,假如异常类型DegradeException则为降级熔断。

Sentinel 异常处理

系统提供了默认的异常处理机制,假如默认处理机制不满足我们需求,我们可以自己进行定义。定义方式上可以直接或间接实现BlockExceptionHandler接口,并将对象交给spring管理。
在这里插入图片描述

package com.jt.provider.controller;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 自定义限流,降级等异常处理对象
 */
@Slf4j
@Component
public class ServiceBlockExceptionHandler
     implements BlockExceptionHandler {
    /**
     * 用于处理BlockException类型以及子类类型异常
     */
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       BlockException e) throws Exception {
        //设置响应数据编码
        response.setCharacterEncoding("utf-8");
        //告诉客户端响应数据的类型,以及客户端显示内容的编码
        response.setContentType("text/html;charset=utf-8");
        //向客户端响应一个json格式的字符串
        //String str="{\"status\":429,\"message\":\"访问太频繁了\"}";
        Map<String,Object> map=new HashMap<>();
        map.put("status", 429);
        map.put("message","访问太频繁了");
        String jsonStr=new ObjectMapper().writeValueAsString(map);
        PrintWriter out = response.getWriter();
        out.print(jsonStr);
        out.flush();
        out.close();
    }
}

小节面试分析

何为降级熔断?(让外部应用停止对服务的访问,生活中跳闸,路障设置-此路不通)
为什么要进行熔断呢?(平均响应速度越来越慢或经常出现异常,这样可能会导致调用链堆积,最终系统崩溃)
Sentinel中限流,降级的异常父类是谁?(BlockException)
Sentinel 出现降级熔断时,系统底层抛出的异常是谁?(DegradeException)
Sentinel中异常处理接口是谁?(BlockExceptionHandler)
Sentinel中异常处理接口下默认的实现类为? (DefaultBlockExceptionHandler)
假如Sentinel中默认的异常处理规则不满足我们的需求怎么办?(自己定义)
我们如何自己定义Sentinel中异常处理呢?(直接或间接实现BlockExceptionHandler )
Sentinel熔断降级策略有哪些?(慢调用比例、异常比例、异常数)

Sentinel热点规则分析(重点)

概述
何为热点?热点即经常访问的数据。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制。
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制。
    热点参数限流会统计传入参数中的热点数据,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。其中,Sentinel会利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。

快速入门
第一步:在sca-provider中添加如下方法,例如:
参数列表使用@RequestParam时,访问时必须加参数

	@GetMapping("/sentinel/findById")
    @SentinelResource("resource")
    public String doFindById(@RequestParam("id") Integer id){
        return "resource id is "+id;
    }

第二步:服务启动后,选择要限流的热点链路,如图所示:
在这里插入图片描述

第三步:设置要限流的热点,如图所示:
在这里插入图片描述

热点规则的限流模式只有QPS模式(这才叫热点)。参数索引为@SentinelResource注解的方法参数下标,0代表第一个参数,1代表第二个参数。单机阈值以及统计窗口时长表示在此窗口时间超过阈值就限流。

第四步:多次访问热点参数方法,前端会出现如下界面,如图所示:
在这里插入图片描述

然后,在后台出现如下异常表示限流成功。

com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: 2

其中,热点参数其实说白了就是特殊的流控,流控设置是针对整个请求的;但是热点参数他可以设置到具体哪个参数,甚至参数针对的值,这样更灵活的进行流控管理。
一般应用在某些特殊资源的特殊处理,如:某些商品流量大,其他商品流量很正常,就可以利用热点参数限流的方案。

特定参数设计
配置参数例外项,如图所示:
在这里插入图片描述

其中,这里表示参数值为5时阈值为100,其它参数值阈值为1.

小节面试分析

如何理解热点数据?(访问频度比较高的数据,某些商品、谋篇文章、某个视频)
热点数据的限流规则是怎样的?(主要是针对参数进行限流设计)
热点数据中的特殊参数如何理解?(热点限流中的某个参数值的阈值设计)
对于热点数据的访问出现限流以后底层异常是什么?(ParamFlowException)

Sentinel系统规则(了解)

概述
系统在生产环境运行过程中,我们经常需要监控服务器的状态,看服务器CPU、内存、IO等的使用率;主要目的就是保证服务器正常的运行,不能被某些应用搞崩溃了;而且在保证稳定的前提下,保持系统的最大吞吐量。

快速入门
Sentinel的系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load(负载)、RT(响应时间)、入口 QPS 、线程数和CPU使用率五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。如图所示:
在这里插入图片描述

系统规则是一种全局设计规则,其中,

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
  • CPU使用率:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
    线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
    说明,系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务。

小节面试分析

如何理解sentinel中的系统规则?(是对所有链路的控制规则,是一种系统保护策略)
Sentinel的常用系统规则有哪些?(RT,QPS,CPU,线程,Load-linux,unix)
Sentinel系统保护规则被触发以后底层会抛出什么异常?(SystemBlockException)

Sentinel授权规则(重要)

概述
很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。例如微信中的黑名单。

快速入门
sentinel可以基于黑白名单方式进行授权规则设计,如图所示:
在这里插入图片描述

黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • 资源名:即限流规则的作用对象
  • 流控应用:对应的黑名单/白名单中设置的规则值,多个值用逗号隔开.
  • 授权类型:白名单,黑名单(不允许访问).
    案例实现:

定义请求解析器,用于对请求进行解析,并返回解析结果,sentinel底层在拦截到用户请求以后,会对请求数据基于此对象进行解析,判定是否符合黑白名单规则,例如:

第一步:定义RequestOriginParser接口的实现类,在接口方法中解析请求参数数据并返回,底层会基于此返回值进行授权规则应用。

@Component
public class DefaultRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String origin = request.getParameter("origin");//这里的参数名会与请求中的参数名一致
        return origin;
    }
}

第二步:定义流控规则,如图所示:
在这里插入图片描述

第三步:执行资源访问,检测授权规则应用,当我们配置的流控应用值为app1时,假如规则为黑名单,则基于
http://ip:port/path?origin=app1的请求不可以通过,其请求处理流程如图下:
在这里插入图片描述

拓展:尝试基于请求ip等方式进行黑白名单的规则设计,例如:

第一步: 修改请求解析器,获取请求ip并返回,例如:

@Component
public class DefaultRequestOriginParser  implements RequestOriginParser {
    //解析请求源数据
    @Override
    public String parseOrigin(HttpServletRequest request) {
        //获取访问请求中的ip地址,基于ip地址进行黑白名单设计(例如在流控应用栏写ip地址)
        String ip= request.getRemoteAddr();
        System.out.println("ip="+ip);
        return ip;
    }//授权规则中的黑白名单的值,来自此方法的返回值
}

第二步:在sentinel控制台定义授权规则,例如:
在这里插入图片描述

第三步:规则定义后以后,基于你的ip地址,进行访问测试,检测黑白名单效果.

小节面试分析

如何理解Sentinel中的授权规则?(对指定资源的访问给出的一种简易的授权策略)
Sentinel的授权规则是如何设计的?(白名单和黑名单)
如何理解Sentinel中的白名单?(允许访问的资源名单)
如何理解Sentinel中的黑名单?(不允许访问的资源名单)、
Sentinel如何识别白名单和黑名单?(在拦截器中通过调用RequestOriginParser对象的方法检测具体的规则)
授权规则中RequestOriginParser类的做用是什么?(对流控应用值进行解析,检查服务访问时传入的值是否与RequestOriginParser的parseOrigin方法返回值是否相同。)

网关简介

背景分析
我们知道,一个大型系统在设计时,经常会被拆分为很多个微服务。那么作为客户端要如何去调用 这么多的微服务呢?客户端可以直接向微服务发送请求,每个微服务都有一个公开的URL,该URL可以直接映射到具体的微服务,如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。这样的架构,会存在着诸多的问题,例如,客户端请求不同的微服务可能会增加客户端代码或配置的复杂性。还有就是每个服务,在调用时都需要独立认证。并且存在跨域请求,也在一定程度上提高了代码的复杂度。基于微服务架构中的设计及实现上的问题,为了在项目中简化前端的调用逻辑,同时也简化内部服务之间互相调用的复杂度,更好保护内部服务,提出了网关的概念。

网关概述
网关本质上要提供一个各种服务访问的入口,并提供服务接收并转发所有内外部的客户端调用,还有就是权限认证,限流控制等等。Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 等技术开发的一个网关组件,它旨在为微服务架构提供一种简单有效的统一的 API入口,负责服务请求路由、组合及协议转换,并且基于 Filter 链的方式提供了权限认证,监控、限流等功能。

Spring Cloud Gateway优缺点分析:

  • 优点:
    性能强劲:是第一代网关Zuul的1.6倍。
    功能强大:内置了很多实用的功能,例如转发、监控、限流等
    设计优雅,容易扩展。
  • 缺点:
    依赖Netty与WebFlux(Spring5.0),不是传统的Servlet编程模型(Spring MVC就是基于此模型实现),学习成本高。
    需要Spring Boot 2.0及以上的版本,才支持

快速入门

业务描述
通过网关作为服务访问入口,对系统中的服务进行访问,例如通过网关服务去访问sca-provider服务.

入门业务实现
第一步:创建sca-gateway模块(假如已有则无须创建),其pom.xml文件如下:
注入SpringCloud-Gateway依赖

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
 </dependency>

第二步:创建application.yml(假如已有则无须创建),添加相关配置,代码如下:

server:
  port: 9000
spring:
  application:
    name: sca-gateway
  cloud:
    gateway:
        routes: #配置网关路由规则
          - id: route01  #路由id,自己指定一个唯一值即可
            uri: http://localhost:8081/ #网关帮我们转发的url
            predicates: ###断言(谓此):匹配请求规则
              - Path=/nacos/provider/echo/**  #请求路径定义,此路径对应uri中的资源
            filters: ##网关过滤器,用于对谓词中的内容进行判断分析以及处理
              - StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos

其中:路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:

  • id,路由标识符,区别于其他 Route。
  • uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
  • predicate,断言(谓词)的作用是进行条件判断,只有断言都返回真,才会执行路由。
  • filter,过滤器用于修改请求和响应信息。
    第三步:创建项目启动类,例如:
package com.cy;

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}

第四步:启动项目进行访问测试,

依次启动sca-provider,sca-gateway服务,然后打开浏览器,进行访问测试,例如:
域名被uri修改,第一层路径被filters去除
在这里插入图片描述

小节面试分析?

什么是网关?服务访问(流量)的一个入口,类似生活中的“海关“
为什么使用网关?(服务安全,统一服务入口管理,负载均衡,限流,鉴权)
Spring Cloud Gateway 应用的初始构建过程(添加依赖,配置)
Gateway 服务的启动底层是通过谁去实现的?(Netty网络编程框架-ServerSocket)
Gateway 服务做请求转发时一定要在注册中心进行注册吗?(不一定,可以直接通过远端url进行服务访问)

负载均衡设计

为什么负载均衡?
网关才是服务访问的入口,所有服务都会在网关层面进行底层映射,所以在访问服务时,要基于服务serivce id(服务名)去查找对应的服务,让请求从网关层进行均衡转发,以平衡服务实例的处理能力。

Gateway中负载均衡实现?
第一步:项目中添加服务发现依赖,代码如下:

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

第二步:修改其配置文件,代码如下

server:
  port: 9000
spring:
  application:
    name: sca-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true  #开启通过服务注册中心的serviceId创建路由
      routes:
        - id: route01
          ##uri: http://localhost:8081/
          uri: lb://sca-provider # lb为服务前缀(负载均衡单词的缩写),不能随意写
          predicates: ###匹配规则
              - Path=/nacos/provider/echo/**
          filters:
              - StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos

其中,lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略。同时建议开发阶段打开gateway日志,代码如下:

logging:
  level:
    org.springframework.cloud.gateway: debug

第三步:启动服务,进行访问测试,并反复刷新分析,如图所示:
在这里插入图片描述
在这里插入图片描述

执行流程分析(重要)

根据官方的说明,其Gateway具体工作流程,如图所示:
在这里插入图片描述

客户端向Spring Cloud Gateway发出请求。
如果Gateway Handler Mapping 通过断言predicates(predicates)的集合确定请求与路由(Routers)匹配,则将其发送到Gateway Web Handler。
Gateway Web Handler 通过确定的路由中所配置的过滤器集合链式调用过滤器(也就是所谓的责任链模式)。
Filter由虚线分隔的原因是, Filter可以在发送代理请求之前和之后运行逻辑。
处理的逻辑是 在处理请求时 排在前面的过滤器先执行,而处理返回相应的时候,排在后面的过滤器先执行。

SpringCloud gateway处理请求的基本流程
xxxHandlerMapping:RoutePredicateHandlerMappin。将url交给对应的处理器
xxxPredicateFactory:PathRoutePredicateFactory。根据断言判断是否合理(路由,请求方式,请求时间,请求头)
xxxWebHandler:FilteringWebHandler
Filters:GatewayFilter,GlobalFilter
在这里插入图片描述

小节面试分析?

网关层面是如何实现负载均衡的?(通过服务名去查找具体的服务实例)
网关层面是如何通过服务名查找服务实例的?(Ribbon)
你了解Ribbon中的哪些负载均衡算法?(轮询,权重,hash,……可通过IRule接口进行查看分析)
网关进行请求转发的流程是怎样,有哪些关键对象?(XxxHandlerMapping,Handler,。。。)
网关层面服务的映射方式怎样的?(谓词-path,…,服务名/服务实例)
网关层如何记录服务的映射?(通过map,并要考虑读写锁的应用)

断言(Predicate)增强分析(了解)

Predicate 简介
Predicate(断言)又称谓词,用于条件判断,只有断言结果都为真,才会真正的执行路由。断言其本质就是定义路由转发的条件。

Predicate 内置工厂
SpringCloud Gateway包括一些内置的断言工厂(所有工厂都直接或间接的实现了RoutePredicateFactory接口),这些断言或谓词工程负责创建谓词对象,并通过这些谓词对象判断http请求的合法性,常见谓词工厂如下:

基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:

1) AfterRoutePredicateFactory:判断请求日期是否晚于指定日期
2) BeforeRoutePredicateFactory:判断请求日期是否早于指定日期
3) BetweenRoutePredicateFactory:判断请求日期是否在指定时间段内

-After=2020-12-31T23:59:59.789+08:00[Asia/Shanghai]

当且仅当请求时的时间After配置的时间时,才转发该请求,若请求时的时间不是After配置的时间时,则会返回404 not found。时间值可通过ZonedDateTime.now()获取。

基于header的断言工厂HeaderRoutePredicateFactory

判断请求Header是否具有给定名称且值与正则表达式匹配。例如:

-Header=X-Request-Id, \d+

基于Method请求方法的断言工厂,

MethodRoutePredicateFactory接收一个参数,判断请求类型是否跟指定的类型匹配。例如:

-Method=GET

基于Query请求参数的断言工厂,QueryRoutePredicateFactory :

接收两个参数,请求param和正则表达式, 判断请求参数是否具 有给定名称且值与正则表达式匹配。例如:

-Query=pageSize,\d+

Predicate 应用案例实践
内置的路由断言工厂应用案例,例如:

server:
  port: 9000
spring:
  application:
    name: sca-gateway
  cloud:
    nacos:
      server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true #开启通过服务中心的serviceId 创建路由的功能
      routes:
        - id: bd-id
          ##uri: http://localhost:8081/
          uri: lb://sca-provider
          predicates: ###匹配规则
              - Path=/nacos/provider/echo/**
              - Before=2021-01-30T00:00:00.000+08:00
              - Method=GET
          filters:
            -  StripPrefix=1 # 转发之前去掉1层路径

说明:当条件不满足时,则无法进行路由转发,会出现404异常。

小节面试分析

何为谓词?(网关中封装了判断逻辑的一个对象)
谓词逻辑的设计是怎样的?(谓词判断逻辑返回值为true则进行请求转发)
你了解哪些谓词逻辑?(path,请求参数,请求方式,请求头,….)
我们可以自己定义谓词工厂对象吗?(可以的)

过滤器(Filter)增强分析(了解)

概述
过滤器(Filter)就是在请求传递过程中,对请求和响应做一个处理。Gateway 的Filter从作用范围可分为两种:GatewayFilter与GlobalFilter。其中:

GatewayFilter:应用到单个路由或者一个分组的路由上。
GlobalFilter:应用到所有的路由上。
局部过滤器设计及实现
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下:
案例分析:

基于AddRequestHeaderGatewayFilterFactory,为原始请求添加Header。

例如,为原始请求添加名为 X-Request-Foo ,值为 Bar 的请求头:

spring:   cloud:
           gateway:
             routes:
               - id: add_request_header_route
                 uri: https://example.org
                 filters:
                   - AddRequestHeader=X-Request-Foo, Bar

基于AddRequestParameterGatewayFilterFactory,为原始请求添加请求参数及值,

例如,为原始请求添加名为foo,值为bar的参数,即:foo=bar。

spring:
  cloud:
    gateway:
      routes:
        - id: add_request_parameter_route
          uri: https://example.org
          filters:
            - AddRequestParameter=foo, bar

基于PrefixPathGatewayFilterFactory,为原始的请求路径添加一个前缀路径

例如,该配置使访问${GATEWAY_URL}/hello 会转发到uri/mypath/hello。

spring:
  cloud:
    gateway:
      routes:
        - id: prefixpath_route
          uri: https://example.org
          filters:
            - PrefixPath=/mypath

基于RequestSizeGatewayFilterFactory,设置允许接收最大请求包的大小

,配置示例:

spring:
  cloud:
    gateway:
      routes:
        - id: request_size_route
      uri: http://localhost:8080/upload
      predicates:
        - Path=/upload
      filters:
        - name: RequestSize
          args:
            # 单位为字节
            maxSize: 5000000

如果请求包大小超过设置的值,则会返回 413 Payload Too Large以及一个errorMessage

全局过滤器设计及实现
全局过滤器(GlobalFilter)作用于所有路由, 无需配置。在系统初始化时加载,并作用在每个路由上。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。一般内置的全局过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们 自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。 例如,当客户端第一次请求服务时,服务端对用户进行信息认证(登录), 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证 以后每次请求,客户端都携带认证的token 服务端对token进行解密,判断是否有效。学过spring中的webflux技术的同学可以对如下代码进行尝试实现(没学过的可以忽略).

package com.cy.filters;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String username=exchange.getRequest()
                .getQueryParams().getFirst("username");
        if (!"admin".equals(username)) {
            System.out.println("认证失败");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        //调用chain.filter继续向下游执行
        return chain.filter(exchange);

    }

    @Override
    public int getOrder() {
        return 0;
    }
}

启动Gateway服务,假如在访问的url中不带“user=admin”这个参数,可能会出现异常.

小节面试分析

网关过滤器的作用是什么?(对请求和响应数据做一个预处理)
网关过滤器的类型有哪些?(局部过滤器,全局过滤器)
如何理解局部过滤器?(针对具体链路的应用的过滤器,需要进行配置)
你了解哪些局部过滤器?
如何理解全局过滤器?(作用于所有请求链路)
如何自己定义全局过滤器?(直接或间接实现GlobalFilter接口)
假如现在让你进行平台的网关自研设计,你可以吗?(可以)

限流设计及实现

限流简述
网关是所有外部请求的公共入口,所以可以在网关进行限流,而且限流的方式也很多,我们采用Sentinel组件来实现网关的限流。Sentinel支持对SpringCloud Gateway、Zuul等主流网关进行限流。参考网址如下:

https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

限流快速入门
第一步:添加依赖
在原有spring-cloud-starter-gateway依赖的基础上再添加如下两个依赖,例如:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

第二步:添加sentinel及路由规则(假如已有则无需设置)

routes:
  - id: route01
    uri: lb://sca-provider
    predicates: ###匹配规则
      - Path=/provider/echo/**
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8180
      eager: true  #true表示此服务启动时,就可以在sentinel面板看到我们的服务信息(可选)

第三步:启动网关项目,检测sentinel控制台的网关菜单。
启动时,添加sentinel的jvm参数,通过此菜单可以让网关服务在sentinel控制台显示不一样的菜单,代码如下。

-Dcsp.sentinel.app.type=1

假如是在idea中,可以参考下面的图进行配置
在这里插入图片描述

Sentinel 控制台启动以后,界面如图所示:
在这里插入图片描述

说明,假如没有发现请求链路,API管理,关闭网关项目,关闭sentinel,然后重启sentinel,重启网关项目.

第四步:在sentinel面板中设置限流策略,如图所示:
在这里插入图片描述

第五步:通过url进行访问检测是否实现了限流操作
在这里插入图片描述

基于请求属性限流
定义指定routeId的基于属性的限流策略如图所示:
在这里插入图片描述

通过postman进行测试分析
在这里插入图片描述

自定义API维度限流(重点)
自定义API分组,是一种更细粒度的限流规则定义,它允许我们利用sentinel提供的API,将请求路径进行分组,然后在组上设置限流规则即可。

第一步:新建API分组,如图所示:
在这里插入图片描述

第二步:新建分组流控规则,如图所示:
在这里插入图片描述

第三步:进行访问测试,如图所示
在这里插入图片描述

定制流控网关返回值

定义配置类,设计流控返回值,代码如下:

@Configuration
public class GatewayConfig {
    public GatewayConfig(){
        GatewayCallbackManager.setBlockHandler( new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map<String,Object> map=new HashMap<>();
                map.put("state",429);
                map.put("message","two many request");
                String jsonStr=JSON.toJSONString(map);
                return ServerResponse.ok().body(Mono.just(jsonStr),String.class);
            }
        });
    }
}

其中,Mono 是一个发出(emit)0-1个元素的Publisher对象。

网关解决跨域问题

spring:
  cloud:
    gateway:
		globalcors:
       		cors-configurations:
          		'[/**]': #任意请求采用下面方式
            		allowedOrigins: "*"	#允许跨域域名
            		allowedMethods: "*" #允许请求方式
            		 # - GET 允许get请求方式
            		allowedHeaders: "*" #允许请求头
            		addMapping: "*" #允许跨域的路径
            		allowCredentials: true #是否允许证书 不再默认开启
            		maxAge: 7200 #允许跨域时间

小节面试分析?

网关层面结合sentinel实现限流,其限流的类型有几种?(两种-route id,api)
网关层面可以自定义限流后的异常处理结果吗?(可以)
你知道Sentinel底层限流的算法有哪些?(滑动窗口,令牌桶,漏斗,。。。)

总结(Summay)

重难点分析
网关(Gateway)诞生的背景?(第一:统一微服务访问的入口,第二:对系统服务进行保护,第三进行统一的认证,授权,限流)
网关的选型?(Netifix Zuul,Spring Cloud Gateway,…)
Spring Cloud Gateway的入门实现(添加依赖,路由配置,启动类)
Spring Cloud Gateway中的负载均衡?(网关服务注册,服务的发现,基于uri:lb://服务id方式访问具体服务实例)
Spring Cloud Gateway中的断言配置?(掌握常用几个就可,用时可以通过搜索引擎去查)
Spring Cloud Gateway中的过滤器配置?(掌握过滤器中的两大类型-局部和全局)
Spring Cloud Gateway中的限流设计?(Sentinel)
FAQ 分析
Gateway在互联网架构中的位置?(nginx->gateway–>微服务–>微服务)
Gateway底层负载均衡的实现?(Ribbon)
Gateway应用过程中设计的主要概念?(路由id,路由uri,断言,过滤器)
Gateway中你做过哪些断言配置?(after,header,path,cookie,…)
Gateway中你用的过滤器有哪些?(添加前缀,去掉前缀,添加请求头,…,负载均衡,…)
BUG分析
503 异常?(服务不可用,检查你调用的服务是否启动ok,路由uri写的是否正确)
启动时解析.yml配置文件异常(格式没有对齐,单词写错)

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要基于Nacos实现Spring Cloud Gateway的动态网关路由,可以按照以下步骤进行操作: 1. 添加依赖:在Spring Cloud Gateway项目的pom.xml文件中添加相应的依赖,包括spring-cloud-starter-gateway和spring-cloud-starter-alibaba-nacos-discovery等。 2. 配置Nacos注册中心:在application.properties或application.yml配置文件中添加Nacos注册中心的相关配置,包括Nacos服务器地址、命名空间、分组等信息。 3. 配置动态路由:创建一个RouteLocator Bean,并在其中使用Nacos的服务发现来定义动态路由规则。可以通过Nacos的配置中心来管理路由规则的动态更新。 ```java @Configuration public class GatewayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("service_route", r -> r.path("/api/v1/**") .uri("lb://service-provider")) .build(); } } ``` 上述示例中,定义了一个名为service_route的路由规则,将请求路径以/api/v1/开头的请求转发到名为service-provider的微服务上。 4. 启动Gateway应用:启动Spring Cloud Gateway应用,它会自动从Nacos注册中心获取动态路由规则并进行路由转发。 5. 管理动态路由:使用Nacos的配置中心来管理动态路由规则。可以通过Nacos的控制台或API来添加、修改或删除路由规则,Gateway应用会自动更新并生效。 通过以上步骤,就可以基于Nacos实现Spring Cloud Gateway的动态网关路由了。你可以根据实际需求和业务场景,添加更多的路由规则和配置。希望对你有所帮助!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值