SpringCloud-Eureka配置和使用

SpringCloud-Eureka配置

注意:Eureka新旧版本情况,旧版本已经被弃用,需要(建议)使用新的版本

旧版本:spring-cloud-starter-eureka-serverspring-cloud-starter-eureka 例子如下,如果使用下面这种方法,有可能在配置其他方面报错,如Ribbon


<!-- 服务端,面对注册中心 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
<!-- 客户端, 面对服务提供者和服务消费者-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

新版本spring-cloud-starter-netflix-eureka-serverspring-cloud-starter-netflix-eureka-client 推荐使用新版本

<!-- 服务端,面对注册中心,注册中心导入此依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            <version>3.1.2</version>
        </dependency>
<!-- 客户端, 面对服务提供者和服务消费者,provider和consumer导入此依赖-->
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>3.1.2</version>
        </dependency>

服务端配置

application.yaml配置如下

server:
  port: 7001

#Eureka 服务端配置,(服务端,创建服务,让其他客户端(provider,consumer)可以注册服务或拿取注册服务)
eureka:
  instance:
    hostname: eureka7001.com #eureka服务端名称
  client:
    register-with-eureka: false # 是否将自己注册到Eureka服务器中,本身是服务器,无需注册
    fetch-registry: false # false表示自己就是注册中心,只需要维护服务实例,无需检索服务
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个defaultZone地址
#      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 单机设置
	#开启集群设置如下,连接其他的注册主机
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

启动类配置

package com.laoliu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer  //开启Eureka服务端服务
public class EurekaServer7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001.class);
    }
}

服务提供者(provider)配置

application.yaml配置

server:
  port: 8001

# mybatis 设置
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  type-aliases-package: com.laoliu.springcloud.pojo
  mapper-locations:
    - classpath:mybatis/mapper/**/*.xml

# spring相关配置
spring:
  application:
    name: springcloud-provider-dept  #实例名称,服务消费者使用的rest请求,需要这个实例名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springcloud
    username: root
    password: root
    dbcp2:
      min-idle: 5
      initial-size: 5
      max-total: 5
      max-wait-millis: 200
#eureka 客户端配置
eureka:
  client:
    service-url:
      # 将8001微服务发布到1台eureka集群配置中,发现在集群中的其余注册中心也可以看到,但是平时我们保险起见,都发布!
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: springcloud-provider-dept8001
    prefer-ip-address: true

#监控短点配置,springboot2.5以后,默认只开启health,需要手动打开其他
management:
  endpoints:
    web:
      exposure:
        include: "*" # 全部打开

#info配置
info:
  app.name: laoliu-springcloud
  company.name: www.laoliu.com
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}

启动类配置

package com.laoliu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient //开启客户端服务
@EnableDiscoveryClient //扫描所有的服务提供到注册中心中
public class DeptProvider8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider8001.class);
    }
}

服务消费者(consumer)配置

pom.xml

<dependencies>
        <dependency>
            <groupId>com.laoliu</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.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <!--
            springCloud2020 版本 把Bootstrap被默认禁用,
            spring.config.import加入了对解密的支持。对于Config Client、Consul、Vault和Zookeeper的配置导入,
            如果需要使用原来的配置引导功能,
            那么需要将org.springframework.cloud:spring-cloud-starter-bootstrap依赖引入到工程中
            这样才能正常使用springCloud
        -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
    </dependencies>

注意:SpringCloud 从2020 版本 把Bootstrap被默认禁用,spring.config.import加入了对解密的支持。对于Config Client、Consul、Vault和Zookeeper的配置导入,如果需要使用原来的配置引导功能,那么需要将org.springframework.cloud:spring-cloud-starter-bootstrap依赖引入到工程中这样才能正常使用springCloud

application.yaml配置(此配置集成Ribbon)

server:
  port: 80

eureka:
  client:
    register-with-eureka: false # false 不是服务提供者,不需要注册到Eureka中
    fetch-registry: true # true 消费者需要检索注册中心服务才能调用实例,否则找不到实例,调用失败,报错!!!!!
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

启动类配置

package com.laoliu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient //开启客户端
public class DeptConsumerRibbon80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumerRibbon80.class,args);
    }
}

ConfigBean配置

package com.laoliu.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfigBean {

    @Bean
    @LoadBalanced //类属Ribbon,使用该注解说明让这个RestTemplate在请求时拥有客户端负载均衡的能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

controller(例子说明)

package com.laoliu.springcloud.controller;

import com.laoliu.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class DeptConsumerController {

    /**
     *  使用RestTemplate访问restful接口
     * (url,requestMap,ResponseBean.class) 这三个参数分别代表
     *  REST请求地址,请求参数,Http响应转换 被 转换成的对象类型
     */
    @Autowired
    private RestTemplate restTemplate;

//    private static final String REST_URL_PREFIX = "http://localhost:8001"; 没使用Euraka时的路径

	/**
	*	路径前缀发生改变,不在是地址,而是实例名称,就是服务提供者的实例名,作为Eureka的客户端,已经在配置文件application中配置好注册中心地址,
	*	所以很好理解,配合Ribbon,负载均衡,选取合适的注册中心再通过实例名称和服务路径进行服务调用,此时实例名称大小写都可以
	*/
    private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";
    

    @PostMapping("/consumer/dept/add")
    public boolean add(Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add",dept,Boolean.class);
    }

    @GetMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id){
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/"+id,Dept.class);
    }

    @GetMapping("/consumer/dept/list")
    public List<Dept> getAll(){
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list",List.class);
    }

    @GetMapping("/consumer/dept/discovery")
    public Object discovery(){
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery",Object.class);
    }
}

