• SpringCloud自定义负载均衡策略--LoadBalancer


    使用spring-cloud-starter-netflix-eureka-client依赖实现

    负载均衡策略


    最近在学习SpringCloud负载均衡的时候遇到了一点麻烦,网上的视频、博客基本都是使用spring-cloud-starter-netflix-ribbon这个依赖实现的(但springcloud在2020.0.0之后,移除掉了netflix-ribbon 使用eureka-client中的loadbalancer,使用自定义负载均衡不使用IRule接口,所以就遇到了很多问题),但这里也会复盘一下传统的实现!之后再讲新方法的实现,当然,也可以直接跳到二、使用LoadBalancer实现直接阅读新方法的使用


    注意这里需要电脑上已经跑起了注册中心、服务提供者,能有多个最好:

    image-20220514162438532

    像这种主机上跑多个服务的就要在修改电脑的host文件了,也很简单,就是做一个简单地映射:

    image-20220514163917560

    启动注册中心与需求提供者,浏览器访问:http://eureka7001.com:7001/

    image-20220514162733591

    可以看到三个需求提供者已经注册进去了,实例名称都是一样的,关联的其他集群也都配置正常!

    此时启动80端口,访问http://localhost/consumer/list,可以看到查出了结果:

    image-20220514163117327

    这里就可以自定义负载均衡的策略了,让不同情况、不同时间下访问服务端使用不同的需求提供者,多访问几次后时这种效果,默认的是轮询访问:

    image-20220514163420061

    下面就来介绍如何使用及自定义负载均衡策略

    以下都只介绍80接口的客户端的那一个模块中的自定义负载均衡模块的实现

    一、使用IRule接口实现

    1.导入Maven依赖

    
         org.springframework.cloud
         spring-cloud-starter-ribbon
         1.4.6.RELEASE
    
    
         org.springframework.cloud
         spring-cloud-starter-eureka
         1.4.6.RELEASE
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.yml配置

    server:
      port: 80
    
    # Eureka配置
    eureka:
      client:
        register-with-eureka: false #不向eureka中注册自己,默认为true,false表示不注册(由提供者去注册)
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
        fetch-registry: true    #表示是否从Eureka Server获取注册的服务信息,默认为true,false表示不获取
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.使用springcloud配置的负载均衡算法

    默认的配置一共有7种,如果我们不指定,那默认的就是轮询算法,就是需求提供者轮个上

    image-20220514165553623

    比如我们可以直接在任意的配置类中引入以下代码使随机访问需求提供者生效均衡策略生效:

    @Bean
    public IRule myRule(){
        return new RandomRule();
    }
    
    • 1
    • 2
    • 3
    • 4

    此时在多次访问http://localhost/consumer/list时就能看到每次访问到的数据都是随机出现的,说明这种策略已经生效了

    当我们要自定义呢

    4.自定义负载均衡算法

    此时最好单独写出相应的配置类,但要注意一点,直接引用springcloud官方的一句话吧:

    CustomConfiguration类必须是@Configuration类,但请注意,对于主应用程序上下文,它不在@ComponentScan中。否则,它由所有@RibbonClients共享。如果您使用@ComponentScan(或@SpringBootApplication),则需要采取措施避免将其包括在内(例如,可以将其放在单独的,不重叠的程序包中,或指定要在@ComponentScan

    先贴个目录结构图:

    image-20220514170240691

    也就是当前自定义的负载均衡配置文件不能被springboot的启动类给扫描到!否则会有所影响!

    也不解释了,也没必要,这里不是在探究源码,贴出定义的规则

    MengRandomRule.java文件:

    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.ILoadBalancer;
    import com.netflix.loadbalancer.Server;
    
    import java.util.List;
    import java.util.concurrent.ThreadLocalRandom;
    
    public class MengRandomRule extends AbstractLoadBalancerRule {
        public MengRandomRule() {
        }
        // ??注意这里的两个变量要设为全局变量
        // 每个服务访问5次,然后换下一个服务(3个)
        // total= 0,默认为0,如果为5,我们换下一个服务
        // index=0,默认0,如果total=5,index+1
        private int total = 0;// 被调用的次数
        private int currentIndex = 0; // 当前是谁在提供服务
    
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {
                return null;
            } else {
                Server server = null;
    
                while(server == null) {
                    if (Thread.interrupted()) {
                        return null;
                    }
    
                    List upList = lb.getReachableServers();// 获得活着的服务
                    List allList = lb.getAllServers();// 获得全部的服务
                    int serverCount = allList.size();
                    if (serverCount == 0) {
                        return null;
                    }
    //                int index = this.chooseRandomInt(serverCount);// 生成区间随机数
    //                server = (Server)upList.get(index);// 从活着的服务随机获取一个
    
                    //========================================================
                    //这里被等号包裹的部分就是自定义的规则,至于其他部分其实都是直接扣的源码直接吧这部分一改,很简单吧!
                    if(total<5){
                        server = upList.get(currentIndex);
                        total++;
                    }else{
                        total=0;
                        currentIndex++;
                        if(currentIndex>=upList.size()){
                            currentIndex = 0;
                        }
                        server = upList.get(currentIndex);//从活着的服务中获取指定的服务来进行操作
                    }
    
                    //========================================================
                    if (server == null) {
                        Thread.yield();
                    } else {
                        if (server.isAlive()) {
                            return server;
                        }
    
                        server = null;
                        Thread.yield();
                    }
                }
    
                return server;
            }
        }
    
        protected int chooseRandomInt(int serverCount) {
            return ThreadLocalRandom.current().nextInt(serverCount);
        }
    
        public Server choose(Object key) {
            return this.choose(this.getLoadBalancer(), key);
        }
    
        public void initWithNiwsConfig(IClientConfig clientConfig) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    MengRule.java文件:

    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MengRule {
    
        @Bean
        public IRule myRule(){
            return new MengRandomRule();// 改为我们自定义的算法,每个服务执行5次
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    主启动类DeptConsumer_80.java文件:

    import com.meng.myrule.MengRule;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    
    @SpringBootApplication
    @EnableEurekaClient
    // 在微服务启动的时候就能去加载我们自定义的Ribbon类
    @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MengRule.class)
    public class DeptConsumer_80 {
        public static void main(String[] args) {
            SpringApplication.run(DeptConsumer_80.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    配置完以上代码后再次启动80服务,访问http://localhost/consumer/list可以发现得到的数据是每5次变化一番的!

    至此,使用IRule接口自定义负载均衡策略介绍完毕!

    但要注意的是,springboot及相对应的springcloud的版本不能太高,开局也已经介绍过原因了,否则会产生No instances available for xxx 这种错误!这也没办法,只能继续学习新事物→→→

    二、使用LoadBalancer实现

    负载均衡 Spring Cloud LoadBalancer

    目前最新版的springboot是2.6.7,对应的springcloud版本是2021.0.2版本的

    使用这种方式配置负载均衡策略不会有任何问题

    想深入了解的话可以比较一下前后两种方式的源码的变化

    1.导入Maven依赖

    这里只需要导入一个spring-cloud-starter-netflix-eureka-client依赖即可,其内部内置了Ribbon包:

    
         org.springframework.cloud
         spring-cloud-starter-netflix-eureka-client
         3.1.2
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.yml配置

    这里的配置文件并没有丝毫改变

    server:
      port: 80
    
    # Eureka配置
    eureka:
      client:
        register-with-eureka: false #不向eureka中注册自己,默认为true,false表示不注册(由提供者去注册)
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
        fetch-registry: true    #表示是否从Eureka Server获取注册的服务信息,默认为true,false表示不获取
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.使用springcloud配置的负载均衡算法

    这里并不需要再像上面那样需要避开springboot的扫描,直接在config包下配置即可!
    image-20220514172509944

    配置内容如下:

    KonanRandomRule.java文件:

    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
    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;
    
    @Configuration
    public class KonanRandomRule {
    
        @Bean
        ReactorLoadBalancer randomLoadBalancer(Environment environment,
                                                                LoadBalancerClientFactory loadBalancerClientFactory) {
            String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    
            //返回随机轮询负载均衡方式
            return new RandomLoadBalancer(loadBalancerClientFactory.
                    getLazyProvider(name, ServiceInstanceListSupplier.class),
                    name);
    
              //轮询加载,默认就是这个,这里作为演示
    //        return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
    //                ServiceInstanceListSupplier.class),name);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    将上述配置注入spring容器中 KonanRule.java文件:

    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    //注入自定义负载均衡规则
    @Configuration
    public class konanRule {
    
        // 参数 serviceInstanceListSupplierProvider 会自动注入
        @Bean
        public ReactorServiceInstanceLoadBalancer customLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider) {
            return new KonanRandomRule(serviceInstanceListSupplierProvider);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    主启动类DeptConsumer_80.java配置:

    import com.konan.springcloud.myrule.konanRule;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    //import org.springframework.cloud.netflix.ribbon.RibbonClient;
    
    @SpringBootApplication
    @EnableEurekaClient
    //@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = KonanRule.class) //不再使用了
    @LoadBalancerClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = konanRule.class)
    public class DeptConsumer_80 {
        public static void main(String[] args) {
            SpringApplication.run(DeptConsumer_80.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    再次启动当前服务,访问http://localhost/consumer/list可以看到每次访问到的数据都是随机调用的不同需求提供者的api,说明这种策略也已经生效了

    那要是自定义呢同样,我们也写一个配置类实现

    4.自定义负载均衡算法

    可以看到上图中还有一个文件: MyLoadBalancerRule.java 这个文件的内容就是我们自定义的策略,同样,我们先修改konanRule.java 这个文件,让其注册服务给到 MyLoadBalancerRule.java,将这个配置注册到spring容器中,只需修改一处即可:

    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    //注入自定义负载均衡规则
    @Configuration
    public class konanRule {
    
        // 参数 serviceInstanceListSupplierProvider 会自动注入
        @Bean
        public ReactorServiceInstanceLoadBalancer customLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider) {
            //修改这里指向自定义的配置文件
            return new KonanRandomRule(serviceInstanceListSupplierProvider);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里给出一份MyLoadBalancerRule.java 配置的实例,也是让我们的服务每5次就更换一回(被注释掉的那个是随机算法策略):

    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.cloud.client.ServiceInstance;
    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.Response;
    import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import reactor.core.publisher.Mono;
    
    import java.util.List;
    
    public class MyLoadBalancerRule implements ReactorServiceInstanceLoadBalancer {
        private int total=0;    // 被调用的次数
        private int index=0;    // 当前是谁在提供服务
    
        // 服务列表
        private ObjectProvider serviceInstanceListSupplierProvider;
    
        public MyLoadBalancerRule(ObjectProvider serviceInstanceListSupplierProvider) {
            this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        }
    
        @Override
        public Mono> choose(Request request) {
            ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();
            return supplier.get().next().map(this::getInstanceResponse);
        }
    
        //使用随机数获取服务
    //    private Response getInstanceResponse(
    //            List instances) {
    //        System.out.println("进来了");
    //        if (instances.isEmpty()) {
    //            return new EmptyResponse();
    //        }
    //
    //        System.out.println("进行随机选取服务");
    //        // 随机算法
    //        int size = instances.size();
    //        Random random = new Random();
    //        ServiceInstance instance = instances.get(random.nextInt(size));
    //
    //        return new DefaultResponse(instance);
    //    }
    
        //每个服务访问5次,然后换下一个服务
        private Response getInstanceResponse(List instances) {
            System.out.println("进入自定义负载均衡");
            if (instances.isEmpty()) {
                return new EmptyResponse();
            }
    
            System.out.println("每个服务访问5次后轮询");
            int size = instances.size();
    
            ServiceInstance serviceInstance=null;
            while (serviceInstance == null) {
                System.out.println("===");
                if (total < 5) {
                    serviceInstance = instances.get(index);
                    total++;
                } else {
                    total=0;
                    index++;
                    if (index>=size) {
                        index=0;
                    }
                    serviceInstance=instances.get(index);
                }
            }
    
            return new DefaultResponse(serviceInstance);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    启动springcloud-consumer-dept-80服务,访问http://localhost/consumer/lis 正是每五次更换一次数据,控制台也输出了相应的信息,说明自定义负载策略配置成功!


    贴一张学习笔记的完整代码图:

    image-20220514175816348


    完整代码实现(IRule版本,springboot是2.1.2的版本):

    https://gitee.com/mengwenbiao/gitmeng?_from=gitee_search

    完整代码实现(LoadBalancer实现,springboot是2.6.7的版本,目前最新版一套完美解决方案):

    暂时没有托管到git上,可以私信我

    SpringCloud官方文档: https://www.springcloud.cc

    参考文章: https://blog.csdn.net/weixin_50518271/article/details/111449560

    参考文章: https://blog.csdn.net/qq_31142237/article/details/90486836

    )

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    uni-app基于vue实现商城小程序
    Vue3你需要知道的组件化知识点
    C++:C++入门基础
    java实现备忘录模式
    华为ACL实验
    ARM架构源码编译RXTX LINUX JAVA串口开发项目部署
    【学习笔记】AGC028/AGC007
    Java Opencv识别图片上的虫子
    MongoDB - 构造复杂查询条件执行查询
    Redis实战案例及问题分析之好友关注功能(关注、共同好友、消息推送)
  • 原文地址:https://blog.csdn.net/m0_67392931/article/details/126116442