我的编程空间,编程开发者的网络收藏夹
学习永远不晚

【SpringCloud系列】开发环境下重写Loadbalancer实现自定义负载均衡

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

【SpringCloud系列】开发环境下重写Loadbalancer实现自定义负载均衡

前言

        spring-cloud-starter-netflix-ribbon已经不再更新了,最新版本是2.2.10.RELEASE,最后更新时间是2021年11月18日,详细信息可以看maven官方仓库:https://search.maven.org/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon,SpringCloud官方推荐使用spring-cloud-starter-loadbalancer进行负载均衡。我们在开发的时候,多人开发同一个微服务,都注册到同一个nacos,前端请求的时候,网关Gateway默认轮训请求注册中心的服务,OpenFeign也会轮询请求注册中心的服务,这样就会导致前端有时会无法请求到我们本地写的接口,而是请求到别人的服务中。所以我们可以重写Loadbalancer默认的负载均衡策略,实现自定义负载均衡策略,不管是Gateway还是OpenFeign都只能请求到我们自己本地的服务。

        我的版本如下:

        2.7.3
        2021.0.4
        2021.0.4.0

一、添加负载方式配置

        1、定义负载均衡方式的枚举

public enum LoadBalancerTypeEnum {        DEV,        GATEWAY,        ROUND_ROBIN,        RANDOM;}

        2、添加配置类,默认使用轮训方式

import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;@Data@ConfigurationProperties(prefix = "spring.cloud.loadbalancer")public class LoadBalanceProperties {    private LoadBalancerTypeEnum type = LoadBalancerTypeEnum.ROUND_ROBIN;}

二、参考默认实现自定义

        默认的负载均衡策略是这个类:

org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer

        我们参考这个类实现自己的负载均衡策略即可,RoundRobinLoadBalancer实现了ReactorServiceInstanceLoadBalancer这个接口,实现了choose这个方法,如下图:

        在choose方法中调用了processInstanceResponse方法,processInstanceResponse方法中调用了getInstanceResponse方法,所以我们我们可以复制RoundRobinLoadBalancer整个类,只修改getInstanceResponse这个方法里的内容就可以实现自定义负载均衡策略。

        在自定义的类中,我们实现了四种负载均衡策略

        1、getRoundRobinInstance方法是直接复制的RoundRobinLoadBalancer类中的实现;

        2、getRandomInstance方法参考org.springframework.cloud.loadbalancer.core.RandomLoadBalancer类中的实现;

        3、getDevelopmentInstance方法是返回所有服务中和当前机器ip一致的服务,如果没有,则轮训返回;

        4、getGatewayDevelopmentInstance方法是返回所有服务中和网关请求头中ip一致的服务。

