OCP开源项目:Spring Cloud Gateway模块中动态路由的实现
Posted Coder编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OCP开源项目:Spring Cloud Gateway模块中动态路由的实现相关的知识,希望对你有一定的参考价值。
1.前言
本章将介绍OCP开源项目:Spring Cloud Gateway模块中动态路由的实现。
2. Spring Cloud Gateway
Spring Cloud Gateway旨在提供一种简单而有效的方式来路由到API,并为他们提供横切关注点,例如:安全性,监控/指标和弹性。
2.1 Spring Cloud Gateway特征
基于Spring Framework 5,Project Reactor和Spring Boot 2.0构建
能够匹配任何请求属性上的路由。
谓词和过滤器特定于路线。
Hystrix断路器集成。
Spring Cloud DiscoveryClient集成
易于编写谓词和过滤器
请求率限制
路径重写
2.2 项目实战
接下来,开始我们的 Spring Cloud Gateway 限流之旅吧!
2.2.1 Spring Cloud Gateway 限流
2.2.2 OCP子项目new-api-gateway
pom.xml
<!--基于 reactive stream 的redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--spring cloud gateway 相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.2.3 配置HostName的规则限流
目前只对user-center用户中心进行限流
spring:
cloud:
gateway:
discovery:
locator:
lowerCaseServiceId: true
enabled: true
routes:
# =====================================
- id: api-eureka
uri: lb://eureka-server
order: 8000
predicates:
- Path=/api-eureka/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name : default
fallbackUri: 'forward:/defaultfallback'
- id: api-user
uri: lb://user-center
order: 8001
predicates:
- Path=/api-user/**
filters:
- GwSwaggerHeaderFilter
- StripPrefix=1
- name: Hystrix
args:
name : default
fallbackUri: 'forward:/defaultfallback'
- name: RequestRateLimiter #对应 RequestRateLimiterGatewayFilterFactory
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶的容积 放入令牌桶的容积每次一个
redis-rate-limiter.burstCapacity: 3 # 流速 每秒
key-resolver: "#{@ipAddressKeyResolver}" # SPEL表达式去的对应的bean
2.2.4 新增配置类RequestRateLimiterConfig
配置类新建在com.open.capacity.client.config路径下
package com.open.capacity.client.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
/**
* 定义spring cloud gateway中的 key-resolver: "#{@ipAddressKeyResolver}" #SPEL表达式去的对应的bean
* ipAddressKeyResolver 要取bean的名字
*
*/
@Configuration
public class RequestRateLimiterConfig {
/**
* 根据 HostName 进行限流
* @return
*/
@Bean("ipAddressKeyResolver")
public KeyResolver ipAddressKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
/**
* 根据api接口来限流
* @return
*/
@Bean(name="apiKeyResolver")
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
/**
* 用户限流
* 使用这种方式限流,请求路径中必须携带userId参数。
* 提供第三种方式
* @return
*/
@Bean("userKeyResolver")
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
}
2.2.5 压力测试
接下来配置东西弄完之后 我们开始进行压力测试,压力测试之前,由于new-api-gateway有全局拦截器 AccessFilter 的存在,如果不想进行登录就进行测试的。先把
"/api-auth/**" 的判断中的注释掉。接下来我们开用postman进行测试
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// TODO Auto-generated method stub
String accessToken = extractToken(exchange.getRequest());
if(pathMatcher.match("/**/v2/api-docs/**",exchange.getRequest().getPath().value())){
return chain.filter(exchange);
}
if(!pathMatcher.match("/api-auth/**",exchange.getRequest().getPath().value())){
// if (accessToken == null) {
// exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// return exchange.getResponse().setComplete();
// }else{
// try {
// Map<String, Object> params = (Map<String, Object>) redisTemplate.opsForValue().get("token:" + accessToken) ;
// if(params.isEmpty()){
// exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// return exchange.getResponse().setComplete();
// }
// } catch (Exception e) {
// exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// return exchange.getResponse().setComplete();
// }
// }
}
return chain.filter(exchange);
}
1.打开postman 选择Collections 点击新建按钮
2.点击新建的Collection 新建一个request
4.切换成Tests的tab下,选择右边Status code is 200选项,这里可以选择其他的方法,根据自己的api定义。
5.这里需要注意:操作完刚刚的数据,必须点击save进行保存,否则无法生效;
请求配置
6. 点击 Run,就可以查看结果,有五次成功,五次失败;限流成功返回429
在前一章,我们已经做了简单spring cloud gateway 介绍 和 限流,接下来,spring cloud gateway最重要的,也是最为关键的 动态路由,首先,API网关负责服务请求路由、组合及协议转换,客户端的所有请求都首先经过API网关,然后由它将匹配的请求路由到合适的微服务,是系统流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启,如果有新的服务要上线时,可以通过动态路由配置功能上线。
3. Spring Cloud Gateway动态路由实现
首先,springcloudgateway配置路由有2种方式:
yml配置文件
面向对象配置(代码方式配置)
3.1 yml配置
3.2 代码方式配置
3.3 路由初始化
srping cloud gateway网关启动时,路由信息默认会加载内存中,路由信息被封装到RouteDefinition对象中,
org.springframework.cloud.gateway.route.RouteDefinition
该类有的属性为 :
@NotEmpty
private String id = UUID.randomUUID().toString();
//路由断言定义
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
//路由过滤定义
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
//对应的URI
@NotNull
private URI uri;
private int order = 0;
一个RouteDefinition有个唯一的ID,如果不指定,就默认是UUID,多个RouteDefinition组成了gateway的路由系统,所有路由信息在系统启动时就被加载装配好了,并存到了内存里。
3.4 网关的自动配置
org.springframework.cloud.gateway.config.GatewayAutoConfiguration
//RouteLocatorBuilder 采用代码的方式注入路由
@Bean
public RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) {
return new RouteLocatorBuilder(context);
}
//PropertiesRouteDefinitionLocator 配置文件路由定义
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
return new PropertiesRouteDefinitionLocator(properties);
}
//InMemoryRouteDefinitionRepository 内存路由定义
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
return new InMemoryRouteDefinitionRepository();
}
//CompositeRouteDefinitionLocator 组合多种模式,为RouteDefinition统一入口
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> GatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties);
}
//CachingRouteLocator 为RouteDefinition提供缓存功能
@Bean
@Primary
//TODO: property to disable composite?
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}
装配yml文件的,它返回的是PropertiesRouteDefinitionLocator,该类继承了RouteDefinitionLocator,RouteDefinitionLocator就是路由的装载器,里面只有一个方法,就是获取路由信息的。
org.springframework.cloud.gateway.route.RouteDefinitionLocator
RouteDefinitionLocator 类图如下:
子类功能描述:
CachingRouteDefinitionLocator:RouteDefinitionLocator包装类, 缓存目标RouteDefinitionLocator 为routeDefinitions提供缓存功能
CompositeRouteDefinitionLocator -RouteDefinitionLocator包装类,组合多种 RouteDefinitionLocator 的实现,为 routeDefinitions提供统一入口
PropertiesRouteDefinitionLocator-从配置文件(GatewayProperties 例如,YML / Properties 等 ) 读取RouteDefinition
RouteDefinitionRepository-从存储器( 例如,内存 / Redis / mysql 等 )读取RouteDefinition
DiscoveryClientRouteDefinitionLocator-从注册中心( 例如,Eureka / Consul / Zookeeper / Etcd 等
推荐参考文章:https://www.jianshu.com/p/b02c7495eb5e
3.5 编写动态路由
新建数据脚本,在 sql目录下 02.oauth-center.sql
#
# Structure for table "sys_gateway_routes"
#
DROP TABLE IF EXISTS sys_gateway_routes;
CREATE TABLE sys_gateway_routes
(
`id` char(32) NOT NULL COMMENT 'id',
`uri` VARCHAR(100) NOT NULL COMMENT 'uri路径',
`predicates` VARCHAR(1000) COMMENT '判定器',
`filters` VARCHAR(1000) COMMENT '过滤器',
`order` INT COMMENT '排序',
`description` VARCHAR(500) COMMENT '描述',
`delFlag` int(11) DEFAULT '0' COMMENT '删除标志 0 不删除 1 删除',
`createTime` datetime NOT NULL,
`updateTime` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb4 COMMENT '服务网关路由表';
/**
* 路由实体类
*/
public class GatewayRoutes {
private String id;
private String uri;
private String predicates;
private String filters;
private Integer order;
private String description;
private Integer delFlag;
private Date createTime;
private Date updateTime;
//省略getter,setter
}
/**
* 路由的Service类
*/
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware, IDynamicRouteService {
/**
* 新增路由
*
* @param gatewayRouteDefinition
* @return
*/
@Override
public String add(GatewayRouteDefinition gatewayRouteDefinition) {
GatewayRoutes gatewayRoutes = transformToGatewayRoutes(gatewayRouteDefinition);
gatewayRoutes.setDelFlag(0);
gatewayRoutes.setCreateTime(new Date());
gatewayRoutes.setUpdateTime(new Date());
gatewayRoutesMapper.insertSelective(gatewayRoutes);
gatewayRouteDefinition.setId(gatewayRoutes.getId());
redisTemplate.opsForValue().set(GATEWAY_ROUTES_PREFIX + gatewayRouteDefinition.getId(), JSONObject.toJSONString(gatewayRouteDefinition));
return gatewayRoutes.getId();
}
/**
* 修改路由
*
* @param gatewayRouteDefinition
* @return
*/
@Override
public String update(GatewayRouteDefinition gatewayRouteDefinition) {
GatewayRoutes gatewayRoutes = transformToGatewayRoutes(gatewayRouteDefinition);
gatewayRoutes.setCreateTime(new Date());
gatewayRoutes.setUpdateTime(new Date());
gatewayRoutesMapper.updateByPrimaryKeySelective(gatewayRoutes);
redisTemplate.delete(GATEWAY_ROUTES_PREFIX + gatewayRouteDefinition.getId());
redisTemplate.opsForValue().set(GATEWAY_ROUTES_PREFIX + gatewayRouteDefinition.getId(), JSONObject.toJSONString(gatewayRouteDefinition));
return gatewayRouteDefinition.getId();
}
/**
* 删除路由
* @param id
* @return
*/
@Override
public String delete(String id) {
gatewayRoutesMapper.deleteByPrimaryKey(id);
redisTemplate.delete(GATEWAY_ROUTES_PREFIX + id);
return "success";
}
}
/**
* 核心类
* getRouteDefinitions() 通过该方法获取到全部路由,每次有request过来请求的时候,都会往该方法过。
*
*/
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
public static final String GATEWAY_ROUTES_PREFIX = "geteway_routes_";
@Autowired
private StringRedisTemplate redisTemplate;
private Set<RouteDefinition> routeDefinitions = new HashSet<>();
/**
* 获取全部路由
* @return
*/
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
/**
* 从redis 中 获取 全部路由,因为保存在redis ,mysql 中 频繁读取mysql 有可能会带来不必要的问题
*/
Set<String> gatewayKeys = redisTemplate.keys(GATEWAY_ROUTES_PREFIX + "*");
if (!CollectionUtils.isEmpty(gatewayKeys)) {
List<String> gatewayRoutes = Optional.ofNullable(redisTemplate.opsForValue().multiGet(gatewayKeys)).orElse(Lists.newArrayList());
gatewayRoutes
.forEach(routeDefinition -> routeDefinitions.add(JSON.parseObject(routeDefinition, RouteDefinition.class)));
}
return Flux.fromIterable(routeDefinitions);
}
/**
* 添加路由方法
* @param route
* @return
*/
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(routeDefinition -> {
routeDefinitions.add( routeDefinition );
return Mono.empty();
});
}
/**
* 删除路由
* @param routeId
* @return
*/
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
List<RouteDefinition> collect = routeDefinitions.stream().filter(
routeDefinition -> StringUtils.equals(routeDefinition.getId(), id)
).collect(Collectors.toList());
routeDefinitions.removeAll(collect);
return Mono.empty();
});
}
}
/**
* 编写Rest接口
*/
@RestController
@RequestMapping("/route")
public class RouteController {
@Autowired
private IDynamicRouteService dynamicRouteService;
//增加路由
@PostMapping("/add")
public Result add(@RequestBody GatewayRouteDefinition gatewayRouteDefinition) {
return Result.succeed(dynamicRouteService.add(gatewayRouteDefinition));
}
//更新路由
@PostMapping("/update")
public Result update(@RequestBody GatewayRouteDefinition gatewayRouteDefinition) {
return Result.succeed(dynamicRouteService.update(gatewayRouteDefinition));
}
//删除路由
@DeleteMapping("/{id}")
public Result delete(@PathVariable String id) {
return Result.succeed(dynamicRouteService.delete(id));
}
}
3.6 测试编写的动态路由
GET localhost:9200/actuator/gateway/routes
1.使用该接口,查看gateway下的全部路由,测试路由 /jd/** 并没有找到
POST 127.0.0.1:9200/route/add
参数由json格式构建 对应 com.open.capacity.client.dto.GatewayRouteDefinition 类
{
"id": "",
"uri": "lb://user-center",
"order": 1111,
"filters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/jd/**"
}
}
],
"description": "测试路由新增"
}
添加成功,返回对应id,查看mysql,redis 都已经保存成功
在访问刚刚 获取全部路由的接口,发现我们的/jd/**已经注册到我们的网关上
GET localhost:9200/jd/users-anon/login?username=admin
这个时候,我们没有重启项目,依然可以访问我们自定义的路由,到此,我们已经完成了添加操作,后续的删除,更新,就是简单调用下API就完成!
划重点
以上来自开源项目OCP: https://gitee.com/owenwangwen/open-capacity-platform
项目监控
http://106.13.3.200:3000 用户名/密码:admin/1q2w3e4r
群号:483725710(备注:Coder编程)欢迎大家加入~
文末
欢迎关注并star~
以上是关于OCP开源项目:Spring Cloud Gateway模块中动态路由的实现的主要内容,如果未能解决你的问题,请参考以下文章
阿里开源项目大 PK,Dubbo VS Spring Cloud
PassJava 开源 : Spring Cloud 整合Gateway网关 #私藏项目实操分享#
PassJava 开源 : Spring Cloud 整合 Nacos 配置中心 #私藏项目实操分享#
SpringCloud微服务安全网关安全 3-8 用开源项目spring-cloud-zuul-ratelimit 做网关上的限流