简单自由 · 2019年11月09日

Spring Cloud gateway 网关四 动态路由

微服务当前这么火爆的程度,如果不能学会一种微服务框架技术。怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习。说没有时间?没有精力?要学俩个框架?而Spring Cloud alibaba只需要你学会一个就会拥有俩种微服务治理框架技术。何乐而不为呢?加油吧!骚猿年

在之前的 zuul 我们讲了。怎么去动态的获取路由。作为Spring Cloud 亲儿子的存在 gateway 不可能不支持动态路由。今天我们初探一下gateway 的动态路由。

需求前置。在了解动态路由。可能会能想到,我们的数据要存储在数据库中或者redis 当中。还要有存储的实体类。

思考:


配置中心刷新routes配置信息。路由信息刷新改变。利用事件发布,利用配置中心完成动态刷新路由。本次改造我们使用自定义存储方式达到手动触发动态路由

InMemoryRouteDefinitionRepository 默认使用。这个类就是把握们当前的所有的路由routes 存储在内存当中。当服务重启或者刷新,内存就不复存在。ps: 因为本项目是用nacos 注册中心也是配置中心。可以存储在nacos 配置中心里面。
RouteDefinitionRepository 接口是InMemoryRouteDefinitionRepository 是它的接口类 继承了 RouteDefinitionLocator 、RouteDefinitionWriter俩个接口。

RouteDefinitionLocator 接口

file

RouteDefinitionWriter 用来实现路由的添加与删除。

思考:

基于这个原则我们在动态添加或者删除路由的时候,就可以根据这个接口实现去满足我们动态的控制路由规则。

RouteDefinitionRepository 这个接口成为了关键点我们来重新动态路由其实也是基于这个接口来实现

实体类

package com.xian.cloud.model;

import lombok.Data;

/**
 * @Author: xlr
 * @Date: Created in 5:10 PM 2019/9/29
 */
@Data
public class GatewayRoutesEntity {

    private Long id;

    private String serviceId;

    private String uri;

    private String predicates;

    private String filters;

}

GatewayRoutesService 接口

package com.xian.cloud.service;

import com.xian.cloud.model.GatewayRoutesEntity;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;

import java.util.List;

/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 16:08
 */
public interface GatewayRoutesService {

    List<GatewayRoutesEntity> findAll() throws Exception;

    String loadRouteDefinition() throws Exception;

    GatewayRoutesEntity save(GatewayRoutesEntity gatewayDefine) throws Exception;

    void deleteById(String id) throws Exception;

    boolean existsById(String id)throws Exception;

    List<PredicateDefinition> getPredicateDefinition(String predicates) ;

    List<FilterDefinition> getFilterDefinition(String filters) ;

}

GatewayRoutesService 实现类

package com.xian.cloud.service.impl;

import com.alibaba.fastjson.JSON;
import com.xian.cloud.model.GatewayRoutesEntity;
import com.xian.cloud.service.GatewayRoutesService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

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

/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 16:09
 */
@Service
@Slf4j
public class GatewayRoutesServiceImpl implements GatewayRoutesService {

    public static final String GATEWAY_DEFINE_LIST_KEY = "gateway_routes_list_key";

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;

    @Override
    public List <GatewayRoutesEntity> findAll() throws Exception {

        Long size = redisTemplate.opsForList().size( GATEWAY_DEFINE_LIST_KEY );
        List<GatewayRoutesEntity> list = redisTemplate.opsForList().range( GATEWAY_DEFINE_LIST_KEY, 0, size );
        return list;
    }

    @Override
    public String loadRouteDefinition() {
        try {
            List <GatewayRoutesEntity> gatewayDefineServiceAll = findAll();
            if (gatewayDefineServiceAll == null) {
                return "none route defined";
            }
            for (GatewayRoutesEntity gatewayDefine : gatewayDefineServiceAll) {
                RouteDefinition definition = new RouteDefinition();
                definition.setId( gatewayDefine.getServiceId() );
                definition.setUri( new URI( gatewayDefine.getUri() ) );
                List <PredicateDefinition> predicateDefinitions = getPredicateDefinition(gatewayDefine.getPredicates());
                if (predicateDefinitions != null) {
                    definition.setPredicates( predicateDefinitions );
                }
                List <FilterDefinition> filterDefinitions = getFilterDefinition(gatewayDefine.getFilters());
                if (filterDefinitions != null) {
                    definition.setFilters( filterDefinitions );
                }
                routeDefinitionWriter.save( Mono.just( definition ) ).subscribe();
                publisher.publishEvent( new RefreshRoutesEvent( this ) );
            }
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
            return "failure";
        }
    }

