一、微服务演变
1、单体架构(Monolithic Architecture)
是一种传统的软件架构模式,应用程序的所有功能和组件都集中在一个单一的应用中。
在单体架构中,应用程序通常由一个大型的、单一的代码库组成,其中包含了所有的功能模块和业务逻辑。这个应用程序作为一个整体部署和运行在一个应用服务器上,并共享相同的内存和数据库。
当单体架构项目的性能无法满足需求时,但又希望继续沿用单体架构的话,你可以采取以下一些优化手段来改善性能,如以下方法:
-
使用缓存:引入缓存机制,将经常请求的数据缓存起来,减少对数据库等后端系统的访问,以提高性能。
-
数据库优化:进行数据库性能调优,包括建立索引、优化查询语句、使用合适的数据库引擎等,以提高数据库的响应速度。
-
使用消息队列:引入消息队列来解耦耗时操作,将其转化为异步的处理任务。这样可以提高并发处理能力和系统的响应性能。
-
水平扩展:通过复制多个实例来处理更多的请求。可以使用负载均衡器将请求分发到不同的实例上,从而提高系统的整体性能和容量。
-
引入分布式架构:将应用程序拆分为多个独立的服务,并通过网络进行通信和协作。这样可以将负载分散到多个节点上,提高整体性能和可伸缩性。
接下来就说一下单体架构优化中的引入分布式架构。
2、分布式架构
将系统的各个组件部署在不同的计算机节点上,并通过网络进行通信和协作的软件架构模式。在分布式架构中,各个节点可以独立运行和处理任务,并通过消息传递、远程过程调用或其他通信机制进行数据交换和协调。
在使用分布式时,我们就会有一下的疑问:
- 服务拆分到什么程度?
- 服务集群地址如何维护?
- 服务之间如何实现远程调用?
- 服务健康状况如何感知?
3、微服务
微服务是一种经过改良好的架构设计的分布式架构方案,微服务架构特征:
- 单一职责:微服务拆分力度更小,每个服务都对应唯一的服务能力,做的单一职责,避免重复开发
- 面向服务:微服务对外暴露业务接口
- 自治:团队独立、技术独立、数据独立、部署独立
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
假如我们有一个电商系统,其中有以下一些微服务:
- 用户服务(User Service)负责用户的注册、登录、信息管理等功能。
- 商品服务(Product Service)负责商品的查询、添加、更新等功能。
- 订单服务(Order Service)负责订单的创建、支付、取消等功能。
现在假设用户服务依赖于商品服务来获取商品信息,并且订单服务依赖于用户服务来获取用户信息。这里我们可以看到微服务的几个特征是如何应用的:
- 单一职责:每个微服务都具有清晰的职责。例如,用户服务只负责用户相关的功能,而不涉及商品或订单。
- 面向服务:每个微服务都对外暴露业务接口,其他微服务可以通过调用这些接口来访问所需的功能。例如,用户服务可以提供获取用户信息的接口给订单服务使用。
- 自治:每个微服务的团队在技术和数据上都是独立的。例如,用户服务的团队可以独立开发、测试、部署和扩展该服务,无需依赖其他团队。
- 隔离性强:微服务之间的调用需要做好隔离、容错和降级,以避免出现级联问题。例如,当商品服务不可用时,用户服务可以使用缓存或默认数据来避免影响用户操作。
这个例子中提供了不同的微服务来处理不同的功能,并且彼此解耦、独立运行。每个微服务都具有单一职责,对外提供明确定义的业务接口,团队在技术和数据上具有自治能力,同时采取适当的隔离措施来保证系统的弹性和稳定性。这些特征有助于提高开发效率、灵活性和可维护性,使得微服务架构在构建大型、复杂系统时具有优势。
4、 总结
单体架构特点:简单方便,高度耦合,扩展性差,合适小型项目。如:学生管理系统。
分布式架构特点:松耦合,扩展性好,但架构复杂,难道大,适合大型互联网项目。如:京东、淘宝
微服务:一种良好的分布式架构方案。
- 优点:拆分粒度更小、服务更独立、耦合更低
- 缺点:架构非常复杂,运维、监控、部署难道更高
5、微服务架构
微服务这方案需要技术框架来落地实现,全球的换联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是SpringCloud和阿里巴巴的Dubbo。
5.1、 微服务技术对比
Dubbo、Spring Cloud、Spring Cloud和Spring Cloud Alibaba都是用于构建分布式系统的开源框架。尽管它们的目标相同,但它们在实现方式和功能特点上有所不同。
-
Dubbo:Dubbo是一个高性能的分布式服务框架,由阿里巴巴开发。它基于传统的服务治理理念,提供了服务注册、发现、路由、负载均衡、容错等功能。Dubbo的核心特点是高性能和低延迟的RPC调用,适用于大规模的微服务架构。Dubbo提供了对多种协议(如Dubbo协议、REST协议)和注册中心(如ZooKeeper、Consul)的支持。
-
Spring Cloud:Spring Cloud是一个由Pivotal开发的微服务框架,构建在Spring Framework之上,使得构建分布式系统更加便捷。它提供了一系列的组件和模块,用于实现服务注册与发现、负载均衡、断路器、配置管理、消息总线等功能。Spring Cloud采用了Spring Boot作为底层的开发框架,提供了更简洁、快速搭建分布式系统的解决方案。
-
Spring Cloud Alibaba:Spring Cloud Alibaba是Spring Cloud与Alibaba开放平台合作的结果,提供了一些在云原生应用开发中常用的解决方案。它主要基于Spring Cloud框架,结合了一些Alibaba技术栈,如Nacos(服务注册与发现)、Sentinel(流量控制和熔断降级)、RocketMQ(消息驱动)等。Spring Cloud Alibaba旨在提供云原生应用开发的全栈解决方案。
虽然Dubbo和Spring Cloud都是用于构建分布式系统的框架,但Dubbo更加注重于高性能的RPC调用和服务治理,而Spring Cloud则提供了一整套更全面的微服务解决方案。而Spring Cloud Alibaba则是在Spring Cloud的基础上,进一步整合了Alibaba的一些技术,为云原生应用提供更全面的开发支持。选择适合的框架取决于具体的需求、技术栈和团队偏好。
5.2、企业需求
-
Spring Cloud + Spring Cloud Alibaba:Spring Cloud提供了丰富的微服务组件和解决方案,包括服务注册与发现、负载均衡、断路器、配置管理等。Spring Cloud Alibaba扩展了Spring Cloud,整合了阿里巴巴技术栈,如Nacos(服务注册与发现)、Sentinel(流量控制和熔断降级)、RocketMQ(消息驱动)等。组合使用这两个框架可以获得全面的云原生应用开发解决方案,适用于构建现代化的微服务架构。
-
Dubbo原生模式 + Spring Cloud Alibaba:Dubbo是一个高性能的RPC框架,提供了服务治理、负载均衡、容错等功能。Spring Cloud Alibaba扩展了Dubbo,为Dubbo提供了更多云原生的支持,如Nacos作为注册中心、Sentinel用于流量控制和熔断降级等。通过将Dubbo和Spring Cloud Alibaba集成,可以获得高性能的RPC调用和全面的云原生的服务治理解决方案。
无论是使用Spring Cloud + Spring Cloud Alibaba还是Dubbo原生模式 + Spring Cloud Alibaba,都可以受益于Spring Cloud和Spring Cloud Alibaba提供的丰富的微服务功能和云原生支持。具体选择哪种组合取决于企业需求、技术栈和团队实际情况。需要评估技术要求、性能需求、开发复杂度等因素,选择适合的框架组合来构建稳定、高效的分布式系统。
二、spring cloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:
https://spring.io/projects/spring-cloud
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
springCloud与SpringBoot的版本兼容关系
本文学习版本是Hoxton.SR10,因此对应的springboot版本是2.3.x版本。
1、服务拆分及远程调用
1.1、服务拆分
1.1.1、服务拆分注意事项
- 不同微服务,不要重复开发相同业务
- 微服务数据独立,不要访问其他微服务的数据库
- 微服务可以将自己的业务暴露为接口,供其它微服务调用
1.1.2、项目实战
实战代码:阿里云下载
把数据库文件那到数据库管理工具里执行和idea中导入cloud-demo项目
最终数据库结构:
对与cloud-demo这个项目主要看一下用户和订单如何进行项目拆分的,结合这单体架构的结果对比一下有什么变化和不同。
1.1.3、总结
- 微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务。
- 微服务可以将业务暴露为接口,供其他微服务使用。
- 不同微服务都应该有自己独立的数据库。
1.2、远程调用
需求:根据订单id查询订单的同时,把订单所属的用户信息一起返回。
1.2.1、远程调用方法
在我们的controller类中使用@GetMapping("/user/{id}")向外暴露了一个接口来访问,可以将user信息放回为json数据格式,我们可以想一下浏览器可以发一个Ajax请求来获取数据,我们的订单服务可不可以发一个Ajax请求来获取数据呢?
1.2.2、实现远程调用步骤
1)注册RestTemplate
Spring Cloud中也使用了RestTemplate类。RestTemplate是Spring框架中的一部分,它在Spring Cloud项目中被广泛用于进行微服务之间的通信。
在微服务架构中,各个微服务之间通常通过RESTful API进行通信。为了简化这个过程,Spring Cloud对RestTemplate进行了增强,以便更好地支持微服务架构。在Spring Cloud中,RestTemplate被称为"服务调用"的一部分。
通过使用RestTemplate,开发人员可以方便地发起HTTP请求来调用其他微服务的API。Spring Cloud还提供了一些增强功能,例如服务发现和负载均衡。开发人员可以使用服务名代替具体的URL,Spring Cloud会自动根据服务名找到可用的实例并进行负载均衡,从而实现更灵活和高效的服务调用。
需要注意的是,自Spring Cloud 2020.0.0版本(即Hoxton.SR9及之后的版本)开始,官方推荐使用WebClient替代RestTemplate作为HTTP客户端,因为WebClient提供了更强大、更灵活的功能,并且更适用于非阻塞的响应式编程模型。但是,为了向后兼容,RestTemplate仍然可以继续使用并被支持。
在order-service的OrderApplication中注册ResteTemplate,将ResteTemplate用@Bean注解注册为spring管理的对象,以后不管在什么地方都可以使用到RestTemplate对象。
完成调用主要是在service层中实现,向被调用服务发起一个Rest请求,在需要调用其他服务的服务中使用,实现步骤:
1、在服务启动类中注册RestTemplate,如这里是在order的启动类中
2、在要调用其他服务的服务中的service层中自动装配RestTemplate对象
3、使用RestTemplate的api来实现即可,里面有很多api,这里我使用的是getForObject()方法
4、将远程调用返回的数据封装到要封装的对象中即可,这里我使用的是将远程调用获取的user对象信息封装到order对象中。
1.3、提供者与消费者
服务提供者:一次业务中,被其他微服务调用的服务。(提供接口给其他服务调用)如:user服务
服务消费者:一次业务中,调用其他微服务的服务。(调用其他微服务提供的接口)如order服务
总结:
1.服务调用关系
- 服务提供者:暴露接口给其它微服务调用
- 服务消费者:调用其它微服务提供的接口
- 提供者与消费者角色其实是相对的
- 一个服务可以同时是服务提供者和服务消费者
三、Eureka注册中心
1、服务调用出现的问题
在前面我们使用RestTemplate来实现服务远程调用,在写url时使用的是硬编码方式,就会产生以下的问题,这些问题是值得我们考虑一下的。
- 服务消费者该如何获取服务提供者的地址信息?
- 如果有多个服务提供者,消费者该如何选择?
- 消费者如何得知服务提供者的健康状态?
2、 Eureka基本原理
Eureka是Netflix开源的服务治理框架,在Spring Cloud中广泛应用。它的基本原理是建立了一个分布式的服务注册中心,用于管理和维护各个微服务实例的注册和发现。
以下是Eureka的基本原理:
-
Eureka服务器:Eureka由一个或多个Eureka服务器组成,它们构成了服务注册中心。每个微服务实例都将自己的信息注册到Eureka服务器,包括服务名、主机名、端口号等。
-
服务注册:微服务启动时,会向Eureka服务器发送注册请求,将自己的信息注册到注册中心。注册中心维护一个服务注册表,记录所有已注册的微服务实例。
-
服务发现:其他微服务需要调用某个服务时,首先向注册中心发送查询请求,获得目标服务的实例列表。注册中心将会返回所有可用的服务实例信息,包括IP地址、端口号等。
-
服务监控:Eureka服务器会定期向已注册的微服务实例发送心跳请求,微服务实例返回响应以证明自己的健康状态。如果一个微服务长时间未发送心跳消息或返回异常状态,Eureka服务器将从注册表中删除该实例。
-
服务同步:Eureka服务器之间会相互复制注册表信息,以保证数据的一致性。当有新的微服务实例注册或注销时,注册中心会通知其他服务器进行注册表更新。
通过Eureka提供的服务注册和发现机制,微服务之间可以动态地发现和调用其他微服务,从而实现了服务之间的解耦和灵活性。Eureka还提供了负载均衡、故障恢复等一些附加功能,使得微服务架构更加可靠和高效。
回顾之前我们的几个问题:
消费者该如何获取服务提供者具体信息?
- 服务提供者启动时向eureka注册自己的信息
- eureka保存这些信息
- 消费者根据服务名称向eureka拉取提供者信息
如果有多个服务提供者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表中挑选一个
消费者如何感知服务提供者健康状态?
- 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
- eureka会更新记录服务列表信息,心跳不正常会被剔除
- 消费者就可以拉取到最新的信息
总结
在Eureka架构中,微服务角色有两类:
1、EurekaServer:服务端,注册中心
- 记录服务信息
- 心跳监控
2、EurekaClient: 客户端
1)Provider:服务提供者,例如案例中的 user-service
- 注册自己的信息到EurekaServer
- 每隔30秒向EurekaServer发送心跳
2)consumer:服务消费者,例如案例中的 order-service
- 根据服务名称从EurekaServer拉取服务列表
- 基于服务列表做负载均衡,选中一个微服务后发起远程调用
3、手动实战
3.1、搭建eureka服务注册中心(服务名称)
1、创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2、启用Eureka服务器:创建一个启动类,并使用@EnableEurekaServer
注解来启用Eureka服务器功能。
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
3、配置Eureka服务器:在应用程序的配置文件(如application.properties
或application.yml
)中配置Eureka服务器的相关信息,例如:
# 服务器配置
server:
port: 10001 # Eureka服务器运行的端口号
# Spring应用程序配置
spring:
application:
name: eurekaserver # Eureka服务器应用程序的名称
# Eureka客户端配置
eureka:
client:
service-url:
defaultZone: http://localhost:10001/eureka/
# Eureka客户端注册自身的Eureka服务器的URL
# 在本例中,将Eureka服务器的URL设置为运行在10001端口的本地服务器
3.2、注册user-service
这个操作是在user-service项目下实现的,主要是将服务信息注册到Eureka服务端,eureka将这些服务信息保存到注册表中进行管理。
将user-service服务注册到EurekaServer步骤:
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.yml位置文件中编写配置
spring:
application:
name: userservice #配置服务名称
eureka:
client:
service-url:
defaultZone: http://localhost:10001/eureka/ #配置eureka服务地址信息
其实orderservice的注册也是这两个步骤,我就写了。
做完之后,我我们可以浏览一下eureka的服务网站看一下:http://localhost:10001
这里的端口号是你在自己的eureka的配置文件application.yml配置的eureka端口号一致。
总结:
1.服务注册
- 引入eureka-client依赖
- 在application.yml中配置eureka地址
2.无论是消费者还是提供者,引入eureka-client依赖
知道eureka(服务注册中心)地址后,都可以完成服务注册
3.3、在order-service完成服务拉取
服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡
1.修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
String url ="http://userservice/user/" + order.getUserId();
2.在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
@Bean
@LoadBalanced #负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
总结
1.搭建EurekaServer
- 引入eureka-server依赖
- 添加@EnableEurekaServer注解
- 在application.yml中配置eureka地址
2.服务注册
- 引入eureka-client依赖
- 在application.yml中配置连接eureka服务注册中心地址
3.服务发现
- 引入eureka-client依赖
- 在application.yml中配置eureka服务注册中心地址
- 给RestTemplate添加@LoadBalanced注解
- 用服务提供者的服务名称远程调用
四、Ribbon负载均衡
Ribbon是一个负载均衡解决方案,主要用于在分布式系统中将负载均匀地分发给多个服务实例。它是Netflix开 源的一个组件,常用于微服务架构中。
1、负载均衡流程
Ribbon的负载均衡原理可以概括如下:
-
服务注册:Ribbon首先需要与服务注册中心(如Eureka、Consul等)进行交互,获取可用的服务实例列表。
-
负载均衡策略:Ribbon支持多种负载均衡策略,如随机策略、轮询策略、权重策略等。根据选择的策略,Ribbon会根据服务实例的状态、性能等因素来选择一个合适的服务实例。
-
服务调用:一旦选择了一个服务实例,Ribbon会将请求发送给该实例。它会维护一个与服务实例的长连接,并在需要时将请求发送给该实例。
-
失败处理:如果请求在与服务实例的通信中失败,Ribbon会尝试选择另一个可用的服务实例进行重试,以增加系统的可用性。
Ribbon还可以与其他组件配合使用,例如Netflix的Hystrix熔断器,用于实现服务的容错和故障保护。
总之,Ribbon通过动态获取服务实例列表并根据负载均衡策略选择合适的实例来进行负载均衡,从而提高系统的性能、可用性和可扩展性。
源码执行流程:
2、Ribbon负载均衡策略
Ribbon负载均衡规则是一个叫做IRule的接口来实现的,每个子接口都是一种规则:
2.1、 负载均衡策略
2.2、调整负责均衡策略的规则
通过定义IRule实现可以修改负载均衡规则,有两种方式:
方式一:代码方式
如消费者服务order-service,只要在OrderApplication类中定义一个新的IRule:
@Bean
public IRule iRule(){
return new RandomRule();
}
其实也不一定要在OrderApplication启动类中配置,也可以自己创建一个配置类来配置,在有@configuration注解的类就可以。
这种配置方案是全局的配置,只要使用了这种配置方案,以后不管你调用的是user-service服务还是order-service服务都是使用这里的配置方案。
方式二:配置文件方式
在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice: #被调用微服务器的服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
这种配置方案是只针对某个微服务的,是局部配置。
3、Ribbon饿加载
Ribbon默认是采用懒加载,即第一次访问才会创建LoadBalanceClient,请求时间会很长。
而饿加载则会在项目启动时创建,降低每一次访问的耗时
3.1、配置开启配置饥饿加载
#当服务饥饿加载
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: userservice #指定对userservice这个服务饥饿加载
#当有多个服务需要饥饿加载可以用下面的方式:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: #指定对userservice这个服务饥饿加载
- userservice
- xxservice
总结
1.Ribbon负载均衡规则
- 规则接口是IRule
- 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询
2.负载均衡自定义方式
- 代码方式:配置灵活,但修改时需要重新打包布
- 配置方式:直观,方便,无需重新打包发布但是无法做全局配置
3.饥饿加载
- 开启饥饿加载
- 指定饥饿加载的微服务名称
五、Nacos注册中心
微服务的注册中心我们已经学了eureka,大家肯定会有疑问,为啥还有学校nacos呢?
其实nacos不仅有服务注册中心和服务发现功能,还有更加强大的功能。
1、nacos和eureka对比
Nacos和Eureka是两个常用的服务发现和注册工具,它们都具有类似的功能,但在一些方面存在一些差异。以下是Nacos和Eureka的一些对比:
-
开发者生态圈:Nacos是由Alibaba开发并开源,拥有庞大的Alibaba生态圈支持,而Eureka是由Netflix开发并开源,得到了Netflix和Spring Cloud社区的广泛应用。
-
功能和特性:Nacos提供了更多的功能和特性,除了服务注册与发现外,还包括配置管理、动态DNS、动态路由和流量管理等功能。Eureka主要关注服务注册与发现的功能。
-
容错性:Nacos具有更强的容错性,支持多数据中心的分布式部署,可以保证在网络分区和节点故障情况下的高可用性。Eureka在这方面的容错性相对较弱。
-
数据一致性:Nacos使用Raft算法来实现数据一致性和高可用性,而Eureka使用的是AP模型,即优先保证可用性而不保证强一致性。
-
社区活跃度:Nacos的开源社区活跃度相对较高,有更多的贡献者和更新的版本发布。Eureka的开源社区相对较少活跃,更新较为缓慢。
选择使用Nacos还是Eureka可以根据具体需求和项目背景来决定。如果需要更多的功能和特性,以及较强的容错性和高可用性,Nacos可能是更好的选择。如果项目已经依赖于Netflix和Spring Cloud生态圈,或者对于服务注册和发现的简单功能需求,Eureka可能是更适合的选项。
2、Nacos下载安装服务注册中心
2.1下载nacos
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
这里我提供了阿里云盘下载:Nacos1.4.1
2.2、解压Nacos
我这里使用的是在Windows下安装的。
直接解压到一个没有中文字符的路径下。
目录说明:
-
- bin:启动脚本
-
- conf:配置文件
2.2.1、配置端口
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
**如果无法关闭占用8848端口的进程**,也可以进入nacos的conf目录,修改配置文件中的端口:
修改其中的内容修改为一个没有被占用的端口号。
2.2.2.启动nacos服务注册中心
启动非常简单,进入bin目录,进入cmd窗口执行下面的命令
startup.cmd -m standalone
#startup.cmd -m standalone 的含义是执行Nacos服务器的启动脚本,并使用独立模式启动Nacos服务器。
#这将在单个节点上运行Nacos,并使用默认的配置文件和端口。这个命令适用于简单部署或测试,并非生产环境下常见的集群模式启动命令。
执行后的效果如图:
在浏览器输入地址:http://127.0.0.1:8848/nacos即可:
用户名和密码都是:nacos
网页直接就是中文的,对于英语不太好的比eureka的友好。
3、将服务注册到nacos服务注册中心
1、在cloud-demo父工程中添加spring-cloud-alibaba的管理依赖:
<!--nacos版本管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2、注释掉order-service和user-service中原有的eureka依赖。
3、添加nacos的客户端依赖:
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
4、修改user-service & order-service中的application.yml文件,注释掉eureka的地址,添加nacos地址:
spring:
cloud:
nacos:
server-addr: localhost:8848
启动项目测试:
其实eureka和nacos在spring-cloud中除了依赖和端口号配置不同外其他的使用都是一样的,他们底层使用的接口都是一样的,所以代码其他地方根本不用做改变。
总结:
1.Nacos服务搭建
- 下载安装包
- 解压
- 在bin目录下运行指令:startup.cmd -mstandalone
2.Nacos服务注册或发现
- 引入nacos.discovery依赖
- 配置nacos地址spring.cloudnacos.server-addr
4、Nacos服务分级存储模型
之前我们在学中,我们的服务分层都是只有两层的,第一层是服务,第二层是实例(如user-service、order-service),在我们部署时,都是将这些服务我实例部署到一个机器上或者一个机房里,但是如果这个机房收到破坏(地震、火灾)时,服务就无法访问了。
而nacos服务分层存储模型则引出了集群的概念,一个集群就好比是我们的一个机房,而我们的一个服务下面就可以部署很多个集群,每个集群就可以部署在全球各地(北京、上海、深圳等),因此服务的层次结构就变成了三层(服务、集群、实例)。
4.1、服务跨集群调用问题
- 服务调用尽可能选择本地集群的服务,跨集群调用延迟高
- 本地集群不可以访问时,在去访问其他集群
4.2、服务集群实现
1、修改application.yml,添加如下内容,配置服务集群名称
spring:
cloud:
nacos:
server-addr: localhost:8848 #nacos 服务端地址
cluster-name: YN #配置集群名称,也就是机房位置,例如:YN ,云南
2、在Nacos控制台可以看到集群变化
现在的集群变成了yn,在没有配置集群名称是default。
总结
1.Nacos服务分级存储模型
- 一级是服务,例如userservice
- 二级是集群,例如杭州或上海
- 三级是实例,例如杭州机房的某台部署了userservice的服务器
2.如何设置实例的集群属性
- 修改application.yml文件,添加spring.cloud.nacos.discovery.cluster-name属性即可
4.3、Nacos集群负载均衡
Nacos集群负载均衡的规则其实是优先选择本集群内的服务,如果本服务挂了,才会去选择其他集群的服务来访问。
配置
1、修改order-service中的application.yml,设置集群为HZ:
spring:
cloud:
nacos:
server-addr: Localhost:8848 # nacos 服务端地加discovery:cLuster-name: HZ
discovery:
cluster-name: bj #配集群名称,也就是机房位置
2、然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:
userservice: #服务名称
ribbon:
NFLoadBalancerRulecassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则
3、注意将user-service的权重都设置为1
总结:
1.NacosRule负载均衡策略
- 优先选择同集群服务实例列表
- 本地集群找不到提供者,才去其它集群寻找,并且会报警告
- 确定了可用实例列表后,再采用随机负载均衡挑选实例
4.4、根据权重负载均衡
假设有一个微服务架构的电子商务网站,其中包括以下几个服务:商品服务
、订单服务
和用户服务
。这些服务都注册到了Nacos中进行服务发现,并且它们有不同的实例数和性能配置。在这种情况下,可以通过基于权重的负载均衡来实现对这些服务实例的合理请求分配。以下是一些示例场景:
-
不同实例性能差异较大:
假设
商品服务
有3个实例,其中实例A和实例B配置较高,实例C配置较低。此时,可以设置实例A和实例B的权重为2,实例C的权重为1。这样,每次有请求到来时,有较高性能的实例A和实例B将会处理更多的请求,而较低性能的实例C将会处理较少的请求,从而实现了性能调控。 -
服务实例容量不均衡:
假设
订单服务
有5个实例,其中实例A、B和C的容量比实例D和E大。可以为实例A、B和C设置较高的权重值,如3,而为实例D和E设置较低的权重值,如1。这样,请求将均衡分配到实例A、B和C之间,并保持实例D和E的请求数较少。 -
降低故障实例的负载:
假设
用户服务
有4个实例,其中实例A由于某种原因,出现了故障或不稳定情况。为了减少对实例A的请求分配,可以为其设置较低的权重值,如0,而将其他正常的实例设置为较高的权重值,如3。这样,请求将主要被分配给其他正常的实例,降低了对故障实例A的负载。
通过在Nacos中设置服务实例的权重,可以根据实际情况动态调整请求的负载比例。这样能够充分利用资源、提高系统性能、保证服务稳定性,并对不同实例进行合理分配。
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。
配置
4.5、环境隔离-namespace
1、认识环境隔离-namvespace
应用场景:
假设有一个电商平台,该平台设计了一个基于Nacos的命名空间配置和服务注册方案,以支持环境隔离和多租户功能。
首先,假设该电商平台有三个环境:开发环境、测试环境和生产环境。每个环境都有独立的配置和服务需求。
-
环境隔离和配置管理:
-
使用Nacos的命名空间功能,平台管理员可以创建三个命名空间:dev-namespace、test-namespace和prod-namespace。
-
在dev-namespace中,可以配置开发环境所需的各种参数,如数据库连接信息、调试模式等。
-
在test-namespace中,可以配置测试环境所需的参数,如测试数据库连接信息、测试数据源等。
-
在prod-namespace中,可以配置生产环境所需的参数,如正式数据库连接信息、生产级别的服务配置等。
-
每个命名空间下的配置项是相互隔离的,这样就保证了不同环境配置的独立性,并且可根据需求灵活管理和更新配置信息。
-
-
服务注册和版本管理:
-
在每个命名空间中,可以注册相应环境需要的服务实例,如商品服务、订单服务等。
-
平台管理员可以使用命名空间功能来管理不同环境的服务注册表,确保每个环境只能访问属于自己的服务实例。
-
每个命名空间可以独立管理服务实例的版本,例如,在开发环境中可以注册和测试新的服务版本,而在生产环境中则使用稳定的服务版本。
-
通过这样的命名空间配置和服务注册方案,电商平台能够实现对不同环境的配置和服务进行隔离,并支持多租户功能。开发团队可以独立管理各自环境的配置和服务,而不会相互干扰。此外,平台还可以利用灰度发布和版本管理功能,在不同命名空间中进行服务版本控制,确保系统稳定性和可用性。
2、配置命名空间
1)创建命名空间(在nacos服务页面创建)
2、将服务配置到指定的命名空间中(修改application.yml配置文件)
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://192.168.10.130:3306/cloud-user?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
application:
name: userservice #配置服务名称
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: yn #集群名称,地址在云南
namespace: 074da7cb-c3e3-4848-b893-3d15114e8729 #命令空间id
此时,配置了namespace的服务已经被配置到dev中了
如果此时使用order-service去调用user-service就会报错了
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No instances available for userservice] with root cause
servlet .service()的servlet [dispatcherServlet]在上下文与路径[]抛出异常[请求处理失败;嵌套异常是java.lang.IllegalStateException: No instances available for userservice],有根本原因
5、nacos注册中介细节分析
服务注册:服务提供者启动时,会向服务注册中心发起注册请求,包括服务名、IP 地址、端口等。注册中心会将这些信息保存起来。
服务发现:服务发现有两种(pull和push相结合)
- pull:服务消费者会每隔30秒定时主动向服务注册中心发起请求,来拉取服务信息,在服务消费者中有一个服务列表缓存,用于缓存从服务注册中心拉取下的服务信息。
- push:当有服务停掉后服务注册中心会主动推送变更信息给消费者,消费者就会更新服务列表缓存。
心跳与健康检查:也是有两种(对临时实力和非临时实例)
- 临时实例:采用心跳检测,是临时实例主动向注册中心发送心跳检测,如果一段时间注册中心没有收到临时实例心跳检测,就会剔除该临时实例。
- 非临时实例:采用nacos主动询问,nacos主动询问非临时实例的健康状态,如果非临时实例停止了,也不会剔除非临时实例,只是修改非临时实例的健康转态,会等待非临时实例健康。
负载均衡:负载均衡和流量控制是由服务消费者一侧的客户端组件来实现的,而不是由注册中心来处理。当客户端从Nacos注册中心获取到可用的服务实例列表后,负载均衡和流量控制的责任落在了客户端的实现上。
5.1、临时实例与非临时实例
Nacos中的实例分为临时实例和非临时实例,它们在生命周期和用途上有所不同:
1、非临时实例(Persistent Instance):
- 非临时实例是指注册到Nacos注册中心的服务实例,其生命周期不会受到外部因素的影响,除非主动取消注册或服务下线。
- 这种实例适用于通常情况下稳定运行的服务,它们的注册信息会被持久化存储在Nacos服务器中,提供持久性的服务发现和注册功能。
2、临时实例(Ephemeral Instance):
- 临时实例是指服务实例在持有连接的客户端断开连接时,会自动从Nacos注册中心上注销的一种实例类型。
- 当客户端与Nacos注册中心建立心跳连接后,临时实例会周期性地向注册中心进行连接进行检测。如果一段时间注册中心没有收到临时实例的心态检测,注册中心会将对应的实例注销。
- 临时实例适用于动态扩缩容、临时性访问等场景,允许实例根据连接状态进行动态管理。
临时实例和非临时实例在实践中有不同的应用场景:
-
对于稳定运行的服务,应使用非临时实例。这样服务实例的注册信息将持久存在于注册中心中,即使服务发生故障或重启,也能保证注册信息的持久性,其他服务能够继续发现和使用该实例。
-
对于临时性的任务,如定时任务、临时数据处理服务等,可以选择使用临时实例。这样在任务完成后,实例会自动注销,降低注册中心中无用实例的数量,也能更好地适应动态需求变化。
需要注意的是,临时实例的自动注销是基于与Nacos注册中心之间的心跳连接,而非基于服务实例的具体运行状态。因此,在使用临时实例时,需要相应地配置和管理心跳连接,以保证实例的不间断注册和注销。
5.2、配置临时实例与非临时实例(默认是临时实例)
服务注册到nacos时,可以选择注册为临时或非临时实例,通过在application.yml文件中添加下面的配置来设置:
spring:
cloud:
nacos:
discovery: #discovery:发现,透露 ; ephemeral:短暂的
ephemeral: false #设置为非临时实例
总结
1.Nacos与eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
2.Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
6、Nacos配置管理
以下是几个使用Nacos配置管理的实际业务场景的例子:
-
微服务配置管理:
假设你的公司采用了微服务架构,有多个微服务需要连接到不同的数据库。通过使用Nacos配置管理,你可以将每个微服务的数据库连接信息存储在Nacos中,并动态地更新配置。例如,当你需要更改数据库的连接地址或密码时,你可以直接在Nacos中修改配置,而无需重新部署微服务,所有微服务将自动获取最新的配置信息。
-
多环境配置管理:
假设你的应用在开发、测试和生产环境中运行,并且每个环境都有不同的配置。使用Nacos配置管理,你可以为每个环境创建不同的配置文件,并通过Nacos的命名空间和配置组进行组织。开发人员可以通过选择不同的命名空间和配置组,轻松地切换到不同的环境,而不必手动修改配置文件。
-
动态路由配置:
假设你的微服务架构中使用了Spring Cloud Gateway作为API网关,并且你希望能够动态路由请求到不同的后端服务。通过使用Nacos配置管理,你可以将路由规则存储在Nacos中,并通过Spring Cloud Gateway与Nacos集成。当需要更新路由规则时,你可以直接在Nacos中修改配置,Spring Cloud Gateway将自动更新路由,并将请求动态地转发到相应的后端服务。
-
定时任务配置:
假设你的应用需要执行定时任务,例如生成报表或清理数据。通过使用Nacos配置管理,你可以将定时任务的触发时间、任务参数等信息存储在Nacos中,并由任务调度器定期从Nacos获取最新的配置。当你需要调整定时任务的执行时间或参数时,只需在Nacos中修改配置,任务调度器将自动根据最新的配置执行任务。
这些例子只是Nacos配置管理功能的一部分,Nacos还提供了更多的特性,例如配置监听、配置推送等,可以根据具体的业务需求进行灵活使用。
6.1、统一配置管理
6.1.1、配置更改热更新
在Nacos中添加配置信息:
在弹出表单中填写配置信息:
在nacos服务页面中配置,让服务找到nacos的配置:
1、引入nacos的配置管理客户依赖:
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2、在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
spring:
application:
name: userservice #配置服务名称
cloud:
nacos:
server-addr: localhost:8848 #配置nacos服务地址
config:
file-extension: yaml #文件后缀
profiles:
active: dev #开发环境,这里是dev
可以在Usercontroller类中获取一下nacos配置的信息来验证一下:
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("/now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
6.1.2、配置自动刷新
nacos中的配置文件变更后,微服务无需重新启动就可以感知。不过需要通过下面两种配置实现:
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
方式二:使用@ConfigurationProperties注解
这种方式使用的是约束大于配置的配置方法。
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
使用了Spring框架的注解(Data,Component,ConfigurationProperties)。这段代码定义了一个名为PatternProperties的类。
@ConfigurationProperties注解是Spring框架中用于绑定配置属性的注解。它的prefix属性指定了配置文件中的属性前缀为"pattern",这意味着在配置文件中,所有以"pattern"开头的属性会被绑定到PatternProperties类的对应属性上。
PatternProperties类中只有一个私有的String类型属性dateformat,它对应配置文件中的"pattern.dateformat"属性。在配置文件中,你可以设置这个属性的值,例如:
pattern:
dateformat: yyyy-MM-dd HH:mm:ss
这样,当你运行程序时,Spring框架会自动将配置文件中的值绑定到PatternProperties类的dateformat属性上,你可以通过获取PatternProperties实例的方式来访问并使用这个属性的值。
在类中使用nacos中配置时可以通过以下方式获取:
总结
Nacos配置更改后,微服务可以实现热更新,方式:
- 通过@Value注解注入,结合@RefreshScope来刷新
- 通过@ConfigurationProperties注入,自动刷新
注意事项:
- 不是所有的配置都适合放到配置中心,维护起来比较麻烦
- 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
7、Nacos集群搭建
7.1、集群结构图
官方给出的Nacos集群图:
其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。
DNS(Domain Name System)、SLB(Server Load Balancer)和Nacos可以一起使用来构建一个完整的服务发现和负载均衡的系统。
DNS是用于将域名解析为对应的IP地址的系统。在服务发现和负载均衡中,DNS可以被用来解析服务名称为对应的服务实例的IP地址。例如,假设有一个服务名为"my-service",通过将其注册到DNS中,可以将"my-service"解析为具体的服务实例的IP地址。
SLB是一种负载均衡器,用于将流量分发到多个后端服务实例上,从而实现负载均衡和高可用性。SLB可以接收客户端请求,并根据负载均衡算法将请求分发到多个服务实例上。例如,当有多个服务实例提供相同的服务时,SLB可以根据负载情况,将请求分发到负载较低的实例上,以实现流量的均衡分布。
Nacos是一个用于服务发现、配置管理和服务治理的开源项目。在服务发现和负载均衡中,Nacos作为服务注册中心和配置中心,可以用来管理各个服务实例的注册和注销,以及维护服务实例的元数据信息。Nacos可以提供给SLB和DNS所需的服务实例信息,从而实现服务发现和负载均衡的功能。
结合这三个组件,整体的架构可以如下所示:
-
服务实例注册:服务实例可以将自己的元数据信息注册到Nacos中,包括服务名称、IP地址、端口等信息。
-
Nacos服务注册中心:Nacos注册中心负责存储和管理所有服务实例的元数据信息。
-
Nacos配置中心:Nacos配置中心负责存储和管理应用程序的配置信息。
-
DNS解析:客户端可以通过DNS解析服务名称为具体的服务实例的IP地址。
-
SLB负载均衡器:SLB可以接收客户端请求,并根据负载均衡算法将请求分发到多个服务实例上。
通过这样的结构,客户端可以通过DNS解析获取到服务实例的IP地址,并通过SLB将请求发送到可用的服务实例上,实现负载均衡。同时,Nacos作为服务注册中心和配置中心,可以管理服务实例的注册和配置信息,以确保服务的可用性和配置的一致性。这样的架构既提供了可靠的服务发现和负载均衡,也保证了配置的集中管理和动态更新能力。
我们计划的集群结构:
三个nacos节点的地址:
| 节点 | ip | port |
| nacos1 | 192.168.150.1 | 8845 |
| nacos2 | 192.168.150.1 | 8846 |
| nacos3 | 192.168.150.1 | 8847 |
7.2、集群搭建
搭建集群的基本步骤:
- 搭建数据库,初始化数据库表结构
- 下载nacos安装包
- 配置nacos
- 启动nacos集群
- nginx反向代理
六、Feign远程调用
RestTemplate方法调用存在的问题
先看一下我们以前利用RestTemplate发起远程调用的代码:
存在下面的问题:
- 代码可读性差,编程体验不统一
- 参数复杂URL难维护
1、Feign的介绍
Feign 提供了一种简单且优雅的方式来定义和调用基于HTTP的远程服务。通过使用Feign,您可以在客户端代码中定义接口,然后Feign会根据这些接口的定义自动生成实际的HTTP请求,并将其转发到远程服务。这样,您可以像调用本地方法一样调用远程服务的方法,无需显式地处理HTTP请求和响应。
Feign 还支持对请求进行编码和解码、错误处理、请求和响应拦截器等功能,使得在微服务架构中处理远程服务变得更加方便和高效。
使用Feign的一些优点包括:
- 简化了客户端代码,使其更易于维护和理解。
- 减少了手动处理HTTP请求和响应的工作量。
- 支持多种编解码器,可处理多种数据格式。
- 可与Spring Cloud等微服务框架无缝集成。
在使用Feign时,您需要定义一个Java接口,该接口包含与远程服务相对应的方法和参数。然后,通过在应用程序的配置中启用Feign并使用Spring的依赖注入功能,您可以将Feign客户端注入到您的代码中,从而实现对远程服务的调用。
Feign作为一个声明式的HTTP客户端,在微服务架构和分布式系统中有许多应用场景。以下是一些常见的使用场景:
-
微服务间的通信:在微服务架构中,各个服务之间需要频繁地进行通信,Feign可以帮助简化服务间的HTTP通信,使得调用远程服务更加方便。
-
服务消费者:当一个服务需要调用其他服务提供的API时,可以使用Feign来作为客户端来消费这些服务,而无需手动处理HTTP请求和响应。
-
代理远程API:Feign可以将远程服务的API映射为本地接口,使得调用远程服务的过程就像调用本地方法一样简单。
-
负载均衡:结合负载均衡的工具(如Ribbon),Feign可以实现在多个服务实例之间进行负载均衡,从而提高系统的可用性和性能。
-
声明式的错误处理:Feign支持定义统一的错误处理逻辑,使得在发生错误时可以采取一致的处理方式,从而减少重复代码。
-
数据格式处理:Feign支持多种编解码器,可以处理不同的数据格式,例如JSON、XML等,使得数据的传输和解析更加灵活和便捷。
-
请求拦截与日志:Feign支持请求和响应拦截器,可以在发送请求和接收响应时进行拦截和处理,例如记录日志、鉴权等。
总体而言,Feign适用于任何需要在微服务架构中进行HTTP通信的场景,特别是当您希望简化远程服务调用的代码并增加可读性和可维护性时,Feign是一个非常有用的工具。
2、定义和使用Feign客户端
在之前我是在调用其他服务提供的接口是使用的是RestTempale,为什么还要学Feign呢?
Feign和RestTemplate都是在Spring框架中用于进行HTTP请求的工具,但它们在使用方式和特点上有一些区别。
-
声明式 vs. 编程式:
- Feign是一个声明式的HTTP客户端,它允许您通过定义接口来描述对远程服务的请求,并自动生成底层HTTP调用。Feign使用注解来配置请求的URL、HTTP方法、请求参数等信息,使得代码更加简洁和易读。
- RestTemplate是一个编程式的HTTP客户端,您需要在代码中显式地构建HTTP请求,包括指定URL、HTTP方法、请求头、请求体等信息。虽然可以通过RestTemplate灵活地控制请求细节,但相比Feign,代码可能会更冗长和复杂。
-
整合Spring Cloud vs. 单独使用:
- Feign是Spring Cloud项目的一部分,它与Spring Cloud的其他组件(如Eureka、Ribbon、Hystrix等)紧密集成,使得在微服务架构中使用Feign更加方便,并且提供了一些额外的特性,如负载均衡、服务发现等。
- RestTemplate是Spring Framework的一部分,它可以单独使用,没有与Spring Cloud的深度集成。如果您在非微服务环境中,或者不需要Spring Cloud提供的其他功能,RestTemplate是一个不错的选择。
-
自动化的负载均衡:
- Feign与Ribbon(Spring Cloud中的负载均衡组件)集成,可以自动进行负载均衡,使得在多个服务实例中选择合适的目标服务。
- RestTemplate在默认情况下不支持自动的负载均衡,您需要手动编写代码来实现负载均衡,或者结合Ribbon来实现自动化负载均衡。
-
请求拦截器和错误处理:
- Feign允许您定义请求和响应拦截器,从而可以在发送请求和接收响应时进行拦截和处理,例如记录日志、鉴权等。它还提供了声明式的错误处理机制,让您可以统一处理请求错误。
- RestTemplate也支持请求和响应拦截器,但是在处理错误时可能相对繁琐,需要通过捕获异常等方式来处理请求错误。
综上所述,如果您在使用Spring Cloud和微服务架构,特别是在Feign与Ribbon、Eureka等组件进行集成时,Feign可能是更好的选择,因为它提供了一种简单且声明式的方式来定义和调用远程服务。然而,如果您在非微服务环境或不需要Spring Cloud提供的其他功能,RestTemplate仍然是一个可行的选择,尤其是当您需要更多的灵活性和对HTTP请求的直接控制时。
2.1、Feign实战
定义和使用Feign客户端需要以下步骤:
1、添加依赖:首先,您需要在项目中添加Feign的依赖。如果您是使用Spring Boot项目,可以在pom.xml中添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、启用Feign客户端:为了使Feign客户端生效,您需要在Spring Boot应用程序的主类上添加@EnableFeignClients
注解,这将启用Feign客户端的自动配置和发现。
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
3、创建Feign客户端接口:接下来,您需要定义一个Java接口,该接口将包含与远程服务相对应的方法和参数。这些方法的定义类似于普通的Spring组件接口,但是您可以使用Spring的注解来定义远程服务的URL、HTTP方法和其他相关信息。
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("userservice") //使用@FeignClient来指导这个接口所有接口方法要访问的服务名称
public interface UserClient {
//定义调用接口
@GetMapping("/user/{id}")
User findUserById(@PathVariable("id") Long id);
}
4、使用Feign客户端:现在您可以在其他组件或服务中注入Feign客户端,并使用它来调用远程服务的方法。
package cn.itcast.order.service;
import cn.itcast.order.clients.UserClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient; //注入User的Feign客户端
public Order queryOrderById(Long id){
Order order = orderMapper.findById(id);
Long userId = order.getUserId();
//使用Feign远程调用
User user = userClient.findUserById(userId);
order.setUser(user);
return order;
}
}
总结
Feign的使用步骤
- 引入依赖
- 添加@EnableFeignClients注解
- 编写FeignClient接口
- 使用FeignClient中定义的方法代替RestTemplate
3、自定义Feign的配置
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
一般我们需要配置的就是日志级别。
日志级别
Feign支持四种不同的日志级别,您可以根据需要选择适合的日志级别。这些日志级别用于控制Feign在发送请求和接收响应时记录的日志信息的详细程度。
-
NONE:该日志级别最低,不记录任何日志信息。如果您不想在控制台输出任何关于Feign请求和响应的日志,可以选择此级别。
-
BASIC:在BASIC级别下,Feign仅记录请求方法、URL和响应状态码的基本信息。这对于快速了解请求的基本情况很有帮助,但不会记录请求和响应的详细内容。
-
HEADERS:在HEADERS级别下,Feign将记录请求和响应的头部信息,包括请求头和响应头。这样可以更详细地查看请求和响应的头部信息,有助于调试和了解请求的上下文。
-
FULL:FULL级别是最详细的日志级别,它会记录请求和响应的所有详细信息,包括请求头、请求体、响应头和响应体。如果您需要完整的请求和响应信息来进行详细的调试和排查问题,FULL级别是最合适的选择。
3.1自定义Feign的配置方式
方式一:配置文件方式
当使用配置文件的方式来配置Feign的自定义配置时,您可以借助Spring Boot的属性配置功能来实现。通过在配置文件(如application.properties或application.yml)中添加特定的属性,您可以自定义Feign的行为。
首先,您需要在配置文件中添加Feign的相关属性。以YAML格式的配置文件为例,假设您想要配置Feign的日志级别为FULL,可以这样写:
1、全局生效
# application.yml
feign:
client:
config:
default: #这里用default就是全局配置,如果是写服务器名称,则是针对在某个微服务的配置
loggerLevel: full #日志级别
2、局部生效
# application.yml
feign:
client:
config:
orderservice: #这里用服务名称,则只针对这个服务的配置
loggerLevel: full #日志级别
方式二:java代码方式,需要先声明一个Bean:
在使用Feign时,您可以通过自定义配置来修改其行为和属性。为了自定义Feign的配置,您需要创建一个配置类,并在其中添加相关的Bean定义。下面是一个简单的示例来说明如何自定义Feign的配置:
package cn.itcast.order.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}
全局配置:
如果是全局配置,则把它放到@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = FeignConfig.class)
局部配置:
如果是局部配置,则把它放到@FeignClient这个注解中:
@FeignClient(value = "userservice",configuration = FeignConfig.class)
总结
Feign的日志配置
1.方式一是配置文件,feign.client.config.xxx.loggerLevel
- O如果xxx是default则代表全局
- 如果xxx是服务名称,例如userservice则代表某服务
2.方式二是java代码配置Logger.Level这个Bean
- 如果在@EnableFeignClients注解声明则代表全局
- 如果在@FeignClient注解中声明则代表某服务
4、Feign的性能优化
Feign的底层客户端实现是通过集成了其他HTTP客户端库来实现的。具体来说,Feign支持两种主要的HTTP客户端实现:JDK的URLConnection和Apache HttpClient。
-
JDK的URLConnection:这是Java标准库中提供的用于HTTP通信的API。Feign可以直接使用JDK的URLConnection来发送HTTP请求。它是轻量级的,对于简单的HTTP通信,可能足够满足需求。如果您在项目中没有引入其他HTTP客户端库,Feign会默认使用JDK的URLConnection作为底层的客户端实现。
-
Apache HttpClient:Apache HttpClient是Apache基金会提供的一个功能丰富、灵活的HTTP客户端库。它提供了许多高级功能,如连接池、重试机制、认证等。如果您在项目中引入了Apache HttpClient的依赖,Feign会自动选择使用Apache HttpClient作为底层的客户端实现。
-
OkHttp:OkHttp是Square公司开发的一款高效的HTTP客户端库。它支持HTTP/2、连接池、拦截器等现代特性。如果您在项目中引入了OkHttp的依赖,Feign会自动切换到OkHttp作为底层实现。
Feign的这种设计使得您可以根据需要灵活地选择底层的HTTP客户端。默认情况下,如果项目中没有引入其他HTTP客户端库,Feign将使用JDK的URLConnection作为底层客户端。如果您希望使用Apache HttpClient或OkHttp,只需在项目中添加相应的依赖,Feign会自动检测并使用它们作为底层实现。
通过这种方式,Feign可以同时满足不同项目对HTTP客户端的需求,并提供简便的远程服务调用方式。
因此优化Feign的性能主要包括:
- 使用连接池代替默认的URLConnection
- 日志级别,做好使用basic或者none
4.1、 Feign性能优化HttpClient的支持:
1、Feign添加HttpClient的支持:
引入依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
配置连接池:
#feign自定义配置,配置日志级别
feign:
client:
config:
default: #这里用default就是全局配置,如果是写服务器名称,则是针对在某个微服务的配置
loggerLevel: basic #日志级别
httpclient:
enabled: true #开启feign对HttpClient的支持
max-connections: 200 #最大连接数量
max-connections-per-route: 50 #每个路径的最大连接数量
总结
Feign的优化
1.日志级别尽量用basic
2.使用HttpClient或OKHttp代替URLConnection
- 引入feign-httpClient依赖
- 配置文件开启httpClient功能,设置连接池参数
5、Feign的最佳实际
方式一(继承):给消费者的FeignClient和提供者的controller系统定义一个父接口作为标准。
这种实现方式虽然可以让Controller和Feign Client共享同一个接口定义,但存在一些问题和注意事项:
-
潜在的耦合:共享同一个接口定义会让Controller和Feign Client在代码层面产生耦合,导致它们紧密地关联在一起。一旦接口定义发生变化,两者都需要进行相应的修改,这可能影响到多个模块。
-
不符合单一职责原则:Controller负责处理HTTP请求和返回响应,而Feign Client负责远程服务调用。将它们共享同一个接口可能会让代码功能变得混乱,违反了单一职责原则。
-
难以做到完全解耦:尽管接口定义可以共享,但是在Feign Client的实现中,仍然需要涉及到远程服务调用的逻辑。这会让Feign Client的实现和Controller之间仍然有一定的耦合。
-
接口定义可能会变得复杂:为了适应不同的调用场景,共享接口可能会变得复杂,可能需要添加各种参数和注解,从而导致接口的冗长和不易维护。
-
功能不一致问题:在Controller和Feign Client共享同一个接口的情况下,两者的功能可能不完全一致。在Controller中可能需要做一些本地逻辑处理,而在Feign Client中可能需要进行额外的远程服务调用。
综上所述,虽然共享接口可以在一定程度上减少重复代码,但也会引入潜在的问题和复杂性。在实际开发中,更常见的做法是将Controller和Feign Client分别定义独立的接口,通过接口定义来规范各自的功能和职责。在需要共享方法定义的情况下,可以使用Java接口继承来实现方法的复用,而避免在Feign Client中直接实现Controller的方法。这样可以更好地保持代码的清晰和可维护性,并符合单一职责原则。
方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
以前实现
假如:我们有order-service、pay-service和user-service两个服务,现在order-service和pay-service都要去调用user-service提供的接口,我们就得order-service和pay-service服务里都要实现UserClient,假如我们不止只有这两个服务调用user-service呢,有10个、20个服务都要调用,那UserClient就得写10遍、20遍,就会重复的开发代码了。
现在
我们独立创建一个项目,将哪些重复的代码编写的东西全部放到这个项目/模块中(如UserClient、User实体类、DefaultConfig等),我们可以把这个项目的内容打成jar包,假如以后服务需要的时候直接引用依赖就可以
总结
Feign的最佳实践
- 让controller和FeignClient继承同一接口
- 将FeignClient、poJo、Feign的默认配置都定义到一个项目中,供所有消费者使用
抽取FeignClient
实现最佳实践方式二的步骤如下:
1、创建一个module,命名为feign-api,然后引入feign的starter依赖
引入feign的starter依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
之前在order-service服务模块中饿这些东西就可以删除了。
3、在order-service中引入feign-api的依赖
<!--引入feign的同一api-->
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
这个依赖是我们项目中的feign-api模块里的内容。
4、修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
5、重启测试
当定义的FeignClient不在springApplication的包扫描范围时,这些FeignClient无法使用问题
重启order-service服务后我们会发现报错了
Field userClient in cn.itcast.order.service.OrderService required a bean of type 'cn.itcast.feign.clients.UserClient' that could not be found.
该错误信息表明在OrderService类中的userClient字段的类型为cn.itcast.feign.clients.UserClient,但是Spring容器中找不到该类型的Bean。
说明我们在自动装配UserClient时是找不到这个对象的实例的,原因是我们现在在order-service模块中使用的UserClient是通过添加了feign-api依赖而导进来的,在order-service模块启动时spring只会对启动类所在目录及这个目录里所有类进行扫描注册到spring容器中的,而UserClient所在的包就没有被扫描到,所在在order-service这个模块中的spring容器中就找不到了对于的对象实例了。
解决办法方式一:指定FeignClient所在包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
解决办法方式二:指定FeignClient字节码
@EnableFeignClients(clients = {UserClient.class})
总结
不同包的FeignClient的导入有两种方式
- 在@EnableFeignClients注解中添加basePackages,指定FeignClient所在的包
- 在@EnableFeignClients注解中添加clients,指定具体FeignClient的字节码
七、Gateway服务网关
网关(Gateway)服务在微服务架构中起着重要的作用。它是位于客户端和后端微服务之间的中间层,用于处理和转发请求。为什么要使用网关服务呢?以下是一些主要的原因:
-
统一入口:网关服务提供了一个统一的入口,客户端只需要向网关发送请求,而不需要直接调用各个微服务。这样简化了客户端的调用逻辑,同时也方便地对请求进行统一管理和处理。
-
路由和负载均衡:网关服务可以根据请求的URL路径或其他条件将请求路由到相应的微服务实例。它还可以配合负载均衡算法,确保请求被均匀地分发给不同的服务实例,提高了系统的可用性和性能。
-
安全控制:通过网关服务,可以实现对请求进行安全控制和认证。网关可以验证请求的身份、权限以及合法性,确保只有授权的请求能够访问相应的微服务。
-
聚合和分解请求:网关服务可以聚合多个微服务的请求,将它们合并为一个请求返回给客户端,从而减少了客户端与后端服务之间的请求次数,降低了网络开销。
-
缓存:网关服务可以对请求的响应进行缓存,从而减少重复请求对后端服务的压力,提高了系统的性能和响应速度。
-
降级和容错:网关服务可以实现对后端服务的降级和容错处理。当某个微服务出现故障或不可用时,网关可以提供默认的响应或调用备用服务,避免了系统级的故障。
-
监控和日志:通过网关服务,可以实现对请求和响应进行监控和日志记录。这有助于实时追踪请求的流程和性能,发现问题并进行及时的处理。
综上所述,网关服务在微服务架构中扮演了一个重要的角色,它提供了统一入口、路由和负载均衡、安全控制、聚合和分解请求、缓存、降级和容错、监控和日志等功能,为整个系统提供了更高效、更安全、更稳定的请求处理和管理能力。
1、网关技术
在springcloud中网关的实现有两种:gateway和zuul
Zuul和Spring Cloud Gateway都是常用的网关实现,用于在微服务架构中处理和转发请求。它们都可以作为反向代理和请求路由器,但在设计和功能上有一些区别。
-
Zuul:
Zuul是Netflix提供的网关服务,被称为Netflix Zuul。它是一个基于Servlet的网关实现,构建在传统的Spring Cloud项目上。Zuul 1.x版本采用阻塞式I/O模型,Zuul 2.x版本采用非阻塞式I/O模型,基于Netty。
特点:
- Zuul 1.x的阻塞式I/O模型限制了并发性能,虽然可以通过多实例部署来提高吞吐量,但对于高并发场景可能不够高效。
- Zuul 2.x基于非阻塞式I/O模型,可以提供更好的性能和吞吐量。
- Zuul支持动态路由和过滤器等功能,可以实现请求的动态转发和预处理。
- 配置方式较为灵活,可以使用Groovy或Java DSL配置路由和过滤器。
-
Spring Cloud Gateway:
Spring Cloud Gateway是Spring Cloud项目中的网关服务,从Spring Cloud 2.x版本开始引入。它是基于Spring 5和Spring Boot 2构建的,采用了WebFlux框架,支持响应式编程。
特点:
- Spring Cloud Gateway采用基于异步非阻塞的WebFlux框架,提供了更好的性能和响应能力,适用于高并发场景。
- 支持动态路由和过滤器等功能,可以实现请求的动态转发和预处理。
- 提供了丰富的过滤器,支持全局和局部过滤器的定义和使用。
- 配置方式灵活,可以使用YAML或Java配置路由和过滤器。
综合来说,Zuul和Spring Cloud Gateway都是成熟的网关实现,各有优势。如果您在使用Spring Cloud项目,推荐使用Spring Cloud Gateway,特别是在需要高并发和响应能力的场景下。而如果您在使用Netflix项目,可以选择使用Netflix Zuul 2.x版本或更高的版本,或考虑迁移到Spring Cloud Gateway。选择哪个网关取决于您的项目需求、技术栈和预期的性能要求。
总结
网关的作用:
- 对用户请求做身份认证、权限校验
- 将用户请求路由到微服务,并实现负载均衡
- 对用户请求做限流
2、搭建网关服务
2.1、搭建网关服务的步骤:
-
创建Gateway项目:首先,创建一个新的Spring Boot项目作为Gateway服务网关。
-
添加依赖:在Gateway项目中,添加Spring Cloud Gateway和Nacos的依赖,以便使用Gateway和Nacos的功能。在
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>pom.xml
文件中添加以下依赖:
</dependencies><!-- Spring Cloud Nacos 作为服务注册和发现中心 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- 其他依赖 -->
3、配置网关路由:在application.yml
中配置网关的路由规则,将请求路由到对应的微服务。例如:
server:
port: 10009 # 配置服务端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # 配置Nacos注册中心的地址和端口
gateway:
routes:
- id: userservice_route # 路由规则的唯一标识,用于识别该路由规则。自定义的标识可以方便管理和维护。
uri: lb://userservice # 指定目标微服务的服务名。lb://前缀表示使用负载均衡器来选择目标微服务的实例。这里目标微服务的服务名是userservice。
predicates: # 断言条件列表,用于匹配请求是否符合该路由规则。
- Path=/user/** # 匹配的URL路径,当请求的URL路径以/user/开头时,该路由规则会匹配。**表示匹配任意后续路径。
- id: orderservice_route # 另一个路由规则的唯一标识,用于识别该路由规则。
uri: lb://orderservice # 指定目标微服务的服务名。这里目标微服务的服务名是orderservice。
predicates: # 断言条件列表,用于匹配请求是否符合该路由规则。
- Path=/order/** # 匹配的URL路径,当请求的URL路径以/order/开头时,该路由规则会匹配。**表示匹配任意后续路径。
4、启动nacos服务
#Windows系统命令
startup.cmd -m standalone
#Linux系统命令
sh startup.sh -m standalone
就可以直接使用gateway的ip+端口来访问数据了,可以直接让gateway来将这些访问地址转发到相应的服务实例上,从而来访问数据(如访问地址:localhost:10009/order/101)
2.2、搭建网关服务流程图
-
客户端发起请求:客户端(如Web应用、移动端应用、或其他服务)发起HTTP请求。
-
请求到达Gateway:请求首先到达Gateway服务网关。
-
Gateway进行路由匹配:Gateway根据预先配置的路由规则,匹配请求的URL路径、请求头等信息,找到对应的微服务实例。
-
服务发现:Gateway需要通过服务注册中心(如Nacos)来获取微服务实例的信息。它向Nacos发送服务发现请求,Nacos会返回目标微服务的实例列表。
-
负载均衡:Gateway使用负载均衡算法从目标微服务实例列表中选择一个实例,将请求转发给该微服务实例。
-
微服务接收请求:目标微服务实例接收到请求。
-
微服务处理请求:微服务实例处理请求,并根据业务逻辑执行相应的操作。
-
微服务返回响应:微服务实例生成响应结果,并将响应返回给Gateway。
-
Gateway接收响应:Gateway接收到微服务实例的响应。
-
Gateway处理响应:Gateway可以对微服务实例的响应进行处理,如添加响应头、日志记录等。
-
响应返回客户端:Gateway将处理后的响应返回给客户端。
通过服务注册中心(如Nacos)来进行服务发现,Gateway能够动态地获取微服务的实例信息,从而实现请求的动态转发和负载均衡。这样,Gateway就能找到相应的服务并将请求转发给它们。感谢您的指正,希望这次的回答更加准确。
总结
网关搭建步骤
-
创建项目,引入nacos服务发现和gateway依赖
-
配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括
- 路由id:路由的唯一标示
- 路由目标 (uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
- 路由断言 (predicates): 判断路由的规则
- 路由过滤器 (filters):对请求或响应做处理
3、路由断言工厂Route Predicate Factory
- 路由断言工厂(Route Predicate Factory)是Spring Cloud Gateway中的一种配置方式,用于根据请求的条件来进行路由匹配。它可以根据请求的不同属性(例如URL路径、请求头、请求方法等)来判断请求是否匹配某个路由规则,如果匹配成功,则将请求转发到相应的目标地址(服务实例)。
- 例如:Path=/order/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicata.PathRoutePredicateFactory类来处理
- 像这种短语工厂在SpringCloudGateway还有十几个
3.1、spring提供了11种基本的Predicate工厂
当使用其他的Predicate工厂时,可到spring官网上看如何使用:spring route
总结
- PredicateFactory的作用是什么?
读取用户定义的断言条件,对请求做出判断
- Path=/user/**是什么含义?
路径是以/user开头的就认为是符合的
4、路由过滤器(Route Filter)
路由过滤器(Route Filter)是Spring Cloud Gateway中的另一个重要组件,它用于在请求被路由到目标服务之前或之后,对请求或响应进行一系列的处理操作。通过路由过滤器,您可以对请求和响应进行修改、增强或验证,从而实现更加灵活和强大的网关功能。
例如:
假设我们有一个微服务架构,包含多个服务,其中一个是认证服务(Authentication Service),负责处理用户的身份认证。其他服务(如用户服务、订单服务等)需要保护某些资源,只允许经过认证的用户访问。
在这种情况下,我们可以使用Spring Cloud Gateway作为网关,通过路由过滤器来实现请求认证和授权:
-
全局过滤器(Global Filter):我们可以创建一个全局过滤器来拦截所有的请求,在这个过滤器中进行用户身份认证的检查。比如,我们可以检查请求中是否包含有效的身份令牌(Token),以确定请求是否是经过认证的用户发起的。
-
局部过滤器(Route-Specific Filter):对于特定需要授权访问的服务,我们可以在路由配置中使用局部过滤器。在这个过滤器中,我们可以对请求进行权限验证,检查用户是否有权限访问该服务提供的资源。如果用户没有合法的权限,则可以拒绝请求或返回相应的错误信息。
这样,通过路由过滤器,我们可以实现全局的请求认证,并对需要授权访问的服务进行权限控制。用户在发起请求时,首先经过全局过滤器进行认证,然后再经过局部过滤器进行权限验证。只有通过认证和授权的请求才能继续访问后端的微服务。
这种方式使得认证和授权逻辑从业务服务中剥离出来,统一交给网关进行处理,简化了业务服务的实现和管理,同时提高了系统的安全性和可维护性。
4.1、过滤工厂 GratewayFilterFactory
在Spring Cloud Gateway中,过滤工厂(Filter Factory)是用于创建过滤器的工厂类,它用于生成过滤器实例并配置过滤器的行为。过滤工厂是一种更高级别的抽象,它将过滤器的创建和配置过程封装在一起,使得配置网关过滤器更加简单和灵活。
过滤工厂与过滤器的区别在于:
- 过滤器:是过滤器实际的执行逻辑,它需要开发者自己实现并继承特定的Filter接口(如GlobalFilter或GatewayFilter),完成请求和响应的处理。
- 过滤工厂:是用于创建过滤器的工厂类,它将过滤器的创建和配置封装起来,通过配置工厂的参数,可以快速创建不同类型的过滤器,并指定过滤器的行为。
过滤工厂的优势在于,它提供了一些预定义的工厂,可以轻松地为常见的过滤器场景创建过滤器,而无需开发者自己编写过滤器的实现。通过简单的配置,开发者可以使用这些预定义的过滤工厂创建自定义的过滤器,从而实现特定的路由和请求处理逻辑。
spring提供了31种不同的路由过滤器工厂,一些常用的过滤工厂包括:
通过使用过滤工厂,您可以在网关中快速配置和管理过滤器,实现对请求和响应的灵活处理。过滤工厂是Spring Cloud Gateway提供的一种高级配置方式,帮助开发者更加方便地定制和扩展网关的功能。
如果想使用其他的可以到过往进行学习:spring GatewayFilterFactory
4.1.1、案例
给所有进入userservice的请求添加一个请求头
默认过滤器
总结
- 过滤器的作用是什么?
对路由的请求或响应做加工处理,比如添加请求头
配置在路由下的过滤器只对当前路由的请求生效
- defaultFilters的作用是什么?
对所有路由都生效的过滤器
5、全局过滤器 GlobalFilter
全局过滤器(Global Filter):我们可以创建一个全局过滤器来拦截所有的请求,在这个过滤器中进行用户身份认证的检查。比如,我们可以检查请求中是否包含有效的身份令牌(Token),以确定请求是否是经过认证的用户发起的。
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。定义方式是实现GlobalFilter接口。
5.1、实现步骤
自定义来,实现GlobalFilter接口,添加@Order注解
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定义全局过滤器:用于校验请求中的authorization参数是否合法
*/
@Component
@Order(-1)
public class AuthorizeFilter implements GlobalFilter {
/**
* 过滤器方法,用于校验请求中的authorization参数是否合法
* @param exchange 服务器WebExchange对象,包含请求和响应信息
* @param chain GatewayFilterChain对象,用于传递请求给下一个过滤器或路由处理器
* @return Mono<Void>,表示请求的处理结果
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 获取authorization参数
String authorization = params.getFirst("authorization");
// 校验authorization参数
if ("ltc".equals(authorization)) {
// 授权通过,请求继续传递给下一个过滤器或路由处理器
return chain.filter(exchange);
} else {
// 授权不通过,设置响应状态码为403(禁止访问),并结束处理,返回禁止访问的响应
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
}
}
总结
全局过滤器的作用是什么?
- 对所有路由都生效的过滤器,并且可以自定义处理逻辑
实现全局过滤器的步骤?
- 实现GlobalFilter接口
- 添加@Order注解或实现Ordered接口
- 编写处理逻辑
6、过滤器执行顺序
请求进入网关会碰到三类过滤器: 当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
这是我们就会想,这三个过滤器相同吗?怎么就可以放在同一个集合中进行排序了,别着急。
对于当前路由过滤器和DefaultFilter的配置方法是非常相似的,区别就是当前路由过滤器放在路由内,而DefaultFilter是放在默认的过滤器内的。从java的底层来看他两个本质是一样的,只是作用范围不一样而已。
在配置文件中配置的当前路由过滤器和DefaultFilter(默认过滤器)都是由AddRequestHeaderGatewayFilterFactory过滤器工厂来读取,然后生成了GatewayFilter过滤器,所以这两个过滤器本质都是同一类,他们两个都叫GatewayFilter过滤器。
但是GlobalFilter全局过滤器为什么又可以和GatewayFilter来进行配置执行呢?
在网关中有一个适配器GatewayFilterAdapter,他就可以将GlobalFilter适配成GatewayFilter来用。
GatewayFilterAdapter适配器这个类就实现了GatewayFilter接口,他内部还接收了GlobalFilter全局过滤器,如果我们给他传入一个GlobalFilter全局过滤器,他就会将GlobalFilter适配成GatewayFilter来用。所以在网关中所有的过滤器都会被适配成Gateway来使用,所以这些过滤器就可以放到一个集合中来进行排序了。
这时就会出现一个新的问题了,那放到了一个集合后,有怎么进行排序呢?
6.1、过滤器执如何排序
- 每个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Order接口,或者添加@Order注解来指定order值,由我们自己指定。
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
多余多个路由过滤器 和多个默认路由器:
- 如果有多个路由过滤器,那他们是按照声明的顺序来递增order的值的,来排序他们的执行顺序。
- 多个默认路由也是如此的,也是按声明的顺序来递增order的值,来排序他们的执行顺序。
但是当默认过滤器、路由过滤器和GlobalFilter全局过滤器的order值相同呢,那又该怎么办呢?
- 当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobalFilter全局过滤器的顺序执行。
底层源码中有两个类是用来加载这三种过滤器的:
当服务启动后RouteDefinitionRouteLocator类的getFilter()方法会首先执行,会先去配置文件中加载defaultFilters,然后才会去加载某个路由的route的filter,然后合并。
当getFilter()方法执行完后,FilteringWebHandler类的handle()方法才会进行加载全局过滤器,加载后会与之前加载的过滤器合并后更具order排序,组织过过滤器链
总结
路由过滤器、defaultFilter、全局过滤器的执行顺序?
- order值越小,优先级越高
- 当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器
7、跨域问题处理
7.1、跨越问题
跨域问题(Cross-Origin Issue)是由浏览器的同源策略引起的。同源策略是一种安全机制,限制了网页文档或脚本从一个源(协议、域名、端口号的组合)加载或操作另一个源的内容。如果两个网页的协议、域名和端口号都相同,它们就是同源的,否则就被认为是跨域的。
跨域:域名不一致就是跨域,主要包括:
- 域名不同:www.taobao.com和 www.taobao.org 和 wwwjd.com 和 miaoshajd.com
- 域名相同,端口不同: localhost:8080和localhost8081
跨越问题:浏览器禁止请求的发起者和服务端发生跨域Ajax请求,请求被浏览器拦截的问题。
跨域问题的出现是为了防止恶意网站利用客户端的漏洞进行攻击,保护用户的隐私和安全。浏览器会强制执行同源策略,禁止网页在不同源之间进行以下操作:
- 通过 XMLHttpRequest 或 Fetch API 发送跨域请求。
- 访问其他源的 Cookie、LocalStorage 和 IndexedDB。
- 获取其他源的 DOM 元素。
- 在其他源的窗口中执行脚本。
举个例子来说明跨域问题:假设网站A的域名是 https://www.example.com
,网站B的域名是 https://api.example.com
。如果网站A的网页中使用 XMLHttpRequest 或 Fetch API 向网站B发送请求,那么由于它们不是同源的,浏览器会阻止这个请求,返回一个错误。这就是跨域问题。
7.2、跨越问题解决办法 CORS
只需要在application.yml文件中添加下面的配置即可。
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]': # 配置所有路径的全局CORS设置
allowedOrigins: "https://example.com" # 允许的跨域请求源,这里设置为https://example.com
allowedMethods: "GET, POST, PUT, DELETE" # 允许的请求方法,这里设置为GET、POST、PUT、DELETE
allowedHeaders: "*" # 允许的请求头,这里设置为"*"表示允许任意请求头
allowCredentials: true # 是否允许发送凭证信息(如Cookie),这里设置为true表示允许发送凭证信息
maxAge: 3600 # 预检请求的缓存时间(单位:秒),这里设置为3600秒
add-to-simple-url-handler-mapping: true #处理option请求被拦截问题
配置详细介绍:
-
corsConfigurations
: 这个属性用于配置全局的CORS设置。[/**]
表示匹配所有路径,也就是对所有请求生效的全局CORS配置。 -
allowedOrigins
: 允许的跨域请求源。这里设置为https://example.com
,表示只允许来自https://example.com
域名的请求进行跨域访问。你可以根据实际需求设置允许的域名,也可以使用通配符*
表示允许所有域名的请求。 -
allowedMethods
: 允许的请求方法。这里设置为GET, POST, PUT, DELETE
,表示只允许这些HTTP方法的请求进行跨域访问。你可以根据需要添加或删除允许的方法。 -
allowedHeaders
: 允许的请求头。这里设置为*
,表示允许任意请求头。你也可以设置具体的请求头名称,比如"Content-Type, Authorization"
,只允许这些请求头进行跨域访问。 -
allowCredentials
: 是否允许发送凭证信息(如Cookie)。这里设置为true
,表示允许发送凭证信息。如果设置为false
,则不允许发送凭证信息。 -
maxAge
: 预检请求的缓存时间(单位:秒)。预检请求是指浏览器在发送跨域请求前先发送一个 OPTIONS 请求来检查服务器是否允许跨域访问。这里设置为3600
秒,表示预检请求的结果在 3600 秒内可以被缓存,减少预检请求的次数。 -
add-to-simple-url-handler-mapping
: 这个属性设置为true
,将CORS配置添加到简单URL处理程序映射中。这是一个内部属性,通常不需要手动设置。
通过以上配置,Spring Cloud Gateway 将会在响应中添加相应的CORS响应头,允许来自 https://example.com
域名的跨域请求,允许的方法有 GET、POST、PUT、DELETE,允许任意请求头,允许发送凭证信息(如 Cookie),并且预检请求的缓存时间为 3600 秒。
总结
·CORS跨域要配置的参数包括哪几个?
- 允许哪些域名跨域?
- 允许哪些请求头?
- 允许哪些请求方式?
- 是否允许使用cookie?
- 有效期是多久?
此篇是在学习黑马课springcloud程是做的详细的笔记,内容是自己总结过后,易懂,但是就是篇幅有点多,需要大家多花时间去看。
作者:m0_62498006
来源:CSDN
原文:https://blog.csdn.net/m0_62498006/article/details/131927338
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件