SpringCloud

发布时间 2023-09-29 20:45:56作者: 凉冰24

Springcloud介绍

微服务架构:

在大数据以及高并发的环境下,微服务架构就诞生了。微服务架构是一种将单应用程序作为一套小型服务开发的方法,每种应用程序都在其自己的进程中运行,并与轻量级机制 (通常是HTTP资源的API) 进行通信。服务拆分最小化,服务的集中化管理是最少的它们可以使用不同的存储计算存储数据数据。

微服务架构的特点:

微服务架构中每个服务都有自己的独立的数据库,数据库之间没有任何联系,这样的好处是,”随着业务的不断扩张,服务与服务不需要提供数据库集成,而是提供API接口相互调用。数据库的独立使系统维护简单,性能提高,迁移方便。一个典型的微服务系统,各个服务的数据库可能都不同,每个服务所使用的数据存储技术可以根据业务需求来选择。微服务是直接通过HTTP协议进行通信的,并且可以配合采用消息队列(如RabbitMQ,Kafka等)。

微服务架构中系统会被拆分为若干个微服务,每个微服务又是一个独立的运行程序,测试容易,维护简单。单体架构的应用程序系统部署一次而微服务架构对每一个微服务进行独立部署。这时出现的如Docker等自动化部署工具减少人为控制,降低出错概率。当然微服务架构也不是万能的,因为数据接口间需要通过网络进行通讯,必然会牺牲掉一部分效率,所以一些用户量不大、访问量低、m:很少高并发的服务器仍然建议使用单体架构的后端服务器。

微服务架构设计原则 :

  • 前后端分离:
    • 前后端分离下,前后端交互界面清晰,只考虑接口问题,容易维护
  • AKF扩展拆分:
    • X轴:水平复制;Y轴: 按服务功能拆分;Z轴: 基于类似数据分区
  • 无状态服务:
    • 也就是说url请求没有上下文关系。
  • Restful通讯风格:

微服务拆分注意事项

  • 不同的微服务,不要重复开发相同的业务

  • 微服务数据独立,不要访问其它微服务数据库

  • 微服务可以将自己的业务暴露为接口,供其它微服务调用

Spring Cloud:

  • Spring Cloud是一个微服务治理平台,提供了一套服务框架,包含了服务注册与发现、配置中心、消息中心、负载均衡、数据监控等等。

  • Spring Cloud是一个基于Spring Boot实现的微服务开发杂构,它利用SpringBoot的开发便利性巧妙地筒化了分布式系统的开发。

  • Spring Cloud为开发者提供了快速构建分布式系统的工具,开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源对接。微服务是可以独立部署、水平扩展、独立访问的服务单元。Spring Cloud就是这些微服务的大管家。

image-20230926222927657

Spring Cloud 两代对比:Spring Cloud一套框架标准,其实现是由许多公司的开源产品的整合而来的,这其中第一代标准主要以Netflix公司开源产品的实现为主,第二代标准主要以Alibaba公司开源产品的实现为主,它们之间可以结合使用。

Spring Cloud 第一代 Spring Cloud 第二代
注册中心 Eureka,Consul,ZooKeeper Nacos,Radar
负载均衡 Ribbon Spring Cloud LoadBanlancer(理论)
远程调用 Feign Open Feign
配置中心 Spring Cloud Config Nacos,Apollo,Config Keeper
服务网关 Spring Cloud Zuul Spring Cloud Gateway
容错保护 Hystrix Sentinel,Resilience4j

注册中心(Eureka)

背景

在大数据以及高并发的环境下,一般不再使用单一服务器而是使用服务器集群完成工作。在微服务架构的服务器集群当中,一个业务可能需要有多个微服务配合来共同完成当业务越来越多越复杂的情况下,微服务的数量会越来越庞大,微服务之间的调用关系也越来越复杂。这么复杂的关系,单纯想靠人去记录和维护是不现实的。

如何快速找到相应的微服务地址,如何在多个相同微服务提供者中快速找到最适合的,这是急需解决的问题。

在此情况下微服务的注册中心就应运而生了,它可以记录微服务架构中每一个服务的IP地址、端口以及所对应的服务。这样当需要某一服务时,只需到注册中心去查找最佳的微服务提供者地址和端口即可。

注册中心