Ribbon注意事项

消费者模块所需要的Ribbon在新版的Eureka中已经被弃用(或者说是内置),不可以再导入依赖,导入spring-cloud-starter-netflix-ribbon将会找不到实例,报错!!!

负载均衡算法替换

源码刨析

读取源码可知,新版的Eureka的LoadBalanced实现了两种负载均衡的算法:轮询算法(默认)随机算法

在这里插入图片描述

LoadBalancerClientConfiguration的源码有一项如下:

@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {
    *******
    @Bean
    @ConditionalOnMissingBean //缺少该Bean时将会被启用,即可以自定义替换默认使用的负载均衡算法
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty("loadbalancer.client.name");
        //默认轮询算法
        return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
    
    *******
}

正式替换

在新版的Eureka中使用@LoadBalanced进行客户端请求负载均衡,默认使用的算法是轮询(即轮流调用实例中的服务),可以将算法修改为随机,示例如下:

ConfigBean

package com.laoliu.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
@LoadBalancerClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = CustomLoadBalancerConfiguration.class)//name为服务实例名称,configuration是自定义好的算法类
public class ConfigBean {

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

定义一个CustomLoadBalancerConfiguration类来替换算法

package com.laoliu.springcloud.config;

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.core.env.Environment;

/**
*这个地方可以不加@Configuration,我的理解是@LoadBalancerClient拿到该类Class后通过反射会创建对象,
*这其中的Bean将会被识别创建并替换原先的ReactorLoadBalancer,所以可以不用加,当然加上也可以运行,亲测可以。
*/
public class CustomLoadBalancerConfiguration {

    @Bean //必须,不配置无法实现
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory){
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		//与源码类似,但是创建的是随机算法
        return new RandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class)
                ,name);

    }
}

疑点及猜测

​ 通过源码可以看出,传进的CustomLoadBalancerConfiguration类会通过反射机制创建对应的CustomLoadBalancerConfiguration对象或者类中被标识为Bean的ReactorLoadBalancer,然后替换为原先的算法配置,原本我以为应该是反射后创建的对象给spring托管,但是当我用@Autowired输出验证该对象时为空,证明该对象应该不是由spring托管,继续验证@Bean,当我未加入@Bean时,算法没被替换,当我加入@Bean后,算法替换成功,但是用@Autowired输出验证ReactorLoadBalancer为空,最后我得出结论:

  1. CustomLoadBalancerConfiguration类中ReactorLoadBalancer必须有@Bean标识,反射的对象有两种可能
    • 一种是反射出CustomLoadBalancerConfiguration对象,然后找到标识为@Bean的ReactorLoadBalancer创建并替换原来算法。
    • 另一种是反射机制先识别@Bean标志的ReactorLoadBalancer,然后直接创建并替换原算法(我认为可能性很大
  2. 反射出的ReactorLoadBalancer不由Spring托管,通过@Autowired无法拿出(疑惑又肯定
  3. 基本上可以确定是由@Bean标识然后反射对象进行替换

部分源码

LoadBalancerClient源码:

@Configuration(
    proxyBeanMethods = false
)
@Import({LoadBalancerClientConfigurationRegistrar.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoadBalancerClient {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    Class<?>[] configuration() default {};
}

LoadBalancerClientConfigurationRegistrar源码:

public class LoadBalancerClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
    public LoadBalancerClientConfigurationRegistrar() {
    }

    private static String getClientName(Map<String, Object> client) {
        if (client == null) {
            return null;
        } else {
            String value = (String)client.get("value");
            if (!StringUtils.hasText(value)) {
                value = (String)client.get("name");
            }

            if (StringUtils.hasText(value)) {
                return value;
            } else {
                throw new IllegalStateException("Either 'name' or 'value' must be provided in @LoadBalancerClient");
            }
        }
    }

    private static void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(LoadBalancerClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        //这个获取Bean定义,正是我考虑的由@Bean识别进行反射
        registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition()); 
        
    }

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);
        if (attrs != null && attrs.containsKey("value")) {
            AnnotationAttributes[] clients = (AnnotationAttributes[])((AnnotationAttributes[])attrs.get("value"));
            AnnotationAttributes[] var5 = clients;
            int var6 = clients.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                AnnotationAttributes client = var5[var7];
                registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
            }
        }

        if (attrs != null && attrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            } else {
                name = "default." + metadata.getClassName();
            }

            registerClientConfiguration(registry, name, attrs.get("defaultConfiguration"));
        }

        Map<String, Object> client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName(), true);
        String name = getClientName(client);
        if (name != null) {
        	//拿到configuration中的Class进行配置
            registerClientConfiguration(registry, name, client.get("configuration"));
        }

    }
}

结语

源码部分没有过度深究,如果有大神深入了解过源码,请求在评论区或者私聊我为我指点迷津,上文有错的地方也请各位不吝指出,我将进行改正和学习,最后感谢各位的观看!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值