网关功能-路由转发

我们的微服务网关其中一个作用便是统一入口,根据请求进行路由转发,将我们的请求解析到最终的微服务之中去

gateway网关为我们提供了丰富的路由谓词操作,根们我们的路由谓词进行请求转发

详情请查看>>>>>>>>spring-cloud(十一)GateWay强大的路由谓词(断言)功能

路由谓词转发示例:

spring:
  cloud:
    gateway:
      routes:
      	 # 路由ID
        - id: location
          order: -1
          uri: lb://app-location-center
          predicates:
            # 当请求以/location/** 开头时,最终将被转发到上方url中
            - Path=/location/**
          filters:
          	# 截断一位url请求前缀
            - StripPrefix=1

当然我们也可以不定义路由谓词进行请求转发,使用默认的微服务名进行转发(此种操作需要网关注册到注册中心)

根据微服务名-服务发现进行转发配置

spring:
  cloud:
    gateway:
      discovery:
        locator:
          # 开启服务发现路由转发,会根据请求url中的服务名,将请求转发到对应的服务
          enabled: true

gateway网关路由端点API

gateway 依赖中默认包含了路由端点操作,我们只需要引入依赖即可查看

implementation 'org.springframework.boot:spring-boot-starter-actuator'

服务Yml配置

management:
  endpoint:
    gateway:
      # gateway 端点开启
      enabled: true
  endpoints:
    web:
      exposure:
        include: "gateway"

我们可以在yml中按住ctrl 点后点击enable查看gateway端点底层代码

image-20220412210435923

端点源代码

image-20220412210715118

image-20220412210643038

我们发现,gateway端点中为我们提供了路由信息的CRUD操作

url如下:

# 获取网关已加载所有路由信息
GET http://localhost:8901/actuator/gateway/routes

# 根据路由ID获取路由信息
GET http://localhost:8901/actuator/gateway/routes/{id}

# 刷新路由信息
POST http://localhost:8901/actuator/gateway/refresh

# 新增路由信息
POST http://localhost:8901/actuator/gateway/routes/{id}  与路由json体
.....更详细的请自己查看源码

image-20220412212417660

gateway网关路由操作源码跟踪

新增路由

上方已经展示了路由端点API,接下来,咱们要实现路由持久化可以先看下原本默认API代码,熟悉其中的原理,然后进行改写

我们还是先来到AbstractGatewayControllerEndpoint 这个类中,查看路由新增做了什么事情

image-20220412213456047

我们接下来看,进行理由保存的routeDefinitionWriter

从源码上看,其是注入的RouteDefinitionWriter 这个接口

protected RouteDefinitionWriter routeDefinitionWriter;

RouteDefinitionWriter 接口中提供了路由的新增与删除抽象方法

image-20220412213657316

然后查看其实现类或者子接口

image-20220412214122234

RouteDefinitionRepository接口中又做了什么事情呢?查看源码,其无任何自身的抽象接口,仅仅继承了路由加载器接口RouteDefinitionLocator (只有路由查询) 以及路由操作写入接口RouteDefinitionWriter(只有路由新增与删除)

哦,懂了,其实RouteDefinitionRepository 就是将两个接口的抽象方法聚在了一起罢了

public interface RouteDefinitionRepository
      extends RouteDefinitionLocator, RouteDefinitionWriter {

}

image-20220412213956385

然后跟踪到最底层的实现类InMemoryRouteDefinitionRepository,这就是路由操作的最终实现

我们发现,其对路由的增删查操作均是基于jvm内存操作的,并无任何持久化操作,这也是为何使用端点操作路由服务重启后,功能失效的原因

image-20220412214841008

路由刷新

使用ApplicationEventPublisher 事件发布后,最终会被路由监听到,然后从路由加载器拉取最新路由信息

image-20220412215714159

image-20220412220913522

image-20220412220617212

gateway启动时路由从何加载

会从接口RouteDefinitionLocator

image-20220412211934874

其每个子类实现的getRouteDefinitions方法获取路由信息,然后加载到内存之中

image-20220412212024628

比如我们的服务发现路由加载器

image-20220412212216490

配置文件路由加载器

image-20220412212308388

然后后续拉取则从内存缓存中拉取路由信息

image-20220412220913522

gateway动态路由实现

动态路由实现并持久化

持久化核心逻辑

写入持久层,刷新内存缓存,进行事件发布

package com.leilei.gateway.service;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.leilei.gateway.entity.Result;
import com.leilei.gateway.entity.form.RouteForm;
import com.leilei.gateway.entity.po.RouteInfo;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author lei
 * @create 2022-04-11 15:35
 * @desc
 **/
