请选择 进入手机版 | 继续访问电脑版

[java] 设置 gateway+nacos动态路由管理流程

[复制链接]
查看189 | 回复29 | 2021-9-13 03:12:15 | 显示全部楼层 |阅读模式
目次

设置 gateway+nacos动态路由

第一步:起首 是设置设置 文件的设置 列表

然后在设置 读取设置 类上增长 革新 注解@RefreshScope

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. import org.springframework.cloud.context.config.annotation.RefreshScope;
  4. import org.springframework.cloud.gateway.filter.FilterDefinition;
  5. import org.springframework.cloud.gateway.route.RouteDefinition;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.stereotype.Component;
  8. import javax.validation.Valid;
  9. import javax.validation.constraints.NotNull;
  10. import java.util.ArrayList;
  11. import java.util.Arrays;
  12. import java.util.List;
  13. /**
  14. * @author :lhb
  15. * @date :Created in 2020-09-09 08:59
  16. * @description:GateWay路由配置
  17. * @modified By:
  18. * @version: $
  19. */
  20. @Slf4j
  21. @RefreshScope
  22. @Component
  23. @ConfigurationProperties(prefix = "spring.cloud.gateway")
  24. public class GatewayRoutes {
  25. /**
  26. * 路由列表.
  27. */
  28. @NotNull
  29. @Valid
  30. private List<RouteDefinition> routes = new ArrayList<>();
  31. /**
  32. * 适用于每条路线的过滤器定义列表
  33. */
  34. private List<FilterDefinition> defaultFilters = new ArrayList<>();
  35. private List<MediaType> streamingMediaTypes = Arrays
  36. .asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);
  37. public List<RouteDefinition> getRoutes() {
  38. return routes;
  39. }
  40. public void setRoutes(List<RouteDefinition> routes) {
  41. this.routes = routes;
  42. if (routes != null && routes.size() > 0 && log.isDebugEnabled()) {
  43. log.debug("Routes supplied from Gateway Properties: " + routes);
  44. }
  45. }
  46. public List<FilterDefinition> getDefaultFilters() {
  47. return defaultFilters;
  48. }
  49. public void setDefaultFilters(List<FilterDefinition> defaultFilters) {
  50. this.defaultFilters = defaultFilters;
  51. }
  52. public List<MediaType> getStreamingMediaTypes() {
  53. return streamingMediaTypes;
  54. }
  55. public void setStreamingMediaTypes(List<MediaType> streamingMediaTypes) {
  56. this.streamingMediaTypes = streamingMediaTypes;
  57. }
  58. @Override
  59. public String toString() {
  60. return "GatewayProperties{" + "routes=" + routes + ", defaultFilters="
  61. + defaultFilters + ", streamingMediaTypes=" + streamingMediaTypes + '}';
  62. }
  63. }
复制代码

第二步:设置 监听nacos监听器

  1. import cn.hutool.core.exceptions.ExceptionUtil;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.alibaba.nacos.api.NacosFactory;
  4. import com.alibaba.nacos.api.PropertyKeyConst;
  5. import com.alibaba.nacos.api.config.ConfigService;
  6. import com.alibaba.nacos.api.config.listener.Listener;
  7. import com.alibaba.nacos.api.exception.NacosException;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.apache.commons.collections.CollectionUtils;
  10. import org.apache.commons.collections.MapUtils;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.beans.factory.annotation.Value;
  13. import org.springframework.cloud.context.scope.refresh.RefreshScope;
  14. import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
  15. import org.springframework.cloud.gateway.route.RouteDefinition;
  16. import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
  17. import org.springframework.context.ApplicationEventPublisher;
  18. import org.springframework.context.ApplicationEventPublisherAware;
  19. import org.springframework.stereotype.Component;
  20. import reactor.core.publisher.Mono;
  21. import javax.annotation.PostConstruct;
  22. import javax.annotation.Resource;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Properties;
  26. import java.util.Set;
  27. import java.util.concurrent.ConcurrentHashMap;
  28. import java.util.concurrent.Executor;
  29. import java.util.concurrent.TimeUnit;
  30. import java.util.stream.Collectors;
  31. /**
  32. * @author :lhb
  33. * @date :Created in 2020-09-08 16:39
  34. * @description:监听nacos配置变更
  35. * @modified By:
  36. * @version: $
  37. */
  38. @Slf4j
  39. @Component
  40. public class GateWayNacosConfigListener implements ApplicationEventPublisherAware {
  41. @Autowired
  42. private RouteDefinitionWriter routedefinitionWriter;
  43. private ApplicationEventPublisher publisher;
  44. private static final Map<String, RouteDefinition> ROUTE_MAP = new ConcurrentHashMap<>();
  45. @Autowired
  46. private GatewayRoutes gatewayRoutes;
  47. @Resource
  48. private RefreshScope refreshScope;
  49. @Value(value = "${spring.cloud.nacos.config.server-addr}")
  50. private String serverAddr;
  51. @Value(value = "${spring.cloud.nacos.config.group:DEFAULT_GROUP}")
  52. private String group;
  53. @Value(value = "${spring.cloud.nacos.config.namespace}")
  54. private String namespace;
  55. private String routeDataId = "gateway-routes.yml";
  56. @PostConstruct
  57. public void onMessage() throws NacosException {
  58. log.info("serverAddr={}", serverAddr);
  59. Properties properties = new Properties();
  60. properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
  61. properties.put(PropertyKeyConst.NAMESPACE, namespace);
  62. ConfigService configService = NacosFactory.createConfigService(properties);
  63. this.publisher(gatewayRoutes.getRoutes());
  64. log.info("gatewayProperties=" + JSONObject.toJSONString(gatewayRoutes));
  65. configService.addListener(routeDataId, group, new Listener() {
  66. @Override
  67. public Executor getExecutor() {
  68. return null;
  69. }
  70. @Override
  71. public void receiveConfigInfo(String config) {
  72. log.info("监听nacos配置: {}, 旧的配置: {}, 新的配置: {}", routeDataId, gatewayRoutes, config);
  73. refreshScope.refresh("gatewayRoutes");
  74. try {
  75. TimeUnit.SECONDS.sleep(5);
  76. } catch (InterruptedException e) {
  77. log.error(ExceptionUtil.getMessage(e));
  78. }
  79. publisher(gatewayRoutes.getRoutes());
  80. }
  81. });
  82. }
  83. private boolean rePut(List<RouteDefinition> routeDefinitions) {
  84. if (MapUtils.isEmpty(ROUTE_MAP) && CollectionUtils.isEmpty(routeDefinitions)) {
  85. return true;
  86. }
  87. if (CollectionUtils.isEmpty(routeDefinitions)) {
  88. return true;
  89. }
  90. Set<String> strings = ROUTE_MAP.keySet();
  91. return strings.stream().sorted().collect(Collectors.joining())
  92. .equals(routeDefinitions.stream().map(v -> v.getId()).sorted().collect(Collectors.joining()));
  93. }
  94. /**
  95. * 增加路由
  96. *
  97. * @param def
  98. * @return
  99. */
  100. public Boolean addRoute(RouteDefinition def) {
  101. try {
  102. log.info("添加路由: {} ", def);
  103. routedefinitionWriter.save(Mono.just(def)).subscribe();
  104. ROUTE_MAP.put(def.getId(), def);
  105. } catch (Exception e) {
  106. e.printStackTrace();
  107. }
  108. return true;
  109. }
  110. /**
  111. * 删除路由
  112. *
  113. * @return
  114. */
  115. public Boolean clearRoute() {
  116. for (String id : ROUTE_MAP.keySet()) {
  117. routedefinitionWriter.delete(Mono.just(id)).subscribe();
  118. }
  119. ROUTE_MAP.clear();
  120. return false;
  121. }
  122. /**
  123. * 发布路由
  124. */
  125. private void publisher(String config) {
  126. this.clearRoute();
  127. try {
  128. log.info("重新更新动态路由");
  129. List<RouteDefinition> gateway = JSONObject.parseArray(config, RouteDefinition.class);
  130. for (RouteDefinition route : gateway) {
  131. this.addRoute(route);
  132. }
  133. publisher.publishEvent(new RefreshRoutesEvent(this.routedefinitionWriter));
  134. } catch (Exception e) {
  135. e.printStackTrace();
  136. }
  137. }
  138. /**
  139. * 发布路由
  140. */
  141. private void publisher(List<RouteDefinition> routeDefinitions) {
  142. this.clearRoute();
  143. try {
  144. log.info("重新更新动态路由: ");
  145. for (RouteDefinition route : routeDefinitions) {
  146. this.addRoute(route);
  147. }
  148. publisher.publishEvent(new RefreshRoutesEvent(this.routedefinitionWriter));
  149. } catch (Exception e) {
  150. e.printStackTrace();
  151. }
  152. }
  153. @Override
  154. public void setApplicationEventPublisher(ApplicationEventPublisher app) {
  155. publisher = app;
  156. }
  157. }
复制代码

第三步:设置 nacos的yml文件

  1. spring:
  2. cloud:
  3. gateway:
  4. discovery:
  5. locator:
  6. enabled: true
  7. lower-case-service-id: true
  8. routes:
  9. # 认证中心
  10. - id: firefighting-service-user
  11. uri: lb://firefighting-service-user
  12. predicates:
  13. - Path=/user/**
  14. # - Weight=group1, 8
  15. filters:
  16. - StripPrefix=1
  17. # 转流服务
  18. - id: liveStream
  19. uri: http://192.168.1.16:8081
  20. predicates:
  21. - Path=/liveStream/**
  22. # - Weight=group1, 8
  23. filters:
  24. - StripPrefix=1
  25. - id: firefighting-service-directcenter
  26. uri: lb://firefighting-service-directcenter
  27. predicates:
  28. - Path=/directcenter/**
  29. filters:
  30. - StripPrefix=1
  31. - id: firefighting-service-datainput
  32. uri: lb://firefighting-service-datainput
  33. predicates:
  34. - Path=/datainput/**
  35. filters:
  36. - StripPrefix=1
  37. - id: firefighting-service-squadron
  38. uri: lb://firefighting-service-squadron
  39. predicates:
  40. - Path=/squadron/**
  41. filters:
  42. - StripPrefix=1
  43. - id: firefighting-service-iot
  44. uri: lb://firefighting-service-iot
  45. predicates:
  46. - Path=/iot/**
  47. filters:
  48. - StripPrefix=1
  49. - id: websocket
  50. uri: lb:ws://firefighting-service-notice
  51. predicates:
  52. - Path=/notice/socket/**
  53. filters:
  54. - StripPrefix=1
  55. - id: firefighting-service-notice
  56. uri: lb://firefighting-service-notice
  57. predicates:
  58. - Path=/notice/**
  59. filters:
  60. # 验证码处理
  61. # - CacheRequest
  62. # - ImgCodeFilter
  63. - StripPrefix=1
  64. - id: websocket
  65. uri: lb:ws://firefighting-service-notice
  66. predicates:
  67. - Path=/notice/socket/**
  68. filters:
  69. - StripPrefix=1
  70. - id: firefighting-service-supervise
  71. uri: lb://firefighting-service-supervise
  72. predicates:
  73. - Path=/supervise/**
  74. filters:
  75. - StripPrefix=1
  76. - id: firefighting-service-new-supervise
  77. uri: lb://firefighting-service-new-supervise
  78. predicates:
  79. - Path=/new/supervise/**
  80. filters:
  81. - StripPrefix=2
  82. - id: firefighting-service-train
  83. uri: lb://firefighting-service-train
  84. predicates:
  85. - Path=/train/**
  86. filters:
  87. - StripPrefix=1
  88. - id: firefighting-support-user
  89. uri: lb://firefighting-support-user
  90. predicates:
  91. - Path=/support/**
  92. filters:
  93. - StripPrefix=1
  94. - id: firefighting-service-firesafety
  95. uri: lb://firefighting-service-firesafety
  96. predicates:
  97. - Path=/firesafety/**
  98. filters:
  99. - StripPrefix=1
  100. - id: firefighting-service-bigdata
  101. uri: lb://firefighting-service-bigdata
  102. predicates:
  103. - Path=/bigdata/**
  104. filters:
  105. - StripPrefix=1
  106. - id: firefighting-service-act-datainput
  107. uri: lb://firefighting-service-act-datainput
  108. predicates:
  109. - Path=/act_datainput/**
  110. filters:
  111. - StripPrefix=1
  112. - id: firefighting-service-publicity
  113. uri: lb://firefighting-service-publicity
  114. predicates:
  115. - Path=/publicity/**
  116. filters:
  117. - StripPrefix=1
  118. - id: firefighting-service-preplan
  119. uri: lb://firefighting-service-preplan
  120. predicates:
  121. - Path=/preplan/**
  122. filters:
  123. - StripPrefix=1
  124. - id: firefighting-service-uav
  125. uri: lb://firefighting-service-uav
  126. predicates:
  127. - Path=/uav/**
  128. filters:
  129. - StripPrefix=1
  130. - id: firefighting-service-ard-mgr
  131. uri: lb://firefighting-service-ard-mgr
  132. predicates:
  133. - Path=/ard_mgr/**
  134. filters:
  135. - StripPrefix=1
  136. - id: admin-server
  137. uri: lb://admin-server
  138. predicates:
  139. - Path=/adminsServer/**
  140. filters:
  141. - 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,我们通过下列方式来设置头部。

  1. ## nginx的匹配规则设置header
  2. proxy_set_header req_context "{'version': '1.0'}"
复制代码

如许 子,我们就为版本是1.0的流量设置了头部,此中 的参数可以恣意 添加,这里仅枚举 最紧张 的一个参数。

别的 ,还有一个题目 ,流量只有从Nginx进来,才会带上这个头部。假如 是在内部直接访问某个中央 的服务,那么这个时间 流量是没有头部的。对此,我们的办理 方案是filter,通过filter可以动态地拦截哀求 ,修改哀求 头部,为其初始化一个默认值。

  1. public class FlowDyeFilter implements Filter {
  2. @Override
  3. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  4. //1. 获取servletrequest
  5. HttpServletRequest request = (HttpServletRequest)servletRequest;
  6. //2. 获取请求头部
  7. String context = request.getHeader(ContextUtil.REQUEST_CONTEXT);
  8. //3. 初始化请求上下文,如果没有,就进行初始化
  9. initContext(context);
  10. try {
  11. filterChain.doFilter(servletRequest, servletResponse);
  12. } finally {
  13. ContextUtil.clear();
  14. }
  15. }
  16. public void initContext(String contextStr) {
  17. //json转object
  18. Context context = JSONObject.parseObject(contextStr, GlobalContext.class);
  19. //避免假初始化
  20. if (context == null) {
  21. context = new Context();
  22. }
  23. //这里进行初始化,如果没值,设置一个默认值
  24. if (StringUtils.isEmpty(context.getVersion())) {
  25. context.setVersion("master");
  26. }
  27. ...
  28. //存储到上下文中
  29. ContextUtil.setCurrentContext(context);
  30. }
  31. }
复制代码

通过这个filter,保证了在中央 环节访问时,流量仍然 被染色。

<2> 流量透传

流量在入口被染色后,必要 透传到整个链路,因此必要 对服务做一些处理,下面分几种情况 分别处理。

1~ Spring Cloud Gateway

对于Gateway,保证哀求 在转发过程中的header不丢,这个是必要的。这里通过Gateway自带的GlobalFilter来实现,代码如下:

  1. public class WebfluxFlowDyeFilter implements GlobalFilter, Ordered {
  2. @Override
  3. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  4. //1. 获取请求上下文
  5. String context = exchange.getRequest().getHeaders().getFirst(ContextUtil.REQUEST_CONTEXT);
  6. //2. 构造ServerHttpRequest
  7. ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().header(ContextUtil.REQUEST_CONTEXT, context).build();
  8. //3. 构造ServerWebExchange
  9. ServerWebExchange serverWebExchange = exchange.mutate().request(serverHttpRequest).build();
  10. return chain.filter(serverWebExchange).then(
  11. Mono.fromRunnable( () -> {
  12. ContextUtil.clear();
  13. })
  14. );
  15. }
  16. }
复制代码

2~ SpringCloud:Feign

这一类也是最常用的,SC的服务集群都通过Feign举行 交互,因此只必要 设置 Feign透传即可。在这里,我们使用 了Feign自带的RequestInterceptor,实现哀求 拦截。代码如下:

  1. @Configuration
  2. public class FeignAutoConfiguration {
  3. @Bean
  4. public RequestInterceptor headerInterceptor() {
  5. return requestTemplate -> {
  6. setRequestContext(requestTemplate);
  7. };
  8. }
  9. private void setRequestContext(RequestTemplate requestTemplate) {
  10. Context context = ContextUtil.getCurrentContext();
  11. if (context != null) {
  12. requestTemplate.header(ContextUtil.REQUEST_CONTEXT, JSON.toJSONString(ContextUtil.getCurrentContext()));
  13. }
  14. }
  15. }
复制代码

3~ HTTP

末了 一类,也是用得最少的一类,即直接通过HTTP发送哀求 。比如CloseableHttpClient,RestTemplate。办理 方案直访问 代码:

  1. //RestTemplate
  2. HttpHeaders headers = new HttpHeaders();
  3. headers.set(ContextUtil.REQUEST_CONTEXT,JSONObject.toJSONString(ContextUtil.getCurrentContext()));
  4. //CloseableHttpClient
  5. HttpGet httpGet = new HttpGet(uri);
  6. httpGet.setHeader(ContextUtil.REQUEST_CONTEXT,JSONObject.toJSONString(ContextUtil.getCurrentContext()));
复制代码

只必要 粗暴地在发送头部中增长 header即可,而其哀求 上下文直接通过当火线 程上下文获取即可。

<3> 设置 负载规则

完成了流量染色,下面就差服务发现了。服务发现基于注册中央 nacos,因此必要 修改负载规则。在这里,我们设置 Ribbon的负载规则,修改为自定义负载平衡 器NacosWeightLoadBalancerRule。

  1. @Bean
  2. @Scope("prototype")
  3. public IRule getRibbonRule() {
  4. return new NacosWeightLoadBalancerRule();
  5. }
复制代码
  1. public class NacosWeightLoadBalancerRule extends AbstractLoadBalancerRule {
  2. @Override
  3. public Server choose(Object o) {
  4. DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
  5. String name = loadBalancer.getName();
  6. try {
  7. Instance instance = nacosNamingService.getInstance(name);
  8. return new NacosServer(instance);
  9. } catch (NacosException ee) {
  10. log.error("请求服务异常!异常信息:{}", ee);
  11. } catch (Exception e) {
  12. log.error("请求服务异常!异常信息:{}", e);
  13. }
  14. return null;
  15. }
  16. }
复制代码

从代码中可以看到,终极 通过nacosNamingService.getInstance()方法获取实例。

<4> 设置 智能路由规则

上面的负载规则,终极 调用的是nacosNamingService.getInstance()方法,该方法内里 定义了智能路由规则,紧张 功能是根据流量举行 服务精准匹配。

规则如下:

1~开关判定 :是否开启路由功能,没有则走nacos默认路由。

2~获取实例:根据流量,获取对应实例。此中 ,路由匹配按照肯定 的优先级举行 匹配。

路由规则:IP优先 > 环境 + 组 > 环境 + 默认组

表明 一下这个规则,起首 是获取实例,必要 先获取nacos上面的全部 可用实例,然后遍历,从中选出一个最合适的实例。

然后IP优先的含义是,假如 在本地调试服务,那么从本地直接访问网站,哀求 就会优先访问本地服务,那么就便于开辟 职员 调试了,debug,本地开辟 都不是题目 了!

着实 是,环境 + 组,这个规则代表了假如 存在对应的环境和组都雷同 的服务,那作为最符合的实例肯定优先返回,着实 是环境 + 默认组,末了 假如 都没有,就访问基准环境(master)。

注:环境和组的概念对应nacos上的namespace和group,如有不懂,请自行查看nacos官方文档。

终极 代码如下:

  1. public class NacosNamingService {
  2. public Instance getInstance(String serviceName, String groupName) throws NacosException {
  3. //1. 判断智能路由开关是否开启,没有走默认路由
  4. if (!isEnable()) {
  5. return discoveryProperties.namingServiceInstance().selectOneHealthyInstance(serviceName, groupName);
  6. }
  7. Context context = ContextUtil.getCurrentContext();
  8. if (Context == null) {
  9. return NacosNamingFactory.getNamingService(CommonConstant.Env.MASTER).selectOneHealthyInstance(serviceName);
  10. }
  11. //2. 获取实例
  12. return getInstance(serviceName, context);
  13. }
  14. public Instance getInstance(String serviceName, Context context) throws NacosException {
  15. Instance envAndGroupInstance = null;
  16. Instance envDefGroupInstance = null;
  17. Instance defaultInstance = null;
  18. //2.1 获取所有可以调用的命名空间
  19. List<Namespace> namespaces = NacosNamingFactory.getNamespaces();
  20. for (Namespace namespace : namespaces) {
  21. String thisEnvName = namespace.getNamespace();
  22. NamingService namingService = NacosNamingFactory.getNamingService(thisEnvName);
  23. List<Instance> instances = new ArrayList<>();
  24. List<Instance> instances1 = namingService.selectInstances(serviceName, true);
  25. List<Instance> instances2 = namingService.selectInstances(serviceName, groupName, true);
  26. instances.addAll(instances1);
  27. instances.addAll(instances2);
  28. //2.2 路由匹配
  29. for (Instance instance : instances) {
  30. // 优先本机匹配
  31. if (instance.getIp().equals(clientIp)) {
  32. return instance;
  33. }
  34. String thisGroupName = null;
  35. String thisServiceName = instance.getServiceName();
  36. if (thisServiceName != null && thisServiceName.split("@@") != null) {
  37. thisGroupName = thisServiceName.split("@@")[0];
  38. }
  39. if (thisEnvName.equals(envName) && thisGroupName.equals(groupName)) {
  40. envAndGroupInstance = instance;
  41. }
  42. if (thisEnvName.equals(envName) && thisGroupName.equals(CommonConstant.DEFAULT_GROUP)) {
  43. envDefGroupInstance = instance;
  44. }
  45. if (thisEnvName.equals(CommonConstant.Env.MASTER) && thisGroupName.equals(CommonConstant.DEFAULT_GROUP)) {
  46. defaultInstance = instance;
  47. }
  48. }
  49. }
  50. if (envAndGroupInstance != null) {
  51. return envAndGroupInstance;
  52. }
  53. if (envDefGroupInstance != null) {
  54. return envDefGroupInstance;
  55. }
  56. return defaultInstance;
  57. }
  58. @Autowired
  59. private NacosDiscoveryProperties discoveryProperties;
  60. }
复制代码

<5> 设置 智能路由定时使命

刚才在先容 智能路由的匹配规则时,提到“获取全部 可以调用的定名 空间”。这是由于 ,nacos上大概 有很多个定名 空间namespace,而我们必要 把全部 namespace上的全部 可用服务都获取到,而nacos源码中,一个namespace对应一个NamingService。因此我们必要 定时获取nacos上全部 的NamingService,存储到本地,再通过NamingService去获取实例。因此我们的做法是,设置 一个监听器,定期监听nacos上的namespace变化,然后定期更新,维护到服务的内部缓存中。代码如下:

  1. Slf4j
  2. @Configuration
  3. @ConditionalOnRouteEnabled
  4. public class RouteAutoConfiguration {
  5. @Autowired(required = false)
  6. private RouteProperties routeProperties;
  7. @PostConstruct
  8. public void init() {
  9. log.info("初始化智能路由!");
  10. NacosNamingFactory.initNamespace();
  11. addListener();
  12. }
  13. private void addListener() {
  14. int period = routeProperties.getPeriod();
  15. NacosExecutorService nacosExecutorService = new NacosExecutorService("namespace-listener");
  16. nacosExecutorService.execute(period);
  17. }
  18. }
复制代码
  1. public static void initNamespace() {
  2. ApplicationContext applicationContext = SpringContextUtil.getContext();
  3. if (applicationContext == null) {
  4. return;
  5. }
  6. String serverAddr = applicationContext.getEnvironment().getProperty("spring.cloud.nacos.discovery.server-addr");
  7. if (serverAddr == null) {
  8. throw new RuntimeException("nacos地址为空!");
  9. }
  10. String url = serverAddr + "/nacos/v1/console/namespaces?namespaceId=";
  11. RestResult<String> restResult = HttpUtil.doGetJson(url, RestResult.class);
  12. List<Namespace> namespaces = JSON.parseArray(JSONObject.toJSONString(restResult.getData()), Namespace.class);;
  13. NacosNamingFactory.setNamespaces(namespaces);
  14. }
复制代码
  1. public class NacosExecutorService {
  2. public void execute(int period) {
  3. executorService.scheduleWithFixedDelay(new NacosWorker(), 5, period, TimeUnit.SECONDS);
  4. }
  5. public NacosExecutorService(String name) {
  6. executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
  7. @Override
  8. public Thread newThread(Runnable r) {
  9. Thread t = new Thread(r);
  10. t.setName("jdh-system-" + name);
  11. t.setDaemon(true);
  12. return t;
  13. }
  14. });
  15. }
  16. final ScheduledExecutorService executorService;
  17. }
复制代码

四. 遇到 的难点

下面是智能路由实现过程,遇到 的一些题目 及办理 方案。

题目 1namespace启动了太多线程,导致线程数过大?

由于 服务必要 维护过多的namespace,每个namespace内部又启动多个线程维护服务实例信息,导致服务总线程数过大。

办理 方案每个namespace设置只启动2个线程,通过下列参数设置:

  1. properties.setProperty(PropertyKeyConst.NAMING_CLIENT_BEAT_THREAD_COUNT, "1");
  2. properties.setProperty(PropertyKeyConst.NAMING_POLLING_THREAD_COUNT, "1");
复制代码

题目 2支持一个线程调用多个服务?

每个哀求 都会创建一个线程,这个线程大概 会调用多次其他服务。

办理 方案既然调用多次,那就创建上下文,并保持上下文,调用竣事 后再扫除 。见代码:

  1. try {
  2. filterChain.doFilter(servletRequest, servletResponse);
  3. } finally {
  4. ContextUtil.clear();
  5. }
复制代码

题目 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等应用。

以上为个人履历 ,渴望 能给大家一个参考,也渴望 大家多多支持脚本之家。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar 浓茶人生 | 2021-9-22 09:53:08 | 显示全部楼层
知识就是力量啊!
回复

使用道具 举报

avatar 韩邑王生1977 | 2021-10-9 07:35:53 | 显示全部楼层
看了这么多帖子,第一次看到这么有深度了!
回复

使用道具 举报

avatar 上善若水8L8 | 2021-10-10 10:09:46 | 显示全部楼层
admin楼主,替我问候您主治大夫!
回复

使用道具 举报

avatar 张柏芝一号胸 | 2021-10-15 22:18:07 | 显示全部楼层
楼上的这是啥态度呢?
回复

使用道具 举报

avatar 123457044 | 2021-10-18 20:28:36 | 显示全部楼层
admin楼主你想太多了!
回复

使用道具 举报

admin楼主,您忘记吃药了吧?
回复

使用道具 举报

收藏了,以后可能会用到!
回复

使用道具 举报

我裤子脱了,纸都准备好了,你就给我看这个?
回复

使用道具 举报

对牛弹琴的人越来越多了!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则