网关、配置管理(热更新)、动态路由

网关、配置管理(热更新)、动态路由

1.什么是网关

就是网络的关口,负责请求的路由、转发、身份校验
以前我们单体项目前端要向后端发起请求,很简单,直接发就行了
file-20251124233747666.jpeg
但是拆分成微服务之后就麻烦了:所以有了网关

就比如找人,你得有户口簿、打野才会给你开门,如果你不知道你要找的人在哪,大爷会带你去,这就叫做网关。
file-20251124233747666.png
下面给你看更加专业的图
file-20251124233747671.png
网关始终请求8080,这毋庸置疑,然后到了网关,他去判断前端请求的业务需要的是哪个微服务处理这个请求

  • 那么这个判断的过程就是请求的路由
  • 接下来网关就会将躯体的请求转发到具体的微服务了,这就是路由转发
  • 当然了,部署上线 后的服务里面可能有多个实例,形成集群,所以要在多个实例之间运用负载均衡
  • 所有的微服务信息就会服务注册注册中心,它里面就会有多有的微服务信息
  • 当然网关也是一个微服务,启动之后也可以去注册中心拉取所有的服务地址
    由于前端只知道网关地址,因此整个微服务对前端来讲,是隐藏起来的黑盒,这样在前端看来现在的后端跟原来的单体架构没什么区别,这时候微服务就不用向外界暴露自己的端口地址了,也是对后端的一种保护。
    file-20251124233747673.png

2. 配置路由规则

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由目标微服务,lb代表负载均衡
predicates: # 路由断言,判断请求是否符合规则,符合则路由到目标
- Path=/items/** # 以请求路径判断,以/items开头则符合
- id: xx
uri: lb://xx-service
predicates:
- Path=/xx/**

3. 网关登录校验

执行顺序

file-20251124233747688.png
网络请求JWT校验要在NettyRoutingFilter之前(PRE确保在路由转发之前进行校验)

自定义过滤器 GloablFilter

直接实现GloablFilter接口即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component  
public class MyGlobalFilter implements GlobalFilter, Ordered {

/*、
* 拦截所有请求
* exchange: 请求上下文, 里面有request和response等信息, 可以获取请求参数、响应结果等信息
* chain: 过滤器链,可以调用下一个过滤器
* 返回值: Mono<Void>: 表示异步处理,返回Mono.empty()表示处理完成
* 这里返回chain.filter(exchange)表示继续处理下一个过滤器,实现了共享请求参数和响应结果
*/ @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// TODO 模拟登录校验逻辑
HttpHeaders headers = exchange.getRequest().getHeaders();
System.out.println("headers = " + headers);
//放心
return chain.filter(exchange);
}

/*‘
* 优先级,数字越小越优先
* 实现了Ordered接口,表示优先级
*/ @Override
public int getOrder() { //优先级,数字越小越优先,Ordered.HIGHEST_PRECEDENCE表示最高优先级
return Ordered.HIGHEST_PRECEDENCE;
}
}

[!NOTE] PS:从网关到微服务的用户传递
Spring Boot自动装配它不仅仅是面试的时候能用到,在实际的开发中也有着重要作用,就好比如,想让每个微服务都获取到用户信息,可以在Common下定义一个SpringMVC拦截器,要想使拦截器生效,需要配置spring.factories的扫描包,然后,最重要的是在配置类里面加上@ConditionalOnClass(DispatcherServlet.class)注解,这样才能实现拦截器。

4. 在微服务之间传递用户信息

OpenFeign传递用户

file-20251124233747694.png
file-20251124233747718.png

5.配置管理

共享配置

  1. 先在nacos添加配置
  2. 微服务拉去共享配置
    具体流程图
    file-20251124233747720.png
1
2
3
4
5
6
7
8
9
10
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
  • 新建bootstrap.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
application:
name: cart-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataId: shared-jdbc.yaml # 共享mybatis配置
- dataId: shared-log.yaml # 共享日志配置
- dataId: shared-swagger.yaml # 共享Swagger配置(原注释“共享日志配置”应为笔误)

配置热更新

当修改配置文件时,微服务无需重启即可使配置生效。

  1. 前提条件:nacos中要有一个与微服务名有关的配置文件
1
2
3
4
[spring.application.name]-[spring.active.profile]-[file-extension]
// 微服务名称 项目profile(可选) 文件名后缀

// 例:user-service-dev-yaml
  1. 微服务中要以特定方式读取需要热更新的配置属性
1
2
3
4
5
6
7
8
9
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

//方式一(推荐)
@Data
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
private int maxItems;
}

6.动态路由

  1. 监听Nacos配置变更的消息
  2. 当配置变更时,将最新的路由信息更新到网关路由表
    以下是在网关微服务下配置动态路由的案例

file-20251124233747720 1.png
JSON:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
[
{
"id": "item",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
}],
"filters": [],
"uri": "lb://item-service"
},
{
"id": "cart",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/carts/**"}
}],
"filters": [],
"uri": "lb://cart-service"
},
{
"id": "user",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
}],
"filters": [],
"uri": "lb://user-service"
},
{
"id": "trade",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/orders/**"}
}],
"filters": [],
"uri": "lb://trade-service"
},
{
"id": "pay",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/pay-orders/**"}
}],
"filters": [],
"uri": "lb://pay-service"
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Component  
@Slf4j
@RequiredArgsConstructor
public class DynamicRouteLoader {

private final NacosConfigManager nacosConfigManager;

private final RouteDefinitionWriter writer;

private final String dataId = "gateway-routes.json";

private final String group = "DEFAULT_GROUP";

private final Set<String> routeIds = new HashSet<>();

@PostConstruct // 在启动时执行
public void initRouteConfigListener() throws NacosException {
//1.项目启动时,先拉取一次配置,并且添加配置监听器
String configInfo = nacosConfigManager.getConfigService()
.getConfigAndSignListener(dataId, group, 5000, new Listener() {
@Override
public Executor getExecutor() {
return null;
}

@Override
public void receiveConfigInfo(String configInfo) {
//2. 监听到配置更新,需要去更新路由表
updateConfigInfo(configInfo);
}
});

// 3.第一次读取到的配置,也需要去更新到路由表
updateConfigInfo(configInfo);

}

public void updateConfigInfo(String configInfo){
log.debug("监听到路由配置信息:{}", configInfo);
// 1.解析配置信息,转为RouteDefinition
List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
//2.删除旧的路由表
for (String routeId : routeIds) {
writer.delete(Mono.just(routeId)).subscribe();
}
routeIds.clear();

// 3.更新路由表
for (RouteDefinition routeDefinition : routeDefinitions) {
//3.1更新路由表,注意一定要订阅,否则不会更新
writer.save(Mono.just(routeDefinition)).subscribe();
//3.2记录路由id,便于下一次更新时删除
routeIds.add(routeDefinition.getId());
}
}


}