SpringCloud

SpringCloud

简介

Spring Cloud是一系列框架的有序集合,它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署

Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包

Spring Cloud组成

Spring Cloud 的子项目,大致可分成两类,一类是对现有成熟框架“Spring Boot化”的封装和抽象,也是数量最多的项目

第二类是开发了一部分分布式系统的基础设施的实现,如Spring Cloud Stream扮演的就是kafka, ActiveMQ这样的角色

对于我们想快速实践微服务的开发者来说,第一类子项目就已经足够使用

Spring Cloud Netflix

  • 是对Netflix开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等。

Spring Cloud Config

  • 将配置信息中央化保存, 配置Spring Cloud Bus可以实现动态修改配置文件

Spring Cloud Stream  

  • 分布式消息队列,是对Kafka, MQ的封装

Spring Cloud Security

  • 对Spring Security的封装,并能配合Netflix使用

Spring Cloud Zookeeper

  • 对Zookeeper的封装,使之能配置其它Spring Cloud的子项目使用

Spring Cloud Eureka

  • 是 Spring Cloud Netflix 微服务套件中的一部分,它基于Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能。

SpringCloud

  • SpringCloud是目前国内使用最广泛的微服务框架

    官网地址:https://spring.io/projects/spring-cloud

  • SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验

服务注册发现:

Eureka,Nacos,Consul

服务远程调用:

OpenFeign,Dubbo

服务链路监控:

Zipkin,Sltuth

统一配置管理:

SpringCloudConfig,Nacos

统一网关路由:

SpringCloudGateway,Zuul

流控,降级,保护:

Hystix,Sentinel

SpringCloud 与 SpringBoot 版本兼容性

Release Train Boot Version
2021.0.x aka Jubilee 2.6.x, 2.7.x (Starting with 2021.0.3)
2020.0.x aka Ilford 2.4.x, 2.5.x (Starting with 2020.0.3)
Hoxton 2.2.x, 2.3.x (Starting with SR5)
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

服务的拆分与远程调用

1、不同微服务,不要重复开发相同业务

2、微服务数据独立,不要访问其它微服务的数据库

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

SpringCloud坐标

 <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>{spring-boot-docs-version}</version>
       <relativePath /> <!-- lookup parent from repository -->
   </parent>

<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>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

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

根据订单id查询订单功能

  • 订单表
  • 用户表

需求:根据订单id查询订单的同时,把订单所属的用户信息一起返回

根据微服务的原则,我们只能从订单服务向用户服务发起远程调用来获取用户信息,符合单一职责原则

如何在java代码中发出http请求?

注册RestTemplate

在order-service的OrderApplication启动类中注册RestTemplate

@SpringBootApplication
public class OrderApplication {

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

