spingcloud中使用Eureka和Ribbon实现客户端的负载均衡

使用的版本

springboot:2.4.1
spingcloud:2020.0.1

Eureka服务端配置

1、pom依赖

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

2、yaml配置

server:
  port: 7001

# Eureka配置
eureka:
  instance:
    hostname: eureka7001.com  #Eureka服务端的实例名称
  client:
    register-with-eureka: false # 表示是否向eureka注册中心注册自己   (服务器不需要注册)
    fetch-registry: false # 如果为false ,则表示自己为注册中心
    service-url:
      # 单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      # 集群(关联) :
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

这里实现了eureka客户端的集群

3、主启动类设置

@SpringBootApplication
@EnableEurekaServer  // 服务端的启动类
public class EurekaServer_7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer_7001.class,args);
    }
}

Eureka客户端的服务提供者

1、pom配置

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <!--        actuator完善监控信息-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--        需要拿到实体类,所以要配置api module-->
        <dependency>
            <groupId>com.zhong</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
<!--        junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
<!--        test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        jetty-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
<!--        热部署工具-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

2、yml 配置

server:
  port: 8001
# mybatis 配置
mybatis:
  type-aliases-package: com.zhong.springcloud.pojo
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml
# spring 配置
spring:
  application:
    name: springcloud-provider-dept  # 三个服务名字一致是前提
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
# Eureka 的配置
# 服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: springcloud-provider-dept8001 # 修改eureka上的默认描述信息
    prefer-ip-address: true
# info配置
info:
  app.name: zhong
  company.name: blog.zhong.com

3、主启动类配置

// 启动类
@EnableEurekaClient   // 在服务开启后,自动注册到eureka中,
@SpringBootApplication
@EnableDiscoveryClient
public class DeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8001.class,args);
    }
}

Eureka客户端消费者和Ribbon配置

1、pom配置

        <dependency>
            <groupId>com.zhong</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.ribbon</groupId>
            <artifactId>ribbon-loadbalancer</artifactId>
            <version>2.7.18</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.netflix.ribbon</groupId>
            <artifactId>ribbon-core</artifactId>
            <version>2.7.18</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.zhong</groupId>
            <artifactId>springcloud</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>

这里要注意:在spring2020的版本中spring-cloud-starter-netflix-eureka-client依赖中已经去除了Ribbon依赖,官方移除了Netflix的组件。
需要额外的添加依赖,另外,实现Ribbon的自定义负载均衡也有改变。

2、yml配置

server:
  port: 80
  
spring:
  application:
    name: springcloud-consumer-dept
# eureka 配置
eureka:
  client:
    healthcheck:
      enabled: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    instance:
      instance-id: springcloud-consumer-dept80 # 修改eureka上的默认描述信息
    register-with-eureka: false # 表示是否向eureka注册中心注册自己   (消费者不需要注册)
    fetch-registry: true

3、config配置RestTemplate

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

4、编写自定义负载均衡器

按照官方文档进行配置https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer

public class CustomLoadBalancerConfiguration {

    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new MyLoadBalancer(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}

这里使用了自己定义的MyLoadBalancer
定义如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.zhong.myrule;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.*;
import reactor.core.publisher.Mono;

public class MyLoadBalancer implements ReactorServiceInstanceLoadBalancer {

//    ======
    private int total = 0; // 被调用的次数
    private int currentIndex = 0; // 当前是谁在提供
//    ======


    private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);
    private final String serviceId;
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public MyLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map((serviceInstances) -> {
            return this.processInstanceResponse(supplier, serviceInstances);
        });
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
        }

        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {


        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + this.serviceId);
            }

            return new EmptyResponse();
        } else {

//            int index = ThreadLocalRandom.current().nextInt(instances.size());
//            ServiceInstance instance = (ServiceInstance)instances.get(index);
            // ============================
            ServiceInstance instance = null;
            if (total < 5){
//                server = upList.get(currentIndex);
                instance = (ServiceInstance)instances.get(currentIndex);
                total ++;
            }else{
                total = 1;
                currentIndex++;
                if (currentIndex >= instances.size()){
                    currentIndex = 0;
                }
//                server = upList.get(currentIndex);
                instance = (ServiceInstance)instances.get(currentIndex);
            }
            // ============================
            return new DefaultResponse(instance);
        }
    }
}

5、编写controller测试

package com.zhong.springcloud.controller;


import com.zhong.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;


import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import java.util.List;

public class DeptConsumerController {

    @Autowired
    private RestTemplate restTemplate; // 提供rest中五种便捷访问远程http服务的方法
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    // Ribbon ,这里的前缀地址应该是一个变量,通过服务名来访问
    // 原本地址:http://localhost:8001
    // ribbon: http://SPRINGCLOUD-PROVIDER-DEPT
    private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";
@RequestMapping("/consumer/dept/list")
    public List<Dept> list(){
        return restTemplate.getForObject(REST_URL_PREFIX+ "/dept/list",List.class);
    }
    @RequestMapping("/consumer/dept")
    public String test(){
        ServiceInstance choose = this.loadBalancerClient.choose("springcloud-provider-dept");
        System.out.println("测试:"+choose.getServiceId()+":"+choose.getHost()+":"+choose.getPort());
        return "测试";
    }
}

使用/consumer/dept进行测试
得到输出结果

测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8002
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8002
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8002
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8002
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8002
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8001
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8002
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8002
测试:SPRINGCLOUD-PROVIDER-DEPT:192.168.146.1:8002

可以得到按照自定义的规则进行负载均衡

总结

本人按照狂神说java里得springcloud进行学习,在eureka服务端和eureka客户端提供者配置时没有遇到问题,但在eureka和Ribbon实现客户端得负载均衡时遇到了两个问题。

通过服务名进行获取服务时出现找不到服务得错误

解决方法:
在服务提供者得yml配置中添加prefer-ip-address: true
在服务消费者里面pom依赖中使用spring-cloud-starter-netflix-eureka-client 3.0版本,去除Ribbon依赖。
至此可以使用服务名获取服务,采用轮询得默认方式

在使用自定义负载均衡时不生效

解决方法:
http://oschina.net/question/3430787_2320770?sort=default文章中说明了springcloud2020版本中去除了Ribbon依赖,并且自定义负载均衡使用http://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer官网得解决方案,要在主启动类上配置@LoadBalancerClient 注解,问题解决

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容