字面意思就是网关,它的主要作用是路由转发,鉴权,限流等等。它的主要核心是Route,Predicate,Filter。这三大核心呢也是后面实现动态路由的关键。
Gateway 引入这里呢我项目中使用的是spring cloud gateway。
Gateway 配置 Gateway 基于yml配置org.springframework.cloud spring-cloud-starter-netflix-eureka-clientorg.springframework.cloud spring-cloud-starter-gateway
要想学会动态路由,那我们首先要知道Gateway相关的组成部分
spring:
application:
name: smart-cms-gateway
profiles:
active: dev
cloud:
nacos:
discovery:
# namespace: ${config.register.namespace} # 命名空间
server-addr: http://localhost:8848/ # 配置nacos注册中心
gateway:
discovery:
locator:
enabled: true # 让网关可以发现nacos中的微服务
lower-case-service-id: true
# 跨域配置,vue前端需要加changeOrigin=true
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: '*'
exposedHeaders:
- content-type
allowCredentials: true
allowedMethods:
- GET
- OPTIONS
- PUT
- DELETe
- POST
routes:
- id: smart-auth
# lb代表从注册中心获取服务,且已负载均衡方式转发
uri: lb://smart-cms-auth
predicates:
- Path=/smart-auth/**
# # 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
filters:
- StripPrefix=1
- name: RequestRateLimiter #请求数限流
args:
key-resolver: "#{@pathKeyResolver}" # 系统配置文件 com.smart.cms.ratelimiter.RateConfiguration 下有多种限流方式,也可以自己写实现
redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 10 #令牌桶总容量
由上图可知,在到达路由前的定位是locator,这个词很重要,然后代码中也有注释,自行解读。
Gateway 底层源码分析先看下图
RouteDefinitionLocator 是路由定义定位器的顶级接口,它的主要作用就是读取路由的配置信息,会发现他有五种实现方式,其中四种是实现类,一个是接口类,为什么这么设计呢,是因为Spring对于开发人员来说有一点是非常好的,那就是扩展性,可以让程序员随自己发挥。RouteDefinitionRepository也是今天的主角了,其他的四种实现方式呢,就不做过多介绍了,字面翻译过来就能理解,尤其是和yml配置一起解读的话会更通俗易懂。RouteDefinitionRepository继承了RouteDefinitionLocator和RouteDefinitionWriter,因此实现了路由的增删改查。所以如果我们要实现动态路由就需要实现RouteDefinitionRepository,然后重写它的增删改查方法。
动态路由具体实现实现动态路由就需要实现RouteDefinitionRepository,本文是基于redis实现,读者可自行选择其他方式
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
@Autowired
private RedisTemplate redisTemplate;
@Override
public Flux getRouteDefinitions() {
// 也可以采用map存储,没有做过分布式下redis和内存的对比,有空可以试试
List routeDefinitions = new ArrayList<>();
redisTemplate.opsForHash().values(GlobalConstants.GATEWAY_ROUTES).stream().forEach(routeDefinition -> {
routeDefinitions.add(JSON.parseObject(routeDefinition.toString(), RouteDefinition.class));
});
return Flux.fromIterable(routeDefinitions);
}
@Override
public Mono save(Mono route) {
return route
.flatMap(routeDefinition -> {
redisTemplate.opsForHash().put(GlobalConstants.GATEWAY_ROUTES, routeDefinition.getId(),
JSON.toJSONString(routeDefinition));
return Mono.empty();
});
}
@Override
public Mono delete(Mono routeId) {
return routeId.flatMap(id -> {
if (redisTemplate.opsForHash().hasKey(GlobalConstants.GATEWAY_ROUTES, id)) {
redisTemplate.opsForHash().delete(GlobalConstants.GATEWAY_ROUTES, id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(new RuntimeException("路由文件没有找到: " + routeId)));
});
}
}
实现了基本的代码后,我们需要将它进行发布,这里是设置的重点,就是我们开头说到的三大核心Route,Predicate,Filte
@Component
public class GatewayRouteAdvice implements ApplicationEventPublisherAware, CommandLineRunner {
@Autowired
private RedisRouteDefinitionRepository routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Autowired
private GatewayRouteService gatewayRouteService;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@Override
public void run(String... args){
//从数据库拿到路由配置
List gatewayRouteList = gatewayRouteService.lambdaQuery().eq(GatewayRateLimiter::getEnabled, 1).list();
log.info("网关配置信息:=====>"+ JSON.toJSONString(gatewayRouteList));
gatewayRouteList.forEach(gatewayRoute -> {
RouteDefinition definition = new RouteDefinition();
// 该参数必须唯一
definition.setId(gatewayRoute.getPath());
URI uri = UriComponentsBuilder.fromUriString("lb://"+gatewayRoute.getServiceId()).build().toUri();
definition.setUri(uri);
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName("Path");
Map predicateParams = new HashMap<>(8);
predicateParams.put("pattern", "/"+gatewayRoute.getPath()+"/**");
predicate.setArgs(predicateParams);
FilterDefinition filterDefinition2 = new FilterDefinition();
// 一个 -属性名称为一个FilterDefinition,下面的参数为HashMap
filterDefinition2.setName("StripPrefix");
Map filterParams2 = new HashMap<>(8);
// 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
filterParams2.put("_genkey_0", "1");
filterDefinition2.setArgs(filterParams2);
// 路径去前缀
FilterDefinition filterDefinition = new FilterDefinition();
// 该参数为唯一值,不可变,且下列配置为限流配置
filterDefinition.setName("RequestRateLimiter");
Map filterParams = new HashMap<>(8);
// 令牌桶流速
filterParams.put("redis-rate-limiter.replenishRate", gatewayRoute.getLimiterRate());
//令牌桶容量
filterParams.put("redis-rate-limiter.burstCapacity", gatewayRoute.getLimiterCapacity());
// 限流策略(#{@BeanName})
filterParams.put("key-resolver", "#{@"+gatewayRoute.getKeyResolver()+"}");
filterDefinition.setArgs(filterParams);
definition.setFilters(Arrays.asList(filterDefinition, filterDefinition2));
definition.setPredicates(Arrays.asList(predicate));
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
});
// 配置信息加载后后,发起一次RefreshRoutesEvent事件即可让配置生效,这个步骤是最重要的,也是核心
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
public void deleteRoute(String routeId){
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
}
}
以上就是动态路由实现的具体过程了,欢迎大家来探讨。