    /**
     * 获取所有的 自定义路由规则
     * @param gatewayDefine
     * @return
     * @throws Exception
     */
    @Override
    public GatewayRoutesEntity save(GatewayRoutesEntity gatewayDefine) throws Exception {
        log.info( "save RouteDefinition : {}",gatewayDefine );
        redisTemplate.opsForList().rightPush(  GATEWAY_DEFINE_LIST_KEY, gatewayDefine );
        return gatewayDefine;
    }

    @Override
    public void deleteById(String id) throws Exception {
        List <GatewayRoutesEntity> all = findAll();
        for (GatewayRoutesEntity gatewayDefine : all) {
            if(gatewayDefine.getServiceId().equals( id )){
                redisTemplate.opsForList().remove( GATEWAY_DEFINE_LIST_KEY,0, gatewayDefine);
            }
        }
    }

    @Override
    public boolean existsById(String id) throws Exception {
        List <GatewayRoutesEntity> all = findAll();
        for (GatewayRoutesEntity gatewayDefine : all) {
            if(gatewayDefine.getServiceId().equals( id )){
                return true;
            }
        }
        return false;
    }

    @Override
    public List<PredicateDefinition> getPredicateDefinition(String predicates) {
        if ( StringUtils.isNotBlank( predicates )) {
            List<PredicateDefinition> predicateDefinitionList = JSON.parseArray(predicates, PredicateDefinition.class);
            return predicateDefinitionList;
        } else {
            return null;
        }
    }
    @Override
    public List<FilterDefinition> getFilterDefinition(String filters) {
        if (StringUtils.isNotBlank( filters )) {
            List<FilterDefinition> filterDefinitionList = JSON.parseArray(filters, FilterDefinition.class);
            return filterDefinitionList;
        } else {
            return null;
        }
    }
}

然后我们在进行 RouteDefinitionLocator 、RouteDefinitionWriter 俩个接口的声明 在配置文件中

package com.xian.cloud.config;

import com.xian.cloud.repository.GatewayRoutesRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * <Description> 动态更新路由
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 17:12
 */
@Configuration
@Slf4j
public class GatewayRoutesDefinitionConfig {


    @Bean
    RouteDefinitionLocator routeDefinitionLocator(){
        return new GatewayRoutesRepository();
    }
        
    @Bean
    @Primary
    RouteDefinitionWriter routeDefinitionWriter(){
        return new GatewayRoutesRepository();
    }
}

RefreshRoutesEvent 事件发布刷新routes事件,通知网关。 这个事件是gateway 的事件。

package com.xian.cloud.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;

/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 17:20
 */
@Component
@Slf4j
public class RefreshRouteService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

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

    /**
     * 刷新路由表
     */
    public void refreshRoutes() {
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }
}

然后我们还差一个手动触发的接口,创建GatewayRoutesController

package com.xian.cloud.controller;

import com.xian.cloud.event.RefreshRouteService;
import com.xian.cloud.model.RestResult;
import com.xian.cloud.model.RestResultBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 17:18
 */
@RestController
@RequestMapping("/gateway")

public class GatewayRoutesController {

    @Autowired
    private RefreshRouteService refreshRouteService;

    @GetMapping("/refreshRoutes")
    public RestResult refreshRoutes(){
        refreshRouteService.refreshRoutes();
        return RestResultBuilder.builder().success().build();
    }
}

到此为止,就完成了所有的代码。

启动服务

其实还有一种很简单直接的写法。参考重新定义 Spring Cloud 实战中的写法。

创建事件增加类DynamicRouteServiceImpl

package com.xian.cloud.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
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 reactor.core.publisher.Mono;