注册中心记录服务和服务地址的映射关系,所有服务注册到这里,当一个服务需要调用其他服务时,会到注册中心查找到相应的服务地址进行调用。类似"通讯录"。

  • 注册中心主要作用有:服务注册/续期/反注册,服务订阅/取消订阅,服务健康检查等。
  • Springcloud中典型的微服务注册中心有Euraka、Nacos、Consul、Zookeeper、Radar等

案例

Eureka实现步骤:

  • 搭建注册中心
    • 引入eureka-server依赖
    • 在application.yml中配置eureka地址。
  • 启动类上添加@EnableEurekaServer
  • 服务注册
    • 引eureka-client依赖
    • 在application.yml中配置eureka地址
  • 服务调用
    • 引入eureka-client依赖
    • 在application.yml中配置eureka地址
    • RestTemplate上添加@LoadBalanced。
    • 使用服务名称进行远程调用

image-20230927000431514

新建一个eureka-server模块

image-20230927000722207

application.yaml

server:
  port: 8090
spring:
  application:
    name: eurekaserver
eureka:
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:8090/eureka

添加注解

image-20230927000849907

给customer-server和ticket-server添加Pom依赖和yaml配置

image-20230927001037131

server:
  port: 8081
spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/service_customer?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
  application:
    name: servicecustomer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8090/eureka

启动全部服务

访问 http://localhost:8090/eureka。能看到有可以使用的服务

image-20230927001452509

修改ticket-server模块的启动类,增加负载均衡。然后修改服务层源码,让其在eureka服务器中拉去customer-server服务。

@SpringBootApplication
public class TicketApplication {

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

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

image-20230927002232906

Restful RestTemplate
Get getForObject(URL+id,class)
Post postForObject(URL,Object,class)
Put put(URL,Object)
Delete delete(URL+id)

重新启动五个服务。打开浏览器访问serviceticket服务,它会在eureka服务器中找到适合的servicecustomer服务,并去拉取servicecustomer服务。

总结

Eureka注册中心主要解决的问题:

  • 服务消费者如何获取服务提供者地址服务提供者启动时向Eureka注册自己的服务信息,Eureka服务器保存这些信息,服务消费者根据服务名称向Eureka服务器拉取服务提供者地址信息。

  • 服务消费者如何感知服务提供者是否健康服务提供者默认会每隔30秒向Eureka服务器发送心跳信息报告健康状态,Eureka服务器会更新记录服务列表信息,心跳不正常会被剔除,服务消费者可以拉取到最新的信息

Eureka主要包含Eureka Server和Eureka Client:

  • 注册中心(Eureka Server)

    • 记录服务信息

    • 心跳监控

  • 服务提供者(Eureka Client)

    • 注册自己的服务信息到注册中心

    • 默认每隔30向注册中心发送一次心跳

  • 服务消费者(Eureka Client)

    • 根据服务名称从注册中心拉取服务列表

    • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

负载均衡(Ribbon)

有多个服务提供者时,服务消费者是如何选择?服务消费者利用负载均衡算法,从服务列表中根据指定的规则挑选一个服务提供者,这就需要用到Ribbon。

测试

image-20230927010417527

image-20230927004509095

连续8次调用http://localhost:8082/ticket/queryById/1

image-20230927004906709

调用的次数是均衡的

使用负载均衡Ribbon

引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>1.4.3.RELEASE</version>
</dependency>

将均衡策略改为随机

image-20230927010646306

Ribbon负载均衡流程

img

Ribbon的IRule常见负载均衡策略

策略名 策略描述
RoundRobinRule 简单轮询方法选择一个server
RandomRule 随机选择一个server
WeightedResponseTimeRule 根据响应时间分配权重,响应时间占比越长权重越小,被选中的机会越小
BastAvailableRule 选择最小并发连接数的server
ZoneAvailableRule 轮询前剔除不可用的zone后选择server

Ribbon的使用方法

  1. 在配置类中注入一个新的IRule即可

    image-20230927011836742

  2. 配置文件中修改,可控制到具体服务提供者

servicecustomer: 
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Ribbon默认是第一次使用服务时加载及延迟加载,所以第一次速度较慢。可以开启立即加载,启动是自动完成加载。

ribbon:
	eager-load:
		enabled: true
		clients:
			_servicecustomer

serviceticket服务配置文件中定义了servicecustomer对它进行立即加载

远程调用(Open Feign)

使用RestTemplate存在一定缺点,URL嵌入之后降低代码可读性,且遇到复杂冗长的URL更加难以维护。

image-20230927012728437

Feign是一个声明式客户端,作用是解决上面的问题。github.com/OpenFeign/feign

