偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Gateway 網(wǎng)關坑我!被這個404 問題折騰了一年?

網(wǎng)絡 網(wǎng)絡管理
同事使用的是?SpringCloud Gateway 3.0.1?+?JDK8,整合了?Nacos?做動態(tài)路由配置。問題是:每次修改 Nacos 的路由配置后,網(wǎng)關的 API 請求就會出現(xiàn) 404 錯誤,但重啟網(wǎng)關后又能恢復正常。

最近同事找我?guī)兔ε挪橐粋€"詭異"的 Bug,說困擾了他們一年多一直沒解決。我接手后花了一些時間定位到了問題根源,今天就來跟大家分享一下這個問題的排查過程和解決方案。

問題描述

同事使用的是 SpringCloud Gateway 3.0.1 + JDK8,整合了 Nacos 做動態(tài)路由配置。問題是:每次修改 Nacos 的路由配置后,網(wǎng)關的 API 請求就會出現(xiàn) 404 錯誤,但重啟網(wǎng)關后又能恢復正常。

聽到這個問題,我的第一反應是:Nacos 配置更新后,網(wǎng)關的緩存數(shù)據(jù)可能沒有及時更新。帶著這個猜想,我開始深入排查。

環(huán)境準備

首先準備了 3 個后端服務實例,端口分別為 8103、12040、12041,在 Nacos 中配置了對應的網(wǎng)關路由:xiaofu-8103xiaofu-12040、xiaofu-12041,并將它們放在同一個權重組 xiaofu-group 中,實現(xiàn)基于權重的負載均衡。

