为了解决上面的问题,微服务引入了 网关 的概念,网关为微服务架构的系统提供简单、有效且统一的API路由管理,作为系统的统一入口,提供内部服务的路由中转,给客户端提供统一的服务,可以实现一些和业务没有耦合的公用逻辑,主要功能包含认证、鉴权、路由转发、安全策略、防刷、流量控制、监控日志等
客户端SpringCloud Gateway发出请求,然后在Gateway Handler Mapping中找到与之请求相匹配的路由,将其发送到Gateway Web Handler,Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会发在代理请求之前(“pre”)或之后(“post”)执行业务逻辑,这样,Filter在“pre”类型的过滤器可以做参数校验,权限校验,流量监控,日志输出,协议转换等;在“post”类型的过滤器可以做响应内容,响应头的修改,日志的输出,流量监控等有着非常重要的作用
1.1、新建gateway子模块
nacos注册中心和配置中心以及服务服搭建可以参考之前的文章,这里基于之前的项目构建gateway服务
springcloud alibaba微服务 – nacos使用以及注册中心和配置中心的应用(保姆级)
1.2、引入依赖
gateway服务依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>com.mdx</groupId>
<artifactId>mdx-shop-common</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
下面是全局的关于springcloud的依赖
spring-cloud.version:2021.0.1
spring-cloud-alibaba.version: 2021.0.1.0
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</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>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
2.1、创建启动类
@SpringBootApplication
@EnableFeignClients
public class MdxShopGateWayApplication {
public static void main(String[] args) {
SpringApplication.run(MdxShopGateWayApplication.class, args);
}
}
2.2、创建application.yml配置文件
使用ip路由的方式:
server:
port: 9010
spring:
application:
name: mdx-shop-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: mdx
group: mdx
gateway:
routes:
- id: mdx-shop-user #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:9090 #匹配后提供服务的路由地址
predicates:
- Path=/user/** #断言,路径相匹配的进行路由
- id: mdx-shop-order
uri: http://localhost:9091
predicates:
- Path=/order/**
2.3、启动并访问Gateway服务
发现报错了…
大致意思是在springboot整合gateway时, gateway组件中的 【spring-boot-starter-webflux】 和 springboot作为web项目启动必不可少的 【spring-boot-starter-web】 出现冲突
我们按照提示: Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.
在配置文件配置下 spring.main.web-application-type=reactive 就好了
main:
web-application-type: reactive
接着在重新启动项目,成功启动
然后我们再依次启动order服务和user服务
通过gateway访问user服务:
http://localhost:9010/user/getOrderNo?userId=mdx123456
其中9010端口为网关服务
通过gateway访问order服务:
http://localhost:9010/order/getOrderNo?userId=mdx123456
其中9010端口为网关服务
可见以上gateway均已成功路由到两个服务
2.4、通过微服务名称的方式来路由服务
把 gateway配置文件中的 uri: http://localhost:9090 改为 uri: lb://mdx-shop-user 这种服务名的形式
server:
port: 9010
spring:
application:
name: mdx-shop-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: mdx
group: mdx
gateway:
routes:
- id: mdx-shop-user #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://mdx-shop-user #匹配后提供服务的路由地址
predicates:
- Path=/user/** #断言,路径相匹配的进行路由
- id: mdx-shop-order
uri: lb://mdx-shop-order
predicates:
- Path=/order/**
main:
web-application-type: reactive
再来测试一下user服务
http://localhost:9010/user/getOrderNo?userId=mdx123456
其中9010端口为网关服务
成功返回
2.5、路由websocket服务
将 uri: lb://mdx-shop-user 改为 uri: lb:ws://mdx-shop-user
routes:
- id: mdx-shop-user #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb:ws://mdx-shop-user #匹配后提供服务的路由地址
predicates:
- Path=/user/** #断言,路径相匹配的进行路由
2.6、测试负载均衡
采用这种路由方式 uri: lb://mdx-shop-user
在gateway添加配置:
开启通过服务中心的自动根据 serviceId 创建路由的功能
gateway:
discovery:
locator:
enabled: true
我们在order服务中写一个测试类,如下
/**
* 测试负载均衡
* @return
*/
@GetMapping("lb")
public String lb(){
System.out.println("test lb");
return "lb";
}
分别启动两个order服务(启动一个order服务之后,修改下端口号再启动一个)
在idea中启动同一个服务的多个端口操作如下:
成功启动了两个order服务
nacos状态如下(启动了两个实例)
我们再来通过网关访问下order服务
http://localhost:9010/order/lb
其中 9010 为网关端口
首先访问一次
我们看到order1服务打印了日志,order2服务没有日志
再访问一次接口
这个时候order2打印了日志,order1没有打印日志
如此实现了简单的负载均衡
微服务都是互相独立的,假如我们的网关和其他服务都在线上已经运行了好久,这个时候增加了一个微服务,这个时候要通过网关访问的话需要通过修改配置文件来增加路由规则,并且需要重启项目,所以我们需要实现动态路由
1、创建路由配置接口
新建路由发布接口
/**
* 路由配置服务
* @author : jiagang
* @date : Created in 2022/7/20 11:07
*/
public interface RouteService {
/**
* 更新路由配置
*
* @param routeDefinition
*/
void update(RouteDefinition routeDefinition);
/**
* 添加路由配置
*
* @param routeDefinition
*/
void add(RouteDefinition routeDefinition);
}
实现类如下
package com.mdx.gateway.service.impl;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;
import reactor.core.publisher.Mono;
/**
* @author : jiagang
* @date : Created in 2022/7/20 11:10
*/
@Service
@Slf4j
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
/**
* 事件发布者
*/
private ApplicationEventPublisher publisher;
@Override
public void update(RouteDefinition routeDefinition) {
log.info("更新路由配置项:{}", routeDefinition);
this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void add(RouteDefinition routeDefinition) {
log.info("新增路由配置项:{}", routeDefinition);
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
其中:
RouteDefinitionWriter:提供了对路由的增加删除等操作
ApplicationEventPublisher: 是ApplicationContext的父接口之一,他的功能就是发布事件,也就是把某个事件告诉所有与这个事件相关的监听器
2、在nacos创建gateway-routes配置文件
将路由信息放到nacos的配置文件下
新建配置文件,并将order服务的路由添加到配置文件
配置路由如下:
[
{
"predicates":[
{
"args":{
"pattern":"/order/**"
},
"name":"Path"
}
],
"id":"mdx-shop-order",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-order",
"order":1
}
]
这个路由配置对应的就是gateway中的RouteDefinition类
3、在本地配置文件下配置路由的data-id和group和命名空间
gateway:
routes:
config:
data-id: gateway-routes #动态路由
group: shop
namespace: mdx
完整配置文件(删除或者注释掉之前配置在本地文件的路由)
server:
port: 9010
spring:
application:
name: mdx-shop-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: mdx
group: mdx
gateway:
discovery:
locator:
enabled: true #开启通过服务中心的自动根据 serviceId 创建路由的功能
main:
web-application-type: reactive
gateway:
routes:
config:
data-id: gateway-routes #动态路由
group: shop
namespace: mdx
4、创建路由相关配置类
创建配置类引入配置
/**
* @author : jiagang
* @date : Created in 2022/7/20 14:44
*/
@ConfigurationProperties(prefix = "gateway.routes.config")
@Component
@Data
public class GatewayRouteConfigProperties {
private String dataId;
private String group;
private String namespace;
}
5、实例化nacos的ConfigService,交由springbean管理
ConfigService 这个类是nacos的分布式配置接口,主要是用来获取配置和添加监听器
由NacosFactory来创建ConfigService
/**
* 将configService交由spring管理
* @author : jiagang
* @date : Created in 2022/7/20 15:27
*/
@Configuration
public class GatewayConfigServiceConfig {
@Autowired
private GatewayRouteConfigProperties 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);
}
}
6、动态路由主要实现
项目启动时会加载这个类
@PostConstruc 注解的作用,在spring bean的生命周期依赖注入完成后被调用的方法
package com.mdx.gateway.route;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mdx.common.utils.StringUtils;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* @author : jiagang
* @date : Created in 2022/7/20 15:04
*/
@Component
@Slf4j
@RefreshScope
public class GatewayRouteInitConfig {
@Autowired
private GatewayRouteConfigProperties 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.update(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.add(definition);
}
} else {
log.warn("当前网关无动态路由相关配置");
}
log.info("结束网关动态路由初始化...");
} catch (Exception e) {
log.error("初始化网关路由时发生错误", e);
}
}
}
如果项目启动时,在发布路由的时候卡在 this.publisher.publishEvent(new RefreshRoutesEvent(this)); 这个地方走不下去
请在GatewayRouteInitConfig这个类加@RefreshScope注解
5、测试动态路由
前面我们已经把本地的yml中的路由注释掉了,现在我们来通过gateway服务来掉一个order服务的接口
接口地址:http://localhost:9010/mdx-shop-order/order/lb
其中9010是网关端口
可以看到路由成功
然后我们再在nacos配置中心加一个user服务的路由
[
{
"predicates":[
{
"args":{
"pattern":"/mdx-shop-order/**"
},
"name":"Path"
}
],
"id":"mdx-shop-order",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-order",
"order":1
},
{
"predicates":[
{
"args":{
"pattern":"/mdx-shop-user/**"
},
"name":"Path"
}
],
"id":"mdx-shop-user",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-user",
"order":2
}
]
然后点发布
可以看到gateway的监听器已经监听到配置的改动
不重新启动gateway的情况下再来通过网关访问下user服务
接口地址:http://localhost:9010/mdx-shop-user/user/getOrderNo?userId=mdx123456
其中9010是网关端口
可以看到成功路由
到这里gateway的使用和nacos动态路由就结束了~
创作不易,点个赞吧~
最后的最后送大家一句话
白驹过隙,沧海桑田
与君共勉
nacos的基础使用可以看这个
springcloud alibaba微服务 – nacos使用以及注册中心和配置中心的应用(保姆级)
项目地址
mdx-shop 完整项目