激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 配置gateway+nacos動態路由管理流程

配置gateway+nacos動態路由管理流程

2021-12-28 13:21夏天的粽子 Java教程

這篇文章主要介紹了配置gateway+nacos動態路由管理流程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

配置gateway+nacos動態路由

第一步:首先是設置配置文件的配置列表

然后在配置讀取配置類上增加刷新注解@RefreshScope

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author :lhb
* @date :Created in 2020-09-09 08:59
* @description:GateWay路由配置
* @modified By:
* @version: $
*/
@Slf4j
@RefreshScope
@Component
@ConfigurationProperties(prefix = "spring.cloud.gateway")
public class GatewayRoutes {
  /**
   * 路由列表.
   */
  @NotNull
  @Valid
  private List<RouteDefinition> routes = new ArrayList<>();
  /**
   * 適用于每條路線的過濾器定義列表
   */
  private List<FilterDefinition> defaultFilters = new ArrayList<>();
  private List<MediaType> streamingMediaTypes = Arrays
          .asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);
  public List<RouteDefinition> getRoutes() {
      return routes;
  }
  public void setRoutes(List<RouteDefinition> routes) {
      this.routes = routes;
      if (routes != null && routes.size() > 0 && log.isDebugEnabled()) {
          log.debug("Routes supplied from Gateway Properties: " + routes);
      }
  }
  public List<FilterDefinition> getDefaultFilters() {
      return defaultFilters;
  }
  public void setDefaultFilters(List<FilterDefinition> defaultFilters) {
      this.defaultFilters = defaultFilters;
  }
  public List<MediaType> getStreamingMediaTypes() {
      return streamingMediaTypes;
  }
  public void setStreamingMediaTypes(List<MediaType> streamingMediaTypes) {
      this.streamingMediaTypes = streamingMediaTypes;
  }
  @Override
  public String toString() {
      return "GatewayProperties{" + "routes=" + routes + ", defaultFilters="
              + defaultFilters + ", streamingMediaTypes=" + streamingMediaTypes + '}';
  }
}

第二步:配置監聽nacos監聽器

import cn.hutool.core.exceptions.ExceptionUtil;
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.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
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.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author :lhb
* @date :Created in 2020-09-08 16:39
* @description:監聽nacos配置變更
* @modified By:
* @version: $
*/
@Slf4j
@Component
public class GateWayNacosConfigListener implements ApplicationEventPublisherAware {
  @Autowired
  private RouteDefinitionWriter routedefinitionWriter;
  private ApplicationEventPublisher publisher;
  private static final Map<String, RouteDefinition> ROUTE_MAP = new ConcurrentHashMap<>();
  @Autowired
  private GatewayRoutes gatewayRoutes;
  @Resource
  private RefreshScope refreshScope;
  @Value(value = "${spring.cloud.nacos.config.server-addr}")
  private String serverAddr;
  @Value(value = "${spring.cloud.nacos.config.group:DEFAULT_GROUP}")
  private String group;
  @Value(value = "${spring.cloud.nacos.config.namespace}")
  private String namespace;
  private String routeDataId = "gateway-routes.yml";
  @PostConstruct
  public void onMessage() throws NacosException {
      log.info("serverAddr={}", serverAddr);
      Properties properties = new Properties();
      properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
      properties.put(PropertyKeyConst.NAMESPACE, namespace);
      ConfigService configService = NacosFactory.createConfigService(properties);
      this.publisher(gatewayRoutes.getRoutes());
      log.info("gatewayProperties=" + JSONObject.toJSONString(gatewayRoutes));
      configService.addListener(routeDataId, group, new Listener() {
          @Override
          public Executor getExecutor() {
              return null;
          }
          @Override
          public void receiveConfigInfo(String config) {
              log.info("監聽nacos配置: {}, 舊的配置: {}, 新的配置: {}", routeDataId, gatewayRoutes, config);
              refreshScope.refresh("gatewayRoutes");
              try {
                  TimeUnit.SECONDS.sleep(5);
              } catch (InterruptedException e) {
                  log.error(ExceptionUtil.getMessage(e));
              }
              publisher(gatewayRoutes.getRoutes());
          }
      });
  }
  private boolean rePut(List<RouteDefinition> routeDefinitions) {
      if (MapUtils.isEmpty(ROUTE_MAP) && CollectionUtils.isEmpty(routeDefinitions)) {
          return true;
      }
      if (CollectionUtils.isEmpty(routeDefinitions)) {
          return true;
      }
      Set<String> strings = ROUTE_MAP.keySet();
      return strings.stream().sorted().collect(Collectors.joining())
              .equals(routeDefinitions.stream().map(v -> v.getId()).sorted().collect(Collectors.joining()));
  }
  /**
   * 增加路由
   *
   * @param def
   * @return
   */
  public Boolean addRoute(RouteDefinition def) {
      try {
          log.info("添加路由: {} ", def);
          routedefinitionWriter.save(Mono.just(def)).subscribe();
          ROUTE_MAP.put(def.getId(), def);
      } catch (Exception e) {
          e.printStackTrace();
      }
      return true;
  }
  /**
   * 刪除路由
   *
   * @return
   */
  public Boolean clearRoute() {
      for (String id : ROUTE_MAP.keySet()) {
          routedefinitionWriter.delete(Mono.just(id)).subscribe();
      }
      ROUTE_MAP.clear();
      return false;
  }
  /**
   * 發布路由
   */
  private void publisher(String config) {
      this.clearRoute();
      try {
          log.info("重新更新動態路由");
          List<RouteDefinition> gateway = JSONObject.parseArray(config, RouteDefinition.class);
          for (RouteDefinition route : gateway) {
              this.addRoute(route);
          }
          publisher.publishEvent(new RefreshRoutesEvent(this.routedefinitionWriter));
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
  /**
   * 發布路由
   */
  private void publisher(List<RouteDefinition> routeDefinitions) {
      this.clearRoute();
      try {
          log.info("重新更新動態路由: ");
          for (RouteDefinition route : routeDefinitions) {
              this.addRoute(route);
          }
          publisher.publishEvent(new RefreshRoutesEvent(this.routedefinitionWriter));
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
  @Override
  public void setApplicationEventPublisher(ApplicationEventPublisher app) {
      publisher = app;
  }
}

第三步:配置nacos的yml文件

spring:
cloud:
  gateway:
    discovery:
      locator:
        enabled: true
        lower-case-service-id: true
    routes:
      # 認證中心
      - id: firefighting-service-user
        uri: lb://firefighting-service-user
        predicates:
          - Path=/user/**
        #          - Weight=group1, 8
        filters:
          - StripPrefix=1
        # 轉流服務
      - id: liveStream
        uri: http://192.168.1.16:8081
        predicates:
          - Path=/liveStream/**
        #          - Weight=group1, 8
        filters:
          - StripPrefix=1
      - id: firefighting-service-directcenter
        uri: lb://firefighting-service-directcenter
        predicates:
          - Path=/directcenter/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-datainput
        uri: lb://firefighting-service-datainput
        predicates:
          - Path=/datainput/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-squadron
        uri: lb://firefighting-service-squadron
        predicates:
          - Path=/squadron/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-iot
        uri: lb://firefighting-service-iot
        predicates:
          - Path=/iot/**
        filters:
          - StripPrefix=1
      - id: websocket
        uri: lb:ws://firefighting-service-notice
        predicates:
          - Path=/notice/socket/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-notice
        uri: lb://firefighting-service-notice
        predicates:
          - Path=/notice/**
        filters:
          # 驗證碼處理
          #            - CacheRequest
          #            - ImgCodeFilter
          - StripPrefix=1
      - id: websocket
        uri: lb:ws://firefighting-service-notice
        predicates:
          - Path=/notice/socket/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-supervise
        uri: lb://firefighting-service-supervise
        predicates:
          - Path=/supervise/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-new-supervise
        uri: lb://firefighting-service-new-supervise
        predicates:
          - Path=/new/supervise/**
        filters:
          - StripPrefix=2
      - id: firefighting-service-train
        uri: lb://firefighting-service-train
        predicates:
          - Path=/train/**
        filters:
          - StripPrefix=1
      - id: firefighting-support-user
        uri: lb://firefighting-support-user
        predicates:
          - Path=/support/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-firesafety
        uri: lb://firefighting-service-firesafety
        predicates:
          - Path=/firesafety/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-bigdata
        uri: lb://firefighting-service-bigdata
        predicates:
          - Path=/bigdata/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-act-datainput
        uri: lb://firefighting-service-act-datainput
        predicates:
          - Path=/act_datainput/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-publicity
        uri: lb://firefighting-service-publicity
        predicates:
          - Path=/publicity/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-preplan
        uri: lb://firefighting-service-preplan
        predicates:
          - Path=/preplan/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-uav
        uri: lb://firefighting-service-uav
        predicates:
          - Path=/uav/**
        filters:
          - StripPrefix=1
      - id: firefighting-service-ard-mgr
        uri: lb://firefighting-service-ard-mgr
        predicates:
          - Path=/ard_mgr/**
        filters:
          - StripPrefix=1
      - id: admin-server
        uri: lb://admin-server
        predicates:
          - Path=/adminsServer/**
        filters:
          - StripPrefix=1

 

nacos的智能路由實現與應用

一. 概述

隨著微服務的興起,我司也逐漸加入了微服務的改造浪潮中。但是,隨著微服務體系的發展壯大,越來越多的問題暴露出來。其中,測試環境治理,一直是實施微服務的痛點之一,它的痛主要體現在環境管理困難,應用部署困難,技術方案配合等。最終,基于我司的實際情況,基于注冊中心nacos實現的智能路由有效地解決了這個問題。本文主要介紹我司在測試環境治理方面遇到的難題與對應的解決方案。

二. 遇到的問題

1. 困難的環境管理與應用部署

隨著公司業務發展,業務逐漸復雜化。這在微服務下帶來的一個問題就是服務的不斷激增,且增速越來越快。而在這種情況下,不同業務團隊如果想并行開發的話,都需要一個環境。假設一套完整環境需要部署1k個服務,那么n個團隊就需要部署n*1k個服務,這顯然是不能接受的。

2. 缺失的技術方案

從上面的分析可以看出,一個環境部署全量的服務顯然是災難性的。那么就需要有一種技術方案,來解決應用部署的難題。最直接的一個想法就是,每個環境只部署修改的服務,然后通過某種方式,實現該環境的正常使用。當然,這也是我們最終的解決方案。下面會做一個介紹。

3. 研發問題

除了這兩個大的問題,還有一些其他的問題也急需解決。包括后端多版本并行聯調和前后端聯調的難題。下面以實際的例子來說明這兩個問題。

<1> 后端多版本并行聯調難

某一個微服務,有多個版本并行開發時,后端聯調時調用容易錯亂。例如這個例子,1.1版本的服務A需要調用1.1版本的服務B,但實際上能調用到嗎???

配置gateway+nacos動態路由管理流程

目前使用的配置注冊中心是nacos,nacos自身有一套自己的服務發現體系,但這是基于namespace和group的同頻服務發現,對于跨namespace的服務,它就不管用了。

<2> 前后端聯調難

前端和后端的聯調困難,這個問題也是經常遇到的。主要體現在,后端聯調往往需要啟動多個微服務(因為服務的依賴性)。而前端要對應到某一個后端,也需要特殊配置(比如指定ip等)。下面這個例子,后端人員開發服務A,但卻要啟動4個服務才能聯調。因為服務A依賴于服務B,C,D。

配置gateway+nacos動態路由管理流程

4. 其他問題

除了以上的問題外,還有一些小的問題也可以關注下:

<1> 測試環境排查問題

這個問題不算棘手,登錄服務器查看日志即可。但是能否再靈活點,比如讓開發人員debug或者在本地調試問題呢?

<2> 本地開發

本地開發,后端往往也依賴多個服務,能不能只啟動待開發的服務,而不啟動其他旁路服務呢?

三. 智能路由的實現

基于這些需求,智能路由應運而生。它正是為了解決這個問題。最終,我們通過它解決了測試環境治理的難題。

智能路由,能根據不同環境,不同用戶,甚至不同機器進行精確路由。下面以一個例子說明。

三個團隊,team1,team2和team3各負責不同的業務需求。其中team1只需要改動A服務,team2只需要改動B服務,team3需要在qa環境上驗證。通過智能路由,team1,team2只在自己的環境上部署了增量應用,然后在訪問該環境的時候,當找不到對應環境的服務時,就從基準環境上訪問。而team3只做qa,因此直接訪問基準環境即可。可以看到,基準環境上部署了全量服務,除此之外,其他小環境都是增量服務。

配置gateway+nacos動態路由管理流程

下面介紹智能路由的具體實現方案。

1. 原理

通過上圖,可以看到,智能路由其實就是流量染色加上服務發現

流量染色:將不同團隊的流量進行染色,然后透傳到整個鏈路中。

服務發現:注冊中心提供正確的服務發現,當在本環境發現不到待調用的服務時,自動訪問基準環境的服務。

通過流量染色,區分出哪些流量是哪些團隊的,從而在服務發現時,能正確調用到正確的服務。

另外,我司使用的注冊中心是nacos,因此本文將主要介紹基于nacos的智能路由實現,其他注冊中心同理,做相應改造即可。

2. 具體實現 <1> 流量染色

智能路由的第一步,就是要做流量的染色,將流量能夠沿著鏈路一直透傳下去。那么就需要找到流量的入口,然后在入口處進行染色。下圖是網站的內部調用情況,可以看到,流量從Nginx進來,最終打到服務集群,因此需要對Nginx進行染色。

配置gateway+nacos動態路由管理流程

流量染色主要是在流量的頭部加上一些標記,以便識別。這里利用了Nginx的proxy_set_header,我們通過下列方式來設置頭部。

## nginx的匹配規則設置header
proxy_set_header req_context  "{'version': '1.0'}"

這樣子,我們就為版本是1.0的流量設置了頭部,其中的參數可以任意添加,這里僅列舉最重要的一個參數。

另外,還有一個問題,流量只有從Nginx進來,才會帶上這個頭部。如果是在內部直接訪問某個中間的服務,那么這個時候流量是沒有頭部的。對此,我們的解決方案是filter,通過filter可以動態地攔截請求,修改請求頭部,為其初始化一個默認值。

public class FlowDyeFilter implements Filter { 
  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      //1. 獲取servletrequest
      HttpServletRequest request = (HttpServletRequest)servletRequest;
      //2. 獲取請求頭部
      String context = request.getHeader(ContextUtil.REQUEST_CONTEXT);
      //3. 初始化請求上下文,如果沒有,就進行初始化
      initContext(context);
      try {
          filterChain.doFilter(servletRequest, servletResponse);
      } finally {
          ContextUtil.clear();
      }
  }

  public void initContext(String contextStr) {
      //json轉object
      Context context = JSONObject.parseObject(contextStr, GlobalContext.class);
      //避免假初始化
      if (context == null) {
          context  = new Context();
      }
      //這里進行初始化,如果沒值,設置一個默認值
      if (StringUtils.isEmpty(context.getVersion())) {
          context.setVersion("master");
      }
      ...
      //存儲到上下文中
      ContextUtil.setCurrentContext(context);
  } 
}

通過這個filter,保證了在中間環節訪問時,流量仍然被染色。

<2> 流量透傳

流量在入口被染色后,需要透傳到整個鏈路,因此需要對服務做一些處理,下面分幾種情形分別處理。

1~ Spring Cloud Gateway

對于Gateway,保證請求在轉發過程中的header不丟,這個是必要的。這里通過Gateway自帶的GlobalFilter來實現,代碼如下:

public class WebfluxFlowDyeFilter implements GlobalFilter, Ordered { 
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      //1. 獲取請求上下文
      String context = exchange.getRequest().getHeaders().getFirst(ContextUtil.REQUEST_CONTEXT);
      //2. 構造ServerHttpRequest
      ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().header(ContextUtil.REQUEST_CONTEXT, context).build();
      //3. 構造ServerWebExchange
      ServerWebExchange serverWebExchange = exchange.mutate().request(serverHttpRequest).build();
      return chain.filter(serverWebExchange).then(
          Mono.fromRunnable( () -> {
              ContextUtil.clear();
          })
      );
  } 
}

2~ SpringCloud:Feign

這一類也是最常用的,SC的服務集群都通過Feign進行交互,因此只需要配置Feign透傳即可。在這里,我們利用了Feign自帶的RequestInterceptor,實現請求攔截。代碼如下:

@Configuration
public class FeignAutoConfiguration { 
  @Bean
  public RequestInterceptor headerInterceptor() {
      return requestTemplate -> {
          setRequestContext(requestTemplate);
      };
  }

  private void setRequestContext(RequestTemplate requestTemplate) {
      Context context = ContextUtil.getCurrentContext();
      if (context != null) {
          requestTemplate.header(ContextUtil.REQUEST_CONTEXT, JSON.toJSONString(ContextUtil.getCurrentContext()));
      }
  } 
}

3~ HTTP

最后一類,也是用得最少的一類,即直接通過HTTP發送請求。比如CloseableHttpClient,RestTemplate。解決方案直接見代碼:

//RestTemplate
HttpHeaders headers = new HttpHeaders();
headers.set(ContextUtil.REQUEST_CONTEXT,JSONObject.toJSONString(ContextUtil.getCurrentContext()));

//CloseableHttpClient
HttpGet httpGet = new HttpGet(uri);
httpGet.setHeader(ContextUtil.REQUEST_CONTEXT,JSONObject.toJSONString(ContextUtil.getCurrentContext()));

只需要粗暴地在發送頭部中增加header即可,而其請求上下文直接通過當前線程上下文獲取即可。

<3> 配置負載規則

完成了流量染色,下面就差服務發現了。服務發現基于注冊中心nacos,因此需要修改負載規則。在這里,我們配置Ribbon的負載規則,修改為自定義負載均衡器NacosWeightLoadBalancerRule。

  @Bean
  @Scope("prototype")
  public IRule getRibbonRule() {
      return new NacosWeightLoadBalancerRule();
  }
public class NacosWeightLoadBalancerRule extends AbstractLoadBalancerRule { 
  @Override
  public Server choose(Object o) {
      DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
      String name = loadBalancer.getName();
      try {
          Instance instance = nacosNamingService.getInstance(name);
          return new NacosServer(instance);
      } catch (NacosException ee) {
          log.error("請求服務異常!異常信息:{}", ee);
      } catch (Exception e) {
          log.error("請求服務異常!異常信息:{}", e);
      }
      return null;
  } 
}

從代碼中可以看到,最終通過nacosNamingService.getInstance()方法獲取實例。

<4> 配置智能路由規則

上面的負載規則,最終調用的是nacosNamingService.getInstance()方法,該方法里面定義了智能路由規則,主要功能是根據流量進行服務精準匹配。

規則如下:

1~開關判斷:是否開啟路由功能,沒有則走nacos默認路由。

2~獲取實例:根據流量,獲取對應實例。其中,路由匹配按照一定的優先級進行匹配。

路由規則:IP優先 > 環境 + 組 > 環境 + 默認組

解釋一下這個規則,首先是獲取實例,需要先獲取nacos上面的所有可用實例,然后遍歷,從中選出一個最合適的實例。

然后IP優先的含義是,如果在本地調試服務,那么從本地直接訪問網站,請求就會優先訪問本地服務,那么就便于開發人員調試了,debug,本地開發都不是問題了!

其實是,環境 + 組,這個規則代表了如果存在對應的環境和組都相同的服務,那作為最符合的實例肯定優先返回,其實是環境 + 默認組,最后如果都沒有,就訪問基準環境(master)。

注:環境和組的概念對應nacos上的namespace和group,如有不懂,請自行查看nacos官方文檔。

最終代碼如下:

public class NacosNamingService { 
  public Instance getInstance(String serviceName, String groupName) throws NacosException {
      //1. 判斷智能路由開關是否開啟,沒有走默認路由
      if (!isEnable()) {
          return discoveryProperties.namingServiceInstance().selectOneHealthyInstance(serviceName, groupName);
      }

      Context context = ContextUtil.getCurrentContext();
      if (Context == null) {
          return NacosNamingFactory.getNamingService(CommonConstant.Env.MASTER).selectOneHealthyInstance(serviceName);
      }
      //2. 獲取實例
      return getInstance(serviceName, context);
  }

  public Instance getInstance(String serviceName, Context context) throws NacosException {
      Instance envAndGroupInstance = null;
      Instance envDefGroupInstance = null;
      Instance defaultInstance = null;
      //2.1 獲取所有可以調用的命名空間
      List<Namespace> namespaces = NacosNamingFactory.getNamespaces();
      for (Namespace namespace : namespaces) {
          String thisEnvName = namespace.getNamespace();
          NamingService namingService = NacosNamingFactory.getNamingService(thisEnvName);
          List<Instance> instances = new ArrayList<>();
          List<Instance> instances1 = namingService.selectInstances(serviceName, true);
          List<Instance> instances2 = namingService.selectInstances(serviceName, groupName, true);
          instances.addAll(instances1);
          instances.addAll(instances2);
          //2.2 路由匹配
          for (Instance instance : instances) {
              // 優先本機匹配
              if (instance.getIp().equals(clientIp)) {
                  return instance;
              }
              String thisGroupName = null;
              String thisServiceName = instance.getServiceName();
              if (thisServiceName != null && thisServiceName.split("@@") != null) {
                  thisGroupName = thisServiceName.split("@@")[0];
              }
              if (thisEnvName.equals(envName) && thisGroupName.equals(groupName)) {
                  envAndGroupInstance = instance;
              }
              if (thisEnvName.equals(envName) && thisGroupName.equals(CommonConstant.DEFAULT_GROUP)) {
                  envDefGroupInstance = instance;
              }
              if (thisEnvName.equals(CommonConstant.Env.MASTER) && thisGroupName.equals(CommonConstant.DEFAULT_GROUP)) {
                  defaultInstance = instance;
              }
          }
      }
      if (envAndGroupInstance != null) {
          return envAndGroupInstance;
      }
      if (envDefGroupInstance != null) {
          return envDefGroupInstance;
      }
      return defaultInstance;
  }

  @Autowired
  private NacosDiscoveryProperties discoveryProperties; 
}

<5> 配置智能路由定時任務

剛才在介紹智能路由的匹配規則時,提到“獲取所有可以調用的命名空間”。這是因為,nacos上可能有很多個命名空間namespace,而我們需要把所有namespace上的所有可用服務都獲取到,而nacos源碼中,一個namespace對應一個NamingService。因此我們需要定時獲取nacos上所有的NamingService,存儲到本地,再通過NamingService去獲取實例。因此我們的做法是,配置一個監聽器,定期監聽nacos上的namespace變化,然后定期更新,維護到服務的內部緩存中。代碼如下:

Slf4j
@Configuration
@ConditionalOnRouteEnabled
public class RouteAutoConfiguration {

  @Autowired(required = false)
  private RouteProperties routeProperties;

  @PostConstruct
  public void init() {
      log.info("初始化智能路由!");
      NacosNamingFactory.initNamespace();
      addListener();
  }

  private void addListener() {
      int period = routeProperties.getPeriod();
      NacosExecutorService nacosExecutorService = new NacosExecutorService("namespace-listener");
      nacosExecutorService.execute(period);
  } 
}
 
  public static void initNamespace() {
      ApplicationContext applicationContext = SpringContextUtil.getContext();
      if (applicationContext == null) {
          return;
      }
      String serverAddr = applicationContext.getEnvironment().getProperty("spring.cloud.nacos.discovery.server-addr");
      if (serverAddr == null) {
          throw new RuntimeException("nacos地址為空!");
      }
      String url = serverAddr + "/nacos/v1/console/namespaces?namespaceId=";
      RestResult<String> restResult = HttpUtil.doGetJson(url, RestResult.class);
      List<Namespace> namespaces = JSON.parseArray(JSONObject.toJSONString(restResult.getData()), Namespace.class);;
      NacosNamingFactory.setNamespaces(namespaces);
  }
public class NacosExecutorService { 
  public void execute(int period) {
      executorService.scheduleWithFixedDelay(new NacosWorker(), 5, period, TimeUnit.SECONDS);
  } 
  public NacosExecutorService(String name) {
      executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
          @Override
          public Thread newThread(Runnable r) {
              Thread t = new Thread(r);
              t.setName("jdh-system-" + name);
              t.setDaemon(true);
              return t;
          }
      });
  } 
  final ScheduledExecutorService executorService; 
}

四. 遇到的難點

下面是智能路由實現過程,遇到的一些問題及解決方案。

問題1namespace啟動了太多線程,導致線程數過大?

因為服務需要維護過多的namespace,每個namespace內部又啟動多個線程維護服務實例信息,導致服務總線程數過大。

解決方案每個namespace設置只啟動2個線程,通過下列參數設置:

properties.setProperty(PropertyKeyConst.NAMING_CLIENT_BEAT_THREAD_COUNT, "1");
properties.setProperty(PropertyKeyConst.NAMING_POLLING_THREAD_COUNT, "1");

問題2支持一個線程調用多個服務?

每個請求都會創建一個線程,這個線程可能會調用多次其他服務。

解決方案既然調用多次,那就創建上下文,并保持上下文,調用結束后再清除。見代碼:

try {
  filterChain.doFilter(servletRequest, servletResponse);
} finally {
  ContextUtil.clear();
}

問題3:多應用支持:Tomcat,Springboot,Gateway?

我們內部有多種框架,如何保證這些不同框架服務的支持?

解決方案針對不同應用,開發不同的starter包。

問題4:SpringBoot版本兼容問題

解決方案:針對1.x和2.x單獨開發starter包。

五. 帶來的收益

1. 經濟價值

同樣的資源,多部署了n套環境,極大提高資源利用率。(畢竟增量環境和全量環境的代價還是相差很大的)

2. 研發價值

本地開發排查測試問題方便,極大提高研發效率。前面提到的IP優先規則,保證了這一點。本地請求總是最優先打到本地上。

3. 測試價值

多部署n套環境,支持更多版本,提高測試效率。同時只需要部署增量應用,也提高部署效率。

六. 總結

通過智能路由,我司實現了部署成本大幅減少,部署效率大幅提高,研發測試效率大幅提高。

最后總結下智能路由的主要功能:

1. 多環境管理:支持多環境路由,除了基準環境外,其他環境只部署增量應用。

2. 多用戶支持:支持多用戶公用一套環境,避免開發不同版本造成的沖突。

3. 前端研發路由:對前端研發人員,可以方便快捷地同一個任意后端人員對接。

4. 后端研發路由:對后端研發人員,無論什么環境都可以快速調試,快速發現問題。

5. 友好且兼容:對微服務無侵入性,且支持 Web、WebFlux、Tomcat等應用。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/qq_35184946/article/details/116134823

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
主站蜘蛛池模板: 欧美视频黄色 | 久成人| 性欧美videos另类极品 | 欧美一级欧美 | 亚洲国产成人久久成人52 | 精品久久久久久亚洲精品 | 国产精品视频导航 | 国产精品av久久久久久无 | 欧美 中文字幕 | 国产精品视频免费网站 | 一区二区三区日韩在线观看 | 日韩精品一区二区久久 | 国产午夜电影 | 91www成人久久 | 91九色视频在线播放 | 亚洲第一页视频 | 草久网| 色视频在线播放 | 91午夜少妇三级全黄 | 欧美 国产 亚洲 卡通 综合 | 欧美日韩在线播放 | 欧美性生活区 | 免费毛片小视频 | 91久久极品少妇韩国 | 欧美一级鲁丝片免费看 | 性爱视频在线免费 | 91看片王 | 欧美在线 | 亚洲 | 草久免费 | 精品视频一区二区三区四区 | 久久av免费| 狠狠操操 | 欧美一级免费视频 | 亚洲3p激情在线观看 | 久久最新网址 | 最新黄色电影网站 | 91看片在线免费观看 | 久久久中 | 国产免费传媒av片在线 | 日本娇小18xxxⅹhd | 超碰人人做人人爱 |