@Service
@Log4j2
public class DynamicRouteService implements ApplicationEventPublisherAware {

    private final RouteDefinitionWriter routeDefinitionWriter;

    private final RouteService routeService;

    private ApplicationEventPublisher publisher;

    public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter, RouteService routeService) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeService = routeService;
    }


    /**
     * 增加路由
     *
     * @param routeForm
     * @return
     */
    public Result<Boolean> add(RouteForm routeForm) {
        RouteDefinition definition = convert(routeForm);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        enduranceRule(routeForm.getName(), routeForm.getDescription(), definition);
        publishRouteEvent();
        System.out.println(JSON.toJSONString(definition));
        return Result.success(true);
    }

    /**
     * 更新路由
     *
     * @param routeForm
     * @return
     */
    public Result<Boolean> update(RouteForm routeForm) {
        RouteDefinition definition = convert(routeForm);
        try {
            this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
        } catch (Exception e) {
            return Result.fail("未知路由信息");
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            enduranceRule(routeForm.getName(), routeForm.getDescription(), definition);
            publishRouteEvent();
            return Result.success(true);
        } catch (Exception e) {
            return Result.fail("路由信息修改失败!");
        }
    }

    /**
     * 删除路由
     *
     * @param id
     * @return
     */
    public Result<Boolean> delete(String id) {
        this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        routeService.remove(buildWrapper(id));
        publishRouteEvent();
        return Result.success();
    }

    private void publishRouteEvent() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }


    public Result<Boolean> flushRoute() {
        publishRouteEvent();
        return Result.success(true);
    }

    public LambdaQueryWrapper<RouteInfo> buildWrapper(String routeId) {
        return new QueryWrapper<RouteInfo>().lambda()
                .eq(RouteInfo::getRouteId, routeId)
                .last("limit 1");
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }



    /**
     * 把自定义请求模型转换为
     *
     * @param form
     * @return
     */
    private RouteDefinition convert(RouteForm form) {
        RouteDefinition definition = new RouteDefinition();
        definition.setId(form.getId());
        definition.setOrder(form.getOrder());
        //设置断言
        List<PredicateDefinition> predicateDefinitions = form.getPredicates().stream()
                .distinct().map(predicateInfo -> {
                    PredicateDefinition predicate = new PredicateDefinition();
                    predicate.setArgs(predicateInfo.getArgs());
                    predicate.setName(predicateInfo.getName());
                    return predicate;
                }).collect(Collectors.toList());
        definition.setPredicates(predicateDefinitions);

        // 设置过滤
        List<FilterDefinition> filterList = form.getFilters().stream().distinct().map(x -> {
            FilterDefinition filter = new FilterDefinition();
            filter.setName(x.getName());
            filter.setArgs(x.getArgs());
            return filter;
        }).collect(Collectors.toList());
        definition.setFilters(filterList);
        // 设置URI,判断是否进行负载均衡
        URI uri;
        if (form.getUri().startsWith("http")) {
            uri = UriComponentsBuilder.fromHttpUrl(form.getUri()).build().toUri();
        } else {
            uri = URI.create(form.getUri());
        }
        definition.setUri(uri);
        return definition;
    }


    /**
     * 数据落库
     */
    public void enduranceRule(String name, String description, RouteDefinition definition) {
        String id = definition.getId();
        List<PredicateDefinition> predicates = definition.getPredicates();
        List<FilterDefinition> filters = definition.getFilters();
        int order = definition.getOrder();
        URI uri = definition.getUri();
        RouteInfo routeInfo = new RouteInfo();
        routeInfo.setName(name);
        routeInfo.setRouteId(id);
        routeInfo.setUri(uri.toString());
        routeInfo.setPredicates(JSON.toJSONString(predicates));
        routeInfo.setFilters(JSON.toJSONString(filters));
        routeInfo.setEnabled(true);
        routeInfo.setDescription(description);
        routeInfo.setOrderNum(order);
        routeInfo.setDeleteFlag(false);
        RouteInfo one = routeService.getOne(buildWrapper(id));
        if (one == null) {
            routeService.save(routeInfo);
        } else {
            routeInfo.setId(one.getId());
            routeService.updateById(routeInfo);
        }

    }
}

自定义路由操作(可以在此类进行持久化处理也可以像我上方一样,持久层交由其他服务类去做)

package com.leilei.gateway.service;

import javassist.NotFoundException;
import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author lei
 * @create 2022-04-11 16:20
 * @desc 自定义内存路由管理仓,开启日志打印
 **/
@Service
@Log4j2
public class MyRouteDefinitionRepository implements RouteDefinitionRepository {