/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/09 10:40
 */
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    @Qualifier("routeDefinitionRepositor")
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;

    /**
     * 添加路由实体类
     * @param definition
     * @return
     */
    public boolean add(RouteDefinition definition){
        routeDefinitionWriter.save((Mono<RouteDefinition>) Mono.just(definition).subscribe());
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return true;
    }

    /**
     *
     * @param definition 路由实体类
     * @return
     */
    public boolean update(RouteDefinition definition){
        try {
            routeDefinitionWriter.delete(Mono.just(definition.getId()));
        }catch (Exception e){
            log.error("update 失败。没有找到对应的路由ID :{}",definition.getId());
        }

        routeDefinitionWriter.save((Mono<RouteDefinition>) (Mono.just(definition)).subscribe());
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return true;
    }

    /**
     * serviceId
     * @param id
     * @return
     */
    public boolean del(String id){
        routeDefinitionWriter.delete(Mono.just(id));
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return true;
    }

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

修改controller 添加三个方法

package com.xian.cloud.controller;

import com.xian.cloud.event.DynamicRouteServiceImpl;
import com.xian.cloud.event.RefreshRouteService;
import com.xian.cloud.model.RestResult;
import com.xian.cloud.model.RestResultBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.web.bind.annotation.*;

/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 17:18
 */
@RestController
@RequestMapping("/gateway")
public class GatewayRoutesController {

    @Autowired
    private RefreshRouteService refreshRouteService;

    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;

    @GetMapping("/refreshRoutes")
    public RestResult refreshRoutes(){
        refreshRouteService.refreshRoutes();
        return RestResultBuilder.builder().success().build();
    }

    /**
     *
     * @param definition
     * @return
     */
    @RequestMapping(value = "routes/add",method = RequestMethod.POST)
    public RestResult add(@RequestBody RouteDefinition definition){
        boolean flag = dynamicRouteService.add(definition);
        if(flag){
            return RestResultBuilder.builder().success().build();
        }
        return RestResultBuilder.builder().failure().build();
    }

    /**
     *
     * @param definition
     * @return
     */
    @RequestMapping(value = "routes/update",method = RequestMethod.POST)
    public RestResult update(@RequestBody RouteDefinition definition){
        boolean flag = dynamicRouteService.add(definition);
        if(flag){
            return RestResultBuilder.builder().success().build();
        }
        return RestResultBuilder.builder().failure().build();
    }

    /**
     *
     * @param serviceId
     * @return
     */
    @RequestMapping(value = "routes/del",method = RequestMethod.POST)
    public RestResult update(@RequestParam("serviceId") String serviceId){
        boolean flag = dynamicRouteService.del(serviceId);
        if(flag){
            return RestResultBuilder.builder().success().build();
        }
        return RestResultBuilder.builder().failure().build();
    }
}

增删改。三个接口对外暴露。

以上就是gateway 的动态刷新路由。

思考

动态刷新能满足我们在服务运维上的管理方便。但是因为这个整体使用的nacos 配置中心做的。这样做的成本考虑。动态刷新是不是真的有必要这么做。完全可以在配置中心配置然后刷新。就能触发路由的整体刷新。如果需要自己搭建运维管理中心。有自己的管理体系可以实现动态路由。

摘自参考 spring cloud 官方文档

示例代码地址

服务器nacos 地址 http://47.99.209.72:8848/nacos

往期地址 spring cloud 文章地址

spring cloud alibaba 简介

Spring Cloud Alibaba (nacos 注册中心搭建)

Spring Cloud Alibaba 使用nacos 注册中心

Spring Cloud Alibaba nacos 配置中心使用

spring cloud 网关服务

Spring Cloud zuul网关服务 一

Spring Cloud 网关服务 zuul 二

Spring Cloud 网关服务 zuul 三 动态路由

Spring Cloud alibaba网关 sentinel zuul 四 限流熔断

Spring Cloud gateway 网关服务 一

Spring Cloud gateway 网关服务二 断言、过滤器

Spring Cloud gateway 三 自定义过滤器GatewayFilter

如何喜欢可以关注分享本公众号。
file

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。转载请附带公众号二维码

推荐阅读
关注数
1
文章数
16
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息