一、API 网关 (Gateway) 简介
1、什么是gateway
spring cloud gateway是一个服务器端的入口点,作为客户端和后端服务之间的中间层,负责请求路由、组合和协议转换。它充当系统的"前门",所有客户端请求都首先通过API网关,然后被路由到适当的后端服务。它是建立在 Spring Boot 2.x、 Spring WebFlux 和 Project Reactor之上,旨在为微服务架构提供简单、有效和统一的API路由管理方式
2、核心功能
- 请求路由:将客户端请求转发到相应的后端服务
- 协议转换:处理不同协议之间的转换(如HTTP到gRPC)
- 负载均衡:在多个服务实例之间分配请求
- 认证授权:验证请求的合法性,检查访问权限
- 限流熔断:控制请求速率,防止系统过载
- 缓存:缓存常用响应,减少后端压力
- 监控日志:记录请求指标和日志用于分析和监控
- 请求/响应转换:修改请求或响应内容
3、gateway在微服务中的优势
- 解耦客户端与服务端:客户端只需知道网关地址,无需了解内部服务结构
- 简化客户端:网关可以聚合多个服务请求,减少客户端调用次数
- 统一安全控制:集中处理认证、授权和安全策略
- 提高性能:通过缓存、压缩和协议优化提升响应速度
- 弹性设计:通过熔断、降级等机制提高系统可靠性
- 易于监控:集中收集所有API调用的指标和日志
4、gateway工作流程
客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是,过滤器可以在代理请求发送之前和之后运行逻辑。所有的
"pre"
(前)过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后,"post"
(后)过滤器逻辑被运行。
5、Zuul实现SpringCloud网关及不足之处
在SpringCloud中网关的实现包括两种gateway、zuul。Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。二者主要区别:
对比项 | Spring Cloud Gateway | Netflix Zuul |
---|---|---|
架构模型 | 基于 Reactor 和 Netty 的 异步非阻塞 模型 | 基于 Servlet 的 同步阻塞 模型 |
性能 | 更高吞吐量,支持 长连接(WebFlux) | 性能较低,受限于 Servlet 阻塞 IO |
依赖 | 需要 Spring WebFlux 和 Project Reactor | 依赖 Spring MVC 和 Servlet API |
功能扩展 | 支持 自定义过滤器、断言 和 全局过滤器 | 主要依赖 Groovy 脚本扩展过滤器 |
协议支持 | 支持 HTTP/2、WebSocket、gRPC | 仅支持 HTTP/1.x |
服务发现 | 原生支持 Eureka、Consul、Nacos | 需配合 Netflix Eureka 使用 |
负载均衡 | 集成 Ribbon 或 Spring Cloud LoadBalancer | 依赖 Ribbon |
熔断限流 | 支持 Hystrix、Resilience4j、Sentinel | 主要依赖 Hystrix |
配置方式 | YAML/Properties + Java DSL(更灵活) | 主要依赖 Groovy 脚本或 Java 配置 |
社区支持 | Spring 官方维护,持续更新 | Netflix 已停止维护(Zuul 1.x) |
适用场景 | 高并发、低延迟 的微服务网关 | 传统 同步阻塞 架构的简单网关 |
二、构建SpringCloud Gateway服务
1、新建子模块jd-shop-gateway服务
微服务搭建以及nacos注册和配置方式可以参考之前的文章:
SpringCloud Alibaba微服务框架搭建-CSDN博客
SpringCloud Alibaba微服务--Nacos注册中心和配置中心应用-CSDN博客
2、引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.mdx</groupId>
<artifactId>mdx-shop-common</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
</dependencies>
3、编写基础配置和路由规则
(1)路由基础配置
路由id:路由的唯一标示
路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
路由断言(predicates):判断路由的规则,
路由过滤器(filters):对请求或响应做处理
(2)设置bootstrap.yml配置文件
server:
port: 8091
spring:
application:
name: jd-shop-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: jd
config:
server-addr: 127.0.0.1:8848
extension-configs:
- data-id: ${spring.application.name}.yaml
group: DEFAULT_GROUP
refresh: true
file-extension: yml
namespace: jd
group: DEFAULT_GROUP
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: mdx-shop-user #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://127.0.0.1:8021 #匹配后提供服务的路由地址
predicates:
- Path=/user/** #断言,路径相匹配的进行路由
- id: mdx-shop-order
uri: http://127.0.0.1:8081
predicates:
- Path=/order/**
main:
web-application-type: reactive
4、重启服务,测试访问接口
通过gateway访问user服务,访问地址http://localhost:8091/user/getOrderNo?userId=T1234
8091为网关端口号
通过gateway访问order服务,访问地址http://127.0.0.1:8091/order/getOrderId?userId=T1234
8091为网关端口号
5、通过微服务名称的形式进行路由
将uri: http://127.0.0.1:8021 换成 uri: lb://jd-shop-user形式
重启服务,在测试一下user服务接口,访问
http://localhost:8091/user/getOrderNo?userId=T1234
8091为网关端口号
6、测试负载均衡
采用这种路由方式 uri: lb://jd-shop-user
在gateway添加配置:
开启通过服务中心的自动根据 serviceId 创建路由的功能
discovery:
locator:
enabled: true
lower-case-service-id: true
启动两个order服务,为另一个order服务新开一个端口号
启动两个order服务
nacos中可以看到有两个order服务实例
通过gateway访问order服务,访问地址http://127.0.0.1:8091/order/getOrderId?userId=T1234
可以发现访问的是OrderApplication服务接口
再次访问接口,发现访问的是OrderApplication2服务接口,实现了简单的负载均衡
三、通过nacos实现动态路由
对于微服务来说,如果使用配置文件来管理路由规则的话,每增加一个服务或者修改一个服务,都需要重启gateway服务,这样很麻烦,因此我们通过nacos来动态配置路由,就不需要进行服务重启了,编写对应的配置类,实现自动监听nacos配置文件的更新,实现动态刷新。
1、创建路由配置类
新建路由发布接口
public interface RouteService {
/**
* 添加路由信息
* @return
*/
void addRoute(RouteDefinition routeDefinition);
/**
* 修改路由信息
* @return
*/
void updateRoute(RouteDefinition routeDefinition);
/**
* 删除路由信息
* @return
*/
void deleteRoute(String routeId);
}
新建RouteServiceImpl类实现接口
@Service
@Slf4j
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
/**
* 事件发布者
*/
private ApplicationEventPublisher publisher;
@Override
public void addRoute(RouteDefinition routeDefinition) {
log.info("添加路由:{}", routeDefinition);
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void updateRoute(RouteDefinition routeDefinition) {
log.info("更新路由:{}", routeDefinition);
routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).subscribe();
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void deleteRoute(String routeId) {
log.info("删除路由:{}", routeId);
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
2、在nacos创建gateway-routes配置文件
(1)新建配置
json内容对应着RouteDefinition 类,Json内容:
[
{
"predicates":[
{
"args":{
"pattern":"/order/**"
},
"name":"Path"
}
],
"id":"jd-shop-order",
"uri":"lb://jd-shop-order",
"order":1
},
{
"predicates":[
{
"args":{
"pattern":"/user/**"
},
"name":"Path"
}
],
"id":"jd-shop-user",
"uri":"lb://jd-shop-user",
"order":2
}
]
这里面博主没有加过滤链:
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],作用:当请求路径为
/api/user
时,网关会截掉第一个路径部分(/api
),将请求转发到后端服务时路径变为/user
。
(2)修改bootstrap.yml配置文件,添加路由配置文件dataId、group等,之前路由规则删除或注释掉。
server: port: 8091 spring: application: name: jd-shop-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: jd config: server-addr: 127.0.0.1:8848 extension-configs: - data-id: ${spring.application.name}.yaml group: DEFAULT_GROUP refresh: true file-extension: yml namespace: jd group: DEFAULT_GROUP gateway: discovery: locator: enabled: true lower-case-service-id: true # routes: # - id: jd-shop-user #路由的ID,没有固定规则但要求唯一,建议配合服务名 # uri: lb://jd-shop-user #匹配后提供服务的路由地址 ## uri: http://127.0.0.1:8021 # predicates: # - Path=/user/** #断言,路径相匹配的进行路由 # # - id: jd-shop-order # uri: lb://jd-shop-order ## uri: http://127.0.0.1:8081 # predicates: # - Path=/order/** main: web-application-type: reactive gateway: routes: config: data-id: gateway-routes #动态路由 group: DEFAULT_GROUP namespace: jd
3、创建路由相关配置类
(1)创建配置类引入配置
@ConfigurationProperties(prefix = "gateway.routes.config")
@Component
@Data
public class GatewayRoutesConfigProperties {
private String dataId;
private String group;
private String namespace;
}
(2)实例化nacos的ConfigService,交由springbean管理
@Configuration
public class GatewayServiceConfig {
@Autowired
private GatewayRoutesConfigProperties configProperties;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Bean
public ConfigService configService() throws NacosException {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosConfigProperties.getServerAddr());
properties.setProperty(PropertyKeyConst.NAMESPACE, configProperties.getNamespace());
return NacosFactory.createConfigService(properties);
}
}
(3)路由监听类实现,项目启动时会加载这个类
@PostConstruc 注解的作用,在spring bean的生命周期依赖注入完成后被调用的方法
@Component
@Slf4j
@RefreshScope
public class GatewayRouteInitConfig {
@Autowired
private GatewayRoutesConfigProperties configProperties;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private RouteService routeService;
/**
* nacos 配置服务
*/
@Autowired
private ConfigService configService;
/**
* JSON 转换对象
*/
private final ObjectMapper objectMapper = new ObjectMapper();
@PostConstruct
public void init() {
log.info("开始网关动态路由初始化...");
try {
// getConfigAndSignListener()方法 发起长轮询和对dataId数据变更注册监听的操作
// getConfig 只是发送普通的HTTP请求
String initConfigInfo = configService.getConfigAndSignListener(configProperties.getDataId(), configProperties.getGroup(), nacosConfigProperties.getTimeout(), new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
if (StringUtils.isNotEmpty(configInfo)) {
log.info("接收到网关路由更新配置:\r\n{}", configInfo);
List<RouteDefinition> routeDefinitions = null;
try {
routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
});
} catch (JsonProcessingException e) {
log.error("解析路由配置出错," + e.getMessage(), e);
}
for (RouteDefinition definition : Objects.requireNonNull(routeDefinitions)) {
routeService.updateRoute(definition);
}
} else {
log.warn("当前网关无动态路由相关配置");
}
}
});
log.info("获取网关当前动态路由配置:\r\n{}", initConfigInfo);
if (StringUtils.isNotEmpty(initConfigInfo)) {
List<RouteDefinition> routeDefinitions = objectMapper.readValue(initConfigInfo, new TypeReference<List<RouteDefinition>>() {
});
for (RouteDefinition definition : routeDefinitions) {
routeService.addRoute(definition);
}
} else {
log.warn("当前网关无动态路由相关配置");
}
log.info("结束网关动态路由初始化...");
} catch (Exception e) {
log.error("初始化网关路由时发生错误", e);
}
}
}
4、测试动态路由
(1)重启服务,通过gateway访问order服务,访问地址http://127.0.0.1:8091/order/getOrderId?userId=T1234
8091为网关端口号
(2)在nacos配置中添加user服务路由规则,点击发布后,gateway的监听器已经监听到配置的改动
在不重启gateway服务前提下,通过gateway访问user服务,访问地址http://localhost:8091/user/getOrderNo?userId=T12345
可以看到成功路由到对应服务
SpringCloud Gateway网关服务就介绍到这里,创作不易,记得点赞收藏哟
上一篇文章: