Soul網關的數據註冊和同步數據流探究

Soul網關的Http/SpringMvc 數據註冊和同步數據流探究

Soul-Admin端數據的探究

首先啓動客戶端項目soul-examples-http的過程中看到控制檯會輸出和
file
可以很明顯的看到這段信息就是我們註解了@SoulSpringMvcClient的接口信息,那麼這個操作是在哪裏產生的了。
全局搜索了register success 字樣。發現在soul-examples-http依賴的子項目soul-client-springmvc中有對應的SpringMvcClientBeanPostProcessor來進行註冊的代碼,這個SpringMvcClientBeanPostProcessor實現了Spring的BeanPostProcessor接口,如果我們想在Spring容器中完成bean實例化、配置以及其他初始化方法前後要添加一些自己邏輯處理。我們需要定義一個或多個BeanPostProcessor接口實現類,然後註冊到Spring IoC容器中。因此這裏就是soul的客戶端將數據註冊到soul-admin的入口,
於是我在註冊這裏進行了斷點調試,看看他到底是在那個接口註冊從而將數據傳遞到soul-admin項目和網關,
file
可以看到http的項目是在控制檯項目的http://localhost:9095/soul-client/springmvc-register 接口進行了註冊。
隨後我們轉到這個接口

    /**
     * Register spring cloud string.
     *
     * @param springCloudRegisterDTO the spring cloud register dto
     * @return the string
     */
    @PostMapping("/springcloud-register")
    public String registerSpringCloud(@RequestBody final SpringCloudRegisterDTO springCloudRegisterDTO) {
        return soulClientRegisterService.registerSpringCloud(springCloudRegisterDTO);
    }

深入到service層可以看到是應用的數據利用到上一節的Spring的事件處理機制來實現了
file
隨後斷點進入到實現了Spring的事件監聽接口ApplicationListener的事件分發類DataChangedEventDispatcher
file
可以看到,我啓動的時候因爲沒有增加插件,所以此時是進入了SELECTOR斷點中,由於此時我選擇的zookeepr作爲數據同步的組件。我找到了ZookeeperDataChangedListener中進行斷點 ,發現這裏數據同步是將數據放入到zookeeper
file

    private void createZkNode(final String path) {
        if (!zkClient.exists(path)) {
            zkClient.createPersistent(path, true);
        }
    }
    
    /**
     * create or update zookeeper node.
     * @param path node path
     * @param data node data 
     */
    private void upsertZkNode(final String path, final Object data) {
        if (!zkClient.exists(path)) {
            zkClient.createPersistent(path, true);
        }
        zkClient.writeData(path, data);
    }
    
    private void deleteZkPath(final String path) {
        if (zkClient.exists(path)) {
            zkClient.delete(path);
        }
    }
    
    private void deleteZkPathRecursive(final String path) { 
        if (zkClient.exists(path)) {
            zkClient.deleteRecursive(path);
        }
    }

zookeeper內寫入節點和更新數據的流程如上

Soul-Boostrap端數據的探究

打開soul-boostrap可以看到。類很少,只有兩個。一個SoulNettyWebServerFactory和HealthFilter。HealthFilter是用來做服務健康檢查的。而SoulNettyWebServerFactory就是spring webflux應用的一個響應式Server工廠類。具體的可以去看https://www.jianshu.com/p/ada196969995 這篇文章
但是此時我們還是沒有達到我們想要的請求轉發的東西。去pom文件中找到了核心的網關的項目soul-spring-boot-starter-gateway但是發現其中一個類都沒有,但是其中依賴了soul-web模塊,因此對soul-web模塊進行了解
首先可以看到配置類SoulConfiguration,

   /**
     * Init SoulWebHandler.
     *
     * @param plugins this plugins is All impl SoulPlugin.
     * @return {@linkplain SoulWebHandler}
     */
    @Bean("webHandler")
    public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
        List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
        final List<SoulPlugin> soulPlugins = pluginList.stream()
                .sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
        soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
        return new SoulWebHandler(soulPlugins);
    }

