赞
踩
目录
什么是微服务:是一种经过良好设计的分布式架构。微服务特征:
职责单一:每个服务只做一件事,服务的粒度非常细
服务自治:每个服务可以有独立的团队、独立的技术、独立的数据、独立交付部署
面向服务:所有服务都要遵循相同的协议暴露访问接口。SpringCloud使用的是HTTP协议
服务隔离:服务之间应该做好隔离与保护,防止出现服务雪崩
微服务核心组件:
注册中心:解决服务治理问题的组件。Nacos,Eureka
远程调用:解决服务之间互相访问的组件,发起远程调用。HttpClient、RestTemplate、Feign
服务保护:解决服务之间的隔离,防止出现雪崩。Sentinel,Hystrix
配置中心:解决服务的配置文件散乱、不好处理的问题。Nacos,SpringCloudConfig
服务网关:解决 众多微服务对象提供统一的访问入口。SpringCloudGateway, Zuul
注册中心Nacos-使用入门:
安装启动Nacos。Nacos的管理界面地址是 http://localhost:8848/nacos
所有微服务要整合Nacos:
添加依赖:先锁定SpringCloudAlibaba依赖版本,再给每个微服务添加nacos-discovery依赖坐标
修改配置:每个微服务的配置文件里,需要有两项配置
服务名
Nacos的地址
修改引导类:添加@EnableDiscoveryClient
注册中心Nacos-运行原理:
服务注册:每个服务在启动时都会把自己的地址信息上报给Nacos。由Nacos来维护所有活跃微服务的地址
服务续约:
临时实例:由微服务定时向Nacos发送心跳进行续约。5s一次心跳,15s没有心跳会被Nacos标记成不健康状态,30s清除服务实例的地址信息
非临时实例:由Nacos主动探测微服务的健康状态,如果服务有问题也只是标记成不健康状态,不会剔除信息
服务发现(服务拉取):微服务会从Nacos里拉取地址列表
注册中心Nacos-分级存储模型:
每个服务下边可以有多个集群,每个集群里边可以有多个服务实例。实际部署时把相同集群的实例部署到同一机房
好处:灾备,同集群优先访问
给微服务配置集群:只要修改配置文件,设置每个实例所属的集群名称
spring.cloud.nacos.discovery.cluster-name
注册中心Nacos-实现环境隔离
相同环境名称空间的实例之间,可以互相发现、互相调用
不同环境名称空间的实例之间,是绝对隔离,不可能互相发现、不可能互相调用
做法:
在Nacos添加名称空间namespace,设置id、名称、描述
修改每个微服务的配置文件,设置当前服务实例所属的环境名称空间
spring.cloud.nacos.discovery.namespace=名称空间的id
负载均衡Ribbon-负载均衡的划分
提供者一方的负载均衡:比如nginx。适合于 整个服务端的最前沿,直接面向客户端的访问实现负载均衡
消费者一方的负载均衡:比如Ribbon。适合 微服务之间的互相调用,实现负载均衡
负载均衡Ribbon-使用入门
不需要导依赖,因为很多SpringCloud组件都 内置了Ribbon
不需要额外配置,就有默认的负载均衡效果。默认使用的轮询策略
负载均衡Ribbon-修改负载均衡策略
方式1:修改消费者一方的配置文件
目标服务名.ribbon.NFLoadBalancerRuleClassName = 负载均衡策略的全限定类名
方式2:使用@Bean把负载均衡策略对象放到IoC容器里
常见的负载均衡策略:
RandomRule:随机策略
NacosRule:是Nacos单独提供策略,可以用于实现Nacos分级存储模型时,同集群的优先访问
RoundRobinRule:轮询策略
负载均衡Ribbon-饥饿加载
如果不开启饥饿加载:是当第一次访问时,Ribbon才会从Nacos里拉取服务地址列表;第一次访问通常比较慢
如果开启了饥饿加载:是当微服务启动时,就立即从Nacos里拉取服务地址列表
修改配置文件:
ribbon:
eager-load:
enabled: 是否开启
clients:
- 目标服务名
- 目标服务名
本章节学习目标:
Feign是Netflix公司提供服务调用组件,单独使用Feign比较麻烦。SpringCloud对Feign做了集成封装,提供了声明式服务调用组件Open-Feign。
Open-Feign支持SpringMVC注解。是Spring Cloud提供的一个声明式的伪Http客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
Feign默认集成了Ribbon,所以使用Feign默认就具备负载均衡的效果。
以SpringCloud第1天的服务拆分后的代码为基础:
只要再整合Nacos即可,不需要配置Nacos的cluster-name、namespace
不需要RestTemplate,也不需要配置负载均衡策略
父工程依赖
- <!--打包方式:pom-->
- <packaging>pom</packaging>
-
- <!--SpringBoot父工程坐标-->
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.3.9.RELEASE</version>
- <relativePath/>
- </parent>
-
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <mysql.version>8.0.31</mysql.version>
- <mybatisplus.version>3.4.1</mybatisplus.version>
- </properties>
-
- <!--预先锁定依赖版本号-->
- <dependencyManagement>
- <dependencies>
- <!--SpringCloudAlibaba-->
- <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>
- <!-- mysql驱动 -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>${mysql.version}</version>
- </dependency>
- <!-- MybatisPlus -->
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>${mybatisplus.version}</version>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
- </dependencies>
微服务依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> </dependencies>
用户服务的配置文件
server:
port: 8080
spring:
application:
name: user-service #当前应用服务的名称。可以随意设置,但是不能重复。通常拿模块名作为服务名
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cloud_user
username: root
password: root
cloud:
nacos:
discovery:
server-addr: localhost:8848
logging:
level:
com.itheima.user: debug
pattern:
dateformat: MM-dd HH:mm:ss.SSS
订单服务的配置文件
server:
port: 7070
spring:
application:
name: order-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///cloud_order
username: root
password: root
cloud:
nacos:
discovery:
server-addr: localhost:8848
logging:
level:
com.itheima.order: debug
pattern:
dateformat: MM-dd HH:mm:ss.SSS
用户服务的其它代码
实体类,Mapper接口,Service,Controller,引导类:略
订单服务的其它代码
实体类,Mapper接口,Service,Controller:略
引导类:不需要RestTemplate了,也不需要配置负载均衡策略
- @EnableDiscoveryClient
- @SpringBootApplication
- @MapperScan("com.itheima.order.mapper")
- public class OrderApplication {
- public static void main(String[] args) {
- SpringApplication.run(OrderApplication.class, args);
- }
- }
Feign要在调用者一方配置
锁定SpringCloud依赖版本,并导入openfeign的起步依赖
创建Feign的Client接口
在引导类上添加注解@EnableFeignClients("Client接口所在的包名")
使用Client进行远程调用
1) 添加依赖
给父工程添加SpringCloud依赖的版本锁定。修改父工程的pom.xml,在dependencyManagement里dependencies中添加:
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
在order-service中添加open-feign的依赖坐标
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2) 创建Feign客户端
创建一个UserClient接口,用于配置Feign调用user-service的功能
要求:
接口上添加注解@FeignClient("要调用的服务名")
接口里要有方法
每个方法对应一个请求接口
在方法上添加注解,设置方法的路径。
最终要求:
@FeignClient的配置的服务名 + 方法上配置的路径 和 要调用的方法路径相同
例如下边代码中配置的:http://user-service/user/{id}
- package com.itheima.order.client;
-
- import com.itheima.order.pojo.User;
- import org.springframework.cloud.openfeign.FeignClient;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
-
- @FeignClient("user-service")
- public interface UserClient {
- @GetMapping("/user/{id}")
- User findById(@PathVariable("id") Long id);
- }
3) 启用Feign支持
修改引导类,添加注解@EnableFeignClients("Client接口所在的包名")
使用Feign后,不需要再使用RestTemplate了,要把order-service中所有RestTemplate相关的代码全部删除
- package com.itheima.order;
-
- import org.mybatis.spring.annotation.MapperScan;
- import org.springframework.boot.SpringApplication;
- import org.springframework.cloud.client.loadbalancer.LoadBalanced;
- import org.springframework.cloud.openfeign.EnableFeignClients;
- import org.springframework.context.annotation.Bean;
- import org.springframework.web.client.RestTemplate;
-
- @SpringBootApplication
- @MapperScan("com.itheima.order.mapper")
- @EnableFeignClients("com.itheima.order.client")
- public class OrderApplication {
- public static void main(String[] args) {
- SpringApplication.run(OrderApplication.class, args);
- }
- }
4) 修改调用代码
修改OrderService类,使用UserClient调用用户服务
- package com.itheima.order.service;
-
- import com.itheima.order.client.UserClient;
- import com.itheima.order.mapper.OrderMapper;
- import com.itheima.order.pojo.Order;
- import com.itheima.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;
-
- public Order findById(Long id) {
- Order order = orderMapper.findById(id);
-
- //使用UserClient调用用户服务
- User user = userClient.findById(order.getUserId());
- order.setUser(user);
-
- return order;
- }
- }
依次启动user-service,order-service
打开浏览器输入 http://localhost:7070/order/101 ,可以正常访问
Feign默认已经使用Ribbon做了负载均衡,不需要做额外的配置,还使用Ribbon原本的配置即可
配置示例如下:
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
在之前的开发中,我们通过修改logging.level
来控制日志输出的级别。然后这项配置不会对Feign生效
因为@FeignClient注解的客户端都是接口,我们实际上是通过这个接口的代理对象来进行远程调用的。而每个代理对象都会生成一个新的Feign.Logger实例对象,我们需要额外指定这个日志级别才可以。
步骤:
修改配置文件,设置整体的日志级别
创建Feign配置类,注册Logger.Level用于设置Feign的日志级别
1) 设置整体的日志级别
修改order-service的配置文件,设置日志级别为debug
logging:
level:
com.itheima: debug
2) 配置Feign的日志级别
以下两种方式,用哪种都行
方式一:@Bean方式
单独为Feign配置一个日志类,设置日志级别
- package com.itheima.order.config;
-
- import feign.Feign;
- import feign.Logger;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- @Configuration
- public class FeignConfig {
- /**
- * Feign的日志级别,是通过Logger.Level枚举来指定的
- * 它支持4种日志级别:
- * 1. NONE:不输出任何日志,是默认值
- * 2. BASIC:仅输出请求的方式、URL以及响应状态码、执行时间
- * 3. HEADERS:在BASIC基础上,额外输出请求头和响应头信息
- * 4. FULL:输出所有请求和响应的明细,包括头信息、请求体、元数据
- */
- @Bean
- public Logger.Level feignLog(){
- return Logger.Level.FULL;
- }
- }
方式二:配置文件方式
修改配置文件application.yaml,添加如下配置:
feign:
client:
config:
default:
loggerLevel: FULL
3) 测试效果
依次启动user-service、order-service
打开浏览器访问 http://localhost:7070/order/101
查看控制台日志
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。
这里我们用Apache的HttpClient来演示,步骤如下:
添加httpclient的依赖坐标
配置httpclient连接池
1) 引入依赖
在order-service的pom文件中引入Apache的HttpClient依赖:
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2) 配置连接池
在order-service的application.yml中添加配置:
feign:
httpclient:
enabled: true # 开启feign对HttpClient的支持,默认是true
max-connections: 200 # 最大的连接数,默认200
max-connections-per-route: 50 # 每个路径的最大连接数,默认50
3) 测试效果
接下来,在FeignClientFactoryBean中的loadBalance方法中打断点:
Debug方式重启order-service服务,可以看到这里的client,底层就是Apache HttpClient:
如果要使用Feign发起远程调用:
添加依赖:锁定SpringCloud的依赖版本,再添加openfeign的依赖坐标
编写Feign的接口
调用某个服务的所有方法,全部写在一个接口里。
比如有一个UserClient接口,里边所有方法都是用于向user服务发请求调用的
@FeignClient("目标服务名")
public interface UserClient{
@GetMapping("/user/{id}")
User xxx(@PathVariable("id") Long id);
}
3. 修改引导类,添加@EnableFeignClients("包名")
Feign的原理:Feign会根据我们编写的接口,生成了代理对象。在代理对象里
帮我们构造HTTP请求并发出去
如果要发GET请求,方法上就加@GetMapping;其它请求方式一样
调用方法时的实参,想要Feign帮我们作为什么样的参数,就要在形参上加注解。注意:这些注解不能省略
@PathVariable:Feign会把调用方法时的实参,绑定到请求路径的变量里
@RequestParam:Feign会把调用方法时的实参,作为表单参数发出去
@RequestBody:Feign会把调用方法时的实参,通常是以json形式放在请求体里发出去
帮我们接收HTTP响应转换成 方法返回值 对应的类型
Feign的负载均衡:已经集成了Ribbon,用法和之前一模一样
不做任何配置:也有负载均衡效果。默认的负载均衡策略是:轮询
如果要修改负载均衡策略:
方式1:修改消弱者的配置文件,设置负载均衡策略
方式2:使用@Bean把负载均衡策略对象放到IoC容器里
如果要实现饥饿加载:修改配置文件,开启饥饿加载,并设置哪些服务需要饥饿加载
Feign运行时要打印日志:
修改配置文件,设置全局的日志级别为debug
修改配置文件,设置Feign的日志级别
feign.client.config.default.loggerLevel,值有4个选项:
NONE:不打印日志
BASIC:只打印请求行、响应行
HEADERS:打印请求行、请求头, 响应行、响应头
FULL:打印请求行、头、体, 响应行、头、体
Feign的优化:
Feign底层默认使用的URLConnection技术,在HTTP请求时没有使用连接池技术,所以性能较低
性能优化:把Feign底层改用HttpClient,支持连接池。步骤:
添加httpclient依赖坐标
修改配置文件,开启httpclient的支持。 feign.httpclient.enabled=true
本章节学习目标:
在微服务架构中,一个系统会被拆分为很多个微服务。那么作为消费者要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在消费者一端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:
客户端多次请求不同的微服务,需要有微服务的地址,增加客户端代码或配置编写的复杂性
认证复杂,每个服务都需要独立认证。
存在跨域请求,在一定场景下处理相对复杂
上面的这些问题可以借助API网关来解决。所谓的API网关,就是指系统的统一入口。它封装了应用程序的内部结构,为客户端提供统一服务。
一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
添加上API网关之后,系统的架构图变成了如下所示:
Spring Cloud Gateway是Spring基于Spring5.0、SpringBoot2.0、Project Reactor等技术开发的网关技术
旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
它不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控和限流。
它是用于代替NetFlix Zuul的一套解决方案:webflux
Spring Cloud Gateway组件的核心是一系列的过滤器,通过过滤器可以将客户端的请求转发到应用的微服务(这个过程叫路由)。Spring Cloud Gateway是站在整个微服务最前沿的防火墙和代理器,隐藏微服务节点的ip信息、从而加强安全保护。
Spring Cloud Gateway本身也是一个微服务,需要注册到注册中心
Spring Cloud Gatewa的核心功能是:路由和过滤
一个路由的配置信息,由一个id、一个目的地url、一组断言工厂、一组过滤器组成。
断言是一种判断规则;如果客户端的请求符合要求的规则,则这次请求将会被路由到目的地
Spring Cloud Gateway的断言函数输入类型是Spring5.0框架中的ServerWebExchange,它允许开发人员自定义匹配来自HTTP请求中任何信息
Spring Cloud Gateway中的Filter可以对请求和响应进行过滤修改。是一个标准的Spring WebFilter。它分为两类:
Gateway Filter:局部过滤器(路由过滤器),应用于单个路由或者一组路由,通常由SpringCloudGateway内置好
Global Filter:全局过滤器,应用于所有路由
浏览器通过api网关,将以/user开头的请求转发到用户微服务
创建一个模块:网关模块,导入gateway的依赖
创建引导类:开启服务注册@EnableDiscoveryClient
创建配置文件:
网关的端口
设置服务名称
设置注册中心的地址
配置网关的路由:给每个微服务设置路由信息
1) 创建模块导入依赖
<dependencies>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos-discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
2) 创建引导类
- package com.itheima;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
-
- @EnableDiscoveryClient
- @SpringBootApplication
- public class GatewayApplication {
- public static void main(String[] args) {
- SpringApplication.run(GatewayApplication.class, args);
- }
- }
3) 编写配置文件
server:
port: 10000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 #注册中心地址
gateway:
routes: #路由配置,是个数组
- id: user-service # 路由id
uri: lb://user-service #路由目的地的地址: lb 表示从注册中心拉取服务列表,并启用负载均衡
predicates: #断言,什么样的请求可以到达目标地
- Path=/user/**
依次启动user-service、api-gateway
打开浏览器输入 http://localhost:10000/user/1,发现可以查询到id为1的用户
我们在配置文件中写的predicates
断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个,而我们需要掌握的只有Path
所有断言工厂的使用方式都是 在网关的路由配置中,使用predicates
配置的:
spring:
cloud:
gateway:
routes:
- id: 路由唯一标识
uri: lb://user-service #路由目的地的地址
predicates: #断言,可以配置多个
- Path=/user/** # - 断言名称=配置值
其它断言工厂参考:Spring Cloud Gateway
Gateway的过滤器会对请求或响应进行拦截,完成一些通用操作。在Gateway中, Filter的生命周期(执行时机)只有两个:
PRE: 这种过滤器在请求被路由之前调用,可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等
POST:这种过滤器在路由到微服务以后执行,可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
Gateway的Filter可分为两种
GatewayFilter
:应用到单个路由上,是局部过滤器,必须要配置到配置文件
它需要实现GatewayFilterFactory接口,并且需要在配置文件中配置才会生效;
GatewayFilter也可以配置为默认过滤器,针对所有路由进行过滤
GlobalFilter
:应用到所有的路由上,是全局过滤器,不需要配置到配置文件
它不需要在配置文件中配置,只要实现GlobalFilter接口即可
所有的过滤器都可以参考官方手册 Spring Cloud Gateway
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器,
这些过滤器如果配置到单个路由下,就只针对这个路由进行过滤
如果配置到default-filters
下,就针对所有路由进行过滤
网关过滤器列表如下:
在网关中给用户服务和订单服务做了路由配置,要求:
当浏览器访问用户服务时,添加一个响应头:abc=user service
当浏览器访问订单服务时,添加一个响应头:aaa=order service is strong
当浏览器访问任意服务时,添加一个响应头:company=itcast
配置过滤器
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service #用户服务的路由配置
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- AddResponseHeader=abc, user service is strong
- id: order-service #订单服务的路由配置
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- AddResponseHeader=aaa, order service works great
default-filters: #添加到这里的过滤器,对所有路由都生效
- AddResponseHeader=company, itcast
测试效果
重启网关服务
打开浏览器F12的Network进行抓包
访问网址 http://localhost:10000/order/101,可以看到有两个响应头
访问网址 http://localhost:10000/user/1,可以看到有两个响应头
内置全局过滤器
全局过滤器作用于所有路由,而且无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。
SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理。如下:
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的。
全局过滤器类必须实现GlobalFilter接口,重写filter方法,在filter方法里实现过滤逻辑
全局过滤器类可以实现Ordered接口,重写getOrder方法,如果需要设置过滤器执行顺序的话
类上要加注解@Component
- @Component
- public class DemoGlobalFilter implements Ordered, GlobalFilter {
- /**
- * 执行过滤的方法
- */
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
- //如果要处理请求,就从exchange里获取request对象
- ServerHttpRequest request = exchange.getRequest();
- // 获取请求路径
- System.out.println("本次请求路径:" + request.getURI());
- // 获取请求头
- System.out.println("请求头Host:" + request.getHeaders().getFirst("Host"));
-
-
- //如果要处理响应,就从exchange里获取response对象
- ServerHttpResponse response = exchange.getResponse();
- // 设置响应状态码
- response.setStatusCode(HttpStatus.UNAUTHORIZED);
- // 设置响应cookie
- response.addCookie(ResponseCookie.from("cookieName", "cookieValue").build());
- // 结束本次请求,并返回响应
- // return response.setComplete();
-
- //放行
- return chain.filter(exchange);
- }
-
- /**
- * 设置过滤器的执行顺序,值越小,执行的越早
- */
- @Override
- public int getOrder() {
- return 0;
- }
- }
下面,我们一起通过代码的形式自定义一个过滤器,实现用户鉴权
如果本次请求携带了请求头Authorization(token值),则放行;
否则不放行,并且返回状态码401
- package com.itheima.filter;
-
- import org.springframework.cloud.gateway.filter.GatewayFilterChain;
- 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.web.server.ServerWebExchange;
- import reactor.core.publisher.Mono;
-
- import java.util.List;
-
- @Component
- public class AuthGlobalFilter implements GlobalFilter , Ordered{
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
- //获取请求头Authorization
- List<String> authorization = exchange.getRequest().getHeaders().get("Authorization");
- //如果获取不到Authorization
- if (authorization == null || authorization.size() == 0) {
- System.out.println("鉴权失败");
- //设置响应状态码
- exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
- //结束本次调用
- return exchange.getResponse().setComplete();
- }
-
- //放行到下一个过滤器
- return chain.filter(exchange);
- }
-
- @Override
- public int getOrder() {
- //数值越小,执行的优先级越高
- return 0;
- }
- }
当一次请求进入网关后,网关会:
找到所有能拦截本次请求的所有过滤器,包括:GatewayFilter、GlobalFilter
根据所有过滤器的Order排序值进行排序,值越小,优先级越高,执行的越早
GlobalFilter
全局过滤器的排序值:通过实现Ordered
接口或者添加@Order
注解来指定Order值
GatewayFilter
局部过滤器排序值:由框架指定order值,默认按照声明的顺序从1开始递增
如果过滤器的Order值相同,优先级:GlobalFilter > defaultFilter > GatewayFilter
1995年,同源策略由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个同源策略。它是指:一个页面,只允许访问与页面同源的某些资源。
所谓的同源包含:同协议、同域名(同IP)、同端口。假如有一个资源是http://www.itcast.cn/a.html
,那么:
https://www.itcast.cn/user/1
:不同源,因为协议不同
http://itcast.cn/user/1
:不同源,因为域名不同
http://www.itcast.cn:81/user/1
:不同源,因为端口不同
http://www.itcast.cn/user/1
:同源,因为同协议、同域名、同端口
被同源限制的资源有:
Cookie、LocalStorage 和 IndexDB:只能同源的页面进行访问
DOM和js对象 :只能同源的页面才能获得
AJAX :只能向同源的资源发Ajax请求
如果http://localhost:80/index.html
页面上要发起一个Ajax请求,请求的目的地是:http://localhost:8080/user/1
,这就是一个跨域Ajax请求了。
受限于浏览器的同源策略,这次请求是必定发送不成功的
但是目前流行的开发方式是前后端分离,即前端资源使用nginx部署到单独的服务器上,服务端项目部署到其它服务器上,这样的情况下,跨域请求就不可避免了。我们该如何规避浏览器的同源策略,允许浏览器跨域发送Ajax请求呢?
把资料里的index.html
放到Nginx的html目录里,nginx端口使用80
2. 启动Nginx,打开浏览器访问http://localhost/index.html
点击页面上的按钮,发送一次Ajax请求。使用抓包工具可以看到报错
只需要在网关里添加如下配置即可:
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: "*" # 允许哪些网站的跨域请求。 *表示任意网站
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
搭建服务网关,前提:网关也是一个微服务,也需要整合注册中心Nacos
添加依赖:nacos-discovery注册中心相关的依赖,网关的依赖
添加配置:
本身的访问端口
整合注册中心需要的配置:应用服务名,注册中心地址
网关本身的路由等配置
修改引导类:整合注册中心,所以需要添加@EnableDiscoveryClient
网关的过滤器分为:
GatewayFilter:网关Gateway本身提供了大量的GatewayFilter,我们可以直接配置使用,不用写Java代码
GlobalFilter:通常是用于自定义过滤器的
GatewayFilter的使用:
网关Gateway提供了大概30个GatewayFilter,我们可以直接使用
只要修改配置文件:
给每个路由增加过滤器:在路由配置里增加
给所有路由增加过滤器
spring:
cloud:
gateway:
default-filters:
- 过滤器名称=配置的参数值
- 过滤器名称=配置的参数值
routes:
- id: 路由id
uri: 路由目的地lb://目标服务名
predicates:
- Path=/xxx/**
filters:
- 过滤器名称=配置的参数值
- 过滤器名称=配置的参数值
GlobalFilter的创建:
创建类,实现Ordered和GlobalFilter接口
重写Ordered接口的getOrder方法,返回int值。值越小,优先级越高
重写GlobalFilter接口的filter方法,在方法里编写过滤逻辑
exchange.getRequest():获取请求信息对象
request.getURI().getPath():获取本次请求的资源路径
request.getHeaders().getFirst(String name):获取本次请求的请求头的值
exchange.getResponse():获取响应信息对象
在方法里要放行:return chain.filter(exchange);
在方法里不放行:
response.setStatus(HttpStatus.枚举项)
return response.setComplete();
在类上添加@Component
跨域问题:
涉及浏览器的同源策略:多个资源同协议、同域名(ip)、同端口 是同源的;任意一个不同,就是跨域
浏览器默认情况,访问跨域资源,会出现跨域问题CORS。解决方式:
在网关的配置文件里,添加跨域的配置
首先我们来看一下,微服务架构下关于配置文件的一些问题:
配置文件相对分散,不利于维护。
在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散在各个微服务中,不好统一配置和管理。
配置文件不方便区分环境。
微服务项目可能会有多个环境,例如:测试环境、预发布环境、生产环境。每一个环境所使用的配置理论上都是不同的,一旦需要修改,就需要我们去各个微服务下手动维护,这比较困难。
配置文件无法实时更新。
我们修改了配置文件之后,必须重新启动微服务才能使配置生效,这对一个正在运行的项目来说是非常不友好的。
基于上面这些问题,我们就需要配置中心的加入来解决这些问题。
可以统一管理配置文件
配置文件可以区分环境。给每个微服务设置多个配置文件,在启动微服务时,可以设置拉取指定的配置文件
比如:一个微服务,在配置中心提供多个配置文件。一个开发环境的配置文件,一个测试环境的配置文件,一个生产环境的配置文件
然后:在启动微服务时,可以指定要拉取哪个环境的配置文件
配置文件可以实时更新,不需要重启微服务
配置中心的思路是:
首先把项目中各种配置全部都放到一个集中的地方进行统一管理。
当各个服务需要获取配置的时候,就来配置中心的接口拉取自己的配置。
当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。
SpringCloudAlibaba Nacos本身就可以管理配置文件。
我们只要把配置文件放到nacos上,微服务就可以从nacos里拉取配置、实现配置的动态刷新了
SpringCloud本身提供了一种配置中心:SpringCloudConfig,使用相对麻烦
SpringCloudAlibaba提供了配置中心:Nacos,使用更简单
把配置文件托管到配置中心Nacos
修改微服务
添加依赖坐标nacos-config
删除application.yaml,创建bootstrap.yaml, 在bootstrap.yaml里,配置 从哪拉取配置文件
启动微服务,测试功能是否正常
1) 新增配置
2) 设置配置信息
注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。
基本不会变更的一些配置还是保存在微服务本地比较好。
微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。
但如果尚未读取application.yml,又如何得知nacos地址呢?
因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:
1) 添加依赖坐标
在用户服务的pom.xml中,添加nacos配置中心的坐标
注意:一旦引入了nacos配置中心的坐标,就必须有bootstrap.yaml
配置文件
<!--nacos配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2) 添加bootstrap.yaml
先把用户服务的application.yaml删除掉(配置文件的所有内容,都已经托管到nacos上了)
再给用户服务创建bootstrap.yaml
。注意:名称必须是bootstrap
spring:
cloud:
nacos:
config:
server-addr: localhost:8848 #配置中心nacos的地址
prefix: user-service # 要加载的配置文件,{application}部分
file-extension: yaml # 要加载的配置文件,{extension}后缀名部分
profiles:
active: dev # 激活的环境名称,配置文件中{profile}部分
3) 测试
打开浏览器,所有功能都可以正常使用,说明已经成功拉取到了配置参数
打开浏览器,访问 http://localhost:8080/user/company,可以看到公司名称,也说明成功拉取到了配置参数
Nacos支持动态刷新配置,也叫热更新:只要在nacos里修改了配置,微服务不需要重启,就会自动拉取最新的配置参数。
有两种实现方案:
方案1:使用@Value获取参数值,并在bean对象上添加注解@RefreshScope
方案2【推荐】:使用@ConfigurationProperties封装配置参数,会自动拉取最新配置
1) 方案1: @Value和@RefreshScope
在UserController里读取参数
在UserController里使用@Value读取了company.name配置参数
在UserController上添加注解@RefreshScope
- @RestController
- @RefreshScope
- @RequestMapping("/user")
- public class UserController {
- @Autowired
- private UserService userService;
-
- @Value("${company.name}")
- private String companyName;
-
- @GetMapping("/{id}")
- public User findById(@PathVariable("id") Long id) {
- return userService.findById(id);
- }
-
- @GetMapping("/company")
- public String company(){
- return companyName;
- }
- }
打开浏览器访问 http://localhost:7070/user/company,先查看一下company.name原始值
在nacos里修改company.name
的值
把company.name
的值修改为“传智教育”
打开浏览器刷新 http://localhost:7070/user/company,可以看到已经得到最新的值了
2) 方案2: @ConfigurationProperties
创建Company类,用于封装配置参数
- @Data
- @ConfigurationProperties(prefix = "company")
- public class Company {
- private String name;
- }
2. 在引导类上添加 @EnableConfigurationProperties
,启动Company类
- @EnableConfigurationProperties(Company.class)
- @EnableDiscoveryClient
- @SpringBootApplication
- @MapperScan("com.itheima.user.mapper")
- public class UserApplication {
- public static void main(String[] args) {
- SpringApplication.run(UserApplication.class, args);
- }
- }
3. 修改UserController
去掉@RefreshScope
注解
注入Company
对象
添加方法,浏览器访问时,把Company对象返回给客户端 显示到页面上
- @RestController
- @RequestMapping("/user")
- public class UserController {
- @Autowired
- private UserService userService;
-
- @Value("${company.name}")
- private String companyName;
-
- @Autowired
- private Company company;
-
- @GetMapping("/{id}")
- public User findById(@PathVariable("id") Long id) {
- return userService.findById(id);
- }
-
- @GetMapping("/company")
- public String companyStr(){
- return companyName;
- }
-
- @GetMapping("/companyObj")
- public Company companyObj(){
- return company;
- }
- }
4. 功能测试
先打开浏览器访问 http://localhost:8080/user/companyObj,查看原始参数值
在Nacos里修改company.name的值
再打开浏览器,直接刷新页面,可以看到参数值已经变成最新的了
在实际开发中,一个服务通常有多个配置文件。在不同环境中,只要激活对应的配置文件即可。例如:
user-service-dev.yaml:作为用户服务的开发环境配置
user-service-test.yaml:作为用户服务的测试环境配置
user-service-prod.yaml:作为用户服务的生产环境配置
但是这多个环境的配置文件中,可能有大部分配置参数都是完全相同的,如果在每个文件里都复制一份的话,修改维护起来会比较麻烦,这时候可以使用共享的配置文件的:
再创建一个user-service.yaml:不带环境标识的配置文件,会被所有环境共享
在Nacos配置中心里已经有了user-service-dev.yaml
。
我们再增加一个配置文件 user-service-test.yaml
,内容如下:
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///cloud_user?useSSL=false
username: root
password: root
logging:
level:
com.itheima.user: debug
pattern:
dateformat: HH:mm:ss.SSS
company:
name: 传智教育-test
在idea里创建dev和test的启动链接,创建方式如下:
dev环境,激活dev
test环境,激活test
在Nacos的配置中心里,创建一个用户服务的共享配置文件
修改UserController,读取共享配置项aaa
的值
- @RestController
- @RequestMapping("/user")
- public class UserController {
- ...略...;
-
- @Value("${aaa}")
- private String shareValue;
-
- @GetMapping("/share")
- public String share(){
- return shareValue;
- }
- }
同时启动两个环境的用户服务,
打开浏览器访问:
开发环境dev:http://localhost:8080/user/share,页面上可以看到共享的配置项“共享配置值”
测试环境test:http://localhost:8081/user/share,页面上可以看到共享的配置项“共享配置值”
实际开发中,通常是:
在项目内使用bootstrap.yaml中配置 不变的、不需要修改的参数
在配置中心里配置可能变化的、可能修改的参数
当nacos、服务本地同时出现相同属性时,优先级高的生效。优先级从高到低如下:
Nacos里的服务名-{profile}.yaml
:Nacos里激活的某一环境的配置文件
Nacos里的服务名.yaml
:Nacos里的共享配置文件
本地的application-{profile}.yaml
本地的application.yaml
配置中心的作用:
可以统一管理所有微服务的配置文件,方便维护
可以实现参数的热更新
配置中心常见的技术:SpringCloudConfig、Nacos
配置中心Nacos的用法:统一管理配置文件
把配置参数托管到Nacos里。创建配置文件,DataId是 服务名-环境标识.后缀名
微服务要从配置中心拉取配置文件
添加依赖坐标:nacos-config
修改配置文件:不需要application.yaml了,而是需要添加bootstrap.yaml文件。在bootstrap.yaml文件里:
spring.cloud.nacos.config.server-addr,值是配置中心的地址
spring.cloud.nacos.config.prefix,值是服务名
spring.profiles.active,值是环境标识
spring.cloud.nacos.config.file-extension,值是后缀名
配置中心Nacos的用法:实现参数热更新
方式1:微服务里使用@Value读取参数值,并在bean对象加上@RefreshScope
方式2:微服务里使用@ConfigurationProperties读取参数值,它本身就具备参数热更新的能力
参数热更新的效果:
当在配置中心修改了参数值,微服务不需要重启,也能立即得到最新的参数值
多思考,多出声读
远程调用:微服务之间需要互相调用,进行数据的交互==>Feign
使用入门
添加依赖:添加OpenFeign的依赖坐标
创建接口:UserClient接口
接口上加@FeignClient("目标服务名")
接口里方法上加@GetMapping, @PostMapping,……
接口里方法形参加:
@RequestParam:要把方法参数 作为表单参数 发出去
@RequestBody:要把方法参数 作为请求体json发出去
@PathVariable:要把参数 作为路径变量发出去
方法返回值是:期望得到的结果。Feign会帮我们把响应结果转换成 期望的类型
修改引导类:添加@EnableFeignClients("扫描的包名")
打印日志:
要修改配置文件,设置全局的日志级别为 debug
要修改配置文件,设置Feign的日志级别。共有4个:NONE,BASIC,HEADERS,FULL
优化性能:
问题:Feign本身底层使用URLConnection,没有连接池。每次操作要创建连接,之后要关闭销毁连接对象
解决:把Feign底层换成httpclient
添加httpclient的依赖坐标
修改配置文件,启用httpclient
feign:
httpclient:
enabled: true
max-connections: 最大连接数
max-connections-per-route: 每个资源路径的最大连接数
负载均衡:远程调用时,如果目标服务是集群,就需要实现负载均衡==>Ribbon
负载均衡的两种方式
提供者一方的负载均衡:Nginx。适用于整个服务的最前沿,直接面向客户端的请求
消费者一方的负载均衡:Ribbon。适用于微服务之间互相调用时,实现负载均衡
Ribbon的使用:不需要额外添加依赖、不需要做任何配置,默认就有负载均衡效果。
Ribbon的负载均衡策略,常见的:RandomRule,RoundRobinRule,NacosRule
修改负载均衡策略:
方式1,修改消费者一方的配置文件,设置负载均衡策略
方式2,在消费者一方,使用@Bean把负载均衡策略对象放到IoC容器里
饥饿加载
如果不开启饥饿加载,当第一次访问时,Ribbon才会从注册中心拉取服务地址列表。第一次访问通常比较慢
可以开启饥饿加载,作用是微服务一启动,Ribbon就立即从注册中心里拉取服务地址列表。
服务保护:远程调用时,必须要防止目标服务出错导致的级联问题==>Hystrix或Sentinel
注册中心:远程调用时,需要实时获取到目标服务的地址信息==>Nacos
注册中心解决了服务治理的问题
使用入门:
安装开启Nacos。Nacos的管理界面地址 http://localhost:8848/nacos, 帐号nacos,密码nacos
微服务整合Nacos
添加依赖:锁定SpringCloudAlibaba的依赖版本,再添加nacos-discovery坐标
修改配置:
应用服务名,使用spring.application.name配置
注册中心地址,使用spring.cloud.nacos.discovery.server-addr配置
修改引导类:添加@EnableDiscoveryClient
Nacos分级存储模型:
可以把一个服务下边划分多个集群,每个集群下边有多个服务实例。同一集群的实例,部署到同一机房
灾备(异地容灾),同集群优先访问
修改配置文件,使用spring.cloud.nacos.discovery.cluster-name设置当前微服务所属集群名。实现了分级
修改配置文件,使用NacosRule负载均衡策略,实现了同集群优先访问
Nacos的环境隔离:
效果:
不同环境的服务实例之间,是绝对隔离的,不可能互相发现、不可能互相访问;
同环境的服务可以互相发现调用
做法:
在Nacos里创建新的命名空间。设置id、名称、描述
修改配置文件,设置微服务所属的namespace。
使用参数spring.cloud.nacos.discovery.namespace=命名空间的id
服务网关:所有微服务,要有一个统一的访问入口==>SpringCloudGateway
网关的作用:
路由:把客户端的请求,根据判断条件分发到目标微服务上
过滤:拦截客户端的请求进行过滤处理,再决定是否放行
使用入门:创建Module,然后
添加依赖:nacos-discovery,gateway的坐标
修改配置:nacos注册中心的配置,网关路由的配置,跨域的配置
修改引导类:@EnableDiscoveryClient启动服务发现
过滤器GatewayFilter:网关Gateway提供了大量的GatewayFilter,我们可以直接配置使用,不需要写Java代码
spring:
cloud:
gateway:
default-filters:
- 过滤器名=参数
- 过滤器名=参数
routes:
- id: 路由唯一标识
uri: 路由的目的地,写法是 lb://目标服务名
predicates:
- Path=/xx/**
filters:
- 过滤器名=参数
- 过滤器名=参数
globalcors: #全局跨域的配置
过滤器GlobalFilter:通常用于自定义过滤器
创建类,实现GlobalFilter、Ordered接口
重写GlobalFilter接口的filter方法,在方法里编写过滤逻辑。方法里有一些常用的API
exchange.getRequest()
exchange.getResponse()
request.getURI().getPath()
request.getHeaders().getFirst("请求头名称")
如果要放行:return chain.filter(exchange);
如果不放行:
response.setStatus(HttpStatus.UNAUTHORIZED)
return response.setComplete();
重写Ordered接口的getOrder方法,在方法里返回int值。值越小,优先级越高
在类上添加@Component
配置中心:所有微服务的配置文件太散乱、不能热更新==>Nacos
作用:
可以统一管理所有微服务的配置文件
可以实现配置参数的热更新
用法:
把微服务的配置参数,托管到Nacos里。
在Nacos里创建配置,DataId是 应用服务名-环境标识.后缀名
把配置参数添加进去。通常把一些 可能会改变的、修改的参数,放到配置中心
微服务整合配置中心,从配置中心里拉取配置文件
添加依赖:nacos-config
创建配置:不要application.yaml,要创建bootstrap.yaml。把不变的配置放到这个配置文件里
server:
port: 8080
spring:
application:
name: 应用服务名
cloud:
nacos:
config:
server-addr: localhost:8848 #Nacos注册中心的地址
prefix: 应用服务名
file-extension: 后缀名
profiles:
active: 环境标识
参数热更新:
方式1:使用@Value读取参数值,并在bean对象上加@RefreshScope
方式2:使用@ConfigurationProperties读取参数值,它本身就具备参数热更新的能力
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。