Nacos dynamic routing configuration (1)-related source code analysis

1: Configure related nacos information

Other information has been omitted

Gateway.yml in the project

nacos:
  server-addr:xx.xx.xx.xx:8848
  namespace: dev

#省略其他代码。。。。。。。。。

#动态路由相关配置
dynamic:
  route:
    data-id: routes-${spring.application.name}.yaml
    group: ROUTE_GROUP
    server-addr: xx.xx.xx.xx:8848
    namespace: dev

 

2: Create related entity classes

filter

package com.carry.www.entity;

/**
 * 过滤器实体类
 *
 */
import lombok.Data;

import java.util.LinkedHashMap;
import java.util.Map;

@Data
public class FilterEntity {

  // 过滤器对应的Name
  private String name;

  // 路由规则
  private Map<String, String> args = new LinkedHashMap<>();

}

Affirmation:

package com.carry.www.entity;

import lombok.Data;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 路由断言实体类
 *
 */
@Data
public class PredicateEntity {
  // 断言对应的Name
  private String name;

  // 断言规则
  private Map<String, String> args = new LinkedHashMap<>();

}

Routing class

package com.carry.www.entity;

import lombok.Data;

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

/**
 * 路由实体类
 *
 */
@Data
public class RouteEntity {
  // 路由id
  private String id;

  // 路由断言集合
  private List<PredicateEntity> predicates = new ArrayList<>();

  // 路由过滤器集合
  private List<FilterEntity> filters = new ArrayList<>();

  // 路由转发的目标uri
  private String uri;

  // 路由执行的顺序
  private int order = 0;
}

3: Add monitor

Monitor nacos configuration information

package com.carry.www.config;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.carry.www.entity.FilterEntity;
import com.carry.www.entity.PredicateEntity;
import com.carry.www.entity.RouteEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 从Nacos获取动态路由
 * 实现ApplicationEventPublisherAware发布接口来发布路由更新事件
 */
@Configuration
@Slf4j
public class DynamicRoutingConfig implements ApplicationEventPublisherAware {

    @Value("${dynamic.route.server-addr}")
    private String serverAddr;

    @Value("${nacos.namespace}")
    private String namespace;

    @Value("${dynamic.route.data-id}")
    private String dataId;

    @Value("${dynamic.route.group}")
    private String groupId;

    // 保存、删除路由服务
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher applicationEventPublisher;

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

    /**
     * @return
     * @Author carryer
     * @Description 获取nacos配置服务
     * @Date
     * @Param
     **/
    public ConfigService getNacosConfigInfo() throws Exception {
        Properties properties = new Properties();
        properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverAddr);
//        properties.setProperty(PropertyKeyConst.USERNAME, username);
//        properties.setProperty(PropertyKeyConst.PASSWORD, password);
        properties.setProperty(PropertyKeyConst.NAMESPACE, namespace);
        ConfigService configService = NacosFactory.createConfigService(properties);

