从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(六)(优化篇)开发篇-如何解决微服务开发环境请求实例转发到别人机器问题

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(六)(优化篇)开发篇-如何解决微服务开发环境请求实例转发到别人机器问题

image.png

前言:


本篇是上一篇《从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(七) 开发环境使用轻量级在线文档解决知识分享问题》的优化篇


原来是基于下图,分散处理的,分散在各个代码层并且不是统一获取version对应的instance


1.png

本篇将集成为两个共通,统一通过client 端设置version 依次传递 下去,然后各个层通过lb获取version对应的instance,该优化是借鉴了其他一些架构,觉得不错,引入进来了,如下图


image.png

源码部分


新增两个共通模块mini-cloud-common-gateaway,mini-cloud-balancer ,目录结构如下

1.png

mini-cloud-gateaway 源码


mini-cloud-common-gateaway 代码结构以及代码明细,将作为共通被mini-cloud-gateaway 使用

1.png

pom.xml   dependencies

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--gateway 网关依赖,内置webflux 依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gateway-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>

EnableMiniCloudRoute.java 主要作为开启引用MiniCloudRouteAutoConfiguration 自定义路由的开关,不然是无法扫描到 MiniCloudRouteAutoConfiguration 类的

package com.minicloud.common.gateaway.annotation;
import com.minicloud.common.gateaway.config.MiniCloudRouteAutoConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(MiniCloudRouteAutoConfiguration.class)
public @interface EnableMiniCloudRoute {
}

MiniCloudRouteAutoConfiguration.java 主要是注入后续源码的扫描包,便于引用项目扫面到

package com.minicloud.common.gateaway.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.minicloud.common.gateaway")
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class MiniCloudRouteAutoConfiguration {
}
MiniCloudLoadBalancerClientConfiguration.java 
  如果是使用 REACTIVE 作为web引擎,则可以初始化
MiniCloudReactiveLoadBalancerClientFilter和  MiniCloudLoadBalancer 两个类 
MiniCloudReactiveLoadBalancerClientFilter :主要是重写 ReactiveLoadBalancerClientFilter 获取request中的version
VersionMiniCloudLoadBalancer: 主要是获取到version 后获取version 对应lb中的instance
import com.minicloud.common.gateaway.filter.MiniCloudReactiveLoadBalancerClientFilter;
import com.minicloud.common.gateaway.rule.MiniCloudLoadBalancer;
import com.minicloud.common.gateaway.rule.VersionMiniCloudLoadBalancer;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(LoadBalancerProperties.class)
@AutoConfigureBefore(GatewayReactiveLoadBalancerClientAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class MiniCloudLoadBalancerClientConfiguration {
  @Bean
  public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(MiniCloudLoadBalancer miniCloudLoadBalancer,
                                      LoadBalancerProperties properties) {
    return new MiniCloudReactiveLoadBalancerClientFilter(properties, miniCloudLoadBalancer);
  }
  @Bean
  public MiniCloudLoadBalancer grayLoadBalancer(DiscoveryClient discoveryClient) {
    return new VersionMiniCloudLoadBalancer(discoveryClient);
  }
}

MiniCloudReactiveLoadBalancerClientFilter.java

@Slf4j
public class MiniCloudReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter {
  private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
  private LoadBalancerProperties properties;
  private MiniCloudLoadBalancer miniCloudLoadBalancer;
  public MiniCloudReactiveLoadBalancerClientFilter(LoadBalancerProperties properties, MiniCloudLoadBalancer miniCloudLoadBalancer) {
    super(null, properties);
    this.properties = properties;
    this.miniCloudLoadBalancer = miniCloudLoadBalancer;
  }
  @Override
  public int getOrder() {
    return LOAD_BALANCER_CLIENT_FILTER_ORDER;
  }
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
    String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
    if (url == null
        || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
      return chain.filter(exchange);
    }
    // preserve the original url
    ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
    if (log.isTraceEnabled()) {
      log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName()
          + " url before: " + url);
    }
    return choose(exchange).doOnNext(response -> {
      if (!response.hasServer()) {
        throw NotFoundException.create(properties.isUse404(),
            "Unable to find instance for " + url.getHost());
      }
      URI uri = exchange.getRequest().getURI();
      // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
      // if the loadbalancer doesn't provide one.
      String overrideScheme = null;
      if (schemePrefix != null) {
        overrideScheme = url.getScheme();
      }
      DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(
          response.getServer(), overrideScheme);
      URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri);
      if (log.isTraceEnabled()) {
        log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
      }
      exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
    }).then(chain.filter(exchange));
  }
  private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
    URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
    ServiceInstance serviceInstance = miniCloudLoadBalancer.choose(uri.getHost(),exchange.getRequest());
    return Mono.just(new DefaultResponse(serviceInstance));
  }

MiniCloudLoadBalancer

package com.minicloud.common.gateaway.rule;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.server.reactive.ServerHttpRequest;
public interface MiniCloudLoadBalancer {
  ServiceInstance choose(String serviceId, ServerHttpRequest request);
}

VersionMiniCloudLoadBalancer.java

@Slf4j
@AllArgsConstructor
public class VersionMiniCloudLoadBalancer implements MiniCloudLoadBalancer {
  private DiscoveryClient discoveryClient;
  @Override
  public ServiceInstance choose(String serviceId, ServerHttpRequest request) {
    List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
    //注册中心无实例 抛出异常
    if (CollUtil.isEmpty(instances)) {
      log.warn("No instance available for {}", serviceId);
      throw new NotFoundException("No instance available for " + serviceId);
    }
    // 获取请求version,无则随机返回可用实例
    String reqVersion = request.getHeaders().getFirst("version");
    if (StrUtil.isBlank(reqVersion)) {
      return instances.get(RandomUtil.randomInt(instances.size()));
    }
    // 遍历可以实例元数据,若匹配则返回此实例
    for (ServiceInstance instance : instances) {
      Map<String, String> metadata = instance.getMetadata();
      String targetVersion = MapUtil.getStr(metadata, "version");
      if (reqVersion.equalsIgnoreCase(targetVersion)) {
        log.info("gray requst match success :{} {}", reqVersion, instance);
        return instance;
      }
    }
    return instances.get(RandomUtil.randomInt(instances.size()));
  }
}

mini-cloud-common-balancer 源码


mini-cloud-common-balancer 代码结构以及代码明细,将作为共通被各个业务端使用

1.png

pom.xml

 <dependencies>
        <dependency>
            <groupId>org.mini-cloud</groupId>
            <artifactId>mini-cloud-common-core</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-core</artifactId>
        </dependency>
    </dependencies>

spring.factories spring-boot 约定文件,也就是传说的“约定大于配置”

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.minicloud.common.balancer.config.MiniCloudRibbonLoadBalancerConfiguration

MiniCloudRibbonLoadBalancerConfiguration.java

主要为了注入bean: MiniCloudRibbonLoadBalancerRule ,RequestInterceptor

import com.minicloud.common.balancer.fegin.MiniCloudFeignRequestInterceptor;
import com.minicloud.common.balancer.rule.MiniCloudRibbonLoadBalancerRule;
import feign.RequestInterceptor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class MiniCloudRibbonLoadBalancerConfiguration {
  @Bean
  @ConditionalOnMissingBean
  @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  public MiniCloudRibbonLoadBalancerRule ribbonLoadBalancerRule() {
    return new MiniCloudRibbonLoadBalancerRule();
  }
  @Bean
  public RequestInterceptor grayFeignRequestInterceptor() {
    return new MiniCloudFeignRequestInterceptor();
  }
}

MiniCloudFeignRequestInterceptor.java 为了拦截fegin请求并设置上游发来的 version

@Slf4j
public class MiniCloudFeignRequestInterceptor implements RequestInterceptor {
  @Override
  public void apply(RequestTemplate template) {
    String reqVersion = WebUtils.getRequest() != null
        ? WebUtils.getRequest().getHeader("version") : null;
    if (StrUtil.isNotBlank(reqVersion)) {
      log.debug("feign gray add header version :{}", reqVersion);
      template.header("version", reqVersion);
    }
  }

MiniCloudRibbonLoadBalancerRule.java 自定义使用端ribbon loadbalance 路由

@Slf4j
public class MiniCloudRibbonLoadBalancerRule extends AbstractLoadBalancerRule {
  @Override
  public void initWithNiwsConfig(IClientConfig iClientConfig) {
  }
  @Override
  public Server choose(Object key) {
    return choose(getLoadBalancer(), key);
  }
  public Server choose(ILoadBalancer lb, Object key) {
    List<Server> reachableServers = lb.getReachableServers();
    //注册中心无可用实例 抛出异常
    if (CollUtil.isEmpty(reachableServers)) {
      log.warn("No instance available for {}", key);
      return null;
    }
    // 获取请求version,无则随机返回可用实例
    String reqVersion = WebUtils.getRequest() != null
        ? WebUtils.getRequest().getHeader("version") : null;
    if (StrUtil.isBlank(reqVersion)) {
      return reachableServers.get(RandomUtil.randomInt(reachableServers.size()));
    }
    // 遍历可以实例元数据,若匹配则返回此实例
    for (Server server : reachableServers) {
      NacosServer nacosServer = (NacosServer) server;
      Map<String, String> metadata = nacosServer.getMetadata();
      String targetVersion = MapUtil.getStr(metadata,"version");
      if (reqVersion.equalsIgnoreCase(targetVersion)) {
        log.debug("gray requst match success :{} {}", reqVersion, nacosServer);
        return nacosServer;
      }
    }
    return reachableServers.get(RandomUtil.randomInt(reachableServers.size()));
  }
}


相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
11天前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
30 2
|
14天前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
11天前
|
JSON Java 数据格式
【微服务】SpringCloud之Feign远程调用
本文介绍了使用Feign作为HTTP客户端替代RestTemplate进行远程调用的优势及具体使用方法。Feign通过声明式接口简化了HTTP请求的发送,提高了代码的可读性和维护性。文章详细描述了Feign的搭建步骤,包括引入依赖、添加注解、编写FeignClient接口和调用代码,并提供了自定义配置的示例,如修改日志级别等。
30 1
|
14天前
|
人工智能 文字识别 Java
SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
尼恩,一位拥有20年架构经验的老架构师,通过其深厚的架构功力,成功指导了一位9年经验的网易工程师转型为大模型架构师,薪资逆涨50%,年薪近80W。尼恩的指导不仅帮助这位工程师在一年内成为大模型架构师,还让他管理起了10人团队,产品成功应用于多家大中型企业。尼恩因此决定编写《LLM大模型学习圣经》系列,帮助更多人掌握大模型架构,实现职业跃迁。该系列包括《从0到1吃透Transformer技术底座》、《从0到1精通RAG架构》等,旨在系统化、体系化地讲解大模型技术,助力读者实现“offer直提”。此外,尼恩还分享了多个技术圣经,如《NIO圣经》、《Docker圣经》等,帮助读者深入理解核心技术。
SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
19天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
98 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
19天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
34 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
4月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
24天前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
113 2