  1. 在ticket模块中添加openfeign依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 修改Ticket的启动类,增加feign注解

    image-20230927013441173

  2. 建立一个CustomerClient接口,类似Controller

package com.ticket.client;

import com.custmer.domain.Customer;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "servicecustomer")
public interface CustomerClient {
// 和customerController的接口一致
@GetMapping("/customer/queryById/{id}")
Customer queryById(@PathVariable("id")int id);
}
  1. 修改TicketServiceImpl实现
@Service("TicketService")
public class TicketServiceImpl implements TicketService {
    @Autowired
    TicketDao ticketDao;
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    CustomerClient customerClient;

    @Override
    public Ticket queryById(int id) {
        Optional<Ticket> ticket = ticketDao.findById(id);
        Ticket ticketRes = ticket.get();
        Customer customer = customerClient.queryById(ticketRes.getUid());
        ticketRes.setCustomer(customer);
        return ticketRes;
    }
}

PS: 这里坑比较多,建议SpringBoot版本使用3.0以下的。

Feign使用连接池

  • URL Connection:默认方式,不支持连接池
  • Apache HttpClient: 支持连接池
  • OKHttp: 支持连接池

添加Pom依赖

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

配置连接池

feign:
  httpclient:
    enabled: true
    max-connections: 200
    max-connections-per-route: 50

注册中心(Nacos)

  • 是阿里巴巴的开源项目,作为Spring Cloud中的一个组件,相比于Eureka其功能更加丰富,在国内受欢迎程度更高。
  • 提供基于DNS和基于RPC的服务发现,即能被采用http/https的服务注册和发现,也能被采用PRC的服务注册与发现。
  • 动态修改配置并实时生效,非常有利于“配置优先”的服务开发。与Eureka可以无侵入的切换。
  • 可用于单机模式(测试),集群模式和多集群模式(cluster-name区分不同集群)
  • 可以直接手动修改权重配置。
  • 可以通过命名空间来区分不同环境配置。
  • 相比Eureka只能通过任务定时剔除无效服务,Nacos可以主动询问代替心跳检测和手动下线服务,在自动或手动下线服务后使用消息机制快速通知客户端。

nacos下载地址https://github.com/alibaba/nacos/releases

在bin目录下执行startup -m standalone

运行nacos

访问http://192.168.184.1:8848/nacos/index.html

默认 用户名nacos 密码nacos

SpringCloud兼容版本

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2.2.10-RC1* Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.9.RELEASE Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.8.RELEASE Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.7.RELEASE Spring Cloud Hoxton.SR12 2.312.RELEASE
2.2.6.RELEASE Spring Cloud Hoxton.SRg 2.3.2.RELEASE
2.2.1RELEASE Spring Cloud Hoxton.SR3 2.2.5.RELEASE
2.2.0.RELEASE Spring Cloud Hoxton.RELEASE 2.2.X.RELEASE
2.1.4.RELEASE Spring Cloud Greenwich.SR6 2.1.13.RELEASE
2.1.2.RELEASE Spring Cloud Greenwich 2.1.X.RELEASE

要尽量版本合适

Spring Cloud Alibaba Version Sentinel Version Nacos Version RocketMQ Version Dubbo Version Seata Version
2022.0.0.0-RC2 18.6 2.2.1 4.9.4 ~ 1.7.0-native-rc2
2021.0.5.0 18.6 22.0 49.4 ~ 1.61
2.2.10-RC1 1.8.6 2.2.0 4.9.4 ~ 1.61
2022.0.0.0-RC1 18.6 2.2.1-RC 4.9.4 ~ 1.61
2.2.9.RELEASE 1.8.5 2.1.0 4.9.4 ~ 1.5.2
2021.0.4.0 18.5 2.0.4 4.9.4 ~ 15.2
2.2.8RELEASE 1.8.4 2.1.0 4.9.3 ~ 1.5.1
2021.0.1.0 18.3 1.4.2 4.9.2 ~ 14.2
2.2.7.RELEASE 181 2.0.3 4.6.1 2.7.13 13.0
2.2.6.RELEASE 181 14.2 4.4.0 2.7.8 130