        return configService;
    }

    /**
     * @return
     * @Author carryer
     * @Description 初始化路由
     * @Date
     * @Param
     **/
    @Bean
    public void initRouting() {
        try {
            ConfigService configService = this.getNacosConfigInfo();
            String configInfo = configService.getConfig(dataId, groupId, 5000);
            if (null != configInfo) {
                List<RouteEntity> list = JSONObject.parseArray(configInfo).toJavaList(RouteEntity.class);
                for (RouteEntity route : list) {
                    update(assembleRouteDefinition(route));
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            e.printStackTrace();
        }
    }

    /**
     * @return
     * @Author carryer
     * @Description 刷新路由
     * @Date
     * @Param
     **/
    @Bean
    public void refreshRouting() {
        try {
            ConfigService configService = this.getNacosConfigInfo();
            //监听路由变化
            configService.addListener(
                    dataId,
                    groupId,
                    new Listener() {
                        @Override
                        public Executor getExecutor() {
                            return null;
                        }

                        @Override
                        public void receiveConfigInfo(String configInfo) {
                            try {
                                log.info(configInfo);
                                if (null != configInfo) {
                                    List<RouteEntity> list =
                                            JSONObject.parseArray(configInfo).toJavaList(RouteEntity.class);
                                    //更新路由表
                                    for (RouteEntity route : list) {
                                        update(assembleRouteDefinition(route));
                                    }
                                }
                            } catch (Exception e) {
                                log.error(e.getMessage(), e);
                                e.printStackTrace();
                            }
                        }
                    });
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            e.printStackTrace();
        }
    }

    /**
     * @return
     * @Author carryer
     * @Description 路由更新
     * @Date
     * @Param
     **/
    private void update(RouteDefinition routeDefinition) throws Exception {
        //先删除路由
        routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
        //再保存路由
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        //发布事件 发布者是RefreshRoutesEvent 事件是刷新路由
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * @return
     * @Author carryer
     * @Description 实体信息解析
     * @Date
     * @Param
     **/
    private RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) {
        RouteDefinition definition = new RouteDefinition();
        // ID
        definition.setId(routeEntity.getId());

        // Predicates断言
        List<PredicateDefinition> pdList = new ArrayList<>();
        for (PredicateEntity predicateEntity : routeEntity.getPredicates()) {
            PredicateDefinition predicateDefinition = new PredicateDefinition();
            predicateDefinition.setArgs(predicateEntity.getArgs());
            predicateDefinition.setName(predicateEntity.getName());
            pdList.add(predicateDefinition);
        }
        definition.setPredicates(pdList);

        // Filters过滤器
        List<FilterDefinition> fdList = new ArrayList<>();
        for (FilterEntity filterEntity : routeEntity.getFilters()) {
            FilterDefinition filterDefinition = new FilterDefinition();
            filterDefinition.setArgs(filterEntity.getArgs());
            filterDefinition.setName(filterEntity.getName());
            fdList.add(filterDefinition);
        }
        definition.setFilters(fdList);

        // URI
        URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
        definition.setUri(uri);

        return definition;
    }
}

The above code nacos can dynamically add routing, and friends can use it directly

============================= dividing line=================== ==========================

4: Relevant source code analysis

In the above configuration, first load the information in api-gatway.yaml, and then load the configuration information in routes-api-gatway.yaml. The principle is the same, but the listener is routes-api-gatway.yaml (there is json format)
ApplicationEventPublisherAware is an event publishing interface, including events, publishers, and listeners. The publisher of the event here is the class that comes with the gateway, RefreshRoutesEvent, and the event is to refresh the route.


Regardless of initializing the routing or refreshing the routing, the first step is to obtain the relevant information of nacos. ConfigService is obtained by the static factory class NacosFactory

Enter ConfigFactory and find that ConfigService is NacosConfigService obtained by reflection mechanism, NacosConfigService implements ConfigService

Enter NacosConfigService, the construction method inside carries out the relevant assignments of nacos, including namespaces and so on. So far, it can be seen that nacos has been successfully connected and the configuration class of nacos has been returned.

Let's look at the second sentence of code String configInfo = configService.getConfig(dataId, groupId, 5000) This sentence actually means that the nacos configuration class obtains configuration file information according to dataId and groupId, which is the yaml content you configure in the nacos visualization interface .

Look at the code below

Look directly at the getConfigInner method, this method first obtains the local Nacos configuration information, if not, then obtains the configuration information on the server

String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);

Obtain the configuration information on the nacos server.

String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
Nacos的ClientWorker类进行赋值操作,调用getServerConfig方法获取配置返回配置数组

Enter ClientWorker's getServerConfig

The core method in ClientWorker agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout), click to enter ServerHttpAgent
ServerHttpAgent class for http call

The HttpSimpleClient class is called in ServerHttpAgent to call Nacos Open API to obtain configuration from Nacos Server. The
second time here is the url: http://xx.xx.xx.xx:8848/nacos/v1/cs/configs?dataId= routes-api-gatway&group=ROUTE_GROUP&tenant=dev , you can also call it with postman, in fact, what is returned is your configuration file of api-gatway.yaml or routes-api-gatway.yaml in nacos

 

 

The configuration file of routes-api-gatway.yaml in nacos

 

At this point, the first two lines of code have been executed. The following is to analyze and then publish the event.

String configInfo = configService.getConfig(dataId, groupId, 5000) The route string returned by the method is parsed into a collection of route entity classes, and the route is parsed and assigned to the RouteDefinition class in a loop,

Route, assertion, url and other settings in RouteDefinition

The following verification, add a new route with id of service4 and publish it, you will find that the code automatically reads the latest configuration and performs route analysis to update the route

 

 

Guess you like

Origin blog.csdn.net/CarryBest/article/details/112985659