    public  final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap<>());

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        Collection<RouteDefinition> values = routes.values();
        return Flux.fromIterable(values);
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap( r -> {
            log.info("新增路由信息:{}",r);
            routes.put(r.getId(), r);
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            log.info("删除路由信息,路由ID为:{}",id);
            if (routes.containsKey(id)) {
                routes.remove(id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: "+routeId)));
        });
    }


}

从持久层额外加载动态路由

核心原理

启动项目,在从路由加载器RouteDefinitionLocator各个子类拉取路由外,额外进入我们的路由持久层加载路由

改写上方的动态路由服务类,进行项目启动加载

package com.leilei.gateway.service;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.leilei.gateway.entity.Result;
import com.leilei.gateway.entity.form.RouteForm;
import com.leilei.gateway.entity.po.RouteInfo;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author lei
 * @create 2022-04-11 15:35
 * @desc
 **/
@Service
@Log4j2
public class DynamicRouteService implements ApplicationEventPublisherAware, ApplicationRunner {

    private final RouteDefinitionWriter routeDefinitionWriter;

    private final RouteService routeService;

    private ApplicationEventPublisher publisher;

    public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter, RouteService routeService) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeService = routeService;
    }


    /**
     * 增加路由
     *
     * @param routeForm
     * @return
     */
    public Result<Boolean> add(RouteForm routeForm) {
        RouteDefinition definition = convert(routeForm);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        enduranceRule(routeForm.getName(), routeForm.getDescription(), definition);
        publishRouteEvent();
        System.out.println(JSON.toJSONString(definition));
        return Result.success(true);
    }

    /**
     * 更新路由
     *
     * @param routeForm
     * @return
     */
    public Result<Boolean> update(RouteForm routeForm) {
        RouteDefinition definition = convert(routeForm);
        try {
            this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
        } catch (Exception e) {
            return Result.fail("未知路由信息");
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            enduranceRule(routeForm.getName(), routeForm.getDescription(), definition);
            publishRouteEvent();
            return Result.success(true);
        } catch (Exception e) {
            return Result.fail("路由信息修改失败!");
        }
    }

    /**
     * 删除路由
     *
     * @param id
     * @return
     */
    public Result<Boolean> delete(String id) {
        this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        routeService.remove(buildWrapper(id));
        publishRouteEvent();
        return Result.success();
    }

    private void publishRouteEvent() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }


    public Result<Boolean> flushRoute() {
        publishRouteEvent();
        return Result.success(true);
    }

    public LambdaQueryWrapper<RouteInfo> buildWrapper(String routeId) {
        return new QueryWrapper<RouteInfo>().lambda()
                .eq(RouteInfo::getRouteId, routeId)
                .last("limit 1");
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }


    @Override
    public void run(ApplicationArguments args) {
        log.info("----------从持久层加载额外路由信息---------");
        this.loadRouteConfig();
    }

    private void loadRouteConfig() {
        List<RouteDefinition> routeList = routeService.getRouteList();
        log.info("----------持久层额外路由数量:{}---------",routeList.size());
        routeList.forEach(x -> routeDefinitionWriter.save(Mono.just(x)).subscribe());
        publishRouteEvent();
    }


    /**
     * 把自定义请求模型转换为
     *
     * @param form
     * @return
     */
    private RouteDefinition convert(RouteForm form) {
        RouteDefinition definition = new RouteDefinition();
        definition.setId(form.getId());
        definition.setOrder(form.getOrder());
        //设置断言
        List<PredicateDefinition> predicateDefinitions = form.getPredicates().stream()
                .distinct().map(predicateInfo -> {
                    PredicateDefinition predicate = new PredicateDefinition();
                    predicate.setArgs(predicateInfo.getArgs());
                    predicate.setName(predicateInfo.getName());
                    return predicate;
                }).collect(Collectors.toList());
        definition.setPredicates(predicateDefinitions);

        // 设置过滤
        List<FilterDefinition> filterList = form.getFilters().stream().distinct().map(x -> {
            FilterDefinition filter = new FilterDefinition();
            filter.setName(x.getName());
            filter.setArgs(x.getArgs());
            return filter;
        }).collect(Collectors.toList());
        definition.setFilters(filterList);
        // 设置URI,判断是否进行负载均衡
        URI uri;
        if (form.getUri().startsWith("http")) {
            uri = UriComponentsBuilder.fromHttpUrl(form.getUri()).build().toUri();
        } else {
            uri = URI.create(form.getUri());
        }
        definition.setUri(uri);
        return definition;
    }


    /**
     * 数据落库
     */
    public void enduranceRule(String name, String description, RouteDefinition definition) {
        String id = definition.getId();
        List<PredicateDefinition> predicates = definition.getPredicates();
        List<FilterDefinition> filters = definition.getFilters();
        int order = definition.getOrder();
        URI uri = definition.getUri();
        RouteInfo routeInfo = new RouteInfo();
        routeInfo.setName(name);
        routeInfo.setRouteId(id);
        routeInfo.setUri(uri.toString());
        routeInfo.setPredicates(JSON.toJSONString(predicates));
        routeInfo.setFilters(JSON.toJSONString(filters));
        routeInfo.setEnabled(true);
        routeInfo.setDescription(description);
        routeInfo.setOrderNum(order);
        routeInfo.setDeleteFlag(false);
        RouteInfo one = routeService.getOne(buildWrapper(id));
        if (one == null) {
            routeService.save(routeInfo);
        } else {
            routeInfo.setId(one.getId());
            routeService.updateById(routeInfo);
        }

    }
}

