SpringCloud

微服务框架(其实由很多公司开源出来的组件收录整合而成),基于SpringBoot自动装配的特性,SpringBoot也实现了微服务的整合,如此才容易得到推广。

image-20230116195322399

组件

  • 注册中心
  • 负载均衡器Ribbon

版本兼容问题

SpringCloud要使用必须保证有与之匹配的SpringBoot版本(官网查阅)

像Hoxton.SR10大于SR5,则Hoxton对应SpringBoot版本2.3.X。否则不兼容报错

image-20230116195925968

一、服务拆分

微服务治理的服务拆分包括注册发现、远程调用、配置管理

1、微服务事项

  • 根据业务模块拆分(当然一个模块还可以继续拆分更细),做到单一职责,不要重复开发相同业务
  • 微服务数据独立,不要访问其他微服务的数据库
  • 微服务可以将业务暴露为接口,供其它微服务使用

image-20230116201715272

2、微服务项目结构

image-20230116201259749

3、微服务远程调用

远程调用原理

  • 基于RestTemplate发起的http请求实现远程调用
  • http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可(类比前端发送Ajax请求)
  • 服务调用关系
    • 服务提供者:暴露接口给其他微服务调用
    • 服务消费者:调用其它微服务提供的接口
    • 一个服务可以同时是服务提供者和服务消费者——>角色相对

image-200116202708561

基于Feign的远程调用

声明式的http请求调用客户端,而且内部实现了负载均衡

image-20230117134053060

声明式:只需要一个注解声明就能实现功能,类比注解开启事务

基本使用

引入依赖

image-20230117133613213

添加@EnableFeignClients注解

image-20230117133642543

编写FeignClient接口

image-20230117133819806

使用FeignClient中定义的方法代替RestTemplate

image-20230117133901439

自定义配置

SpringBoot能够自动装配,但是呢也能允许我们覆盖重写一些“对象”的配置。

对于Feign来说可以修改的配置主要是以下内容:image-20230117135112759

一般是配置日志,其他的没啥必要,而且记录日志也是要消耗性能的,所以一般都是将日志等级设置为basic或者null

修改配置的方法有两种:

  • 方式一是配置文件,feign.client.config.xxx.loggerLevel
    ①如果xxx是default则代表全局
    ②如果xxx是服务名称,例如userservice则代表某服务
  • 方式二是java代码配置Logger.Level这个Bean
    ①如果在@EnableFeignClients注解声明则代表全局
  • ②如果在@FeignClient注解中声明则代表某服务

image-20230117134837425

性能优化

①为什么需要性能优化

Feign底层发送http请求的客户端实现:

  • URLConnection:默认实现(jdk),不支持连接池.

  • Apache HttpClient:支持连接池(Spring的默认实现)

  • OKHttp:支持连接池

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

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

完成性能优化一般修改配置后需要进行压测来确定具体的数值设定

②案例:Feign添加HttpClient的支持

  • pom.xml引入依赖
  • application.yml配置连接池
    • 最大连接数
    • 单个路径的最大连接数
    • ......

image-20230117140317017

Feign的最佳实践

所谓最佳实践就是大量公司企业在不断踩坑的情形下经验积累出使用Feign的最佳开发方案

1)分析
  • 【继承】让controller和FeignClient继承同一接口

    • 服务紧耦合。但更具“契约性”。
    • 父接口参数列表中的映射不会被继承——>需要在Controller中实现方法

    image-20230117142836201

  • 【抽取/封装】将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用

    冗余度高,如果抽取东西过多,其它一个小业务模块就会引入很多不相关的东西。但更为规范。

    image-20230117143409964

2)实践

流程总结

实现最佳实践方式二的步骤如下:
1.首先创建一个module,命名为feign-api,然后引入feign的starter依赖
2.将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中

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

5.重启测试

步骤实现

1.首先创建一个module,命名为feign-api,然后引入feign的starter依赖image-20230117144939208

2.将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中

image-20230117145213525

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

image-20230117145453670

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

5、注意事项,指定扫描

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。 即SpringBoot默认只在自己的应用范围内扫描包(只有扫描到而且扫描到类上有交付处理的注解才会被SpringBoot当成Bean进行管理,这样才能在调用处正确自动装配)