测试

  1. 修改customer服务的Pom依赖。
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>	<!--修改SpringBoot版本-->
        <relativePath/>
    </parent>

    <groupId>com.custmer</groupId>
    <artifactId>custmer</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
    </properties><!--修改Springcloud和Springcloud alibaba的兼容版本-->

    <dependencies>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency><!--nacos代替eureka-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.0.33</version>
            <scope>runtime</scope>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

  1. customer服务yaml配置文件
server:
  port: 8081
spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/service_customer?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
  application:
    name: servicecustomer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  1. 对Ticket服务做同样的配置修改

  2. 访问http://192.168.184.1:8848/nacos/index.html

    image-20230927151651815

  3. 复制customerserver

image-20230927151949901

配置集群

添加配置

spring  
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        cluster-name: SH

image-20230927153005481

查看服务详情

image-20230927153142396

客户端集群设置

  • ticket配置文件上设置优先访问本集群(集群内为随机)
# 被访问服务名称
servicecustomer:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
  • 只有同集群服务都关闭之后,才去访问不同集群的服务
  • 可以在nacos服务器中直接修改服务节点的权重

命名空间

创建命名空间ID

命名空间

给customer服务和Ticket分配一个命名空间的ID

image-20230927163944578

不同的命名空间不可以互相访问

Nacos非临时实例

  • 不再是心跳检测,而是主动询问

  • 在serviceTicket中修改配置文件

    image-20230927164335199

配置中心(Nacos)

  • 在微服务架构中,每一个微服务都有自己的配置文件,随着微服务越来越多,将来要更改其中的配置难道要逐一去修改吗?
  • 在微服务架构中一般会存在一个配置中心,它可以统一的管理集群中的所有配置。如果有配置需要变更,只需要去配置中心更改,由它通知相应的微服务进行相关配置的热更新

配置的Data ID不是随意的,格式:服务名+环境.yaml

image-20230927165338878

image-20230927165310985

客户端拉取配置

  • customer模块中Pom引入相关依赖
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-nacos-config</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>

customer服务中添加bootstrap.yaml配置,删除application.yaml相同配置。

image-20230927181714689

启动访问localhost:8084/customer/hello

image-20230927182018607

配置文件热部署

修改Nacos中的配置文件。

image-20230927181822874

在服务端的Controller类中添加注解@RefreshScope重启服务

image-20230928004404759

此时。修改Nacos配置文件时,客户端的配置能够实时生效。

Nacos支持多环境配置,如配置共用配置+开发时配置+运营时配置。

总结

Nacos中配置管理的步骤:

  • 在Nacos上添加配置文件在微服务中引入nacos的配置依赖在微服务中添加bootstrap.yml,配置nacos地址、环境、服务名称,文件后级名
  • 多环境下配置文件优先级:服务名-环境.yml>服务名.yml>本地配置

服务网关(SpringCloud Gateway)

网关功能:

  • 身份认证和权限控制
  • 服务路由和负载均衡
  • 请求限流

常见的网关

  • Spring Cloud Zuul(阻塞式)
  • Spring Cloud Gateway(响应式)

Spring Cloud Gateway Diagram

使用步骤:

  1. 新建一个SpringBoot服务Spring-Gateway
  2. 添加Pom依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.12.RELEASE</version>
		<relativePath/>
	</parent>

	<groupId>com</groupId>
	<artifactId>spring-gateway</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<java.version>8</java.version>
		<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
		<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
	</properties>

	<dependencies>

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

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
  1. 添加服务配置
