基于Redis对SpringCloudGateWay实现动态化路由
SpringCloudGateWay的路由存储默认是InMemory,所以只需要改变数据的保存地址即可很方便的解决动态路由第一种:基于Redis保存– 首先SpringCloudGateWay的路由策略类默认为InMemoryRouteDefinitionRepository,实现了RouteDefinitionRepository接口进行的动态路由,那么我们也可以实现此接口创建RedisRout
·
我的个人网站:等不见天亮等时光
- SpringCloudGateWay的路由存储默认是InMemory,所以只需要改变数据的保存地址即可很方便的解决动态路由
- 第一种:基于Redis保存
– 首先SpringCloudGateWay的路由策略类默认为InMemoryRouteDefinitionRepository,实现了RouteDefinitionRepository接口进行的动态路由,那么我们也可以实现此接口 - 创建RedisRouteDefinitionRepository类,实现RouteDefinitionRepository接口
- 第二种基于配置中心(Apollo/nacos),基于Apollo的请转到基于Apollo实现对SpringCloudGagtway的动态化路由
@Component
@Slf4j
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
/**
* 路由保存Key值
*/
public static final String ROUTE_KEY="ROUTE_KEY";
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 获取路由配置信息
*
* @return
*/
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
log.info("[网关系统]动态路由刷新 start");
List<RouteDefinition> routeDefinitions=new ArrayList<>();
redisTemplate.opsForHash().values(ROUTE_KEY).stream().forEach(routeDefinition -> {
routeDefinitions.add(JSON.parseObject(routeDefinition.toString(),RouteDefinition.class));
});
return Flux.fromIterable(routeDefinitions);
}
/**
* 保存路由信息
*
* @param route
* @return
*/
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return null;
}
/**
* 删除路由信息
*
* @param routeId
* @return
*/
@Override
public Mono<Void> delete(Mono<String> routeId) {
return null;
}
- 然后将save和delete方法可以提取到其他的微服务模块中,因为这些操作可能涉及到DB,RouteDefinition类是路由类的参数类,定义了服务的id已经路由的策略,具体含义如下
id:服务的serverId,注册到注册中心的Id信息
uri:lb:使用注册中心方式配置;ws:使用websocket方式配置;http:使用http方式配置,如:ws://cloud-cus;
order:优先级数字类型,越小越优先匹配
predicates:请求路径配置,默认可使用/服务名/**
filters:过滤器配置
限流配置说明
name:限流配置服务类默认使用EpayRateLimiter
key-resolver:限流方式,支持使用apiKeyResolver[API限流]与hostAddrKeyResolver[用户IP地址限流]
redis-rate-limiter.replenishRate:每秒允许通过的请求数
redis-rate-limiter.burstCapacity:令牌桶的最大容量
- 限流策略类如下
/**
* 用户IP限流
*
* @return KeyResolver
*/
@Bean("hostAddrKeyResolver")
@Primary
public KeyResolver hostAddrKeyResolver() {
return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostName());
}
/**
* API路径限流
* @return apiKeyResolver
*/
@Bean("apiKeyResolver")
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
- 参考配置如下所示
{
"id":"test-cus",
"uri":"lb://test-cus",
"order":0,
"predicates":[
{
"args":{
"pattern":"/test-cus/**"
},
"name":"Path"
}
],
"filters":[
{
"name":"RedisRateLimiter",
"args":{
"key-resolver":"#{@hostAddrKeyResolver}",
"redis-rate-limiter.replenishRate":"1000",
"redis-rate-limiter.burstCapacity":"1000"
}
}
]
}
- 实例化Bean提取为对象
– GatewayFilterDefinition保存过滤器等信息
– GatewayPredicateDefinition保存断言信息
– GatewayRouteDefinition 路由模型信息
@Data
public class GatewayFilterDefinition {
/**
* 过滤器名称
*/
private String name;
/**
* 对应的路由规则
*/
private Map<String, String> args = new LinkedHashMap<>();
}
@Data
public class GatewayPredicateDefinition {
/**
* 断言对应的Name
*/
private String name;
/**
* 配置的断言规则
*/
private Map<String, String> args = new LinkedHashMap<>();
}
@Data
public class GatewayRouteDefinition {
/**
* 路由Id唯一
*/
private String id;
/**
* 路由断言集合配置
*/
private List<GatewayPredicateDefinition> predicates=new ArrayList<>();
/**
* 路由过滤器集合配置
*/
private List<GatewayFilterDefinition> filters=new ArrayList<>();
/**
* 元数据
*/
private Map<String,Object> metadata=new HashMap<>();
/**
* 路由规则转发的目标uri
*/
private String uri;
/**
* 路由执行的顺序
*/
private int order=0;
}
- 然后是路由对外提供API,可以集成于管理界面操作
@RestController
public class WebRouterManger {
public static final String ROUTE_KEY="ROUTE_KEY";
private final RedisTemplate<String,Object> redisTemplate;
private final GatewayRoutesMapper routeMapper;
public WebRouterManger(GatewayRoutesMapper routeMapper,RedisTemplate<String,Object> redisTemplate) {
this.routeMapper=routeMapper;
this.redisTemplate=redisTemplate;
}
@GetMapping("/router")
public Flux<GatewayRouteDefinition> list() {
List<GatewayRouteDefinition> routeDefinitions=new ArrayList<>();
redisTemplate.opsForHash().values(ROUTE_KEY).forEach(routeDefinition -> routeDefinitions.add(JSON.parseObject(routeDefinition.toString(),GatewayRouteDefinition.class)));
return Flux.fromIterable(routeDefinitions);
}
/**
* 路由保存
*
* @param routeDefinition 路由信息
* @return CommonResult
*/
@PostMapping("/router")
@Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor=Exception.class)
public Mono<CommonResult> save(@RequestBody GatewayRouteDefinition routeDefinition) {
//判断是否存在
List<GatewayRoutes> routesList=routeMapper.selectByRouteName(routeDefinition.getId());
if (!CollectionUtils.isEmpty(routesList)) {
for (GatewayRoutes gatewayRoutes : routesList) {
int i=routeMapper.deleteByPrimaryKey(gatewayRoutes.getId());
if (i < 0) {
throw new RouterException(HttpStatus.INTERNAL_SERVER_ERROR.value(),"路由保存失败");
}
}
}
GatewayRoutes gatewayRoutes=new GatewayRoutes();
gatewayRoutes.setCreateTime(new Date());
gatewayRoutes.setFilters(JSONArray.toJSONString(routeDefinition.getFilters()));
gatewayRoutes.setRouteId(routeDefinition.getId());
gatewayRoutes.setRouteUri(routeDefinition.getUri());
gatewayRoutes.setRouteOrder(routeDefinition.getOrder());
gatewayRoutes.setPredicates(JSONArray.toJSONString(routeDefinition.getPredicates()));
gatewayRoutes.setRouteStatus("00");
gatewayRoutes.setMetadata(JSON.toJSONString(routeDefinition.getMetadata()));
if (1 == routeMapper.insertSelective(gatewayRoutes)) {
redisTemplate.opsForHash().put(ROUTE_KEY,routeDefinition.getId(),JSON.toJSONString(routeDefinition));
}
return Mono.just(CommonResult.success(""));
}
/**
* 路由删除
*
* @param routeId 微服务Id
* @param id 列表查询到的主键id
* @return CommonResult
*/
@DeleteMapping("/router")
@Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor=Exception.class)
public Mono<CommonResult> delete(@RequestParam("routeId") String routeId,@RequestParam("id") Long id) {
if (routeMapper.deleteByPrimaryKey(id) == 1) {
if (redisTemplate.opsForHash().hasKey(ROUTE_KEY,routeId)) {
redisTemplate.opsForHash().delete(ROUTE_KEY,routeId);
}
}
return Mono.just(CommonResult.success(""));
}
- 路由数据保存到数据库,做二级缓存兜底,数据库SQL
CREATE TABLE `gateway_routes` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`route_id` varchar(64) DEFAULT NULL COMMENT '路由id',
`route_uri` varchar(128) DEFAULT NULL COMMENT '转发目标uri',
`route_order` int(11) DEFAULT NULL COMMENT '路由执行顺序',
`predicates` text DEFAULT NULL COMMENT '断言字符串集合,字符串结构:[{\r\n "name":"Path",\r\n "args":{\r\n "pattern" : "/zy/**"\r\n }\r\n }]',
`metadata` text DEFAULT NULL COMMENT '元数据 JSON格式',
`filters` text DEFAULT NULL COMMENT '过滤器字符串集合,字符串结构:{\r\n "name":"StripPrefix",\r\n "args":{\r\n "_genkey_0":"1"\r\n }\r\n }',
`route_status` char(2) DEFAULT '' COMMENT '00 正常 99 失效',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4;
- 路由刷新时间实际上取的是eureka的探活时间,eureka.client.registry-fetch-interval-seconds,设定60秒后会执行Redis的路由刷新
- 基于Apollo与nacos配置中心这种形式的会更加的便捷,可以考虑使用配置中心做动态化路由,实现方式也比较简单
- 只需要将json配置保存后转换为RouteDefinition 然后调用RefreshRoutesEvent重加载路由信息即可,推荐使用这种方式,后面有时间会写一个比较详细点的基于Apollo或nacos的动态路由
更多推荐
已为社区贡献1条内容
所有评论(0)