目录
3.4.1 首先将nacos中的service-gateway中的json文件删除
观博有逻辑,导航来相助《Spring Cloud Alibaba系列-目录导航》
1. 网关的介绍
1.1 API网关的作用
以如上图所示:模拟客户端请求到微服务会出现如下问题:
- 客户端发送不同的请求到微服务,需要知道不同的微服务的服务地址
- 多次发送请求,会增加网络传输
- 鉴权等会分布在每个微服务中处理,重复操作
- 后端微服务中,可能采用不同的协议,客户端需要按协议适配
如上图所示:在客户端与微服务中增加网关层,作用如下:
- API网关可以把后端的多个服务进行整合,提供唯一的业务接口(请求转发),客户端只需要调用这个接口即可完成数据获取与展示。
- 针对所有的请求进行统一鉴权,限流,熔断,日志。
- 协议转化。针对后端不同的协议,在网关层统一处理后以http对外提供服务。
- 统一错误码处理
- 请求转发,并且可以基于网关实现内、外网隔离。
2. gateway实现路由
前提条件:以前面章节中的生产者与消费者两个微服务为基础微服务创建网关服务
本次创建:创建网关服务
实现的目标:请求使用网关服务讲请求转发到生产者与消费者中
2.1 创建网关服务
在service-cloud父项目中,创建一个名字为service-gateway的模块,如果不会创建参照之前博客
在service-gateway的application.yml中增加如下内容
server:
#设置服务端口
port: 8090
spring:
#设置服务名称
application:
name: service-gateway
#设置nacos注册地址
cloud:
nacos:
discovery:
server-addr: localhost:8848
#设置网关配置
gateway:
routes:
# 添加consumer-8001服务路由
- id: consumer-8001
# 支持普通url和lb://注册中心服务名称
uri: lb://consumer-8001/
#路由条件,根据匹配结果进行路由
predicates:
- Path=/echo/**
# 添加provider-7001服务路由
- id: provider-7001
uri: lb://provider-7001/
predicates:
- Path=/echop/**
创建网关服务(service-gateway)的启动类,如下图:
在provider-7001服务中增加接口如下图:
2.2 测试网关路由
前提条件:启动nacos注册中心,启动消费者(consumer-8001),服务者(provider-7001),网关服务(service-gateway)
启动后再nacos页面如下图:证明网关服务已经注册到nacos中
请求测试:
分别请求 http://localhost:8090/echop/1111(路由到provider-7001) http://localhost:8090/echo/feign/1111(路由到consumer-8001) 查看请求结果
请求的端口为8090,因为是使用网关进行请求的转发,因此端口为网关服务的端口。
3. 动态路由
Spring Cloud Gateway本身不支持直接从Nacos动态加载路由配置表,需要自己编写监听器监听配置变化并刷新路由表。
3.1 增加Nacos监听
增加NacosDynamicRouteService类如下
.
package com.run.ayena.config;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@Component
public class NacosDynamicRouteService implements ApplicationEventPublisherAware {
//设置路由配置的Nacos dataid
private String dataId = "service-gateway";
//设置路由配置的nacos group
private String group = "gateway";
//Nacos 配置中心地址
@Value("${spring.cloud.nacos.config.server-addr}")
private String serverAddr;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
private static final List<String> ROUTE_LIST = new ArrayList<>();
@PostConstruct //服务器加载Servlet的时候运行,并且只会被服务器执行一次
public void dynamicRouteByNacosListener() {
try {
//创建nacos服务
ConfigService configService = NacosFactory.createConfigService(serverAddr);
//根据条件获取nacos配置
configService.getConfig(dataId, group, 5000);
//为nacos配置添加监听
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
//清空路由信息
clearRoute();
try {
//路由信息JSON转为RouteDefinition对象
List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(configInfo, RouteDefinition.class);
//添加路由信息
for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
addRoute(routeDefinition);
}
//推送路由信息
publish();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Executor getExecutor() {
return null;
}
});
} catch (NacosException e) {
e.printStackTrace();
}
}
private void clearRoute() {
for(String id : ROUTE_LIST) {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}
ROUTE_LIST.clear();
}
private void addRoute(RouteDefinition definition) {
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
ROUTE_LIST.add(definition.getId());
} catch (Exception e) {
e.printStackTrace();
}
}
private void publish() {
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
3.2 修改application.yml
将已经配置的路由信息删除
server:
#设置服务端口
port: 8090
spring:
#设置服务名称
application:
name: service-gateway
#设置nacos注册地址
cloud:
nacos:
config:
server-addr: localhost:8848
discovery:
server-addr: localhost:8848
#设置网关配置
gateway:
routes:
3.3 在nacos创建动态路由配置
具体的json文件如下
[
{
"id":"consumer-8001",
"order": 0,
"uri":"lb://consumer-8001/",
"predicates":[
{
"name":"Path",
"args":{
"pattern":"/echo/**"
}
}
]
},
{
"id":"provider-7001",
"order": 1,
"uri":"lb://provider-7001/",
"predicates":[
{
"name":"Path",
"args":{
"pattern":"/echop/**"
}
}
]
}
]
3.4 测试动态路由
启动nacos ,启动生产者服务,消费者服务,网关服务
3.4.1 首先将nacos中的service-gateway中的json文件删除
3.4.2 测试请求状态
由于3.4.1 已经将路由信息删除,此时是没有路由的,因此请求会报错。请求与报错信息如下图:
通过网关请求生产者:
通过网关请求消费者:
3.4.3 在Nacos增加消费者路由测试
请求消费者请求结果如下(已配置路由):
请求生产者请求结果如下(未配置路由):
3.4.4 增加生产者服务路由并测试
测试由网关到生产者请求(已配置网关):
测试由网关到消费者请求(已配置网关):
4. 出现的问题与修改
问题描述:上述已经实现了路由的动态配置,但是出现的问题是,在生产者与消费者不动的情况下,如果重启网关服务,不会在nacos上加载路由信息,导致请求失败,因此对应修改如下:
在NacosDynamicRouteService类中,添加监听的方法中,手动将nacos中的路由信息添加到网关中
package com.run.ayena.config;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@Component
public class NacosDynamicRouteService implements ApplicationEventPublisherAware {
//设置路由配置的Nacos dataid
private String dataId = "service-gateway";
//设置路由配置的nacos group
private String group = "gateway";
//Nacos 配置中心地址
@Value("${spring.cloud.nacos.config.server-addr}")
private String serverAddr;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
private static final List<String> ROUTE_LIST = new ArrayList<>();
@PostConstruct //服务器加载Servlet的时候运行,并且只会被服务器执行一次
public void dynamicRouteByNacosListener() {
try {
//创建nacos服务
ConfigService configService = NacosFactory.createConfigService(serverAddr);
//根据条件获取nacos配置
String config = configService.getConfig(dataId, group, 5000);
stringToRouteAndAddRoure(config);
//为nacos配置添加监听
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
//清空路由信息
clearRoute();
try {
stringToRouteAndAddRoure(configInfo);
//推送路由信息
publish();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Executor getExecutor() {
return null;
}
});
} catch (NacosException e) {
e.printStackTrace();
}
}
private void stringToRouteAndAddRoure(String configInfo){
//路由信息JSON转为RouteDefinition对象
List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(configInfo, RouteDefinition.class);
//添加路由信息
for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
addRoute(routeDefinition);
}
}
private void clearRoute() {
for(String id : ROUTE_LIST) {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}
ROUTE_LIST.clear();
}
private void addRoute(RouteDefinition definition) {
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
ROUTE_LIST.add(definition.getId());
} catch (Exception e) {
e.printStackTrace();
}
}
private void publish() {
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}