server:
  port: 8888
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: servicecustomer
          uri: lb://servicecustomer
          predicates:
            - Path=/customer/**
        - id: serviceticket
          uri: lb://serviceticket
          predicates:
            - Path=/ticket/**

PS:网关路由配置

  • id:唯一id号
  • uri: 路由目标地址,lb(loadbalance)后跟服务名称
  • predicates: 路由断言,根据路径匹配是否符合要求

image-20230928100720799

通过网关的8888端口也能访问服务

测试断言

修改配置中的predicates,增加After(服务在特定时间后才能访问)

spring:
    cloud:
        gateway:
            route:
                - id: servicecustomer
                  url: lb://servicecustomer
			predicates:
                - Path=/customer/**
                - After=2023-09-28T16:17:00.123+08:00[Asua/Shanghai]

过滤器

customer配置文件

image-20230928104134602

自定义过滤器。实现GlobalFilter接口并实现里面的filter方法

package com.springgateway.utils;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Order(-1)// 过滤顺序,越小优先
public class AuthorizerFilter implements GlobalFilter {
    /**
     * @param exchange  // 请求上下文,可以获取Request、Response等
     * @param chain     // 下一个过滤器
     * @return {@link Mono}<{@link Void}>
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String,String> queryParams = request.getQueryParams();
        String authorization = queryParams.getFirst("authorization");
        if ("admin".equals(authorization)){
            return chain.filter(exchange);
        }
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
}

重启网关服务。携带指定的权限

image-20230928105811247

不写或权限不正确。

image-20230928105921821image-20230928105956156

网关路由断言工厂

名称 说明 例子
After 是某个时间点后的请求 After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before 是某个时间点前的请求是 Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between 某两个时间点之前的请求 Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-0121T17:42:47.789-07:00[America/Denver]
Cookie 请求必须包含某些cookie Cookie=chocolate,ch.p //匹配具有一个名为chocolate的cookie的请求,该cookie的值匹配ch.p正则表达式
Header 请求必须包含某些header Header=X-Request-Id,d+ //匹配有一个名为X-Request-Id的请求头其值匹配d+正则表达式(也就是说,它有一个或多个数字的值)
Host 请求必须是访问某个域名 Host=*.somehost.org,*,anotherhost.org
MethodPath 请求方式必须是指定方式 Method=GET,POST
Path 请求路径必须符合指定规则 Path=/red/{segment}/blue/**
Query 请求参数必须包含指定参数 Query=red,green. //请求中包含一个red的查询参数,且该参数的值匹配gree,正则表达式(即green,gree2都是可以的)
RemoteAddr 请求者的ip必须是指定范围 RemoteAddr=192.168.0.1/16 //其中192.168.0.1是一个IP地址,16是一个子网掩码
Weight 权重处理 image-20230928111825963

总结

网关搭建步骤:

  • 引入服务发现和网关的依赖

  • 配置文件application.yml中配置网关信息,包括注册信息和路由信息等。

网关路由配置routers.filters:

  • 路由id: 路由的唯一标识,一般使用服务名称即可
  • 路由目标uri: 路由目标地址,使用Ib时代表根据服务名负载均衡使用地址,使用http时代表使用固定地址
  • 路由断言predicates: 判断路由规则
  • 路由过滤器filters: 对请求或响应做相应处理

默认路由配置default-filters;对所有的路由都生效

  • 自定义(全局)过滤器: 对一切进入网关和微服务响应生效。"写一个自定义(全局)过滤器,实现接口GlobalFilter并实现里面的filter方法

过滤器的执行顺序:

  • 每一个过滤器都必须指定一个int类型的order值order值越小优先级越高,执行顺序越靠前。

  • 自定义(全局)过滤器可以通过@Order注解指定,@默认过滤器和路由过滤器由Spring指定,默认顺序从1开始递增。

  • 当过滤器的order值相同时,会按照默认过滤器>"路由过滤器>自定义(全局)过滤器 的优先级执行。

  • 默认情况下浏览器是不允许跨域的,但是Spring Gateway可以采用CORS方案处理跨域,仅需简单配置即可。

容错保护(Sentinel)

雪崩问题

微服务中的某个服务出现故障,引起整个链路中所有的微服务不可用

什么是服务雪崩

依赖服务都无法完成,导致服务D崩溃

雪崩问题的解决方案:

  • 超时处理: 设定超时时间,“请求超过一定的时间没有相应就返回错误,不会无休止等待。
  • 舱壁模式:"限定每个业务能使用的线程数,避免耗尽整个资源,因此也称为线程隔离。
  • 熔断降级: 由断路器统计业务执行的异常比例,如果超出闽值则会熔断该业务,拦截访问该业务的一切请求。
  • 流量控制: 限制业务访问的QPS,避免服务因流量的突增而故障。(预防措施)
  • 常用的容错保护有Sentiel、Hystrix等

Sentinel监控

下载:https://github.com/alibaba/Sentinel/tags。配置:JDK17、sentinel-dashboard-1.8.3.jar

# Sentinel对JDK版本有要求
# sentinel-dashboard-1.8.3.jar<===>JDK1.8
# sentinel-dashboard-1.8.6.jar<===>JDK17
java -jar sentinel-dashboard-1.8.3.jar --server.port=8899

# 1.8.3使用高版本JDK,需要添加配置。==> 否则会报错"root of context hierarchy"
java --add-opens java.base/java.lang=ALL-UNNAMED -jar sentinel-dashboard-1.8.3.jar --server.port=8899
# 虽然能够启动,但是貌似不能正常使用

打开Sentinel控制台:http://localhost:8899/#/login。默认用户和密码都是sentinel

image-20230928230102272

给Ticket添加Sentinel依赖

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

添加yaml配置

spring:
  cloud:    
    sentinel:
      transport:
        dashboard: localhost:8899   #配置Sentinel实例

启动Ticket、customer、gateway服务。

先访问后才能监控到对应服务 http://localhost:8888/ticket/queryById/1?authorization=admin

image-20230929001147362

Sentinel限流

image-20230929002809152

每秒限访问1次

image-20230929002742291

Sentinel隔离

  • 添加配置,开启feign对Sentinel的支撑

image-20230929005730711

  • 在CustomerClient所在包中新建一个工厂类并将其注册为Bean
package com.ticket.client.fallback;

import com.custmer.domain.Customer;
import com.ticket.client.CustomerClient;
import feign.hystrix.FallbackFactory;

public class CustomerClientFallbackFactory
        implements FallbackFactory<CustomerClient> {
    @Override
    public CustomerClient create(Throwable throwable) {
        return new CustomerClient() {
            @Override
            public Customer queryById(int id) {
                System.out.println("!!!!!!!!!!!!!!!!!!!处理隔离请求!!!!!!!!!!!!!!!!!!!!");
                return new Customer();
            }
        };
    }
}
  • 启动类中将其注册成Bean
    @Bean
    public CustomerClientFallbackFactory userClientFallbackFactory(){
        return new CustomerClientFallbackFactory();
    }
  • 在customerClient的Feign注解中加入fallbackFactory
@FeignClient(value = "servicecustomer",fallbackFactory = CustomerClientFallbackFactory.class)
public interface CustomerClient {
    @GetMapping("/customer/queryById/{id}")
    Customer queryById(@PathVariable("id") int id);
}

重启服务。报错如下!

Caused by: java.lang.AbstractMethodError: com.alibaba.cloud.sentinel.feign.SentinelContractHolder.parseAndValidateMetadata(Ljava/lang/Class;)Ljava/util/List;
	at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:151) ~[feign-core-10.12.jar:na]
	at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:49) ~[feign-core-10.12.jar:na]
	at feign.Feign$Builder.target(Feign.java:269) ~[feign-core-10.12.jar:na]
	at org.springframework.cloud.openfeign.HystrixTargeter.target(HystrixTargeter.java:38) ~[spring-cloud-openfeign-core-2.2.9.RELEASE.jar:2.2.9.RELEASE]
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.loadBalance(FeignClientFactoryBean.java:352) ~[spring-cloud-openfeign-core-2.2.9.RELEASE.jar:2.2.9.RELEASE]
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:388) ~[spring-cloud-openfeign-core-2.2.9.RELEASE.jar:2.2.9.RELEASE]
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:361) ~[spring-cloud-openfeign-core-2.2.9.RELEASE.jar:2.2.9.RELEASE]
	at org.springframework.cloud.openfeign.FeignClientsRegistrar.lambda$registerFeignClient$0(FeignClientsRegistrar.java:246) ~[spring-cloud-openfeign-core-2.2.9.RELEASE.jar:2.2.9.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1235) ~[spring-beans-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556) ~[spring-beans-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	... 43 common frames omitted

原因依赖之间的版本问题。解决方案:使用alibaba默认配置的版本。

出现循环依赖问题!

Error creating bean with name 'com.ticket.client.CustomerClient': Requested bean is currently in creation: Is there an unresolvable circular reference?

原因估计是SpringBoot的扫包问题。使用注解主动扫描client的包。scanBasePackages。

在Sentinel服务器端成功捕获Feign端口数据

image-20230929161956292

在Sentinel控制台上设置隔离。设置同时处理多个不同用户的请求。

image-20230929162231231

使用apifox测试100个线程同时请求

image-20230929163918666

查看拒绝通过和拒绝。

image-20230929163855807

成功的状态码是200,429是被拒绝了

image-20230929164623443

Sentine熔断

修改customer端,模拟慢调用

image-20230929165129602

设置熔断规则

image-20230929165446882

熔断效果

image-20230929170143852

快速刷新。虽然请求成功,但是数据为null。等待之后能够重新获得数据

image-20230929170226402

控制台的数据

image-20230929170018294