有两种方式解决:

  • 方式一:指定Feignclient所在包

  • 方式二:指定FeignClient字节码(用到才导(数组)指定加载的客户端无需扩大包扫描的范围) ✔️

image-20230117150333828

方式二案例:

image-20230117145839012

4、Eureka架构【注册/监控】⭐️

概念原理

在Eureka架构中,微服务角色有两类:

  • EurekaServer:服务端,注册中心
    • 记录服务信息
    • 心跳监控
  • EurekaClient:客户端
    • Provider:服务提供者
      • 注册自己的信息到EurekaServer
      • 每隔30s向EurekaServer发送心跳
    • consumer:服务消费者
      • 根据名称从EurekaServer拉取服务列表
      • 基于服务列表做负载均衡,选中一个微服务后发起远程调用【多个服务提供者,消费者如何选择】

架构图:

image-20230116203754142

搭建Eureka

  • 创建项目,引入spring-cloud-starter-netflix-eureka-server依赖
  • 编写启动类,添加@EnableEurekaServer注解
  • 添加application.yml文件
    • 设置服务端口
    • 设置服务名称
    • 设置服务接口接口(当Eureka集群存在时,用来相互注册)

image-20230116205222367

服务注册

在客户端(相对

  • 引入eureka-client依赖
  • 在application.yml中配置eureka地址——>只要知道eureka地址都可以注册

IDEA模拟多实例注册:右键选择复制一份配置然后并在窗口中选择命令行添加-DServer.port=未被使用的端口来启动。-D表示添加命令行参数。

服务发现

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

image-20230116211355710

流程总结

image-20230116211302941

负载均衡原理及策略更改

负载均衡流程(负载均衡器Ribbon)⭐️

主要由IRule接口定义负载均衡的选择(内含随机等各种算法)

image-20230116213300581

负载均衡策略

Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则

默认选择实现的是zoneAvoidanceRule这个类——>通过配置类修改选择均衡的策略

image-20230116214245290

策略含义

image-20230116214151816

更改负载均衡策略

有两种

  • 代码配置类设置(全局)——>定义新的IRule
  • 针对单个微服务进行配置——>单个微服务的配置文件添加ribbon下的NFLoadBalancerRuleClassName字段属性设置包路径

image-20230116214429839

饥饿加载

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

饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过修改配置文件ribbon-eager-load-enabled:true设置为开启饥饿加载

image-20230116215518155

总结

image-20230116215401673

5、Nacos架构【注册/监控/集群/环境隔离】⭐️

阿里巴巴提供的,比Eureka更为丰富(多出了集群概念),它并没有内嵌到SpringCloud中,需要单独安装和启动

Nacos遵循SpringCloud接口的开发规范——>服务注册和Eureka类似——>只需改依赖就行(区分服务端和客户端依赖)

startup.cmd -m standalone

因为Nacos默认是集群启动,所以如果想要单个启动需要加上其余参数,如上

image-20230116221611013

服务多级存储模型【地域访问/容灾】

分三级

  • 服务
  • 集群
  • 实例

模型抽象图

服务调用尽可能选择本地集群的服务,跨集群调用延迟较高本地集群不可访问时,再去访问其它集群

image-20230116222718075

配置集群

在配置文件中添加spring.cloud.nacos.discovery.cluster-name属性

image-20230116222547821

NacosRule的负载均衡

和Eureka一样基于IRule接口实现负载均衡策略,设置均衡策略方式一样是通过修改配置属性

image-20230117100110441

和Eureka不一样的是Eureka有一个专门的策略NacosRule负载均衡策略

  • 优先选择同集群服务实例列表
  • 本地集群找不到提供者,才去其它集群寻找,并且会报警告
  • 确定了可用实例列表后,再采用随机负载均衡挑选实例

image-20230117095755503

服务实例的权重设置

NacaosRule默认就是按集群选择实例列表然后随机选择,但是如果一个集群里边的实例有些老机器和新机器,自然希望老机器的访问量少一点新机器访问量多一点来分担压力——>解决方案:权重设置

  • Nacos控制台可以设置实例的权重值,0~1之间同集群内的多个实例,
  • 权重越高被访问的频率越高
  • 权重设置为0则完全不会被访问——>系统的平滑升级,用户无感知
    • 升级的时候先针对某个实例,升级完成后再逐步调高权重放一小部分用户进来做测试,确认无误再彻底回调比例并升级其他实例

环境隔离

概念

Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离——>将业务相关的服务和数据至于同一个Group里边。【非必须】

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

image-20230117101639160

环境设置
  • 在控制台创建一个新的namespace(自动生成唯一id)

    image-20230117102152571

    image-20230117102229240

    image-20230117102219139

  • 在IDEA项目配置文件中添加新的属性字段通过唯一id进行环境隔离

    image-20230117102249938

之后Nacos控制台就会显示新创建的namespace,默认都属于public空间

image-20230117102447249

且不同命名空间的实例将不能相互访问。

Nacos和Eureka的区别【主动、集群、隔离】

  • Nacos与eureka的共同点

    • 都支持服务注册和服务拉取
    • 都支持服务提供者心跳方式做健康检测
  • Nacos与Eureka的区别

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

Nacos更加细节

也可以更加主动,不过一般是临时实例来减轻服务器的负担

image-20230117105137222

临时实例和非临时实例的设置(配置文件spring-cloud-nacos-discovery-ephemeral:false 设置为非临时实例)

image-20230117104854803

6、配置管理

微服务成百上千之后我们不可能单独去修改一个配置文件,不仅修改麻烦更新也麻烦——>所以我们将配置交由配置中心进行统一管理就像注册中心一样,而恰巧,Nacos不仅能作为注册中心同时也能充当配置中心完成配置管理服务

我们只是把一些重要的信息配置到Nacos中以便“热更新”而不是把微服务的所有配置都交由Nacos进行管理

反之,注册中心和配置管理也是可以单独出来进行管理

image-20230117105851480

Nacos统一管理配置

直接在控制台创建即可

image-20230117110616378

设置配置内容时只需要把部分“热更新”相关的配置写进去

image-20230117110717154

微服务配置拉取配置

配置获取的逻辑设计

image-20230117111735849

代码实现

  • 在微服务中引入nacos的config依赖
  • 在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件image-20230117111613542

流程总结

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

配置热更新

Nacos配置更改后,微服务可以实现热更新,方式有二:

  • 通过@Value注解注入,需要在类上结合@RefreshScope来刷新
  • 通过@ConfigurationProperties注入,自动刷新✔️
    • 不是所有的配置都适合放到配置中心,维护起来比较麻烦
    • 建议将一些关键参数,需要运行时调整的参数放到nacos配
      置中心,一般都是自定义配置

例子: 通过@ConfigurationProperties注入,自动刷新

Nacos统一管理配置

image-20230117114359715

配置类封装映射配置属性

image-20230117114152961

程序调用读取配置

image-20230117114242383

多环境配置共享

微服务启动时会从nacos读取多个配置文件:【微服务启动时的加载顺序

  • [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
  • [spring.application.name].yaml,例如: userservice.yaml

无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件

image-20230117114828650

小结

微服务会从nacos读取的配置文件:

  • [服务名]-[spring.profile.active].yaml,环境配置
  • [服务名].yaml,默认配置,多环境共享

但是要注意配置文件的优先级,优先级高的将覆盖优先级低的配置

优先级:[服务名]-[环境].yaml>[服务名].yaml>本地配置

搭建Nacos集群【高可用】

大公司肯定不会只有一个Nacos服务器,为保证高可用他们会购置多台服务器来搭建Nacaos集群

集群架构图

image-20230117121229904

集群搭建步骤

详细步骤:

小结:

  1. 搭建MySQL集群并初始化数据库表

    Nacos官方提供了标准的SQL创建,用来记录数据库信息

  2. 下载解压nacos

  3. 修改集群配置(节点信息)、数据库配置

    • nacos结点信息:创建nacos-config-cluster.config添加nacos结点信息【此处模拟单例,实际上是不同的实例ip】

    image-20230117122431103

    • 数据库配置:修改application.properties文件,添加数据库配置。【告知:指定mysql集群,数据库数量、数据库连接配置】

      构建数据库集群保证数据库读写都在集群中完成保证数据共享一致性。

      image-20230117122718273

  4. 分别启动多个nacos节点

    我们可以直接拷贝之前创建的nacos文件夹,然后依次修改application.properties文件的nacos对外服务端口Service.port,然后分别启动模拟不同主机启动了nacos服务。直接在bin目录下执行startup.cmd即可,默认是集群启动。

    image-20230117123905086

  5. nginx反向代理

    作为gateway用来实现对Nacos的负载均衡。以下配置都要在config目录中的http作用域中配置生效。

    image-20230117125942893

之后就可以通过网关Nginx访问我们的集群

之后的配置管理都将持久化到数据库中

image-20230117125726593

二、网关

微服务治理还有一个重要事项就是统一网关

1、什么是网关

  • 身份认证和权限校验

  • 服务路由、负载均衡

  • 请求限流

image-20230117163937154

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

  • gateway

  • zuul

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

2、搭建网关

网关是一个独立的服务,所以需要新建模块,引入相关依赖,配置文件设置相关配置,再启动类启动网关服务。

搭建网关服务的步骤:

1.创建项目(模块),引入nacos服务发现和gateway依赖

image-20230117181644898

2.配置application.yml,包括服务基本信息、nacos地址、路由

image-20230117181755939路由配置包括:

  • 路由id:路由的唯一标示

  • 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡

  • 路由断言(predicates) :判断路由的规则,

  • 路由过滤器((filters) :对请求或响应做处理

    3.通过启动类启动网关服务

3、路由断言工厂

什么是断言

抽象图

image-20230117174123199

网关路由可以配置的内容包括:

  • 路由id:路由唯一标示
  • uri:路由目的地,支持lb和http两种(lb即loadBalanced)
  • predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
  • filters:路由过滤器,处理请求或响应

image-20230117173307915

Predicate工厂

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的

像这样的断言工厂在SpringCloudGateway还有十几个,主要用的是Path,具体情况分析官方文档现查。

image-20230117173151281

问答

  • PredicateFactory的作用是什么?

    读取用户定义的断言条件,对请求做出判断

  • Path=/user/**是什么含义?
    路径是以/user开头的就认为是符合的

4、路由的过滤器配置【过滤器工厂】

Spring提供了31种不同的路由过滤器工厂,都有不同的过滤规则,具体参见官方文档。

image-20230117175624627

分为当前过滤器和全局过滤器,在配置文件中根据层级的不同,作用范围也就不同。

  • 配置当前过滤器

    image-20230117175429178

  • 配置默认过滤器【全局】

    image-20230117175402397

5、全局过滤器【自定义】

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。

GlobalFilter的逻辑需要自己写代码实现。定义方式是实现GlobalFilter接口。

实现全局过滤器的步骤

  • 实现GlobalFilter接口
  • 添加@Order注解或实现Ordered接口编写处理逻辑【必须指定顺序值】

示例:

image-20230117172732459

Tip:

  • GlobalFilter使用的不是HttpServer,而是ServerWebExchange对象,方法有所不同
  • 除了通过@Order()注解定义顺序外也可以实现ordered接口重写getOrdered()方法指定顺序

6、过滤器链执行顺序

按照orderId从小到大的顺序执行,当大小相同则按优先级来:默认过滤器——路由过滤器——全局过滤器

分析:

image-20230117174416374

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器

Tip

网关中提供了一个过滤器适配器,有一个GatewayFilterAdapter类实现了GatewayFilter接口,它可以将全局过滤器类型转换成GatewayFilter类型,当前过滤器和默认过滤器在底层其实也相似,他们最终都会转换成GatewayFilter类型,所以他们可以放到一个集合

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

GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定

路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
当过滤器的order值一样时,会按照defaultFilter >路由过滤器>GlobalFilter的顺序执行。

image-20230117180655726

7、网关的cors跨域配置

首先微服务内部是不会产生跨域问题的,所以只需要对网关进行跨域处理。

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

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

image-20230117183047449

网关封装好的处理方案,只需进行配置皆可

image-20230117183657607

关于CORS跨域要配置的参数包括哪几个?会CV就行。

  • 允许哪些域名跨域?
  • 允许哪些请求头?
  • 允许哪些请求方式?
  • 是否允许使用cookie?
  • 有效期是多久?