• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

SpringCloudGateway实现动态路由

武飞扬头像
Ralph Chen
帮助1

前言

SrpingCloudGateway是SpringCloud的路由网关组件,主要负责微服务间的路由转发除此之外还有负载均衡、接口限流等功能

常规使用时,只需要新建一个SpringBoot项目,加入依赖,在配置文件中配置其他服务的路由信息即可,但是这种方式如果有新的服务加入需要不断更改配置文件,动态路由可以将配置信息转移至数据库进行存储

实现动态路由的核心为实现RouteDefinitionRepository接口,它继承了RouteDefinitionLocator接口(获取路由信息)和RouteDefinitionWriter接口(对路由信息进行写入和删除)

RouteDefinitionRepository的默认实现类为基于内存实现的InMemoryRouteDefinitionRepository

基于Redis实现

在Redis中存储的数据类型为Hash

package com.ralph.gateway.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * 核心配置类,加载数据库的路由配置信息到redis
 * 将定义好的路由表信息通过此类读写到redis中
 */
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    /**
     * redis key
     */
    public static final String GATEWAY_ROUTES = "gateway:routes";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取路由信息
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinition.class));
        List<RouteDefinition> routeDefinitions = new ArrayList<>();
        List<RouteDefinition> values = redisTemplate.opsForHash().values(GATEWAY_ROUTES);
        values.stream().forEach(routeDefinition -> {
            routeDefinitions.add(routeDefinition);
        });
        return Flux.fromIterable(routeDefinitions);
    }

    /**
     * 写入路由信息
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinition.class));
            redisTemplate.opsForHash().put(GATEWAY_ROUTES, routeDefinition.getId(),routeDefinition);
            return Mono.empty();
        });
    }

    /**
     * 删除路由信息
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        routeId.subscribe(id -> {
            redisTemplate.opsForHash().delete(GATEWAY_ROUTES, id);
        });
        return Mono.empty();
    }
}

以上即实现了从Redis中获取路由信息

接下来详解如何将路由信息存储 至Redis中

package com.ralph.gateway.config;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
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.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

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


/**
 *
 * 核心配置类,项目初始化加载数据库的路由配置
 *
**/

@Slf4j
@Service
public class GatewayServiceHandler implements CommandLineRunner {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 路由缓存key
     */
    private static final String GATEWAY_ROUTES = "gateway:routes";

    // springboot启动后执行
    @Override
    public void run(String... args){
        // 向admin模块发送消息,更新路由列表
        log.info("更新路由信息");
        redisTemplate.convertAndSend("systemRouteRequestTopic","1");
    }

    public void loadRouteConfig(List<JSONObject> routeList) {
        redisTemplate.delete(GATEWAY_ROUTES);
        routeList.forEach(gatewayRoute -> {
            RouteDefinition definition = new RouteDefinition();

            PredicateDefinition predicate = new PredicateDefinition();

            FilterDefinition filter = new FilterDefinition();

            URI uri = URI.create(gatewayRoute.getStr("uri"));

            definition.setId(gatewayRoute.getStr("routeId"));
            // 名称是固定的,spring gateway会根据名称找对应的PredicateFactory
            JSONArray predicates = gatewayRoute.getJSONArray("predicates");
            for (Object temObj : predicates) {
                JSONObject temJson = (JSONObject)temObj;
                predicate.setName(temJson.getStr("name"));
                predicate.setArgs(temJson.get("args",Map.class));
            }

            JSONArray filters = gatewayRoute.getJSONArray("filters");
            for (Object temObj : filters) {
                JSONObject temJson = (JSONObject)temObj;
                filter.setName(temJson.getStr("name"));
                filter.setArgs(temJson.get("args",Map.class));
            }

            definition.setPredicates(Arrays.asList(predicate));
            definition.setFilters(Arrays.asList(filter));

            definition.setUri(uri);
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinition.class));
            redisTemplate.opsForHash().put(GATEWAY_ROUTES, definition.getId(),definition);
        });
    }
}

这个类实现了CommandLineRunner接口并重写了run方法,会在SpringBoot启动成功后执行run方法的业务逻辑

当前run方法的逻辑为通过Redis的发布订阅功能向admin服务发送一个消息admin服务在监听到消息后会从mysql数据库获取当前的路由信息并通过相同的方法将路由信息返回至gateway服务

当gateway服务监听到admin的消息后,会调用loadRouteConfig方法,将路由信息存储至redis中

Redis发布订阅

发布消息:

redisTemplate.convertAndSend("systemRouteRequestTopic","1")

该方法可以向渠道systemRouteRequestTopic发送一个消息,内容为1

此时如果有其他服务监听了systemRouteRequestTopic渠道,则会收到消息,进行处理

监听方法:

package com.ralph.admin.listener;

import com.谷歌.common.collect.Lists;
import com.ralph.common.core.constant.RedisKeyConstant;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import java.util.ArrayList;

@Configuration
public class RedisSubConfig {
 
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory factory, RedisMessageListener listener) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        // 监听的渠道列表
        ArrayList<ChannelTopic> channelTopics = Lists.newArrayList(
                new ChannelTopic(RedisKeyConstant.SYS_LOG_TYPE_TOPIC),
                new ChannelTopic(RedisKeyConstant.SYS_ROUTE_REQ_TOPIC));
        container.addMessageListener(listener, channelTopics);
        // 使用lambda,不需要创建listener类
        // container.addMessageListener((message,bytes)->{
        //     // 业务逻辑
        // },channelTopics);
        return container;
    }
}

对监听到的消息进行处理

package com.ralph.admin.listener;

import com.ralph.admin.service.SystemLogService;
import com.ralph.admin.service.SystemRouterService;
import com.ralph.common.core.constant.RedisKeyConstant;
import com.ralph.common.core.entity.SystemLog;
import com.ralph.common.core.utils.RedisUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class RedisMessageListener implements MessageListener {

    private final SystemLogService systemLogService;

    private final SystemRouterService systemRouterService;

    @Override
    public void onMessage(Message message, byte[] pattern) {

        // 监听到的渠道
        String channel = RedisUtils.strDeserialize(message.getChannel()).toString();
        switch (channel){
            case RedisKeyConstant.SYS_LOG_TYPE_TOPIC:
                // 收到的消息内容
                SystemLog systemLog = (SystemLog)RedisUtils.deserialize(message.getBody());
                systemLogService.save(systemLog);
                break;
            case RedisKeyConstant.SYS_ROUTE_REQ_TOPIC:
                systemRouterService.getRouteList();
                break;
        }
    }
}

其他方式

除了使用Redis的发布订阅模式外,还可以使用其他方式实现,如通过feign进行服务间的调用及数据传递也可以实现

完整项目代码地址:https://toscode.gitee.com/ralphchen/ralph-cloud

博客并未粘贴全部代码内容,如果想了解全部流程,可以根据粘贴在博客中的代码的包路径在完整项目代码中找到对应位置


👍 欢迎前往博客主页查看更多内容

👍 如果觉得不错,期待您的点赞、收藏、评论、关注

👍 如有错误欢迎指正!

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfckfjb
系列文章
更多 icon
同类精品
更多 icon
继续加载