import cn.hutool.core.convert.Convert;import cn.hutool.core.lang.TypeReference;import com.ruoyi.common.core.utils.StringUtils;import com.ruoyi.common.core.utils.ip.IpUtils;import com.ruoyi.common.loadbalance.config.LoadBalancerTypeEnum;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.ObjectProvider;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.loadbalancer.DefaultRequest;import org.springframework.cloud.client.loadbalancer.DefaultResponse;import org.springframework.cloud.client.loadbalancer.EmptyResponse;import org.springframework.cloud.client.loadbalancer.Request;import org.springframework.cloud.client.loadbalancer.RequestData;import org.springframework.cloud.client.loadbalancer.RequestDataContext;import org.springframework.cloud.client.loadbalancer.Response;import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;import org.springframework.http.HttpHeaders;import reactor.core.publisher.Mono;import java.util.List;import java.util.Objects;import java.util.Random;import java.util.concurrent.ThreadLocalRandom;import java.util.concurrent.atomic.AtomicInteger;@Slf4jpublic class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer {    private final String serviceId;    private final AtomicInteger position;    private final LoadBalancerTypeEnum type;    private final ObjectProvider serviceInstanceListSupplierProvider;    public CustomSpringCloudLoadBalancer(String serviceId,             LoadBalancerTypeEnum type,             ObjectProvider serviceInstanceListSupplierProvider) {        this(serviceId, new Random().nextInt(1000), type, serviceInstanceListSupplierProvider);    }    public CustomSpringCloudLoadBalancer(String serviceId,             int seedPosition,             LoadBalancerTypeEnum type,             ObjectProvider serviceInstanceListSupplierProvider) {        this.serviceId = serviceId;        this.position = new AtomicInteger(seedPosition);        this.type = type;        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;    }    @Override    public Mono> choose(Request request) {        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);        return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(request, supplier, serviceInstances));    }    private Response processInstanceResponse(Request request,      ServiceInstanceListSupplier supplier,      List serviceInstances) {        Response serviceInstanceResponse = getInstanceResponse(request, serviceInstances);        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());        }        return serviceInstanceResponse;    }    private Response getInstanceResponse(Request request, List instances) {        if (instances.isEmpty()) {            if (log.isWarnEnabled()) {                log.warn("No servers available for service: " + serviceId);            }            return new EmptyResponse();        }        if (Objects.equals(type, LoadBalancerTypeEnum.ROUND_ROBIN)){            return this.getRoundRobinInstance(instances);        }else if (Objects.equals(type, LoadBalancerTypeEnum.RANDOM)){            return this.getRandomInstance(instances);        }else if (Objects.equals(type, LoadBalancerTypeEnum.DEV)){            return this.getDevelopmentInstance(instances);        }else if (Objects.equals(type, LoadBalancerTypeEnum.GATEWAY)){            return this.getGatewayDevelopmentInstance(request, instances);        }        return this.getRoundRobinInstance(instances);    }        private Response getGatewayDevelopmentInstance(Request request, List instances) {        //把request转为默认的DefaultRequest,从request中拿到请求的ip信息,再选择ip一样的微服务        DefaultRequest defaultRequest = Convert.convert(new TypeReference>() {}, request);        RequestDataContext context = defaultRequest.getContext();        RequestData clientRequest = context.getClientRequest();        HttpHeaders headers = clientRequest.getHeaders();        String requestIp = IpUtils.getIpAddressFromHttpHeaders(headers);        log.debug("客户端请求gateway的ip:{}", requestIp);        //先取得和本地ip一样的服务,如果没有则按默认来取        for (ServiceInstance instance : instances) {            String currentServiceId = instance.getServiceId();            String host = instance.getHost();            log.debug("注册服务:{},ip:{}", currentServiceId, host);            if (StringUtils.isNotEmpty(host) && StringUtils.equals(requestIp, host)) {                return new DefaultResponse(instance);            }        }        return getRoundRobinInstance(instances);    }        private Response getDevelopmentInstance(List instances) {        //获取本机ip        String hostIp = IpUtils.getHostIp();        log.debug("本机Ip:{}", hostIp);        //先取得和本地ip一样的服务,如果没有则按默认来取        for (ServiceInstance instance : instances) {            String currentServiceId = instance.getServiceId();            String host = instance.getHost();            log.debug("注册服务:{},ip:{}", currentServiceId, host);            if (StringUtils.isNotEmpty(host) && StringUtils.equals(hostIp, host)) {                return new DefaultResponse(instance);            }        }        return getRoundRobinInstance(instances);    }        private Response getRandomInstance(List instances) {        int index = ThreadLocalRandom.current().nextInt(instances.size());        ServiceInstance instance = instances.get(index);        return new DefaultResponse(instance);    }        private Response getRoundRobinInstance(List instances) {        // 每一次计数器都自动+1,实现轮询的效果        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;        ServiceInstance instance = instances.get(pos % instances.size());        return new DefaultResponse(instance);    }}

        其中的工具类如下:

import java.net.InetAddress;import java.net.URL;import java.net.UnknownHostException;import java.util.List;import java.util.Objects;import javax.servlet.http.HttpServletRequest;import cn.hutool.core.collection.CollectionUtil;import cn.hutool.core.util.URLUtil;import org.springframework.http.HttpHeaders;public class IpUtils{        public static String getHostIp(){        try{            return InetAddress.getLocalHost().getHostAddress();        }catch (UnknownHostException e){        }        return "127.0.0.1";    }        public static String getIpAddressFromHttpHeaders(HttpHeaders httpHeaders){        if (httpHeaders == null){            return "unknown";        }        //前端请求自定义请求头,转发到哪个服务        List ipList = httpHeaders.get("forward-to");        String ip = CollectionUtil.get(ipList, 0);        //请求自带的请求头        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){            ipList = httpHeaders.get("x-forwarded-for");            ip = CollectionUtil.get(ipList, 0);        }        //从referer获取请求的ip地址        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){            //从referer中获取请求的ip地址            List refererList = httpHeaders.get("referer");            String referer = CollectionUtil.get(refererList, 0);            URL url = URLUtil.url(referer);            if (Objects.nonNull(url)){                ip = url.getHost();            }else {                ip = "unknown";            }        }        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){            ipList = httpHeaders.get("Proxy-Client-IP");            ip = CollectionUtil.get(ipList, 0);        }        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){            ipList = httpHeaders.get("X-Forwarded-For");            ip = CollectionUtil.get(ipList, 0);        }        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){            ipList = httpHeaders.get("WL-Proxy-Client-IP");            ip = CollectionUtil.get(ipList, 0);        }        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){            ipList = httpHeaders.get("X-Real-IP");            ip = CollectionUtil.get(ipList, 0);        }        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);    }}

        getIpAddressFromHttpHeaders方法中,是从请求头总拿到了自定义的请求头forward-to,要想实现此功能,就需要前端发送请求的时候携带这个请求头,例如

        在若依的request.js中可以这么写: config.headers['forward-to'] = '192.168.0.145'

三、配置负载均衡策略

       默认的配置在这里:

org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration

        我们参考这个配置,实现自己的配置。启用上面写好的配置类LoadBalanceProperties,然后传到自定义的负载均衡策略类CustomSpringCloudLoadBalancer,其他的复制就可以。

import com.ruoyi.common.loadbalance.core.CustomSpringCloudLoadBalancer;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.env.Environment;@SuppressWarnings("all")@Configuration(proxyBeanMethods = false)@EnableConfigurationProperties(LoadBalanceProperties.class)public class CustomLoadBalanceClientConfiguration {    @Bean    @ConditionalOnBean(LoadBalancerClientFactory.class)    public ReactorLoadBalancer customLoadBalancer(LoadBalanceProperties loadBalanceProperties,           Environment environment,           LoadBalancerClientFactory loadBalancerClientFactory) {        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);        return new CustomSpringCloudLoadBalancer(name, loadBalanceProperties.getType(),                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));    }}

        然后使用LoadBalancerClients注解加载一下配置

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;@LoadBalancerClients(defaultConfiguration = CustomLoadBalanceClientConfiguration.class)public class CustomLoadBalanceAutoConfiguration {}

四、使用

        将以上代码独立成一个模块,然后再其他微服务中的pom文件中引入,然后添加对应的配置就可以实现自定义负载均衡了

        1、在微服务中配置如下即可实现调用其他服务时,调用自己本地开发环境的微服务

    spring.cloud.loadbalancer.type=dev

        2、在网关中配置如下即可实现调用固定某个服务

 spring.cloud.loadbalancer.type=gateway

写在最后的话

        最开始只有想法,但是不知道怎么实现,百度也没找到合适的方案。所以就开始看源码,研究了一下,然后照着源码写,测试了一下真的就实现了。所以,多看看源码还是有好处的。

来源地址:https://blog.csdn.net/WayneLee0809/article/details/128557770

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

【SpringCloud系列】开发环境下重写Loadbalancer实现自定义负载均衡

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录