    /**
     * 创建restTemplate并且注入容器
     * @return
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

restTemplate

采用restful风格的http请求,有get,post,put,delete等方法

获取数据方法:getForObject()

参数

url:请求路径

responseType:返回值类型字节码

接收到的数据都是Json风格,但是我们可以在第二个设置返回值类型,rest模板会自动反序列化数据为对象

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);

        // 2.利用restTemplate发起http请求,查询用户
        // 2.1.url路径
        String url = "http://localhost:8081/user/"+ order.getUserId();
        
        // 2.2.发起http请求,实现远程调用
        User user = restTemplate.getForObject(url, User.class);

        //封装user到order
        order.setUser(user);

        // 4.返回
        return order;
    }
}

★Eureka注册中心

之前我们的订单服务访问用户服务,是通过一次http请求获取到的数据,我们是将userService的ip和视图名称硬编码在了程序中

  • 这种方法无法适应服务器地址的变更,也不利于服务的更新与管理

服务调用关系

服务的提供者:暴露接口给其它微服务调用

服务消费者:调用其它微服务提供的接口

  • 提供者与消费者角色其实是相对的,是根据业务环境决定的

    一个服务可以同时是服务提供者和服务消费者

Eureka的原理

服务消费者和服务提供者都作为eureka-client存在

  • 每个服务在启动时都会将自身的信息注册给eureka,每个微服务都可能成为服务提供者

  • 服务消费者在需要其它服务的时候会从注册中心拉取服务信息

  • 注册中心会返回该服务机器的所有注册地址,通过负载均衡的方式挑选最佳的服务地址

  • 服务消费者向服务地址发起远程调用(服务提供者每隔30秒向eureka发送一次存活状态,确保服务存在,健康状态确认

Eureka的作用

记录和管理微服务

消费者该如何获取服务提供者具体信息?

​ 服务提供者启动时向注册中心注册信息

​ 注册中心保存信息

​ 消费者根据服务名称向注册中心拉取服务提供者信息

如果有多个服务提供者,消费者该如何选择?

​ 服务消费者利用负载均衡算法,从服务列表中挑选

消费者如何感知服务提供者健康状态?

​ 服务提供者每隔30秒向注册中心发送心跳请求,报告健康状态

​ 注册中心会更新记录服务列表信息,不健康的服务会被列表剔除

​ 消费者可以拉取到最新的信息

搭建EurekaService

搭建步骤:

1、创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖,交由springboot自动装配

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

2、编写启动类,添加**@EnableEurekaService注解**,开启eureka服务

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {

    public static void main(String[] args) {

        SpringApplication.run(EurekaApplication.class,args);

    }

}

3、添加application.properties文件,编写配置 (本篇使用到的properties配置也可以使用yaml配置替换)

eureka在注册信息的时候会将自己本身作为一个微服务注册到注册中心,因此也要提供微服务名称与eureka地址,方便将来eureka集群通讯

#服务端口
server.port=10086
#微服务名称
spring.application.name=eurekaserver
#eureka地址
eureka.client.service-url.defaultZone:http://127.0.0.1:10086/eureka

本地访问:http://localhost:10086/

查看eureka注册中心状态

Instances currently registered with Eureka

查看Application服务实例列表,即注册的微服务

Status:UP表示服务存活,DOWN表示服务死亡 + 服务IP地址

注册服务

将user-service服务注册到EurekaService步骤如下:

1、在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、在application.properties文件,编写下面的配置

spring.application.name=userservice
eureka.client.service-url.defaultZone:http://127.0.0.1:10086/eureka

通过idea提供的服务拷贝功能拷贝user服务,CopyConfiguration,在环境VM option中配置新的端口号-Dserver.port=8082,启动服务

可以在注册中心发现两个服务UP(2)

拉取服务

服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡

实现拉取的两种方法:

1、修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口

  • 接口调用的时候,框架内部会将服务名称替换成具体的服务 IP 信息,然后进行调用
String url = "http://userservice/user/"+ order.getUserId();

2、在order-service项目的启动类OrderApplication中的RestTemplate实例注册函数上添加负载均衡注解:

@LoadBalanced注解 告诉restTemplate请求需要被ribbon拦截处理

注解原理

  • 主要的逻辑就是给 RestTemplate 增加拦截器,在请求之前对请求的地址进行替换,或者根据具体的负载策略选择服务地址,然后再去调用
  /**
     * 创建restTemplate并且注入容器
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

总结:搭建EurekaServer

  • 引入eureka-server依赖
  • 添加@EnableEurekaServer注解
  • 在application中配置eureka地址

★Ribbon负载均衡

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现

通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用

Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中

因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具,所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要

参考博客

负载均衡流程

1、order-service先发起请求http://userservice/user/1

2、Ribbon拿到服务名称后会去找eureka-server拉取userservice的服务列表

3、eureka-service返回服务名称对应的服务列表

4、Ribbon根据负载均衡轮循找到对应服务

LoadBalancerInterceptor类

实现了ClientHttpRequestInterceptor接口

由restTemplate发起的请求在被LoadBalanced注解修饰后会经过这个接口的实现类

拦截器会在底层通过rule对象choose(key)方法决定要以何种负载均衡规则来选择列表中的服务

IRule接口

AbstractLoadBalancerRule抽象类

  • AbstractLoadBalancerRule类是负载均衡策略IRule的抽象实现类,在该抽象类中定义了负载均衡器ILoadBalancer对象

  • 该对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息来作为分配依据,并依次设计一些算法来针对特定场景的高级策略

负载均衡规则参考

RoundRobinRule 轮循调度负载均衡规则

直接获取下一个可用实例,如果超过10次没有获取到可用的实例,则返回空且打印异常信息

RandomRule 随机选择服务规则

通过chooseRandomInt方法获取一个随机数,该随机数作为可用服务列表的索引来获取具体的实例

ribbon具体实现流程

1、请求会被LoadBalancerInterceptor(负载均衡拦截器)拦截

2、拦截下的请求会交由RibbonLoadBalancerClient(客户端负载均衡器)进行处理,获取url中的服务名称

3、然后通过DynamicServerListLoadBalancer(动态服务列表负载均衡器)获取到eureka中的服务列表信息

4、再去找IRule接口实现的负载均衡规则来获取到服务资源,返回给客户端负载均衡器

5、最后修改url发送请求

IRule接口

继承关系图

负载均衡策略

内置负载均衡规则类 规则描述
RoundRobinRule 简单轮询服务列表来选择服务器,它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的<clientName>、<clientConfigNameSpace>、ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule 忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule 随机选择一个可用的服务器。
RetryRule 重试机制的选择逻辑

调整负载均衡规则

通过定义IRule实现可以修改负载均衡规则,有两种方式:

两种负载均衡机制的作用范围是不同的

1、代码方法:在order-service中的OrderApplication类中,添加一个新的IRule实现类

全局配置

@Bean
public IRule randomRule(){

    return new RandomRule();

}

2、配置文件方式:在order-service的application.properties文件中,添加新的配置也可以修改规则

指定服务名称配置

#指定服务负载均衡规则
userserver.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalancerClient,请求时间会很长

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

#开启饥饿加载
ribbon.eager-load.enabled=true
#指定饥饿加载的服务名称
ribbon.eager-load.clients=orderserver

如果有多个使用数组下标添加clients[0]=xxserverclients[1]=xxserver

如果是yml中加换行加- xxserver

负载均衡的作用

  • 解决并发压力,提高应用处理性能(增加吞吐量,加强网络处理能力)

  • 提供故障转移,实现高可用

  • 通过添加或减少服务器数量,提供网站伸缩性(扩展性)

  • 安全防护(负载均衡设备上做一些过滤,黑白名单等处理)

总结:

1.Ribbon负载均衡规则

  • 规则接口是IRule
  • 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮循

2.负载均衡自定义方式

  • 代码方式:配置灵活,但修改时需要重新打包发布
  • 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置

3.饥饿加载

  • 开启饥饿加载
  • 指定饥饿加载的微服务名称

★Nacos注册中心

认识Nacos

Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件,相比Eureka功能更加丰富,在国内受欢迎度较高

Nacos官网:https://nacos.io/zh-cn/

下载nacos

在cmd中以单体模式启动 startup.cmd -m standalone

访问nacos终端控制器:http://192.168.119.1:8848/nacos/index.html#/login

用户名和密码都是nacos

Spring Cloud Commons features:

  • DiscoveryClient interface
  • ServiceRegistry interface
  • Instrumentation for RestTemplate to resolve hostnames using DiscoveryClient

由于注册中心都实现了以上springCloud接口规范,因此我们只需要对配置进行修改即可

配置流程

1、在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖

<!--nacos管理依赖-->
<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>

注意:注释掉order-service和user-service中原有的eureka依赖

2、添加nacos的客户端依赖:

<!-- nacos客户端依赖 -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3、修改user-service和order-service中的application.properties文件,注释eureka地址,添加nacos地址:

#nacos服务地址
spring.cloud.nacos.server-addr=localhost:8848

Nacos服务分级存储模型

一个服务的多个实例会部署到多个机房,增高容灾性,一个地区的多个服务实例被成为集群

服务-集群-实例

服务跨集群调用问题

由于不同地域的服务访问存在网络延迟问题,因此服务调用尽可能选择本地集群的服务,跨集群调用延迟较高

只有在本地集群不可用的情况下,才会去访问其它集群

nacos引入集群的概念就是为了防止直接的跨集群调用问题

如何配置实例的集群属性

修改application.properties文件,添加下面配置

#配置集群名称,也就是机房位置,例如:HZ,杭州
spring.cloud.nacos.discovery.cluster-name=HZ

开启对应的Nacos集群负载均衡配置

在order-server中配置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务

#配置负载均衡规则
userserver.ribbon.NFLoadBalancerRuleClassName=com.alibaba.cloud.nacos.ribbon.NacosRule

Nacos负载均衡规则:本地集群优先找到服务列表,在服务列表中随机选择可用服务

如果本地没有可用服务,那么就会发生跨集群访问服务,控制台会发出警告信息,提醒运维人员

总结

Nacos服务分级存储模型,提高了服务的容灾性

  • 一级是服务,例如userserver
  • 二级是集群,例如杭州或上海
  • 三级是实例,例如杭州机房的某台部署了userserver的服务器

根据权重负载均衡

实际部署中会出现这样的场景:

  • 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求
  • 例如在服务器升级时,我们可以先将需要升级的服务器权重调为0,进行升级,然后慢慢将权重提高,测试升级后的功能

Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高

如何配置机器权重?

Nacos控制台可以设置实例的权重值(0~1),首先选中实例后面的编辑按钮

将权重设置为0.1,测试可以发现8081被访问到的频率大大降低

总结

Nacos控制台可以设置实例的权重值,0~1之间

同级群内的多个实例,权重越高被访问的频率越高

权重设置为0则完全不会被访问

环境隔离(namespace)

namespace :nacos中服务存储和数据存储的最外层都是一个名为namespace(命名空间)的东西,用来做最外层隔离,不同命名空间之间的服务是无法互相访问的

Namespace >> Group >> Service/Data

在没设置命名空间的情况下,nacos上注册的服务都在public(保留命名空间)中

命名空间配置

管理员可以在nacos控制台创建新的命名空间分组

我们需要根据命名空间的id(自己设置或系统生成)来配置服务的命名空间

在application.properties中配置

#命名空间对应的id
spring.cloud.nacos.discovery.namespace=d494b1c4-0822-4dbd-8dff-4aec25cdc97e

总结:

  • namespace用来做环境隔离
  • 每个namespace都有唯一id
  • 不同namespace下的服务不可见

Nacos注册中心原理

  • 服务消费者会向注册中心定时拉取服务,通过DynamicServerListLoadBalancer(动态服务列表负载均衡器)缓存拉取到的服务列表数据

  • 服务消费者在获取到了服务信息后会去发起远程调用向服务提供者请求服务信息

  • 服务提供者有两种实例模式:

    • ​ 临时实例:采用心跳监测(每隔30s)来追踪服务健康状态,如果服务宕机则直接剔除服务

    • ​ 非临时实例:不会要求心跳检测,会由nacos注册中心发起请求主动询问(更快)服务健康状态,如果服务宕机,nacos不会将非临时实例从服务列表中剔除,仅仅会标记为不健康状态

  • 相比eureka,nacos注册中心会及时的主动推送服务变更消息给服务消费者

配置临时实例和非临时实例

#设置为非临时实例false,默认是临时实例true
spring.cloud.nacos.discovery.ephemeral=false

总结:

1、Nacos与eureka的共同点

  • 都支持服务注册和服务拉取
  • 都支持服务提供者心跳方式做健康检测

2、Nacos与Eureka的区别

  • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  • Nacos集群默认采用AP模式,当集群中存在非临时实例时,采用CP模式,Eureka默认采用AP模式,但是不支持切换

CAP理论

CAP理论提出就是针对分布式数据库环境的

分布式系统的最大难点,就是各个节点的状态如何同步,CAP 定理是这方面的基本定理,也是理解分布式系统的起点

A (Availability) 可用性

C(Consistency) 一致性

P(Partition tolerance) 分区容错

  • 大多数分布式系统都分布在多个子网络,每个子网络就叫做一个区(partition)分区容错的意思是,区间通信可能失败,一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立
  • 选择一致性C(Consistency),为了保证数据库的一致性,我们必须等待失去联系的节点恢复过来,在这个过程中,那个节点是不允许对外提供服务的,这时候系统处于不可用状态(失去了A属性)
  • 选择可用性 A(Availability),此时,那个失去联系的节点依然可以向系统提供服务,不过它的数据就不能保证是同步的了(失去了C属性)

★Nacos配置管理

配置热部署

配置更改热更新

配置管理服务会去通知微服务配置发生了改变,微服务会去向配置管理服务读取配置信息

新建配置

DataID:配置名称,不能重复,命名方式:服务名称+运行环境名称+配置类型后缀名 例如:userserver-dev.yaml

分组:默认即可

配置内容: 存放需要热更新需求的配置,比如日期格式,开关类型配置

微服务获取配置

默认配置获取步骤如下:

项目启动→读取本地配置文件application.yml→创建spring容器→加载bean

我们需要提前获取nacos的地址,才能去读取nacos中的配置文件,因此我们需要一个比application.yml优先级更高的配置文件bootstrap.yml来读取nacos地址

1、引入Nacos的配置管理客户端依赖

<!--nacos配置管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、在userserver中的resource目录添加一个bootstrap.properties文件,这个文件是引导文件,优先级高于application.properties

#服务名称
spring.application.name=userserver
#开发环境
spring.profiles.active=dev
#Nacos地址
spring.cloud.nacos.server-addr=localhost:8848
#文件后缀名
spring.cloud.nacos.config.file-extension:yaml
#配置文件所在命名空间
spring.cloud.nacos.config.namespace: d494b1c4-0822-4dbd-8dff-4aec25cdc97e

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为配置文件id,来读取配置

3、配置自动刷新

Nacos中的配置文件变更后,微服务无需重启就可以感知,不过需要通过下面两种配置实现:

方式一: 在**@Value注入的变量所在类上添加注解@RefreshScope**

@RefreshScope 用来实现配置、实例热加载

方式二: 使用**@ConfigurationProperties注解(推荐)**

package cn.itcast.user.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {

    private String dateformat;

}

通过聚合该类的方式在需要资源的类中引入配置文件资源

@ConfigurationProperties

  • 该注解有一个prefix属性,通过指定的前缀,绑定配置文件中的配置
  • 该注解可以放在类上,也可以放在方法上
  • 当该注解用于类上,类中的属性会作为配置资源名被自动注入资源(遵循约定大于配置)
  • 当该注解作用于方法上时,如果想要有效的绑定配置,那么该方法需要有@Bean注解且所属Class需要有@Configuration注解

注意事项:

不是所有的配置都适合放到配置中心,维护起来比较麻烦

建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置

总结:

1、在Nacos控制台终端添加署配置文件

2、在微服务中引入nacos配置的maven坐标

3、在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名,这些决定了程序启动时去nacos读取哪个文件

多环境共享配置

此多环境指的是同名服务的不同命名空间环境

微服务启动时会从nacos读取多个配置文件

我们使用服务名+配置文件后缀的命名方式来配置多环境共享配置

例如在nacos终端创建新的配置userserver.yaml,此配置会对所有环境下的userserver服务进行全局配置

注意: 配置也具有环境变量namespace属性,此属性与微服务的环境变量属性不同,应该单独在bootstrap配置文件中指定,不指定默认为使用public环境下的配置文件

#此配置应该在bootstrap配置文件下配置
spring.cloud.nacos.config.namespace: 配置文件id

关于配置优先级问题

多种配置的优先级:

单环境配置(服务名-profile.yaml) >> 多环境共享配置(服务名.yaml) >> 本地配置(application.yaml)

总结:

  • [服务名]-[spring.profile.active].yaml,单环境配置
  • [服务名].yaml,默认配置,多环境共享
  • 优先级:[服务名]-[环境].yaml >[服务名].yaml > 本地配置

★Http客户端Feign

RestTemplate方法调用存在的问题

先来看我们以前利用RestTemplate发起远程调用的代码:

存在下面的问题:

  • 代码可读性差,编程体验不统一
  • 参数复杂URL难以维护

Feign的介绍

Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign

同时Feign在底层集成了Ribbon,会自动做到负载均衡

其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题

Feign的使用

在服务的pom文件中引入feign的依赖

<!--feign客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在启动类上加上注解@EnableFeignClients

@EnableFeignClients springboot对Feign自动装配,启用feign客户端

编写FeignClient接口

创建一个接口,作为客户端

@FeignClient注解 声明当前类是一个Feign客户端,用于微服务直接的调用

package cn.itcast.order.clients;

import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("userserver")
public interface UserClient {
    
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id")Long id);
    
}

使用接口中定义的方法代替RestTemplate

在orderserver中测试

public Order queryOrderById(Long orderId) {

    //查询订单
    Order order = orderMapper.findById(orderId);

    //用Feign远程调用
    User user = userClient.findById(order.getUserId());

    //封装user和order
    order.setUser(user);
    
    //返回
    return order;

}

Feign的配置

Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:

类型 作用 说明
feign.Logger.Level 修改日志级别 包含四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder 响应结果的解析器 http远程调用的结果做解析,例如解析json字符串为java对象
feign.codec.Encoder 请求参数编码 将请求参数编码,便于通过http请求发送
feign. Contract 支持的注解格式 默认是SpringMVC的注解
feign. Retryer 失败重试机制 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

一般我们需要配置的就是日志级别

自定义Feign的配置

方法一、配置文件方式配置:

全局生效

#如果用default就是全局配置,如果写服务名称,则是针对某个微服务的配置
feign.client.config.default.loggerlevel=FULL

局部生效

#如果用default就是全局配置,如果写服务名称,则是针对某个微服务的配置
feign.client.config.userserver.loggerlevel=FULL

方法二、java代码配置:

创建一个类FeignClientConfiguration

需要先声明一个Bean

public class FeignClientConfiguration {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC;
    }
}

如果是全局配置,则把它放到**@EnableFeignClients**这个注解中(启动类中)

@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)

如果是局部配置,则把它放到**@FeignClient**这个注解中(Feign客户端中)

@FeignClient(value = "userserver", configuration = FeignClientConfiguration.class)

Feign的性能优化

Feign底层的客户端实现:

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池

连接池:可以提前创建并存放多个连接对象

因此优化Feign的性能主要包括:

  • 使用连接池代替默认的URLConnection
  • 日志级别,最好用basic或none

Feign添加HttpClient的支持:引入依赖

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

配置http连接池:

#开启feign对HttpClient的支持
feign.httpclient.enabled=true
#最大的连接数
feign.httpclient.max-connections=200
#每个路径的最大连接数
feign.httpclient.max-connections-per-route=50

Feign的最佳实践

方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准(不推荐,会产生紧密耦合)

方法二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO,默认的Feign配置都放到这个模块中,提供给所有消费者使用

定义好feign-api

在API中定义好Client、实体类、默认配置等

引用依赖即可

抽取方式的Feign最佳实践

本质maven的自动包装和导包

1、首先创建一个模块,命名为feign-api,然后引入feign的starter的依赖

2、创建Feign客户端,将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中

3、引依赖,在order-service中引入feign-api的依赖

4、导包,修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包

5、启用Feign客户端,在order-service的启动类上添加注解,由于定义的FeignClient不在SpringBootApplication的扫描包范围,因此我们需要在指定FeignClient所在包加上注解@EnableFeignClients()

属性:

  • basePackages = "扫描包路径" 注入包下的所有客户端实例
  • clients = {UserClient.class} 注入指定多个字节码(字节码数组)客户端实例

★Gateway统一网关

网关功能:

  • 身份认证和权限校验
  • 服务路由、负载均衡
  • 请求限流

在SpringCloud中网关的实现包括两种:

  • gateway
  • zuul

Zuul是基于Servlet的实现,属于阻塞式编程,而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能

搭建网关服务

1、创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖

<!--网关依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

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

添加gateway网关设置

#网关端口
server.port=10010
#服务名称
spring.application.name=gateway
#nacos地址
spring.cloud.nacos.server-addr=localhost:8848
#网关路由配置
#路由id,自定义,只要唯一即可
spring.cloud.gateway.routes[0].id=user-service
#uri: http://127.0.0.1:8081 路由的目标地址http就是固定地址
#路由的目标地址 lb(loadbalance)就是负载均衡,后面跟服务名称
spring.cloud.gateway.routes[0].uri=lb://userservice
#路由断言,也就是判断请求是否符合路由规则的条件
# - Path=/user/** 这个是按照路径匹配,只要以/user/开头就符合要求
spring.cloud.gateway.routes[0].predicates[0]=Path=/user/**

路由配置包括

  • 路由id:路由的唯一标示
  • 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
  • 路由断言(predicates):判断路由的规则,
  • 路由过滤器(filters):对请求或响应做处理

访问流程

用户访问网关

网关通过路由规则判断,将请求路由到微服务

路由断言工厂

Route Predicate Factory

  • 我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由断言的条件
  • 例如Path=/user/**是按照路径匹配,这个规则是由PathRoutePredicateFactory类来处理的
  • 像这样的断言工厂在SpringCloudGateway中还有十多个

Spring提供了11种基本的Predicate工厂:

名称 说明 示例
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-01-21T17:42:47.789-07:00[America/Denver]
Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p
Header 请求必须包含某些header - Header=X-Request-Id, \d+
Host 请求必须是访问某个host(域名) - Host=.somehost.org,.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name
RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24
Weight 权重处理

路由过滤器

GatewayFilter

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理

Spring提供了31种不同的路由过滤器工厂,例如:

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除有一个响应头
RequestRateLimiter 限制请求的流量

实例:

在文件中配置过滤器信息:

spring.cloud.gateway.routes[0].filters[0]=AddRequestHeader=Key,This is value!

在userserver中测试:

@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Key",required = false) String key) {

    System.out.println("Key:"+key);

    return userService.queryById(id);

通过网关访问userserver服务,可以获取到key的值

默认过滤器(对所有路由生效)

通过default-filters来配置

spring.cloud.gateway.default-filters[0]=AddRequestHeader=Key, This is value!

全局过滤器

GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样

区别在于GatewayFilter通过配置定义,处理逻辑是固定的,而GlobalFilter的逻辑需要自己写代码实现

定义方式是实现GlobalFilter接口

public interface GlobalFilter {
    
   /**
    *  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
    *
    * @param exchange 请求上下文,里面可以获取Request、Response等信息    
    * @param chain 用来把请求委托给下一个过滤器   
    * @return {@code Mono<Void>} 返回标示当前过滤器业务结束    	 
    */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);

}