可以看到這裏,加載了一個webhandler的處理器,處理器的主要內容

    public SoulWebHandler(final List<SoulPlugin> plugins) {
        this.plugins = plugins;
        String schedulerType = System.getProperty("soul.scheduler.type", "fixed");
        if (Objects.equals(schedulerType, "fixed")) {
            int threads = Integer.parseInt(System.getProperty(
                    "soul.work.threads", "" + Math.max((Runtime.getRuntime().availableProcessors() << 1) + 1, 16)));
            scheduler = Schedulers.newParallel("soul-work-threads", threads);
        } else {
            scheduler = Schedulers.elastic();
        }
    }
    
    /**
     * Handle the web server exchange.
     *
     * @param exchange the current server exchange
     * @return {@code Mono<Void>} to indicate when request handling is complete
     */
    @Override
    public Mono<Void> handle(@NonNull final ServerWebExchange exchange) {
        MetricsTrackerFacade.getInstance().counterInc(MetricsLabelEnum.REQUEST_TOTAL.getName());
        Optional<HistogramMetricsTrackerDelegate> startTimer = MetricsTrackerFacade.getInstance().histogramStartTimer(MetricsLabelEnum.REQUEST_LATENCY.getName());
        return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
                .doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
    }

主要內容是利用責任鏈對請求的線程數進行處理。handle方法用來處理請求
根據soul-example-springmvc的註解進行請求。可以在抽象的基礎AbstractSoulPlugin接受到這個請求

    @Override
    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        String pluginName = named();
        final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
        if (pluginData != null && pluginData.getEnabled()) {
            final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
            if (CollectionUtils.isEmpty(selectors)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            final SelectorData selectorData = matchSelector(exchange, selectors);
            if (Objects.isNull(selectorData)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            selectorLog(selectorData, pluginName);
            final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
            if (CollectionUtils.isEmpty(rules)) {
                return handleRuleIsNull(pluginName, exchange, chain);
            }
            RuleData rule;
            if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
                //get last
                rule = rules.get(rules.size() - 1);
            } else {
                rule = matchRule(exchange, rules);
            }
            if (Objects.isNull(rule)) {
                return handleRuleIsNull(pluginName, exchange, chain);
            }
            ruleLog(rule, pluginName);
            return doExecute(exchange, chain, selectorData, rule);
        }
        return chain.execute(exchange);
    }

這裏有很明顯的規則和選擇器比較的相關邏輯,用來判斷當前的請求是否位於網關代理的請求中。但是在這裏與上面對應的是,我如何取到上文已經設置的選擇器數據,這個留到後面繼續去探究
緊接着斷點來到了具體的業務的請求插件WebClientPlugin執行後面的業務請求

    @Override
    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
        assert soulContext != null;
        String urlPath = exchange.getAttribute(Constants.HTTP_URL);
        if (StringUtils.isEmpty(urlPath)) {
            Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
        long timeout = (long) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_TIME_OUT)).orElse(3000L);
        int retryTimes = (int) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_RETRY)).orElse(0);
        log.info("The request urlPath is {}, retryTimes is {}", urlPath, retryTimes);
        HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
        WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
        return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
    }

從如上可以看到,網關在代理請求這一塊的邏輯

問題

本文還剩下未解決的問題主要是

  • 如何從abstractSoulPlugin執行完之後到WebClientPlugin的相同方法,是責任鏈模式還是其他的加載過程
  • abstractSoulPlugin是如何加載註冊或修改後的選擇器等數據
  • plugin 中的執行方法是如何獲取到ServerWebExchange的相關請求數據

參考文章 https://blog.csdn.net/u010084384/article/details/113010594

歡迎搜索關注本人與朋友共同開發的微信面經小程序【大廠面試助手】和公衆號【微瞰技術】,以及總結的分類面試題https://github.com/zhendiao/JavaInterview

file
file

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章