一、微服务架构
单体架构:将业务的所有功能集中在一个项
目中开发,打成一个包部署
分布式架构:根据业务功能对系统做拆分,
每个业务功能模块作为独立项目
开发,称为一个服务
微服务架构:
特征:
单一职责:微服务拆分粒度更小,每一个服
务都对应唯一的业务能力,做到
单一职责
自治:团队独立、技术独立、数据独立,独
立部署和交付
面向服务:服务提供统一标准的接口,与语
言和技术无关
隔离性强:服务调用做好隔离、容错、降级,
避免出现级联问题
二、 Spring Cloud
① Spring Cloud 是目前国内使用最广泛的微
服务框架
② Spring Cloud 集成了各种微服务功能组件,
并基于 SpringBoot 实现了这些组件的自
动装配,从而提供了良好的开箱即用体验
③ Spring Cloud 底层是依赖于 SpringBoot
的,并且有版本的兼容关系
2. 五大组件
Eureka:注册中心
Zuul:服务网关
Ribbon:负载均衡
Feign:服务调用
Hystix:熔断器
三、服务拆分和远程调用
任何分布式架构都离不开服务的拆分,微
服务也是一样
1. 服务拆分规则
① 不同微服务,不要重复开发相同业务
② 微服务数据独立,不要访问其它微服务
的数据库
③ 微服务可以将自己的业务暴露为接口,
供其它微服务调用
2. 提供者与消费者
在服务调用关系中,会有两个不同的角色:
服务提供者 (user-service):一次业务中,
被其它微服务调用的服务(提供接口给其它
微服务)
服务消费者 (order-service):一次业务中,
调用其它微服务的服务 (调用其它微服务提
供的接口)
其中,服务提供者与服务消费者的角色并不是
绝对的,而是相对于业务而言
四、Eureka 注册中心
1. Eureka 的作用
在 Eureka 架构中,微服务角色有两类:
EurekaServer:服务端,注册中心
① 记录服务信息
② 心跳监控
EurekaClient:客户端
① Provider:服务提供者
注册自己的信息到 EurekaServer
每隔30秒向 EurekaServer 发送心跳
② consumer:服务消费者
根据服务名称从 EurekaServer 拉取
服务列表
基于服务列表做负载均衡,选中一个
微服务后发起远程调用
2. 搭建 EurekaServer
① 创建项目,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
② 编写启动类,添加 @EnableEurekaServer 注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
③ 编写一个 application.yml 文件
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
④ 启动服务
启动微服务,然后在浏览器访问:http://127.0.0.1:10086
3. 服务注册
将 user-service 注册到 eureka-server 中去
① 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
② 配置文件
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
③ 启动多个 user-service 实例
4. 服务发现
将 order-service 的逻辑修改:向 eureka-server
拉取 user-service 的信息,实现服务发现
① 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
② 配置文件
spring:
application:
name: orderservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
③ 服务拉取和负载均衡
在 order-service 的 OrderApplication
中,给 RestTemplate 这个 Bean 添加
一个 @LoadBalanced 注解
修改 order-service 服务的 OrderService
类中的 queryOrderById 方法。修改访问
的 url 路径,用服务名代替 ip 、端口:
五、Ribbon 负载均衡
1. 负载均衡原理
SpringCloudRibbon 的底层采用了一个
拦截器,拦截了 RestTemplate 发出的
请求,对地址做了修改
2. 负载均衡策略
负载均衡的规则都定义在 IRule 接口中,
而 IRule 有很多不同的实现类:
不同规则的含义如下:
其中默认的实现就是 ZoneAvoidanceRule,
是一种轮询方案
(2) 自定义负载均衡策略
通过定义IRule实现可以修改负载均衡规则,
但一般用默认的负载均衡规则,不做修改
有两种方式:
① 代码方式
在 order-service 中的 OrderApplication 类中,
定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
② 配置文件方式
在 order-service 的 application.yml 文件中,添
加新的配置也可以修改规则:
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
3. 饥饿加载
Ribbon 默认是采用懒加载,即第一次访问时
才会去创建 LoadBalanceClient,请求时间会
很长
而饥饿加载则会在项目启动时创建,降低第一
次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true
clients: userservice
六、Nacos 注册中心
Nacos 是阿里巴巴的产品,现在是 SpringCloud
中的一个组件,相比 Eureka 功能更加丰富,在
国内受欢迎程度较高
Nacos 是 SpringCloudAlibaba 的组件,而
SpringCloudAlibaba 也遵循 SpringCloud 中定义
的服务注册、服务发现规范。因此使用 Nacos 和
使用 Eureka 对于微服务来说,并没有太大区别
主要差异在于:
① 依赖不同
② 服务地址不同
2. 服务注册到 Nacos
① 引入依赖
在父工程的 pom 文件中引入 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>
然后在 user-service 和 order-service 中的 pom
文件中引入 nacos-discovery 依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
注意:不要忘了注释掉 eureka 的依赖
② 配置 nacos 地址
在 user-service 和 order-service 的 application.yml
中添加 nacos 地址:
spring:
cloud:
nacos:
server-addr: localhost:8848
注意:不要忘了注释掉 eureka 的地址
③ 重启
3. 服务分级存储模型
一级是服务
二级是集群
三级是实例
一个服务可以有多个实例,例如我们的
user-service,可以有:
127.0.0.1:8081
127.0.0.1:8082
127.0.0.1:8083
假如这些实例分布于全国各地的不同机房,
例如:
127.0.0.1:8081,在上海机房
127.0.0.1:8082,在上海机房
127.0.0.1:8083,在杭州机房
Nacos 就将同一机房内的实例划分为一个集群
也就是说,user-service 是服务,一个服务可
以包含多个集群,如杭州、上海,每个集群下
可以有多个实例,形成分级模型,如图:
微服务互相访问时,应该尽可能访问同集
群实例,因为本地访问速度更快,当本集
群内不可用时,才访问其它集群
(1) 给 user-service 配置集群
① 修改 user-service 的 application.yml 文件,
添加集群配置:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
② 重启两个 user-service 实例后,我们可以在
nacos 控制台看到下面结果:
③ 我们再次复制一个user-service启动配置,添加属性:
-Dserver.port=8083
-Dspring.cloud.nacos.discovery.cluster-name=SH
④ 启动 UserApplication3 后再次查看 nacos 控制台:
(2) 同集群优先的负载均衡
默认的 ZoneAvoidanceRule 并不能实现
根据同集群优先来实现负载均衡
因此 Nacos 中提供了一个 NacosRule 的
实现,可以优先从同集群中挑选实例
① 给 order-service 配置集群信息
修改 order-service 的 application.yml 文件,
添加集群配置:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
② 修改负载均衡规则
修改 order-service 的 application.yml 文件,
修改负载均衡规则:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
4. 权重配置
实际部署中会出现这样的场景:
服务器设备性能有差异,部分实例所在
机器性能较好,另一些较差,我们希望
性能好的机器承担更多的用户请求。
但默认情况下 NacosRule 是同集群内
随机挑选,不会考虑机器的性能问题
因此,Nacos提供了权重配置来控制访
问频率,权重越大则访问频率越高。
在 nacos 控制台,找到 user-service 的实
例列表,点击编辑,即可修改权重:
注:如果权重修改为 0,则该实例永远不会被访问
5. 环境隔离
Nacos 提供了 namespace 来实现环境隔离功能
nacos 中可以有多个 namespace,namespace 下
可以有 group、service 等,不同 namespace 之间
相互隔离
例如不同 namespace 的服务互相不可见
(1) 创建 namespace
默认情况下,所有 service、data、group 都在
同一个 namespace,名为 public:
① 我们可以点击页面新增按钮,添加一个namespace:
② 然后,填写表单:
③ 就能在页面看到一个新的 namespace:
(2) 给微服务配置 namespace
① 配置 namespace 只能通过修改配置来实现
例如,修改 order-service 的 application.yml
文件:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID
② 重启 order-service 后,访问控制台,可以
看到下面的结果:
③ 此时访问 order-service,因为 namespace
不同,会导致找不到 userservice,控制台
会报错:
6. Nacos 和 Eureka 的区别
(1) Nacos 的服务实例分为两种 l 类型:
临时实例:如果实例宕机超过一定时间,
会从服务列表剔除,默认的类型
非临时实例:如果实例宕机,不会从服务
列表剔除,也可以叫永久实例
配置一个服务实例为永久实例:
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置为非临时实例
(2) Nacos 和 Eureka 整体结构类似,服务注册、
服务拉取、心跳等待,但是也存在一些差异:
(3) Nacos 与 eureka 的共同点
都支持服务注册和服务拉取
都支持服务提供者心跳方式做健康检测
(4) Nacos 与 Eureka 的区别
Nacos 支持服务端主动检测提供者状态:
临时实例采用心跳模式,
非临时实例采用主动检测模式
临时实例心跳不正常会被剔除,非临时
实例则不会被剔除
Nacos 支持服务列表变更的消息推送模
式,服务列表更新更及时
Nacos 集群默认采用AP方式,当集群中
存在非临时实例时,采用CP模式;
Eureka采用AP方式
七、Nacos 配置管理
1. 统一配置管理
当微服务部署的实例越来越多,达到数
十、数百时,逐个修改微服务配置就会
让人抓狂,而且很容易出错。我们需要
一种统一配置管理方案,可以集中管理
所有实例的配置
Nacos 一方面可以将配置集中管理,另一
方可以在配置变更时,及时通知微服务,
实现配置的热更新
(1) 在 nacos 中添加配置文件
然后在弹出的表单中,填写配置信息:
注意:项目的核心配置,需要热更新的配置
才有放到 nacos 管理的必要。基本不
会变更的一些配置还是保存在微服务
本地比较好
(2) 从微服务拉取配置
微服务要拉取 nacos 中管理的配置,并且与
本地的 application.yml 配置合并,才能完成
项目启动
① 引入 nacos-config 依赖
在 user-service 服务中,引入 nacos-config
的客户端依赖:
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
② 添加 bootstrap.yaml
在 user-service 中添加一个 bootstrap.yaml
文件,优先级高于application.yml,
内容如下:
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
在 user-service 中将 pattern.dateformat 这个
属性注入到 UserController 中做测试:
@RestController
@RequestMapping("/user")
public class UserController {
//注入nacos中的配置属性
@Value("${pattern.dateformat}")
private String dateformat;
//编写controller,通过日期格式化器来格式化现在时间并返回
@GetMapping("now")
public String now(){
return LocalDate.now().format(
DateTimeFormatter.ofPattern(dateformat, Locale.CHINA)
);
}
}
2. 配置热更新
最终目的,是修改 nacos 中的配置后,
微服务中无需重启即可让配置生效,
也就是配置热更新
要实现配置热更新,可以使用两种方式:
(1) 方式一
在 @Value 注入的变量所在类上添加注解
@RefreshScope:
(2) 方式二
使用 @ConfigurationProperties 注解代替
@Value 注解
在 user-service 服务中,添加一个类,读
取 patterrn.dateformat 属性:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
在 UserController 中使用这个类代替 @Value:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties patternProperties;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
}
注意: 不是所有的配置都适合放到配置中
心,维护起来比较麻烦 建议将一些
关键参数,需要运行时调整的参数
放到 nacos 配置中心,一般都是自
定义配置
3. 配置共享
其实微服务启动时,会去 nacos 读取多个
配置文件,
① [spring.application.name]-[spring.profiles.active].yaml,
例如:userservice-dev.yaml
② [spring.application.name].yaml,
例如:userservice.yaml
而 [spring.application.name].yaml 不包含环
境,因此可以被多个环境共享
(1) 添加一个环境共享配置
在 nacos 中添加一个 userservice.yaml 文件:
(2) 在 uer-service 中读取共享配置
① 在 user-service 服务中,修改 PatternProperties
类,读取新添加的属性:
② 在 user-service 服务中,修改 UserController,
添加一个方法:
(3) 运行两个 UserApplication,使用不同的
profile
修改 UserApplication2 这个启动项,改变其
profile 值:
UserApplication(8081) 使用的 profile 是
dev,UserApplication2(8082) 使用的
profile 是 test
启动 UserApplication 和 UserApplication2,
访问 http://localhost:8081/user/prop,
结果:
显然 dev 和 test 两个环境,都读取到了
envSharedValue 这个属性的值
(4) 配置共享的优先级
① 多环境配置共享
当 nacos、服务本地同时出现相同属性时,优先
级有高低之分:
[服务名]-[环境].yaml > [服务名].yaml > 本地配置
② 多服务配置共享
环境配置 >服务名.yaml > extension-config >
extension-configs > shared-configs > 本地配置
不同微服务之间可以共享配置文件,通过下面的
两种方式来指定:
方式一:
spring:
application:
name: userservice # 服务名称
profiles:
active: dev # 环境,
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 多微服务间共享的配置列表
- dataId: common.yaml # 要共享的配置文件id
方式二:
spring:
application:
name: userservice # 服务名称
profiles:
active: dev # 环境,
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
extends-configs: # 多微服务间共享的配置列表
- dataId: extend.yaml # 要共享的配置文件id
4. 搭建 Nacos 集群
(1) 集群结构图
(2) 搭建集群的基本步骤
① 搭建数据库,初始化数据库表结构
② 下载 nacos 安装包
③ 配置 nacos
④ 启动 nacos 集群
⑤ nginx 反向代理
1) 配置 nacos:
① 将配置文件重命名为 cluster.conf,添加内容
127.0.0.1.8845
127.0.0.1.8846
127.0.0.1.8847
② 打开注释,修改账号密码
③ 将 nacos 文件夹复制三份,分别命名
为:nacos1、nacos2、nacos3
然后分别修改三个文件夹中的
application.properties,
④ 分别启动三个 nacos 节点:startup.cmd
2) nginx 反向代理
① 修改conf/nginx.conf文件,配置如下:
② 启动 start nginx.exe
③ 在浏览器访问:http://localhost/nacos
④ 修改 java 代码中的 nacos 地址
3) 优化
① 实际部署时,需要给做反向代理的 nginx
服务器设置一个域名,这样后续如果有服
务器迁移 nacos 的客户端也无需更改配置.
② Nacos 的各个节点应该部署到多个不同服
务器,做好容灾和隔离
八、Feign 远程调用
以前利用 RestTemplate 发起远程调用的代码:
缺点:
① 代码可读性差,编程体验不统一
② 参数复杂 URL 难以维护
Feign 是一个声明式的 http 客户端,官方地址:
https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现 http 请求的发
送,解决上面提到的问题
1. Feign 替代 RestTemplate
(1) 引入依赖
在 order-service 服务的 pom 文件中引入 Feign
的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2) 添加注解
在 order-service 的启动类添加注解开启 Feign
的功能:
(3) 编写 Feign 的客户端
在 order-service 中新建一个接口,内容如下:
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
这个客户端主要是基于 SpringMVC 的注解来
声明远程调用的信息,比如:
服务名称:userservice
请求方式:GET
请求路径:/user/{id}
请求参数:Long id
返回值类型:User
这样,Feign 就可以帮助我们发送 http 请求,
无需自己使用 RestTemplate 来发送了
(4) 测试
修改 order-service 中的 OrderService 类中的
queryOrderById 方法,使用 Feign 客户端代
替 RestTemplate:
(5) 总结
使用 Feign 的步骤:
① 引入依赖
② 添加 @EnableFeignClients 注解
③ 编写 FeignClient 接口
④ 使用 FeignClient 中定义的方法代替
RestTemplate
2. 自定义配置
配置 Feign 日志有两种方式:
(1) 配置文件方式
① 局部生效
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
② 全局生效
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
日志的级别分为四种:
NONE:不记录任何日志信息,这是默认值
BASIC:仅记录请求的方法,URL以及响应
状态码和执行时间
HEADERS:在BASIC的基础上,额外记录
了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头
信息、请求体、元数据
(2) Java 代码方式
需要先声明一个Bean:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
如果要全局生效,将其放到启动类的
@EnableFeignClients 这个注解中:
@EnableFeignClients(defaultConfiguration =
DefaultFeignConfiguration .class)
如果是局部生效,则把它放到对应的@FeignClient这个注解中:
@FeignClient(value = "userservice", configuration
= DefaultFeignConfiguration .class)
3. Feign 使用优化
Feign 底层发起 http 请求,依赖于其它的框架
其底层客户端实现包括:
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
因此提高 Feign 的性能主要手段就是
① 使用连接池代替默认的 URLConnection
② 日志级别,最好用 basic 或 none
Feign 添加 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:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
4. 最佳实践
Feign 客户端:
UserController:
代码一样,优化方法有两种:
(1) 继承
给消费者的 FeignClient 和提供者的
controller 定义统一的父接口作为标准
缺陷:
① 不推荐这种方法,服务端和客户端共用
接口,会出现服务紧耦合
② 父接口参数列表中的映射不会被继承,
因此 Controller 中必须再次声明方法、
参数列表、注解
(2) 抽取
将 FeignClient 抽取为独立模块,并且把
接口有关的 POJO、默认的 Feign 配置都
放到这个模块中,提供给所有消费者使用
基于抽取的最佳实践实现方式:
1) 创建一个 module,命名为 feign-api,
然后引入 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 中的 UserClient、User、
DefaultFeignConfiguration 等类或接口
3) 在 order-service 中引入 feign-api 的依赖
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
4) 修改 order-service 中的所有与上述三个组
件有关的 import 部分,改成导入 feign-api
中的包
5) 重启测试
报错是因为 UserClient 现在在 cn.itcast.feign.clients
包下,而 order-service 的 @EnableFeignClients
注解是在 cn.itcast.order 包下,不在同一个包,
无法扫描到 UserClient
6) 解决扫描包问题
方式一:
指定 Feign 应该扫描的包:
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:
指定需要加载的 Client 接口:(精准指定)
@EnableFeignClients(clients = {UserClient.class})
九、Gateway 服务网关
1. 网关的功能特性
① 对用户请求做身份认证和权限校验
② 将用户请求路由到微服务,并实现
负载均衡
③ 对用户请求做限流
在SpringCloud中网关的实现包括两种:
① gateway
② zuul
① Zuul 是基于 Servlet 的实现,属于阻塞
式编程
② SpringCloudGateway 则是基于 Spring5
中提供的 WebFlux,属于响应式编程的
实现,具备更好的性能。
2. 搭建网关服务基本步骤
(1) 创建 SpringBoot 工程 gateway,引入网关
依赖和 nacos 服务发现依赖
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2) 编写启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
(3) 编写基础配置和路由规则
创建 application.yml 文件,内容如下:
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
(4) 启动网关服务进行测试
重启网关,访问 http://localhost:10010/user/1
时,符合 /user/** 规则,请求转发到 uri:
http://userservice/user/1,得到了结果:
(5) 网关路由的流程图
3. 路由断言工厂 (Route Predicate Factory)
路由配置包括:
路由 id:路由的唯一标示
路由目标 (uri):路由的目标地址,http
代表固定地址,lb 代表
根据服务名负载均衡
路由断言 (predicates):判断请求是否符合
要求,符合则转发到路由目的地
路由过滤器 (filters):对请求或响应做处理
在配置文件中写的断言规则只是字符串,
这些字符串会被 Predicate Factory 读取
并处理,转变为路由的判断条件
例如 Path=/user/** 是按照路径匹配,这个
规则是由 org.springframework.cloud.gateway.
handler.predicate.PathRoutePredicateFactory
类来处理的
像这样的断言工厂在 SpringCloudGateway
还有十几个
4. 过滤工厂 (GatewayFilter)
GatewayFilter 是网关中提供的一种过滤器,
可以对进入网关的请求和微服务返回的响应
做处理:
(1) 路由过滤器的种类
Spring 提供了 31 种不同的路由过滤器工厂。
例如:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
(2) 请求头过滤器
例如:给所有进入 userservice 的请求添加一
个请求头:
Truth=itcast is freaking awesome!
只需要修改 gateway 服务的 application.yml
文件,添加路由过滤即可:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
当前过滤器写在 userservice 路由下,因此仅
仅对访问 userservice 的请求有效
(3) 默认过滤器
如果要对所有的路由都生效,则可以将过滤器
工厂写到 default 下。格式如下:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, Itcast is freaking awesome!
5. 全局过滤器 (GlobalFilter)
(1) 全局过滤器的作用
全局过滤器与过滤器工厂都是处理一切进入
网关的请求和微服务响应
区别:
GatewayFilter 通过配置定义,处理逻辑是
固定的;
GlobalFilter 的逻辑需要自己写代码实现,
定义方式是实现 GlobalFilter 接口
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
(2) 自定义全局过滤器
1) 需求:定义全局过滤器,拦截请求,判断
请求的参数是否满足下面条件:
① 参数中是否有 authorization,
② authorization 参数值是否为 admin
如果同时满足则放行,否则拦截
2) 实现:
自定义类,实现GlobalFilter接口,添加
@Order 注解:
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.获取authorization参数
String auth = params.getFirst("authorization");
// 3.校验
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.拦截
// 4.1.禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
(3) 过滤器执行顺序
请求进入网关会碰到三类过滤器:
当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将三个合并到一个过滤器链
(集合) 中,排序后依次执行每个过滤器:
① 每一个过滤器都必须指定一个 int 类型的
order 值,order值越小,优先级越高,执
行顺序越靠前
② GlobalFilter 通过实现 Ordered 接口,或
者添加@Order注解来指定 order 值,由
我们自己指定
③ 路由过滤器和 defaultFilter 的 order 由
Spring 指定,默认是按照声明顺序从 1
递增
④ 当过滤器的 order 值一样时,会按照
defaultFilter > 路由过滤器 > GlobalFilter
的顺序执行
查看源码:
org.springframework.cloud.gateway.route.
RouteDefinitionRouteLocator#getFilters()
方法是先加载 defaultFilters,然后再加载
某个 route 的 filters,然后合并
org.springframework.cloud.gateway.handler.
FilteringWebHandler#handle() 方法会加载
全局过滤器,与前面的过滤器合并后根据
order 排序,组织过滤器链
6. 跨域问题
1. 定义
跨域:域名不一致就是跨域,主要包括:
域名不同: www.taobao.com 和 www.taobao.org
和 www.jd.com 和 miaosha.jd.com
域名相同,端口不同:localhost:8080 和
localhost:8081
跨域问题:浏览器禁止请求的发起者与服务端
发生跨域 ajax 请求,请求被浏览器
拦截的问题
解决方案:CORS
2. 解决方式
在 gateway 服务的 application.yml 文件中,
添加下面的配置:
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期