一、了解eureka如何进行负载均衡
在我们之前的配置代码中,采取RestTemplate这个rest请求模板实现负载均衡操作,并且我们深刻体会到
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
中 @LoadBalanced 的作用。
@LoadBalanced 使得RestTemplate请求操作具有以下功能:
1、能够采取eureka上的服务“别名”方式请求。
2、让请求具有负载均衡的功能。
现在我们就想知道子服务如何实现的负载均衡,所以我们注释@LoadBalanced 。
二、探究、模拟负载均衡
2.1、配置服务消费者
我们采取不带安全校验的eureka注册中心实现验证操作(更简洁明了)。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
//@EnableDiscoveryClient
public class Consumer10001 {
public static void main(String[] args) {
SpringApplication.run(Consumer10001.class, args);
}
@Bean
//@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.2、修改请求控制器
思路:
1、eureka采取别名的方式保存其他服务的端口地址信息,我们应该优先获取eureka上别名对应的微服务的请求地址信息。
2、通过相应的算法,实现一个简单的轮询机制的操作。
所以我们变更处理操作:
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/test")
public class TestController {
private static Logger log = LoggerFactory.getLogger(TestController.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
public static int requestCount = 0;
@RequestMapping("/test1")
public String getTest1(String name){
String getUrl = getService();
if( getUrl == null){
return "根据指定的别名,无法获取指定的请求地址信息";
}
String memberUrl = getUrl+"/product/getProduct?name="+String.valueOf(name);
String result = restTemplate.getForObject(memberUrl, String.class);
return "this is consumer project ,get product result = "+String.valueOf(result);
}
private String getService(){
List<ServiceInstance> serviceInstanceLists = discoveryClient.getInstances("app-bunana-product");
if(serviceInstanceLists == null || serviceInstanceLists.size() == 0){
return null;
}
//获取此时该“别名”对应的请求地址的个数信息
int size = serviceInstanceLists.size();
//获取当前的请求下标信息
int index = requestCount % size;
//请求数递增
requestCount ++;
//通过计算得到的下标,获取对应需要请求的地址和端口信息
ServiceInstance serviceInstance = serviceInstanceLists.get(index);
log.info("获取ServiceInstance对象,他的信息是:{}", serviceInstance.toString());
String getUrl = serviceInstance.getUri().toString();
log.info("获取ServiceInstance对象,url是:{}", getUrl);
return getUrl;
}
}
分别启动eureka注册中心和各项服务,然后测试
请求操作:
http://localhost:10001/test/test1?name=66666
再次请求:
查看日志台输出情况:
此处的操作,我们只是在项目启动后,全局一个当前的请求次数,按照当前总请求次数,取余服务总数,获得当前需要请求的地址下表值,得到具体的请求地址操作,使用的还是
List<ServiceInstance> serviceInstanceLists =
discoveryClient.getInstances("app-bunana-product");
根据别名获取到当前动态从注册中心拉取到的服务别名对应地址信息的集合,按照取余后的值作为下标取得对应下标的url地址信息。
2.3、源码链接
三、@EnableEurekaClient 和 @EnableDiscoveryClient
我们知道,每个服务需要注册至eureka注册中心上时,需要在每个启动类上新增注解@EnableEurekaClient,在子服务自动启动时,会将当前设备的信息和项目的端口信息等注册至服务中心上。
其实还有一个注解也可以实现注册,就是我们常看见的@EnableDiscoveryClient,我们查看注解
得知这是个注解开发。
由于我是用的是Eclipse开发软件,所以快捷键选择 Ctrl + Shift + t 查看 DiscoveryClient ,
继续深入查看 @EnableEurekaClient 和 @EnableDiscoveryClient 注解的关系:
得知:DiscoveryClient 是 EurekaClient 的子类关系。
四、探查 DiscoveryClient 的源码实现
我们查看 @EnableDiscoveryClient 的注解时,发现其作用是开启DiscoveryClient。
经过探查(Ctrl + Shift + t),发现 DiscoveryClient 的类有两个,一个是 netflix、一个是 Springcloud的。
其实真正的源码是netflix的,SpringCloud只是对其又包装了一层结构。
我们查看SpringCloud框架对其的封装:
发现:
在springcloud中,DiscoveryClient 是一个接口形式。
分别具有的作用:
描述信息、ServiceInstance实例对象、服务信息。
作为接口,就必须需要有接口的实例化对象,也就是其实现的子类,我们依据 Eclipse 软件的快捷键 Ctrl + t,查看其子类信息:
此处也说明了为什么 EurekaDiscoveryClient 可以 有服务的 注册上报作用了。
我们回到 com.netflix.discovery; 下的
查看其实现类,此处才是netfliix 的eureka源码部分:
我们需要知道 eureka 子服务,在启动时做了什么,只需要去查看其在加载时,需要执行什么。
查看 com.netflix.discovery.DiscoveryClient 类头信息,我们知道他是一个单例对象,作为单例,也就是在这个类中存在实例化对象的方式(不管懒汉式还是饿汉式)。所以我们只需要看他实例化 DiscoveryClient 类时做了什么就行了。
我们查看其源码记录,可以看到他的一个构造方法:
在这其中,使用了加载eureka client时的一个初始化方法,用于初始化eureka client 使用 DiscoverClient 做了哪些事
我们就分析这个方法了。
4.1、获取注册信息
在我们的配置文件中,针对单个服务,如果需要做到rest请求操作前,我们必须要此时的子服务能够定时从注册中心上拉取其他注册服务的注册信息。
所以配置文件中,我们一般这么配置:
#从注册中心上拉取别的注册服务信息
eureka.client.fetch-registry=true
除此之外,我们也知道默认拉取服务的时间为30秒,当然我们也可以自己配置拉取服务的时间是多少。
eureka.client.registry-fetch-interval-seconds
关于eureka的相关命令的配置,可以参考我的另外一篇博客:
《spring cloud eureka 参数配置》
接下来我们来分析,他是如何识别以下的几点需求的:
1、从注册中心拉取其他注册子服务信息。
2、设置自动拉取其他服务的时间。
我们查看当前 com.netflix.discovery.DiscoveryClient.initScheduledTasks() 中的方法:
从源码截图中我们可以发现:
首先判断我们此时的服务是否需要拉取;
如果需要拉取,接下来我们需要做什么?
从中看出,默认表示是true的,也就是说eureka.client.fetch-registry 的默认配置为 true
继续看这个方法做了什么。
4.1.1、我们先分析int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();是做什么的?
从EurekaClientConfig 接口的实现类 org.springframework.cloud.netflix.eureka.EurekaClientConfigBean 中,我们可以知道,此处表示获取 自动拉取服务的时间,数值默认为30秒。
我们继续看这个类之前的几个参数配置:
再yml中进行配置时,可以参考如下形式:
[注:]支持 驼峰命名、带"-“的命名方式、带”."的命名方式 等。
当我们没有设置
时,他会默认查找是否存在一个 http://localhost:8761/eureka 的注册地址!!
4.1.2、我们再看clientConfig.getCacheRefreshExecutorExponentialBackOffBound()干了什么?
查看其实现子类中的源码,我们发现,他返回了一个参数信息
然后这个参数信息默认为 10。
缓存刷新执行器指数回退相关属性。
在发生超时序列的情况下,它是重试延迟的最大乘数。
然后查看整个方法,我们大致知道
如果项目启动,我们配置(或默认配置)拉取注册服务信息为true时,eureka子服务会从配置文件(或默认配置)中获取各项参数信息,然后创建一个定时任务,定时拉取注册中心的相关注册信息。
4.2、注册至注册中心上
第二个方法中,存在的操作很多,不急 我们慢慢来看:
4.2.1、执行前
方法执行之前,依旧会判断当前的服务是否需要注册至注册中心上!
4.2.2、判断需要执行后
获取续约周期:
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
再源码中的体现,为以下所示:
从中,我们清楚地看到,心跳间隔的默认时间为:30秒;
就像配置文件中,可以对其进行设置一样:
其中,再最后面有一个start(xxxx),
instanceInfoReplicator.start
(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
我们查看其调用的这个方法对应的这个类是什么?
从此处看,com.netflix.discovery.InstanceInfoReplicator 是一个线程,实现了 Runnable 接口,具体的操作,我们就去看其实现的 run 方法做了什么。
再注册执行的流程中,我们发现了其注册的实现逻辑:
从代码中的流程我们发现:
1、采取http rest方式进行将实例化对象信息发送给注册中心。
2、注册完成后,返回注册码信息 204.
4.2.3、关于心跳,准确来说是 子服务将心跳时间请求发送至注册中心 (客户端请求服务器)
为了验证这个问题,我们可以参考源码:
使用了一个定时任务(定时调度)。
再run方法中,重要的是其中的 renew() 操作。