Spingcloud 简单介绍
1、是什么
Spring Cloud 是一款基于 Spring Boot 实现的微服务框架。
Spring Cloud 被称为构建分布式微服务系统的“全家桶”,它并不是某一门技术,而是一系列微服务解决方案或框架的有序集合。它将市面上成熟的、经过验证的微服务框架整合起来,并通过 Spring Boot 的思想进行再封装,屏蔽调其中复杂的配置和实现原理,最终为开发人员提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。
2、为什么会有Springcloud的诞生
-
微服务结构的使用增多
微服务是指一个个微小的服务,主要体现在两个方面
- 微服务体积小,复杂度低:一个微服务通常只提供单个业务功能的服务,即一个微服务只专注于做好一件事,因此微服务通常代码较少,体积较小,复杂度也较低。
- 微服务团队所需成员少:一般情况下,一个微服务团队只需要 8 到 10 名人员(开发人员 2 到 5 名)即可完成从设计、开发、测试到运维的全部工作。
目前大多数服务都是采用微服务,因为单个服务对于目前使用已经不太适用
-
单体架构的弊端相比较之下比较多
- 随着业务复杂度的提高,单体应用(采用单体架构的应用程序)的代码量也越来越大,导致代码的可读性、可维护性以及扩展性下降。
- 随着用户越来越多,程序所承受的并发越来越高,而单体应用处理高并发的能力有限。而多个微服务因为每个服务的职责单一,耦合较少,并发的能力代价也比较低
- 单体应用将所有的业务都集中在同一个工程中,修改或增加业务都可能会对其他业务造成一定的影响,导致测试难度增加。
主要还是市场需要。
3、Spring Cloud服务注册与发现组件 —Eureka
名字来源于古希腊词汇:译为“发现了”。
简单来说,Eureka就是提供了一个服务注册以及获取其他注册服务的一个中心,服务提供者通过注册到eureka的Eureka Client,服务的调用者通过获取注册的服务列表,进行服务的调用。
而Eureka Server,Eureka 服务注册中心,主要用于提供服务注册功能。微服务启动时,回将自己注册到服务器,其他服务提供者这是将自己注册到服务client以供调用者调用。
上图中共涉及到以下 3 个角色:
- 服务注册中心(Register Service):它是一个 Eureka Server,用于提供服务注册和发现功能。
- 服务提供者(Provider Service):它是一个 Eureka Client,用于提供服务。它将自己提供的服务注册到服务注册中心,以供服务消费者发现。
- 服务消费者(Consumer Service):它是一个 Eureka Client,用于消费服务。它可以从服务注册中心获取服务列表,调用所需的服务。
如果是一个单机版注册中心
则只有一个Eureka Server ,该服务的client:service-url只有当前自己
server:
port: 7001 #该 Module 的端口号
eureka:
instance:
hostname: localhost #eureka服务端的实例名称,
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机版服务注册中心
而服务提供者(需要将自己服务注册到Eureka Server 上),需要以下配置
eureka:
client: #将客户端注册到 eureka 服务列表内
service-url:
defaultZone: http://eureka7001.com:7001/eureka #这个地址是 7001注册中心在 application.yml 中暴露出来额注册地址 (单机版)
instance:
instance-id: spring-cloud-provider-8001 #自定义服务名称信息
prefer-ip-address: true #显示访问路径的 ip 地址
如果是一个集群版注册中心
这里的 service-url集群版的就是自己注册到别的Eureka Server上,然后他们几个充当一个服务集群
同样的eureka7002,eureka7003也是同样的将自己注册到别的服务上。
server:
port: 7001 #端口号
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false 表示不向注册中心注册自己。
fetch-registry: false #false 表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机版
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #集群版 将当前的 Eureka Server 注册到 7003 和 7003 上,形成一组互相注册的 Eureka Server 集群
而服务提供者(需要将自己服务注册到Eureka Server 上),需要以下配置
eureka:
client: #将客户端注册到 eureka 服务列表内
service-url:
#defaultZone: http://eureka7001.com:7001/eureka #这个地址是 7001 注册中心在 application.yml 中暴露出来的注册地址 (单机版)
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #将服务注册到 Eureka Server 集群
4、Spring Cloud负载均衡与服务调用组件—Ribbon
在任何一个系统中,负载均衡都是一个十分重要且不得不去实施的内容,它是系统处理高并发、缓解网络压力和服务端扩容的重要手段之一。
负载均衡(Load Balance) ,简单点说就是将用户的请求平摊分配到多个服务器上运行,以达到扩展服务器带宽、增强数据处理能力、增加吞吐量、提高网络的可用性和灵活性的目的。
服务端负载均衡
服务端负载均衡是最常见的负载均衡方式,其工作原理如下图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ook5FTW-1692170355743)(…/…/profiles/typora/Typora/10122164F-0.png)]
客户端负载均衡
相较于服务端负载均衡,客户端服务在均衡则是一个比较小众的概念。
客户端负载均衡的工作原理如下图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xqw26weP-1692170355743)(…/…/profiles/typora/Typora/1012212Q4-1.png)]
服务端负载均衡 VS 客户端负载均衡
下面我们就来对比下,服务端负载均衡和客户端负载均衡到底有什么区别,如下表。
不同点 | 服务端负载均衡 | 客户端负载均衡 |
---|---|---|
是否需要建立负载均衡服务器 | 需要在客户端和服务端之间建立一个独立的负载均衡服务器。 | 将负载均衡的逻辑以代码的形式封装到客户端上,因此不需要单独建立负载均衡服务器。 |
是否需要服务注册中心 | 不需要服务注册中心。 | 需要服务注册中心。 在客户端负载均衡中,所有的客户端和服务端都需要将其提供的服务注册到服务注册中心上。 |
可用服务清单存储的位置 | 可用服务清单存储在位于客户端与服务器之间的负载均衡服务器上。 | 所有的客户端都维护了一份可用服务清单,这些清单都是从服务注册中心获取的。 |
负载均衡的时机 | 先将请求发送到负载均衡服务器,然后由负载均衡服务器通过负载均衡算法,在多个服务端之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。 简单点说就是,先发送请求,再进行负载均衡。 | 在发送请求前,由位于客户端的服务负载均衡器(例如 Ribbon)通过负载均衡算法选择一个服务器,然后进行访问。 简单点说就是,先进行负载均衡,再发送请求。 |
客户端是否了解服务提供方信息 | 由于负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。 | 负载均衡是在客户端发送请求前进行的,因此客户端清楚的知道是哪个服务端提供的服务。 |
Ribbon 实现服务调用
Ribbon 可以与 RestTemplate(Rest 模板)配合使用,以实现微服务之间的调用。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
RestTemplate 是 Spring 家族中的一个用于消费第三方 REST 服务的请求框架。RestTemplate 实现了对 HTTP 请求的封装,提供了一套模板化的服务调用方法。通过它,Spring 应用可以很方便地对各种类型的 HTTP 请求进行访问。
// 配置类注解
@Configuration
public class ConfigBean {
@Bean //将 RestTemplate 注入到容器中
@LoadBalanced //在客户端使用 RestTemplate 请求服务端时,开启负载均衡(Ribbon)
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
使用rabbon
@RestController
public class DeptController_Consumer {
//面向微服务编程,即通过微服务的名称来获取调用地址
private static final String REST_URL_PROVIDER_PREFIX = "http://MICROSERVICECLOUDPROVIDERDEPT"; // 使用注册到 Spring Cloud Eureka 服务注册中心中的服务,即 application.name
@Autowired
private RestTemplate restTemplate; //RestTemplate 是一种简单便捷的访问 restful 服务模板类,是 Spring 提供的用于访问 Rest 服务的客户端模板工具集,提供了多种便捷访问远程 HTTP 服务的方法
//获取指定部门信息
@RequestMapping(value = "/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Integer id) {
return restTemplate.getForObject(REST_URL_PROVIDER_PREFIX + "/dept/get/" + id, Dept.class);
}
//获取部门列表
@RequestMapping(value = "/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PROVIDER_PREFIX + "/dept/list", List.class);
}
}
通过restTemplate.getForObject,restTemplate.postForObject调用http对应的Get,Post的相关的方法,实现跨服务的调用,例如上面调下面的服务。
@GetMapping("/dept/list")
public List<Dept> queryAll() {
return deptService.queryall();
}
@GetMapping("/dept/get/{id}")
public Dept getDept(@PathVariable("id") Long id) {
Dept dept = deptService.queryById(id);
if (dept == null) {
throw new RuntimeException("Fail");
}
return dept;
}
ribbon实现负载均衡,通过注册一个bean实现
@LoadBalanced //配置负载均衡实现RestTemplate
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
@Bean
public IRule myRule() {
// return new RandomRule();//使用随机策略
return new RoundRobinRule();//使用轮询策略
//return new AvailabilityFilteringRule();//使用轮询策略
//return new RetryRule();//使用轮询策略
}
5、Spring Cloud声明式服务调用组件—Feign
Feign 对 Ribbon 进行了集成,利用 Ribbon 维护了一份可用服务清单,并通过 Ribbon 实现了客户端的负载均衡。
Feign 是一种声明式服务调用组件,它在 RestTemplate 的基础上做了进一步的封装。通过 Feign,我们只需要声明一个接口并通过注解进行简单的配置(类似于 Dao 接口上面的 Mapper 注解一样)即可实现对 HTTP 接口的绑定。
注解 | 说明 |
---|---|
@FeignClient | 该注解用于通知 OpenFeign 组件对 @RequestMapping 注解下的接口进行解析,并通过动态代理的方式产生实现类,实现负载均衡和服务调用。 |
@EnableFeignClients | 该注解用于开启 OpenFeign 功能,当 Spring Cloud 应用启动时,OpenFeign 会扫描标有 @FeignClient 注解的接口,生成代理并注册到 Spring 容器中。 |
@RequestMapping | Spring MVC 注解,在 Spring MVC 中使用该注解映射请求,通过它来指定控制器(Controller)可以处理哪些 URL 请求,相当于 Servlet 中 web.xml 的配置。 |
@GetMapping | Spring MVC 注解,用来映射 GET 请求,它是一个组合注解,相当于 @RequestMapping(method = RequestMethod.GET) 。 |
@PostMapping | Spring MVC 注解,用来映射 POST 请求,它是一个组合注解,相当于 @RequestMapping(method = RequestMethod.POST) 。 |
maven依赖、
!--Eureka Client 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Ribbon 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--添加 OpenFeign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
使用feign实现跨服务调用
//添加为容器内的一个组件
@Component
// 服务提供者提供的服务名称,即 application.name
@FeignClient(value = "MICROSERVICECLOUDPROVIDERDEPT")
public interface DeptFeignService {
//对应服务提供者(8001、8002、8003)Controller 中定义的方法
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
public Dept get(@PathVariable("id") int id);
@RequestMapping(value = "/dept/list", method = RequestMethod.GET)
public List<Dept> list();
}
@RestController
public class DeptController_Consumer {
@Resource
private DeptFeignService deptFeignService;
@RequestMapping(value = "/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Integer id) {
return deptFeignService.get(id);
}
@RequestMapping(value = "/consumer/dept/list")
public List<Dept> list() {
return deptFeignService.list();
}
}
@EnableFeignClients //开启 OpenFeign 功能
除此之外,还有超时控制,日志增强等功能。
6、Spring Cloud服务熔断与降级组件 — Hystrix
解决问题:
当微服务系统的一个服务出现故障时,故障会沿着服务的调用链路在系统中疯狂蔓延,最终导致整个微服务系统的瘫痪,这就是“雪崩效应”。为了防止此类事件的发生,微服务架构引入了“熔断器”的一系列服务容错和保护机制。
熔断器
熔断器(Circuit Breaker)一词来源物理学中的电路知识,它的作用是当线路出现故障时,迅速切断电源以保护电路的安全。
Hystrix 服务降级
Hystrix 提供了服务降级功能,能够保证当前服务不受其他服务故障的影响,提高服务的健壮性。
服务降级的使用场景有以下 2 种:
- 在服务器压力剧增时,根据实际业务情况及流量,对一些不重要、不紧急的服务进行有策略地不处理或简单处理,从而释放服务器资源以保证核心服务正常运作。
- 当某些服务不可用时,为了避免长时间等待造成服务卡顿或雪崩效应,而主动执行备用的降级逻辑立刻返回一个友好的提示,以保障主体业务不受影响。我们可以通过重写 HystrixCommand 的 getFallBack() 方法或 HystrixObservableCommand 的 resumeWithFallback() 方法,使服务支持服务降级。
@HystrixCommand(fallbackMethod = "hystrixGet")
@GetMapping("/dept/get/{id}")
public Dept getDept(@PathVariable("id") Long id) {
Dept dept = deptService.queryById(id);
if (dept == null) {
throw new RuntimeException("Fail");
}
return dept;
}
public Dept hystrixGet(@PathVariable("id") Long id){
return new Dept().setDeptno(id)
.setDname("这个id=>"+id+",没有对应的信息,null---@Hystrix~")
.setDb_source("在MySQL中没有这个数据库");
}
Hystrix 服务熔断
熔断机制是为了应对雪崩效应而出现的一种微服务链路保护机制。
当微服务系统中的某个微服务不可用或响应时间太长时,为了保护系统的整体可用性,熔断器会暂时切断请求对该服务的调用,并快速返回一个友好的错误响应。这种熔断状态不是永久的,在经历了一定的时间后,熔断器会再次检测该微服务是否恢复正常,若服务恢复正常则恢复其调用链路。
熔断状态
在熔断机制中涉及了三种熔断状态:
- 熔断关闭状态(Closed):当务访问正常时,熔断器处于关闭状态,服务调用方可以正常地对服务进行调用。
- 熔断开启状态(Open):默认情况下,在固定时间内接口调用出错比率达到一个阈值(例如 50%),熔断器会进入熔断开启状态。进入熔断状态后,后续对该服务的调用都会被切断,熔断器会执行本地的降级(FallBack)方法。
- 半熔断状态(Half-Open): 在熔断开启一段时间之后,熔断器会进入半熔断状态。在半熔断状态下,熔断器会尝试恢复服务调用方对服务的调用,允许部分请求调用该服务,并监控其调用成功率。如果成功率达到预期,则说明服务已恢复正常,熔断器进入关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态。
7、Spring Cloud API网关组件—Gateway
在微服务架构中,一个系统往往由多个微服务组成,而这些服务可能部署在不同机房、不同地区、不同域名下。这种情况下,客户端(例如浏览器、手机、软件工具等)想要直接请求这些服务,就需要知道它们具体的地址信息,例如 IP 地址、端口号等。
这种客户端直接请求服务的方式存在以下问题:
- 当服务数量众多时,客户端需要维护大量的服务地址,这对于客户端来说,是非常繁琐复杂的。
- 在某些场景下可能会存在跨域请求的问题。
- 身份认证的难度大,每个微服务需要独立认证。
我们可以通过 API 网关来解决这些问题,下面就让我们来看看什么是 API 网关。
API 网关
API 网关是一个搭建在客户端和微服务之间的服务,我们可以在 API 网关中处理一些非业务功能的逻辑,例如权限验证、监控、缓存、请求路由等。
API 网关就像整个微服务系统的门面一样,是系统对外的唯一入口。有了它,客户端会先将请求发送到 API 网关,然后由 API 网关根据请求的标识信息将请求转发到微服务实例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V54dpJ0I-1692170355744)(…/…/profiles/typora/Typora/101P46212-0.png)]
图1:两种服务访问方式对比
对于服务数量众多、复杂度较高、规模比较大的系统来说,使用 API 网关具有以下好处:
- 客户端通过 API 网关与微服务交互时,客户端只需要知道 API 网关地址即可,而不需要维护大量的服务地址,简化了客户端的开发。
- 客户端直接与 API 网关通信,能够减少客户端与各个服务的交互次数。
- 客户端与后端的服务耦合度降低。
- 节省流量,提高性能,提升用户体验。
- API 网关还提供了安全、流控、过滤、缓存、计费以及监控等 API 管理功能。
Spring Cloud Gateway 核心概念
Spring Cloud GateWay 最主要的功能就是路由转发,而在定义转发规则时主要涉及了以下三个核心概念,如下表。
核心概念 | 描述 |
---|---|
Route(路由) | 网关最基本的模块。它由一个 ID、一个目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成。 |
Predicate(断言) | 路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务。 |
Filter(过滤器) | 过滤器,我们可以使用它对请求进行拦截和修改,还可以使用它对上文的响应进行再处理。 |
Gateway 的工作流程
Spring Cloud Gateway 工作流程如下图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PPtq67LG-1692170355745)(…/…/profiles/typora/Typora/101P45T2-1.png)]
图2:Spring Cloud Gateway 工作流程
Spring Cloud Gateway 工作流程说明如下:
- 客户端将请求发送到 Spring Cloud Gateway 上。
- Spring Cloud Gateway 通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler。
- Gateway Web Handler 通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑返回响应结果。
- 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
- 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等。
- 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。
- 响应原路返回给客户端。
Predicate 断言
Spring Cloud Gateway 通过 Predicate 断言来实现 Route 路由的匹配规则。简单点说,Predicate 是路由转发的判断条件,请求只有满足了 Predicate 的条件,才会被转发到指定的服务上进行处理。
使用 Predicate 断言需要注意以下 3 点:
- Route 路由与 Predicate 断言的对应关系为“一对多”,一个路由可以包含多个不同断言。
- 一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言。
- 当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hOfdNrSz-1692170355745)(…/…/profiles/typora/Typora/101P42B6-2.png)]
图3:Predicate 断言匹配
相关依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
server:
port: 9527 #端口号
spring:
application:
name: microServiceCloudGateway
cloud:
gateway: #网关路由配置
routes:
#将 micro-service-cloud-provider-dept-8001 提供的服务隐藏起来,不暴露给客户端,只给客户端暴露 API 网关的地址 9527
- id: provider_dept_list_routh #路由 id,没有固定规则,但唯一,建议与服务名对应
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
#以下是断言条件,必选全部符合条件
- Path=/dept/list/** #断言,路径匹配 注意:Path 中 P 为大写
- Method=GET #只能时 GET 请求时,才能访问
eureka:
filters:
- PrefixPath=/dept #在请求路径上增加一个前缀 /dept
eureka:
instance:
instance-id: micro-service-cloud-gateway-9527
hostname: micro-service-cloud-gateway
client:
fetch-registry: true # 在eureka上搜索服务
register-with-eureka: true #表示在eureka上注册
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
新建服务,引入eureka的client,如这里调用的路径localhost:9527
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HU20jaYd-1692170355746)(…/…/profiles/typora/Typora/企业微信截图_16546712251857.png)]
使用 uri: lb://MICROSERVICECLOUDPROVIDERDEPT #动态路由,使用服务名代替上面的具体带端口,可以实现动态路由
如上所述,添加 - PrefixPath=/dept #在请求路径上增加一个前缀 /dept,- Path=/list/**访问相同微服务的时候,可以省去/dept,变成localhost:9527/list,在转换的时候,还是会添加上/dept,用以访问对应服务的/dept/list接口
GlobalFilter 全局过滤器
GlobalFilter 是一种作用于所有的路由上的全局过滤器,通过它,我们可以实现一些统一化的业务功能,例如权限认证、IP 访问限制等。当某个请求被路由匹配时,那么所有的 GlobalFilter 会和该路由自身配置的 GatewayFilter 组合成一个过滤器链。
/**
* 自定义全局网关过滤器(GlobalFilter)
*/
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("进入自定义的全局过滤器 MyGlobalFilter" + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null) {
log.info("参数 uname 不能为 null!");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
//过滤器的顺序,0 表示第一个
return 0;
}
}
访问时url要是不带uname或者为空将不被允许进入服务。