SpringCloud-Eureka配置
注意:Eureka新旧版本情况,旧版本已经被弃用,需要(建议)使用新的版本
旧版本:spring-cloud-starter-eureka-server,spring-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-server,spring-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为空,最后我得出结论:
- CustomLoadBalancerConfiguration类中ReactorLoadBalancer必须有@Bean标识,反射的对象有两种可能:
- 一种是反射出CustomLoadBalancerConfiguration对象,然后找到标识为@Bean的ReactorLoadBalancer创建并替换原来算法。
- 另一种是反射机制先识别@Bean标志的ReactorLoadBalancer,然后直接创建并替换原算法(我认为可能性很大)
- 反射出的ReactorLoadBalancer不由Spring托管,通过@Autowired无法拿出(疑惑又肯定)
- 基本上可以确定是由@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"));
}
}
}
结语
源码部分没有过度深究,如果有大神深入了解过源码,请求在评论区或者私聊我为我指点迷津,上文有错的地方也请各位不吝指出,我将进行改正和学习,最后感谢各位的观看!