实例:

定义全局过滤器,拦截并判断身份

需求:拦截请求,判断请求参数是否含有access参数,参数值是否为true,同时满足则放行

过滤器需要设置顺序

@Order注解 设置过滤器优先级,值越小优先级越高

或者通过实现Ordered接口来设置

@Order(-1)
@Component
public class AccessFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();

        //获取参数中的access参数
        String access = params.getFirst("access");

        //获取值是否为true
        if ("true".equals(access)){

            //放行
            return chain.filter(exchange);

        }

        //设置状态码,401
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);

        //拦截
        return exchange.getResponse().setComplete();
    }

}

验证:

http://localhost:10010/user/1?access=true 可以访问

http://localhost:10010/user/1 状态401

过滤器类型

  • 请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

  • 请求路由后,会将当前路由过滤器DefaultFilterGlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器

  • 路由过滤器和DefaultFilter都属于GatewayFilter

  • GatewayFilterAdapter 网关过滤器适配器,实现了GatewayFilter接口,同时在内部聚合了一个GlobalFilter,作用是将GlobalFilter适配为GatewayFilter

过滤器执行顺序

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照配置文件中的声明顺序从1递增
  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行

**org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()**方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链

跨域问题处理

跨域:域名不一致就是跨域,主要包括:

域名不同: www.taobao.comwww.taobao.orgwww.jd.commiaosha.jd.com

域名相同,端口不同:localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决方案:CORS

跨域资源共享(CORS)

  • 跨域资源共享 (CORS) 是W3C标准,共享指的是:域名A的网页请求域名B的资源

  • 现代浏览器都有同源限制,域名A不允许访问域名B的资源,出于安全原因,浏览器会限制从脚本发起的跨域 HTTP 请求

网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现:

spring:
  cloud:
    gateway:
      globalcors: #全局的跨域处理
        add-to-simple-url-handler-mapping: true #解决options请求被拦截问题
        cors-configurations:
          '[/**]':
            allowedOrigins: #允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: #允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" #允许在请求中携带的头信息
            allowCredentials: true #是否允许携带cookie
            maxAge: 360000 #这次跨域检测的有效期

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:SpringCloud

字数:10.2k

本文作者:Os467

发布时间:2022-08-18, 13:21:39

最后更新:2022-09-05, 00:09:20

原始链接:https://os467.github.io/2022/08/18/springCloud/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