动态路由演示

网关启动,一开始并无额外动态路由信息

image-20220412222049549

查看所有网关加载路由

http://localhost:8901/actuator/gateway/routes
[
    {
        "predicate": "Paths: [/location/**], match trailing slash: true",
        "route_id": "location",
        "filters": [
            "[[StripPrefix parts = 1], order = 1]"
        ],
        "uri": "lb://app-location-center",
        "order": -1
    },
    {
        "predicate": "Paths: [/app-location-center/**], match trailing slash: true",
        "metadata": {
            "nacos.instanceId": null,
            "nacos.weight": "1.0",
            "nacos.cluster": "DEFAULT",
            "nacos.ephemeral": "true",
            "nacos.healthy": "true",
            "preserved.register.source": "SPRING_CLOUD"
        },
        "route_id": "ReactiveCompositeDiscoveryClient_app-location-center",
        "filters": [
            "[[RewritePath /app-location-center/(?<remaining>.*) = '/${remaining}'], order = 1]"
        ],
        "uri": "lb://app-location-center",
        "order": 0
    },
    {
        "predicate": "Paths: [/dodo-server-gateway/**], match trailing slash: true",
        "metadata": {
            "nacos.instanceId": null,
            "nacos.weight": "1.0",
            "nacos.cluster": "DEFAULT",
            "nacos.ephemeral": "true",
            "nacos.healthy": "true",
            "preserved.register.source": "SPRING_CLOUD"
        },
        "route_id": "ReactiveCompositeDiscoveryClient_dodo-server-gateway",
        "filters": [
            "[[RewritePath /dodo-server-gateway/(?<remaining>.*) = '/${remaining}'], order = 1]"
        ],
        "uri": "lb://dodo-server-gateway",
        "order": 0
    },
    {
        "predicate": "Paths: [/app-base-center/**], match trailing slash: true",
        "metadata": {
            "preserved.heart.beat.timeout": "10000",
            "nacos.instanceId": null,
            "nacos.weight": "1.0",
            "nacos.cluster": "DEFAULT",
            "nacos.ephemeral": "true",
            "nacos.healthy": "true",
            "preserved.ip.delete.timeout": "15000",
            "preserved.register.source": "SPRING_CLOUD",
            "preserved.heart.beat.interval": "5000"
        },
        "route_id": "ReactiveCompositeDiscoveryClient_app-base-center",
        "filters": [
            "[[RewritePath /app-base-center/(?<remaining>.*) = '/${remaining}'], order = 1]"
        ],
        "uri": "lb://app-base-center",
        "order": 0
    }
]

访问预添加路由信息

image-20220412222639008

添加路由并持久化

{
	"name":"bilibili路由",
	"description":"测试自定义路由信息",
	"filters": [{
		"name": "StripPrefix",
		"args": {
			"_genkey_0": "1"
		}
	}],
	"id": "test-route",
	"uri": "https://www.bilibili.com/anime/?spm_id_from=333.1007.0.0",
	"order": 2,
	"predicates": [{
		"name": "Path",
		"args": {
			"_genkey_0": "/b3"
		}
	}]
}

image-20220412222234754

数据也已经落到了DB

image-20220412222705545

打印了路由新增信息(加入到内存)

image-20220412222749707

查看网关已加载路由信息

再次访问

我们再次访问http://localhost:8901/b3 网关已经帮我们路由到了我们定义的url了(定义的bilibili)

image-20220412222859971

修改路由后再次访问

image-20220412223147105

image-20220412223123567

发现网关路由信息成功修改,为我们转发到了csdn(虽然css样式丢失了)

image-20220412223237147

项目重启,测试路由从持久层加载

image-20220412223403062

项目源码

gitee-springcloud-alibaba-2022 中的 gateway模块

github-springcloud-alibaba-2022 中的 gateway模块

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