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 限流
![new-api-gateway OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/12cf0a20f632439b9e3a9dc786b90e16.jpg)
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 点击新建按钮
![新建 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/b6d6b0f1af8a47968e5c1a5c1fafd4ed.jpg)
![输入名称与描述 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/dd087c53470c4031a8879930a10d4f2a.jpg)
2.点击新建的Collection 新建一个request
添加request 添加request2
![填入URL OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/6dd83ea60602441cbe780d8c81019dfc.jpg)
4.切换成Tests的tab下,选择右边Status code is 200选项,这里可以选择其他的方法,根据自己的api定义。
![选择Status code is 200 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/70ff38a676b24fd0a18814d3177f17fb.jpg)
5.这里需要注意:操作完刚刚的数据,必须点击save进行保存,否则无法生效;
保存 请求配置
![请求配置 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/66230cf81af44949b453065066d1fe29.jpg)
6. 点击 Run,就可以查看结果,有五次成功,五次失败;限流成功返回429
![运行结果 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/f593d945622446ceac658c1f2b9de8ec.jpg)
![运行结果 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/98c2e44a43954c6aa4d727706f90d646.jpg)
在前一章,我们已经做了简单spring cloud gateway 介绍 和 限流,接下来,spring cloud gateway最重要的,也是最为关键的 动态路由,首先,API网关负责服务请求路由、组合及协议转换,客户端的所有请求都首先经过API网关,然后由它将匹配的请求路由到合适的微服务,是系统流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启,如果有新的服务要上线时,可以通过动态路由配置功能上线。
3. Spring Cloud Gateway动态路由实现
首先,springcloudgateway配置路由有2种方式:
yml配置文件
面向对象配置(代码方式配置)
3.1 yml配置
![yml配置 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/7c1684b0b1c546fd911fe7f5cfbd1785.jpg)
3.2 代码方式配置
![代码方式配置 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/38459be5d6be4b9d92e107c85e9a85c8.jpg)
3.3 路由初始化
srping cloud gateway网关启动时,路由信息默认会加载内存中,路由信息被封装到RouteDefinition对象中,
org.springframework.cloud.gateway.route.RouteDefinition
![RouteDefinition OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/f876e6bf09e64e0faf43601d287af5f0.jpg)
该类有的属性为 :
@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
![4 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/a0108ed208124ed48e86c9db7392c36f.jpg)
//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
![5 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/c667e51b1b804c6a82fd7b20c5541991.jpg)
RouteDefinitionLocator 类图如下:
![RouteDefinitionLocator 类图 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/2ed4214d97884ac991a55b94b33b127c.jpg)
子类功能描述:
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/** 并没有找到
![7 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/fa0df0a512c244d99bf34f8f0507f9ac.jpg)
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 都已经保存成功
![8 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/702908ac0c7b4df8bfd58aa64dc1b844.jpg)
![9 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/d989414a768e4461b76fcb562b834d69.jpg)
![在这里插入图片描述 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/009d2973f64f419386da60a4b0d01f51.jpg)
在访问刚刚 获取全部路由的接口,发现我们的/jd/**已经注册到我们的网关上
![10 OCP开源项目:Spring Cloud Gateway模块中动态路由的实现](https://image.cha138.com/20210428/ba30303589c948d981f1c4034ee74539.jpg)
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 做网关上的限流