- id: xiaofu-8103
  uri: http://127.0.0.1:8103/
  predicates:
    - Weight=xiaofu-group, 2
    - Path=/test/version1/**
  filters:
    - RewritePath=/test/version1/(?<segment>.*),/$\{segment}
- id: xiaofu-12040
  uri: http://127.0.0.1:12040/
  predicates:
    - Weight=xiaofu-group, 1
    - Path=/test/version1/**
  filters:
    - RewritePath=/test/version1/(?<segment>.*),/$\{segment}
- id: xiaofu-12041
  uri: http://127.0.0.1:12041/
  predicates:
    - Weight=xiaofu-group, 2
    - Path=/test/version1/**
  filters:
    - RewritePath=/test/version1/(?<segment>.*),/$\{segment}

使用 JMeter 進行持續(xù)請求測試,為了便于日志追蹤,給每個請求參數(shù)都添加了隨機數(shù)。

圖片圖片

準備完成后啟動 JMeter 循環(huán)請求,觀察到三個實例都有日志輸出,說明網(wǎng)關的負載均衡功能正常。

圖片圖片

問題排查

為了獲取更詳細的日志信息,我將網(wǎng)關的日志級別調整為 TRACE

啟動 JMeter 后,隨機修改三個實例的路由屬性(uri、port、predicates、filters),請求沒有出現(xiàn)報錯,網(wǎng)關控制臺也顯示了更新后的路由屬性,說明 Nacos 配置變更已成功同步到網(wǎng)關。

圖片圖片

接下來嘗試去掉一個實例 xiaofu-12041,這時發(fā)現(xiàn) JMeter 請求開始出現(xiàn) 404 錯誤,成功復現(xiàn)問題!

圖片圖片

查看網(wǎng)關控制臺日志時,驚奇地發(fā)現(xiàn)已刪除的實例 xiaofu-12041 的路由配置仍然存在,甚至還被選中(chosen)處理請求。

問題根源找到了:雖然 Nacos 中刪除了實例路由配置,但網(wǎng)關在實際負載均衡時仍然使用舊的路由數(shù)據(jù)。

圖片圖片

繼續(xù)深入排查,發(fā)現(xiàn)在路由的權重信息(Weights attr)中也存在舊的路由數(shù)據(jù)。

至此基本確定問題:在計算實例權重和負載均衡時,網(wǎng)關使用了陳舊的緩存數(shù)據(jù)。

圖片圖片

源碼分析

通過分析源碼,發(fā)現(xiàn)了一個專門計算權重的過濾器 WeightCalculatorWebFilter。它內部維護了一個 groupWeights 變量來存儲路由權重信息。

當配置變更事件發(fā)生時,會執(zhí)行 addWeightConfig(WeightConfig weightConfig) 方法來添加權重配置。

@Override
public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof PredicateArgsEvent) {
        handle((PredicateArgsEvent) event);
    }
    else if (event instanceof WeightDefinedEvent) {
        addWeightConfig(((WeightDefinedEvent) event).getWeightConfig());
    }
    else if (event instanceof RefreshRoutesEvent && routeLocator != null) {
        if (routeLocatorInitialized.compareAndSet(false, true)) {
            routeLocator.ifAvailable(locator -> locator.getRoutes().blockLast());
        }
        else {
            routeLocator.ifAvailable(locator -> locator.getRoutes().subscribe());
        }
    }

}

addWeightConfig 方法的注釋明確說明:該方法僅創(chuàng)建新的 GroupWeightConfig,而不進行修改。

這意味著它只能新建或覆蓋路由權重,無法清理已刪除的路由權重信息。

void addWeightConfig(WeightConfig weightConfig) {
        String group = weightConfig.getGroup();
        GroupWeightConfig config;
        // only create new GroupWeightConfig rather than modify
        // and put at end of calculations. This avoids concurency problems
        // later during filter execution.
        if (groupWeights.containsKey(group)) {
            config = new GroupWeightConfig(groupWeights.get(group));
        }
        else {
            config = new GroupWeightConfig(group);
        }


        final AtomicInteger index = new AtomicInteger(0);
  ....省略.....

        if (log.isTraceEnabled()) {
            log.trace("Recalculated group weight config " + config);
        }
        // only update after all calculations
        groupWeights.put(group, config);
    }

解決方案

找到問題根源后,解決方案就清晰了。

開始我懷疑可能是springcloud gateway 版本問題,將版本升級到了4.1.0,但結果還是存在這個問題。

圖片圖片

看來只能手動更新緩存解決了,需要監(jiān)聽 Nacos 路由配置變更事件,獲取最新路由配置,并更新 groupWeights 中的權重數(shù)據(jù)。

以下是實現(xiàn)的解決方案代碼:

@Slf4j
@Configuration
public class WeightCacheRefresher {

    @Autowired
    private WeightCalculatorWebFilter weightCalculatorWebFilter;

    @Autowired
    private RouteDefinitionLocator routeDefinitionLocator;

    @Autowired
    private ApplicationEventPublisher publisher;

    /**
     * 監(jiān)聽路由刷新事件,同步更新權重緩存
     */
    @EventListener(RefreshRoutesEvent.class)
    public void onRefreshRoutes() {
        log.info("檢測到路由刷新事件,準備同步更新權重緩存");
        syncWeightCache();
    }

    /**
     * 同步權重緩存與當前路由配置
     */
    public void syncWeightCache() {
        try {
            // 獲取 groupWeights 字段
            Field groupWeightsField = WeightCalculatorWebFilter.class.getDeclaredField("groupWeights");
            groupWeightsField.setAccessible(true);

            // 獲取當前的 groupWeights 值
            @SuppressWarnings("unchecked")
            Map<String, Object> groupWeights = (Map<String, Object>) groupWeightsField.get(weightCalculatorWebFilter);

            if (groupWeights == null) {
                log.warn("未找到 groupWeights 緩存");
                return;
            }

            log.info("當前 groupWeights 緩存: {}", groupWeights.keySet());

            // 獲取當前所有路由的權重組和路由ID
            final Set<String> currentRouteIds = new HashSet<>();
            final Map<String, Map<String, Integer>> currentGroupRouteWeights = new HashMap<>();

            routeDefinitionLocator.getRouteDefinitions()
                    .collectList()
                    .subscribe(definitions -> {
                        definitions.forEach(def -> {
                            currentRouteIds.add(def.getId());

                            def.getPredicates().stream()
                                    .filter(predicate -> predicate.getName().equals("Weight"))
                                    .forEach(predicate -> {
                                        Map<String, String> args = predicate.getArgs();
                                        String group = args.getOrDefault("_genkey_0", "unknown");
                                        int weight = Integer.parseInt(args.getOrDefault("_genkey_1", "0"));

                                        // 記錄每個組中當前存在的路由及其權重
                                        currentGroupRouteWeights.computeIfAbsent(group, k -> new HashMap<>())
                                                .put(def.getId(), weight);
                                    });
                        });

                        log.info("當前路由配置中的路由ID: {}", currentRouteIds);
                        log.info("當前路由配置中的權重組: {}", currentGroupRouteWeights);

                        // 檢查每個權重組,移除不存在的路由,更新權重變化的路由
                        Set<String> groupsToRemove = new HashSet<>();
                        Set<String> groupsToUpdate = new HashSet<>();

                        for (String group : groupWeights.keySet()) {
                            if (!currentGroupRouteWeights.containsKey(group)) {
                                // 整個權重組不再存在
                                groupsToRemove.add(group);
                                log.info("權重組 [{}] 不再存在于路由配置中,將被移除", group);
                                continue;
                            }

                            // 獲取該組中當前配置的路由ID和權重
                            Map<String, Integer> configuredRouteWeights = currentGroupRouteWeights.get(group);

                            // 獲取該組中緩存的權重配置
                            Object groupWeightConfig = groupWeights.get(group);

                            try {
                                // 獲取 weights 字段
                                Field weightsField = groupWeightConfig.getClass().getDeclaredField("weights");
                                weightsField.setAccessible(true);

                                @SuppressWarnings("unchecked")
                                LinkedHashMap<String, Integer> weights = (LinkedHashMap<String, Integer>) weightsField.get(groupWeightConfig);

                                // 找出需要移除的路由ID
                                Set<String> routesToRemove = weights.keySet().stream()
                                        .filter(routeId -> !configuredRouteWeights.containsKey(routeId))
                                        .collect(Collectors.toSet());

                                // 找出權重發(fā)生變化的路由ID
                                Set<String> routesWithWeightChange = new HashSet<>();
                                for (Map.Entry<String, Integer> entry : weights.entrySet()) {
                                    String routeId = entry.getKey();
                                    Integer cachedWeight = entry.getValue();

                                    if (configuredRouteWeights.containsKey(routeId)) {
                                        Integer configuredWeight = configuredRouteWeights.get(routeId);
                                        if (!cachedWeight.equals(configuredWeight)) {
                                            routesWithWeightChange.add(routeId);
                                            log.info("路由 [{}] 的權重從 {} 變?yōu)?{}", routeId, cachedWeight, configuredWeight);
                                        }
                                    }
                                }

                                // 找出新增的路由ID
                                Set<String> newRoutes = configuredRouteWeights.keySet().stream()
                                        .filter(routeId -> !weights.containsKey(routeId))
                                        .collect(Collectors.toSet());

                                if (!routesToRemove.isEmpty() || !routesWithWeightChange.isEmpty() || !newRoutes.isEmpty()) {
                                    log.info("權重組 [{}] 中有變化:刪除 {},權重變化 {},新增 {}",
                                            group, routesToRemove, routesWithWeightChange, newRoutes);

                                    // 如果有任何變化,我們將重新計算整個組的權重
                                    groupsToUpdate.add(group);
                                }

                                // 首先,移除需要刪除的路由
                                for (String routeId : routesToRemove) {
                                    weights.remove(routeId);
                                }

                                // 如果權重組中沒有剩余路由,則移除整個組
                                if (weights.isEmpty()) {
                                    groupsToRemove.add(group);
                                    log.info("權重組 [{}] 中沒有剩余路由,將移除整個組", group);
                                }
                            } catch (Exception e) {
                                log.error("處理權重組 [{}] 時出錯", group, e);
                            }
                        }

                        // 移除不再需要的權重組
                        for (String group : groupsToRemove) {
                            groupWeights.remove(group);
                            log.info("已移除權重組: {}", group);
                        }

                        // 更新需要重新計算的權重組
                        for (String group : groupsToUpdate) {
                            try {
                                // 獲取該組中當前配置的路由ID和權重
                                Map<String, Integer> configuredRouteWeights = currentGroupRouteWeights.get(group);

                                // 移除舊的權重組配置
                                groupWeights.remove(group);
                                log.info("已移除權重組 [{}] 以便重新計算", group);

                                // 為每個路由創(chuàng)建 WeightConfig 并調用 addWeightConfig 方法
                                Method addWeightConfigMethod = WeightCalculatorWebFilter.class.getDeclaredMethod("addWeightConfig", WeightConfig.class);
                                addWeightConfigMethod.setAccessible(true);

                                for (Map.Entry<String, Integer> entry : configuredRouteWeights.entrySet()) {
                                    String routeId = entry.getKey();
                                    Integer weight = entry.getValue();

                                    WeightConfig weightConfig = new WeightConfig(routeId);
                                    weightConfig.setGroup(group);
                                    weightConfig.setWeight(weight);

                                    addWeightConfigMethod.invoke(weightCalculatorWebFilter, weightConfig);
                                    log.info("為路由 [{}] 添加權重配置:組 [{}],權重 {}", routeId, group, weight);
                                }
                            } catch (Exception e) {
                                log.error("重新計算權重組 [{}] 時出錯", group, e);
                            }
                        }

                        log.info("權重緩存同步完成,當前緩存的權重組: {}", groupWeights.keySet());
                    });

        } catch (Exception e) {
            log.error("同步權重緩存失敗", e);
        }
    }
}

