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

 

Spring Cloud Config

 

Spring Cloud Stream  

 

Spring Cloud Security

 

Spring Cloud Zookeeper

 

Spring Cloud Eureka

 

SpringCloud

 

服务注册发现:

Eureka,Nacos,Consul

服务远程调用:

OpenFeign,Dubbo

服务链路监控:

Zipkin,Sltuth

统一配置管理:

SpringCloudConfig,Nacos

统一网关路由:

SpringCloudGateway,Zuul

流控,降级,保护:

Hystix,Sentinel

 

SpringCloud 与 SpringBoot 版本兼容性

Release TrainBoot Version
2021.0.x aka Jubilee2.6.x, 2.7.x (Starting with 2021.0.3)
2020.0.x aka Ilford2.4.x, 2.5.x (Starting with 2020.0.3)
Hoxton2.2.x, 2.3.x (Starting with SR5)
Greenwich2.1.x
Finchley2.0.x
Edgware1.5.x
Dalston1.5.x

 

服务的拆分与远程调用

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

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

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

 

SpringCloud坐标

 

 

根据订单id查询订单功能

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

 

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

 

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

注册RestTemplate

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

 

restTemplate

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

获取数据方法:getForObject()

参数

url:请求路径

responseType:返回值类型字节码

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

 

 

 

 

★Eureka注册中心

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

 

 

服务调用关系

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

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

 

 

Eureka的原理

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

Eureka的作用

记录和管理微服务

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

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

注册中心保存信息

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

 

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

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

 

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

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

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

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

 

搭建EurekaService

搭建步骤:

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

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

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

eureka在注册信息的时候会将自己本身作为一个微服务注册到注册中心,因此也要提供微服务名称与eureka地址,方便将来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的依赖

 

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

 

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

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

 

拉取服务

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

 

实现拉取的两种方法:

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

 

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

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

注解原理

 

总结:搭建EurekaServer

 

 

 

★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抽象类

负载均衡规则参考

 

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实现类

全局配置

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

指定服务名称配置

 

 

饥饿加载

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

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

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

如果是yml中加换行加- xxserver

 

 

负载均衡的作用

 

 

总结:

1.Ribbon负载均衡规则

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:

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

 

配置流程

 

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

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

 

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

 

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

 

 

 

Nacos服务分级存储模型

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

服务-集群-实例

 

服务跨集群调用问题

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

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

 

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

 

如何配置实例的集群属性

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

 

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

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

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

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

 

总结

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

 

 

根据权重负载均衡

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

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中配置

 

总结:

 

Nacos注册中心原理

 

 

 

 

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

 

总结:

1、Nacos与eureka的共同点

2、Nacos与Eureka的区别

 

CAP理论

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

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

A (Availability) 可用性

C(Consistency) 一致性

P(Partition tolerance) 分区容错

 

 

 

★Nacos配置管理

配置热部署

配置更改热更新

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

 

新建配置

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

分组:默认即可

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

 

微服务获取配置

默认配置获取步骤如下:

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

 

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

 

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

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

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

 

3、配置自动刷新

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

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

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

 

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

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

@ConfigurationProperties

 

注意事项:

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

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

 

总结:

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

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

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

 

多环境共享配置

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

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

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

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

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

 

关于配置优先级问题

多种配置的优先级:

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

 

总结:

 

 

 

★Http客户端Feign

RestTemplate方法调用存在的问题

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

 

存在下面的问题:

 

Feign的介绍

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

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

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

 

Feign的使用

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

 

在启动类上加上注解@EnableFeignClients

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

 

编写FeignClient接口

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

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

 

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

在orderserver中测试

 

 

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的配置

 

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

全局生效

 

局部生效

 

方法二、java代码配置:

创建一个类FeignClientConfiguration

需要先声明一个Bean

 

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

 

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

 

 

 

Feign的性能优化

Feign底层的客户端实现:

 

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

 

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

 

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

 

配置http连接池:

 

 

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

属性:

 

 

 

★Gateway统一网关

网关功能:

 

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

 

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

 

 

搭建网关服务

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

 

添加gateway网关设置

 

路由配置包括

 

访问流程

用户访问网关

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

 

路由断言工厂

Route Predicate Factory

 

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限制请求的流量
......

 

实例:

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

 

在userserver中测试:

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

 

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

通过default-filters来配置

 

 

全局过滤器

GlobalFilter

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

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

定义方式是实现GlobalFilter接口

 

实例:

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

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

 

过滤器需要设置顺序

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

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

 

验证:

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

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

 

过滤器类型

 

过滤器执行顺序

 

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方案,并且只需要简单配置即可实现: