SpringCloud负载均衡实现定向路由详情

目录
  • 背景
  • 实现方式
    • 基于ip
    • 基于nacos的元数据
  • 实现原理
    • Gateway服务
    • 普通服务
  • 使用方法
    • metadata模式
    • ip模式
    • auto模式
  • 总结

背景

随着微服务项目的迭代,可能一个服务会有多个实例,这时候就会涉及到负载均衡。然而我们在开发的时候,肯定希望只启动一个项目。然后调试的时候希望负载均衡把请求分配到我们正在开发测试的服务实例上面。

如图所示,我们希望可以指定调用路径也就是定向路由。

实现方式

基于ip

这个很好理解,就是开发者本地正在运行的服务在nacos上面肯定显示你本机的ip;那么只要我得到开发者的ip就能够根据这个ip来过滤nacos上面的服务,达到定向路由的效果。

基于nacos的元数据

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          version: "mfine"

在yaml中配置nacos元数据的version属性。前端在请求header中添加与其对应version属性,就可以实现服务过滤也就是定向路由。

实现原理

Gateway服务

因为gateway底层的不同,所以其负载均衡也与普通服务的不同,因此要特殊处理。先看gateway中load balancer组件调用流程。

首先在gateway中load balancer本身也是一个过滤器,所以流程如下。

  • ReactiveLoadBalancerClientFilter里面有个LoadBalancerClientFactory属性,通过这个工厂获取具体的负载均衡器
  • LoadBalancerClientFactory会载入LoadBalancerClientConfiguration配置
  • LoadBalancerClientConfiguration会初始化我们需要的RoundRobinLoadBalancer,并且会通过构造函数传入LoadBalancerClientFactory对象。

那我们要做什么呢?其实就是截胡。

  • 实现自己的LoadBalancerClientFactory,传入自己LoadBalancerClientConfiguration
  • 在自己的LoadBalancerClientConfiguration初始化自己的RoundRobinLoadBalancer
  • 最后在自己的ReactiveLoadBalancerClientFilter里面传入自己的LoadBalancerClientFactory,获得自己的负载均衡器。

具体源码(只放核心)

MyRoundRobinLoadBalancer

private Response<ServiceInstance> getInstanceResponse(
    List<ServiceInstance> instances, ServerWebExchange exchange) {
    if (instances.isEmpty()) {
        log.warn("No servers available for service: " + this.serviceId);
        return new EmptyResponse();
    }
    try {
        //可重入锁
        if (this.lock.tryLock(10, TimeUnit.SECONDS))
            instances = this.filterServiceInstance(exchange, instances);
        // TODO: enforce order?
        int pos = Math.abs(this.position.incrementAndGet());
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    } catch (InterruptedException e) {
        throw new RuntimeException("自定义负载均衡器,超时等待异常");
    } finally {
        lock.unlock();
    }
}
// 根据附加信息过滤服务
private List<ServiceInstance> filterServiceInstance(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {

    List<ServiceInstance> filteredServices = new ArrayList<>();
    // 自动模式
    if (DevConfigEnum.AUTO.getCode().equals(this.properties.getModel())) {
        filteredServices = autoModel(exchange, serviceInstances);
    }
    // ip 模式
    if (this.properties.getModel().equals(DevConfigEnum.IP.getCode())) {
        filteredServices = ipModel(exchange, serviceInstances);
    }
    // metadata 模式
    if (this.properties.getModel().equals(DevConfigEnum.METADATA.getCode())) {
        filteredServices = metadataModel(exchange, serviceInstances);
    }
    if (filteredServices.isEmpty()) {
        log.info("未发现符合ip或metadata.version服务,将采用原始服务集合");
        return serviceInstances;
    }
    return filteredServices;
}
// 自动模式
private List<ServiceInstance> autoModel(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {
    List<ServiceInstance> filteredServices;

    filteredServices = ipModel(exchange, serviceInstances);
    if (filteredServices.isEmpty()) {
        filteredServices = metadataModel(exchange, serviceInstances);
    }
    return filteredServices;
}
//元数据模式
private List<ServiceInstance> metadataModel(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {
    String version = exchange.getRequest().getHeaders().getFirst("version");
    List<ServiceInstance> filteredServices = new ArrayList<>();
    if (version != null) {
        log.info("version模式:获取metadata.version成功");
        filteredServices = serviceInstances.stream().filter(instance -> {
            String metaVersion = instance.getMetadata().get("version");
            if (metaVersion == null) {
                return false;
            }
            return metaVersion.equals(version);
        }).collect(Collectors.toList());
    }
    return filteredServices;
}
// ip模式
private List<ServiceInstance> ipModel(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {
    List<ServiceInstance> filteredServices = new ArrayList<>();
    try {
        String ipAddress = exchange.getRequest().getHeaders().getFirst("ip");
        if (ipAddress == null) {
            ipAddress = IPUtils.getIpAddress(exchange.getRequest());
        }
        log.warn("ip模式:获取ip成功");
        String finalIpAddress = ipAddress;
        filteredServices = serviceInstances.stream().filter(item -> item.getHost().equals(finalIpAddress))
            .collect(Collectors.toList());
    } catch (UnknownHostException e) {
        log.warn("ip模式:获取ip失败,无法进行定向路由");
    }
    return filteredServices;
}

MyLoadBalancerClientFactory

public class MyLoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
      implements ReactiveLoadBalancer.Factory<ServiceInstance>{

    ................

    public MyLoadBalancerClientFactory() {
        // 传入自己的自动配置
        super(MyLoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
    }

    ...........
}

MyLoadBalancerClientConfiguration

@Configuration
public class MyLoadBalancerClientConfiguration {
    @Autowired
    private MicroServiceDevConfigProperties microServiceDevConfigProperties;
    @Bean
    public ReactorServiceInstanceLoadBalancer reactiveLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        // 初始化自己的负载均衡器
        return new MyRoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, 1000,microServiceDevConfigProperties);
    }
}
@Configuration
public class MyReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter {
    private static final Log log = LogFactory
            .getLog(ReactiveLoadBalancerClientFilter.class);
    private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
    private final MyLoadBalancerClientFactory clientFactory;
    private LoadBalancerProperties properties;
    // 注入自己的LoadBalancerClientFactory
    public MyReactiveLoadBalancerClientFilter(MyLoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        super(null, null);
        this.clientFactory = clientFactory;
        this.properties = properties;
    }
    ........
    private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
        URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
        //获得自己load balancer
        MyRoundRobinLoadBalancer loadBalancer = this.clientFactory
                .getInstance(uri.getHost(), MyRoundRobinLoadBalancer.class);
        if (loadBalancer == null) {
            throw new NotFoundException("No loadbalancer available for " + uri.getHost());
        }
        // 在自己的load balancer里面扩展choose方法,使其接受ServerWebExchange参数
        // 传入ServerWebExchange我们就可以,获取请求信息,方便我们过滤
        return loadBalancer.choose(exchange);
    }
}

我的这种实现较为繁琐,可能大家有更好方式,大家要是有更好更简单的方法,也可以直接替换掉。

普通服务

普通服务实现自定义负载均衡器就很简单了,实现自定义RoundRobinRule就可以了

@Configuration
public class MyRoundRobinLoadBalancer extends RoundRobinRule {
    //不同的使用注入的方式获取请求信息
    @Autowired
    private HttpServletRequest request;
    .....
        private List<Server> filterServers(List<Server> reachableServers) {
        List<Server> servers = new ArrayList<>();
        if (this.properties.getModel().equals(DevConfigEnum.AUTO.getCode())) {
            servers = ipModel(reachableServers);
            if (servers.isEmpty()) {
                servers = metadataModel(reachableServers);
            }
        }
        if (this.properties.getModel().equals(DevConfigEnum.IP.getCode())) {
            servers = ipModel(reachableServers);
        }
        if (this.properties.getModel().equals(DevConfigEnum.METADATA.getCode())) {
            servers = metadataModel(reachableServers);
        }
        if (servers.isEmpty()) {
            return reachableServers;
        }
        return servers;
    }

    private List<Server> metadataModel(List<Server> reachableServers) {
        String version = request.getHeader("version");
        List<Server> servers = new ArrayList<>();
        if (version != null) {
            log.info("metadata模式: 获取version成功");
            servers = reachableServers.stream().filter(item -> {
                NacosServer nacosServer = (NacosServer) item;
                String metaVersion = nacosServer.getMetadata().get("version");
                if (metaVersion == null) {
                    return false;
                }
                return metaVersion.equals(version);
            }).collect(Collectors.toList());
        } else {
            log.warn("metadata模式: header中无version字段且未获取到请求者ip");
        }
        return servers;
    }

    private List<Server> ipModel(List<Server> reachableServers) {
        List<Server> servers = new ArrayList<>();
        try {
            String ip = this.request.getHeader("ip");
            if (ip == null) {
                ip = IPUtils.getIpAddress(request);
            }
            String finalIp = ip;
            servers = reachableServers.stream().filter(item -> item.getHost().equals(finalIp)).collect(Collectors.toList());
            log.info("ip模式: 获取请求者ip成功");
        } catch (UnknownHostException e) {
            log.warn("ip模式: 获取ip失败");
        }
        return servers;
    }
    ........
}

深入思考一下,通过注入的方式获取request信息是否存在多线程安全问题呢?

使用方法

metadata模式

配置yaml:

spring:
  application:
    name: cloud-order
  cloud:
    nacos:
      discovery:
        metadata:
        // 重点
          version: mfine
celi-dev:
  config:
    model: "metadata"

nacos中服务元数据

然后请求头中附带version信息

自定义负载均衡器会通过请求头中的version去nacos中注册服务的元数据里面去比对version信息。

ip模式

配置yaml

celi-dev:
  config:
    model: "ip"
  • 在header中指定IP
  • 依靠请求信息获取ip

配置yaml就好

此不指定ip的时候,后台获取的ip可能不对。取决你本地是否存在多张网卡(虚拟网卡也算),有时候nacos中ip显示也会是虚拟网卡的ip。

使用前请确认你的服务在nacos中的ip是多少,然后在header中指定ip,这样最省事也最稳妥。

一般是先从header中获取ip信息,获取不到再从request对象中分析。

auto模式

配置yaml,其实可以不配置。

celi-dev:
  config:
    model: "auto"

自动模式默认先使用ip模式获取不到ip会自动切换metadata模式。

什么都不配置,默认auto模式

总结

三种模式里面meta模式最繁琐,心智负担最重,但是也是最简单的。ip模式难度在于获取ip的准确性因此加入指定ip的方式。自动模式则二者结合。

到此这篇关于SpringCloud负载均衡实现定向路由详情的文章就介绍到这了,更多相关SpringCloud负载均衡内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入理解Java SpringCloud Ribbon 负载均衡

    目录 前言 1.抛出问题 2.源码解析 2.1.LoadBalancerIntercepor 2.2.LoadBalancerClient 2.3.负载均衡策略IRule 2.4.总结 3.负载均衡策略 总结 前言 该技术博客是关于黑马视频教程的笔记总结! 服务消费者需要通过RestTemplate调用注册中心(Eureka)的服务提供者,但当同一服务名称的服务有多个的时候,我们的服务消费者应该调用哪一个服务呢?这时候就需要我们学习理解Ribbon负载均衡的实现原理. 当我们在RestTempl

  • SpringCloud Zuul实现负载均衡和熔断机制方式

    一.场景 笔者就Zuul网关下实现其负载均衡与熔断机制(雪崩)进行实践,前提是已经导入zuul相关依赖 springboot版本:1.5.9.RELEASE springcloud版本:Dalston.SR5 <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artif

  • springcloud gateway如何实现路由和负载均衡

    简介: gateway主要是做路由 负载,过滤 主要是替代zuul 1.x 性能比zuul好 zuul是基于 Servlet ,gateway是基于spring-webflux 用的netty+reactor yml文件 实现路由 负载 的配置 亲自测试 spring: application: name: xgyx_gateway cloud: discovery: locator: enabled: true gateway: routes: - id: a #随便定义不重复就好 uri:

  • SpringCloud如何实现Zuul集群(负载均衡)

    目录 前言: 一.使用 Nginx+Zuul 实现网关集群 1.创建Eurek注册中心.会员服务.订单服务 (略) 2. 创建Zuul服务 3. 下载Nginx服务器 二. 测试 三.补充 Nginx和网关的区别在什么地方? Nginx也可以实现网关,为什么不用Nginx实现网关呢? 关于Nginx负载均衡故障转移: 前言: 在微服务架构中,有一个组件可以说是必不可少的,那就是微服务网关,微服务网关处理了负载均衡,缓存,路由,访问控制,服务代理,监控,日志等.API网关在微服务架构中正是以微服务

  • SpringCloud LoadBalancerClient 负载均衡原理解析

    目录 深入解析 LoadBalancerClient 接口源码: 1.LoadBalancerClient 源码解析: 2.ILoadBalancer 源码解析: LoadBalancerClient 是 SpringCloud 提供的一种负载均衡客户端,Ribbon 负载均衡组件内部也是集成了 LoadBalancerClient 来实现负载均衡.那么 LoadBalancerClient 内部到底是如何做到的呢?我们先大概讲一下 LoadBalancerClient 的实现原理,然后再深入源

  • SpringCloud负载均衡实现定向路由详情

    目录 背景 实现方式 基于ip 基于nacos的元数据 实现原理 Gateway服务 普通服务 使用方法 metadata模式 ip模式 auto模式 总结 背景 随着微服务项目的迭代,可能一个服务会有多个实例,这时候就会涉及到负载均衡.然而我们在开发的时候,肯定希望只启动一个项目.然后调试的时候希望负载均衡把请求分配到我们正在开发测试的服务实例上面. 如图所示,我们希望可以指定调用路径也就是定向路由. 实现方式 基于ip 这个很好理解,就是开发者本地正在运行的服务在nacos上面肯定显示你本机

  • 详解SpringCloud的负载均衡

    目录 一.什么是负载均衡 二.负载均衡的简单分类 三.为什么需要做负载均衡 四.springCloud如何开启负载均衡 五.IRule 1.RandomRule:表示随机策略,它将从服务清单中随机选择一个服务: 2.ClientConfigEnabledRoundRobinRule:ClientConfigEnabledRoundRobinRule并没有实现什么特殊的处理逻辑,但是他的子类可以实现一些高级策略, 当一些本身的策略无法实现某些需求的时候,它也可以做为父类帮助实现某些策略,一般情况下

  • SpringCloud超详细讲解负载均衡组件Ribbon源码

    目录 前言 项目实战 创建项目 启动项目验证 源码分析 选择服务 地址替换 总结 前言 上一篇文章中我们通过自己开发了一个负载均衡组件,实现了随机算法的负载均衡功能,如果要实现其他算法,还需要修改代码增加相应的功能.这一篇文章,我们将介绍一个更简单的负载均衡实现,使用**@LoadBalanced**注解实现负载均衡的功能. 项目实战 创建项目 同样的,我们的项目现在依然有一个registry注册中心,一个provider服务提供者,接下来,我们再次修改一下consumer服务消费者的代码: @

  • 初步了解代理和负载均衡

    目录 代理 正向代理 反向代理 负载均衡 负载均衡介绍 网络模型和负载均衡 负载均衡和反向代理 带着问题阅读 1.什么是代理,代理有什么好处 2.正向代理和负向代理有什么区别 3.反向代理和负载均衡有什么关系 4.四层负载均衡和七层有什么区别 代理 代理,通俗来说好比是中介的角色,比如在生活中我们处理法律问题.房产交易都会请专业人士代为处理.从网络角度讲,就是为事务参与双方提供连接通道的第三方网络服务器. 在网络场景中,根据被代理的角色和作用划分,代理可分为正向代理和反向代理. 正向代理 正向代

  • SpringCloud Gateway详细分析实现负载均衡与熔断和限流

    目录 环境准备 1.pom依赖 2.yaml配置 3.路由转发和负载均衡测试 user服务暴露接口 返回结果输出 4.gateway熔断实现 4.1 熔断代码 4.2 测试 5.gateway限流 5.1 需要集成redis 5.2 yaml配置 5.3 注入到spring容器 5.4 测试 环境准备 注册中心Nacos,也可以其他 springboot 2.6.8 spring-cloud-dependencies 2021.0.3 1.pom依赖 parent包 <parent> <

  • GateWay动态路由与负载均衡详细介绍

    目录 概述 项目实例 1.gateway-server模块 1.1.pom.xml文件 1.2.application.yml文件 1.3.主函数类 2.login-service模块 2.1.pom.xml文件 2.2.application.yml文件 2.3.LoginController文件 2.4.主函数类 3.功能测试 概述 从之前的配置里面我们可以看到我们的 URL 都是写死的,这不符合我们微服务的要求,我们微服务是只要知道服务的名字,根据名字去找,而直接写死就没有负载均衡的效果了

  • 在eigrp做不等值路由的负载均衡

    在eigrp中如何做到不等值路由的负载均衡 EIGRP Load Balancing 每个路由协议都支持等值路径的负载均衡.除此之外,IGRP和EIGRP也支持不等值路径的负载均衡,使用variance命令. Variance命令向路由器通告一个n值,n值使用variance命令指定.n值为1-128之间,默认为1. 网络拓扑 Variance 在上图 中,router E有三个路径到网络X • E-B-A with a metric of 30 • E-C-A with a metric of

  • SpringCloud与Consul集成实现负载均衡功能

    负载均衡(Load Balance,简称LB)是一种服务器或网络设备的集群技术.负载均衡将特定的业务(网络服务.网络流量等)分担给多个服务器或网络设备,从而提高了业务处理能力,保证了业务的高可用性.负载均衡基本概念有:实服务.实服务组.虚服务.调度算法.持续性等,其常用应用场景主要是服务器负载均衡,链路负载均衡. 一.背景 SpringCloud微服务目前比较流行,其中大都在使用的服务注册与发现是Eureka,最近研究了Consul的集群搭建,现使用Consul实现服务的负载均衡.其主要拓扑结构

随机推荐