如此一來每次更新nacos路由配置,就會監(jiān)聽到配置變更事件,進而用最新的實例數(shù)據(jù)來更新本地的路由權重數(shù)據(jù)。

網(wǎng)上找一圈并沒發(fā)現(xiàn)官方的修改意見,可能是咱們使用方式不對導致的,要不如此明顯的BUG早就有人改了吧!

責任編輯:武曉燕 來源: 程序員小富
相關推薦

2025-02-18 15:17:59

2020-06-09 16:22:26

戴爾

2016-05-24 10:40:32

NodeJS總結

2018-09-17 11:10:06

2020-04-14 10:06:20

微服務Netflix語言

2022-05-09 17:12:32

元宇宙技術生活

2025-03-24 08:00:00

數(shù)據(jù)庫開發(fā)代碼

2009-07-03 10:15:38

2013-05-30 01:16:36

工作總結自由職業(yè)工作經(jīng)驗

2018-09-13 10:42:00

工具代碼機器學習

2015-03-09 17:49:40

SDN

2023-07-11 08:39:16

React前端

2025-09-16 08:17:28

CSSJavaScrip前端

2019-07-09 16:00:18

阿里數(shù)據(jù)庫技術思維

2022-01-03 20:13:08

Gointerface 面試

2019-07-15 09:21:45

技術思維阿里

2015-11-03 11:13:01

技術轉型心得

2020-04-02 14:33:42

MySQLBUG解決方案

2015-07-02 11:12:19

2020-07-20 09:40:49

MySQLBUG數(shù)據(jù)庫
點贊
收藏

51CTO技術棧公眾號