Spring Cloud : Gateway Redis动态路由 (七)
https://zhuyu.blog.csdn.net/article/details/86557165?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control&dist_request_id=&depth_1
目录
2. RouteDefinitionVo 对 RouteDefinition 进行增强
3. RedisRouteDefinitionWriter 对路由节点进行增删改查
Spring Cloud Gateway 学习专栏
1. Spring Cloud : Gateway 服务网关认识(一)
2. Spring Cloud :整合Gateway 学习 (二)
3. Spring Cloud:Gateway 路由定义定位器 RouteDefinitionLocator (三)
4. Spring Cloud : Gateway 网关过滤器 GatewayFilter(四)
5. Spring Cloud : Gateway 网关限流(五)
6. Spring Cloud:Gateway 集成 Sentinel (六)
7. Spring Cloud : Gateway Redis动态路由 (七)
8. Spring Cloud : Gateway 整合Swagger (八)
如果发现本文有错误的地方,请大家毫不吝啬,多多指教,欢迎大家评论,谢谢!
一、概述
Gateway配置路由主要有三种方式
- 用 yml 配置文件
- 手动添加 route 节点
- 写在分布式配置中心,修改配置文件可以动态加载
无论是代码配置还是写在yml文件中,启动网关后将无法修改路由配置,如有新服务要上线,则需要先把网关下线,修改 yml 配置后,再重启网关,怎样代价就比较高。下面讲解是利用Redis装载我们 route 配置文件信息,当我们新增、删除、修改路由信息,会自动刷新自动配置配置信息,不需要重新启动服务。
1. 手动添加 route 节点
@Configuration
public class GatewayRotesConfig {
@Bean
protected RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes().route(
// 请求字服务路径以product/**
r -> r.path("/product/**","/catgory/**")
.uri("lb://api-product")
.id("product")
.filter(new CustomX001Filter())
).build();
}
}
二、Redis 动态加载的配置
1. RouteDefinitionLocator
可以查看上一篇写文章 路由定义定位器 RouteDefinitionLocator
主要通过 RouteDefinitionLocator 入口接口,RouteDefinitionRepository 接口中的方法用来对RouteDefinition进行增、删、查操作。
RouteDefinition 作为 GatewayProperties中的属性,在网关启动的时候读取配置文件中的相关配置信息
2. RouteDefinitionVo 对 RouteDefinition 进行增强
/**
* 扩展此类支持序列化-RouteDefinition
* @author Zou.LiPing
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class RouteDefinitionVo extends RouteDefinition implements Serializable {
private static final long serialVersionUID = -4046906501288976257L;
/**
* 路由名称
*/
private String routeName;
}
3. RedisRouteDefinitionWriter 对路由节点进行增删改查
ApplicationEventPublisherAware 作用事件发布通知
当我们修改 route 节点时,可用 ApplicationEventPublisher 通过发布事件,在利用监听,重新加载 route 配置节点信息,达到自动刷新配置文件信息
/**
* redis 保存路由信息,优先级比配置文件高
* @author Zou.LiPing
*/
@Component
@RequiredArgsConstructor
@Slf4j(topic = "RedisRouteDefinitionWriter")
public class RedisRouteDefinitionWriter implements RouteDefinitionRepository, ApplicationEventPublisherAware {
private final RedisUtils redisUtils;
private ApplicationEventPublisher publisher;
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(r -> {
RouteDefinitionVo vo = new RouteDefinitionVo();
BeanUtils.copyProperties(r, vo);
log.info("保存路由信息{}", vo);
redisUtils.hPut(CacheConstants.ROUTE_KEY,r.getId(), JSON.toJSONString(vo));
refreshRoutes();
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
routeId.subscribe(id -> {
log.info("删除路由信息={}", id);
redisUtils.hDelete(CacheConstants.ROUTE_KEY, id);
refreshRoutes();
});
return Mono.empty();
}
private void refreshRoutes() {
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 动态路由入口
* @return Flux<RouteDefinition>
*/
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> definitionList = new ArrayList<>();
Map<Object, Object> objectMap = redisUtils.hGetAll(CacheConstants.ROUTE_KEY);
if (Objects.nonNull(objectMap)) {
for (Map.Entry<Object, Object> objectObjectEntry : objectMap.entrySet()) {
RouteDefinition routeDefinition = JSON.parseObject(objectObjectEntry.getValue().toString(),RouteDefinition.class);
definitionList.add(routeDefinition);
}
}
log.info("redis 中路由定义条数: {}, definitionList={}", definitionList.size(), definitionList);
return Flux.fromIterable(definitionList);
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
4. RedisUtils 工具类
@Service
public class RedisUtils {
@Autowired
private StringRedisTemplate redisTemplate;
/** -------------------hash相关操作------------------------- */
/**
* 获取存储在哈希表中指定字段的值
*
* @param key
* @param field
* @return
*/
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* 获取所有给定字段的值
*
* @param key
* @return
*/
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 获取所有给定字段的值
*
* @param key
* @param fields
* @return
*/
public List<Object> hMultiGet(String key, Collection<Object> fields) {
return redisTemplate.opsForHash().multiGet(key, fields);
}
public void hPut(String key, String hashKey, String value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public void hPutAll(String key, Map<String, String> maps) {
redisTemplate.opsForHash().putAll(key, maps);
}
/**
* 仅当hashKey不存在时才设置
*
* @param key
* @param hashKey
* @param value
* @return
*/
public Boolean hPutIfAbsent(String key, String hashKey, String value) {
return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}
/**
* 删除一个或多个哈希表字段
*
* @param key
* @param fields
* @return
*/
public Long hDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* 查看哈希表 key 中,指定的字段是否存在
*
* @param key
* @param field
* @return
*/
public boolean hExists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 为哈希表 key 中的指定字段的整数值加上增量 increment
*
* @param key
* @param field
* @param increment
* @return
*/
public Long hIncrBy(String key, Object field, long increment) {
return redisTemplate.opsForHash().increment(key, field, increment);
}
/**
* 为哈希表 key 中的指定字段的整数值加上增量 increment
*
* @param key
* @param field
* @param delta
* @return
*/
public Double hIncrByFloat(String key, Object field, double delta) {
return redisTemplate.opsForHash().increment(key, field, delta);
}
/**
* 获取所有哈希表中的字段
*
* @param key
* @return
*/
public Set<Object> hKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* 获取哈希表中字段的数量
*
* @param key
* @return
*/
public Long hSize(String key) {
return redisTemplate.opsForHash().size(key);
}
/**
* 获取哈希表中所有值
*
* @param key
* @return
*/
public List<Object> hValues(String key) {
return redisTemplate.opsForHash().values(key);
}
/**
* 迭代哈希表中的键值对
*
* @param key
* @param options
* @return
*/
public Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
return redisTemplate.opsForHash().scan(key, options);
}
}
5. RouteController
/**
* route 接口控制
* @date: 2021/4/20 14:52
* @author Zou.LiPing
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/route")
public class RouteController {
private final RedisRouteDefinitionWriter redisRouteDefinitionWriter;
@GetMapping("save")
public void save(RouteDefinition route) {
Mono<RouteDefinition> routeDefinition = Mono.just(route);
redisRouteDefinitionWriter.save(routeDefinition);
}
@GetMapping("delete")
public void delete(String routeId) {
Mono<String> just = Mono.just(routeId);
redisRouteDefinitionWriter.delete(just);
}
@GetMapping("testSave")
public void testSave(RouteDefinition route) {
bulidRouteDefinitionParam(route);
redisRouteDefinitionWriter.save(Mono.just(route));
}
private RouteDefinition bulidRouteDefinitionParam(RouteDefinition definition) {
definition.setId("user");
URI uri = UriComponentsBuilder.fromHttpUrl("http://localhost:8200").build().toUri();
definition.setUri(uri);
//定义第一个断言
Map<String, String> predicateParams = new HashMap<>(8);
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName("Path");
predicateParams.put("pattern", "/user/**");
predicate.setArgs(predicateParams);
//定义Filter
Map<String, String> filterParams = new HashMap<>(8);
FilterDefinition filter = new FilterDefinition();
filter.setName("RequestRateLimiter");
//该_genkey_前缀是固定的,见@link org.springframework.cloud.gateway.support.NameUtils类
filterParams.put("redis-rate-limiter.replenishRate", "1");
filterParams.put("redis-rate-limiter.burstCapacity", "2");
filterParams.put("key-resolver", "#{@remoteAddrKeyResolver}");
filter.setArgs(filterParams);
Map<String, String> filter1Params = new HashMap<>(8);
FilterDefinition RewritePath = new FilterDefinition();
RewritePath.setName("RewritePath");
// /user/(?<segment>.*),/$\{segment}
filter1Params.put("regexp", "/user/(?<segment>.*)");
filter1Params.put("replacement", "/$\\{segment}");
RewritePath.setArgs(filter1Params);
definition.setFilters(Arrays.asList(filter, RewritePath));
definition.setPredicates(Arrays.asList(predicate));
log.info("definition:{}",JSON.toJSONString(definition));
return definition;
}
}
6. 在配置文件中添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
server:
port: 9000
service-url:
user-service: http://localhost:8200
spring:
application:
name: mall-gateway
main:
allow-bean-definition-overriding: true
## redis配置
redis:
database: 0
host: 47.103.20.21
password: zlp123456
port: 6379
timeout: 7000
cloud:
## nacos注册中心
nacos:
discovery:
server-addr: 47.103.20.21:8848
## 整合sentinel
sentinel:
transport:
dashboard: 47.103.20.21:8000
port: 8080
# 服务启动直接建立心跳连接
eager: true
# 暴露 route 端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
三、 测试结果
1. 启动服务步骤
- 先启动 user-service 服务
- 启动 user-product 服务
- 启动 api-gateway 服务
启动项目,查看网关路由信息,访问 http://localhost:9000/actuator/gateway/routes ,因没有配置路由信息,因此返回结果为空数组
2. 动态添加路由节点信息
{
"filters": [{
"args": {
"_genkey_0": "/product/(?<segment>.*)",
"_genkey_1": "/$\\{segment}"
},
"name": "RewritePath"
}],
"id": "product",
"order": 1,
"predicates": [{
"args": {
"_genkey_0": "/product/**"
},
"name": "Path"
}],
"uri": "lb://api-product"
}
postman 添加路由节点
SpringCloud Gateway 中在这个类提供了 AbstractGatewayControllerEndpoint 提供了添加方法
http://127.0.0.1:9000/actuator/gateway/routes/product
http://localhost:9000/actuator/gateway/routes
GatewayControllerEndpoint
3. 删除路由节点
http://127.0.0.1:9000/actuator/gateway/routes/product
源码地址
mall-gateway 这个项目
https://gitee.com/gaibianzlp/zlp-mall-demo.git
参考链接
1. SpringCloud实战十三:Gateway之 Spring Cloud Gateway 动态路由
2. 动态路由配置,使用Mysql,存储路由,实现集群gateway动态路由
更多推荐
所有评论(0)