使用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依赖

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-ribbon</artifactId>
     <version>1.4.6.RELEASE</version>
</dependency>
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-eureka</artifactId>
     <version>1.4.6.RELEASE</version>
</dependency>

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表示不获取

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

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

image-20220514165553623

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

@Bean
public IRule myRule(){
    return new RandomRule();
}

此时在多次访问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<Server> upList = lb.getReachableServers();// 获得活着的服务
                List<Server> 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) {
    }
}

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次
    }
}

主启动类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);
    }
}

配置完以上代码后再次启动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包:

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
     <version>3.1.2</version>
</dependency>

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表示不获取

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<ServiceInstance> 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);

    }
}

将上述配置注入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<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        return new KonanRandomRule(serviceInstanceListSupplierProvider);
    }
}

主启动类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);
    }
}

再次启动当前服务,访问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<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        //修改这里指向自定义的配置文件
        return new KonanRandomRule(serviceInstanceListSupplierProvider);
    }
}

这里给出一份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<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public MyLoadBalancerRule(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();
        return supplier.get().next().map(this::getInstanceResponse);
    }

    //使用随机数获取服务
//    private Response<ServiceInstance> getInstanceResponse(
//            List<ServiceInstance> 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<ServiceInstance> getInstanceResponse(List<ServiceInstance> 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);

    }
}

启动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