Eureka源码解析第一篇----服务注册、心跳续约

@EnableEurekaServer作用

@SpringBootApplication
//为什么加上这个注解,就可以当做注册中心了?
	//可以先解释一下: 1、初始化了Eureka应用上下文环境 2、初始化了Filter过滤器
@EnableEurekaServer
public class AppEureka3000 {

    public static void main(String[] args) {

        SpringApplication.run(AppEureka3000.class);
    }
}

//点开@EnableEurekaServer  就是下面的类
//重要的是@Import({EurekaServerMarkerConfiguration.class})
//因此,EnableEurekaServer这个注解类的作用就是往spring容器中注入一个Bean(EurekaServerMarkerConfiguration)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EurekaServerMarkerConfiguration.class})
public @interface EnableEurekaServer {
}

//点开这个@Import({EurekaServerMarkerConfiguration.class}) 中的类
//@Configuration 这个是表示 是个全注解类 以后会更新spring源码,会着重说一下这个注解
@Configuration
public class EurekaServerMarkerConfiguration {
    public EurekaServerMarkerConfiguration() {
    }

    @Bean
    public EurekaServerMarkerConfiguration.Marker eurekaServerMarkerBean() {
        return new EurekaServerMarkerConfiguration.Marker();
    }

    class Marker {
        Marker() {
        }
    }
}

//还记得springboot中的那个配置文件吗?spring.factories
//springcloud是基于springboot开发的,因此,springcloud也得去找这个配置文件
//这边又得用到springboot的自动配置的原理了,这个是Eureka-server,我们得看看那个配置文件了
//这个springboot自动配置的原理,我会在《springboot源码解析》中详细解释


在这里插入图片描述

这个spring.factories 文件中:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
@Configuration
//@Import它的作用就不用多说了,上面是有解释的 这个就是用来初始化 Eureka环境上下文的
@Import({EurekaServerInitializerConfiguration.class})
//@ConditionalOnBean({Marker.class,就不会加载下面的类})
//这个东西的功能就是:如果spring容器中没有Marker.class,就不会加载EurekaServerAutoConfiguration
@ConditionalOnBean({Marker.class})
@EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
@PropertySource({"classpath:/eureka/server.properties"})
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
    
    //看我这个类的继承,是否感觉很熟悉,不错,在springMVC源码中也是有继承一个MVC****什么的类
    //那,就有疑问了,springcloud完全剥离了页面,怎么还会有MVC方面的东西呢?
    //小孩没娘,说来话长了,这个是个注册服务,那么,我们的服务是不是要注册到这个Eureka上面呢,
    //那么,就得类似Controller那样的,put get啥的,请求Eureka,把微服务注册到这个Eureka上面
    //Eureka用的不是springMVC了,它用的是Jersey
    //这个Jersey框架就注册一个过滤器就行,只要微服务请求过来了,就拦截注册到Eureka上面就行
    
    //因此,这个类,就有两个很重要的作用: 1、初始化Eureka上下文环境 2、初始化 Jersey(过滤器)
    
    //这个类就是用来初始化Jersey(过滤器)
    @Bean
    public FilterRegistrationBean jerseyFilterRegistration(Application eurekaJerseyApp) {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new ServletContainer(eurekaJerseyApp));
        bean.setOrder(2147483647);
        bean.setUrlPatterns(Collections.singletonList("/eureka/*"));
        return bean;
    }
}

如果是springMVC项目的话,我们把项目启动后,就要写Controller层了,@post @get什么的

springcloud是在springboot基础上对Eureka进行了继承,初始化的时候也是初始化了一个MVC容器,只不过这个不是springMVC而是Jersey,其实是一个道理,我们也要像springMVC那样写Controller了,只不过在这里不叫Controller而是叫做Resource,spring整合了Eureka,所以Eureka核心代码不是在上面的那个包。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sd8WpgVa-1598332998406)(C:\Users\hunktimes\AppData\Roaming\Typora\typora-user-images\image-20200803132247826.png)]

服务注册

看上面的图片,Eureka很重要的一个Controller类,ApplicationResource。(说Controller说习惯了)

private final PeerAwareInstanceRegistry registry;

//这个方法就是服务注册
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info, @HeaderParam("x-netflix-discovery-		 	replication") String isReplication) {
    
    //Netflix, Amazon,认为是来源MyOwn
    //DataCenterInfo 这个里面有个泛型,就是上面的三个。说明,微服务来源于什么地方
    //1、云服务中心 2、亚马逊、自己   90%的都是来源于自己的,我们就认为是来源MyOwn
    DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
    //我只粘贴了重要的代码,想看完整代码,请自己去打开对应的类去看
    //这边,我们要注册微服务了
    this.registry.register(info, "true".equals(isReplication));
}

我们看到最后会调用一个接口的register() 这边用到一个设计模式,责任链,我先把类的关系图摆上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4kWAfF1a-1598332998412)(C:\Users\hunktimes\AppData\Roaming\Typora\typora-user-images\image-20200803133958607.png)]

我是要着重说一个这张图的结构,这张图只有最下面的那个类 InstanceRegistry.java 是spring开发的,其它都是Eureka的类。

Eureka是个很牛逼的开发团队开发的,spring当了一次很卑微的舔狗,继承了人家的类。

其实,我们在打开上面的类的时候,有心的话,就会发现,只有最下面的那个类,包名是org.springframework

其他的都是这种类的路径。

最顶层的一个类:InstanceRegistery 这个是个接口。这个里面写了好多方法, 比如说服务注册

AbstractInstanceRegistry 这个类就实现了接口InstanceRegistery ,完成了服务注册的业务代码。

PeerAwareInstanceRegistryImpl 这个类就是完成了集群信息同步的功能。

责任分的很明确,不同的类干不一样的事情。

责任链就是这样的,每个类完成了不同的业务,耦合度其实是很低的。

这个springcloud开发的类,InstanceRegistery 这个就是里面有监听器,检测是否服务注册之类的,

如果,哪天,我们要加入新的功能,我们只需要继承springcloud的这个类 InstanceRegistery 就行,

不用改它原有的代码,很是方便,是不是!!!我们要学会这种方式,

聚是一坨屎,散是满天星。 也是很友好的描述了这种写法。降低代码的耦合度。

关于监听器,我会在《springboot源码解析》中,以案例的方式来详细阐述一下。

接着上面的类ApplicationResource,接着说我们的服务注册。

this.registry.register(info, "true".equals(isReplication));
//上面是服务注册的代码,直接调用到了springcloud开的类InstanceRegistry中
//InstanceRegistry.java 


//看这个类,实现了一个接口 ApplicationContextAware
//我可以先说一下,实现这个接口,就是为了拿到spring上下文对象。
//实现这个接口的作用就是,等我们spring环境初始化完成后,我们就可以通过setApplicationContext()
//把spring环境上下文对象拿到
//在springboot整合Eureka中,Eureka里面根本没有架构监听器,监听器是由spring架构的
//既然要用到监听器,我们肯定得先拿到spring上下文对象。因此,实现了接口ApplicationContextAware
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements 
     ApplicationContextAware {
	
    private ApplicationContext ctxt;
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.ctxt = context;
    }
    
	public void register(final InstanceInfo info, final boolean isReplication) {
    
		//下面的这个方法handleRegistration()就是spring监听器的架构了
        //this.publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, 					isReplication));
        this.handleRegistration(info, this.resolveInstanceLeaseDuration(info), 		   	 		    isReplication);
		//上面的 this.handleRegistration() 仅仅是做了一个发布监听的作用
        //下面才是去实现注册的地方
        super.register(info, isReplication);   
}
    
//super.register(info, isReplication);点开就是下面的方法
public void register(InstanceInfo info, boolean isReplication) {
    
    //这个是心跳续约时间,默认90S  这个可以面试的时候,说说,springcloud默认就是90S
    //当然,这个可以在application.yml配置文件中可以配置的
    int leaseDuration = 90;
    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
    	leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }
    //这个是服务注册的核心方法  就是上面责任链说明的,有个类专门的搞服务注册的
    super.register(info, leaseDuration, isReplication);
    //这个方法是做 集群同步的, 什么时候才能同步? 肯定是服务注册完之后,才能同步到其它服务器上面
    this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register,			 
    		info.getAppName(), info.getId(), info, (InstanceStatus)null, isReplication);
}
    
    
//类AbstractInstanceRegistry.java 
//super.register(info, leaseDuration, isReplication); 这个方法
    
private final Lock read;
private final Lock write;
protected final Object lock;
//这个数据结构,我需要说明一下,
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new 	ConcurrentHashMap();

//leaseDuration  这个是过期时间  
register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    
    this.read.lock();
    //这边有个东西还是要说一下的,我们要是以断点的方式走进的话,这个gMap不是空的
    //这个问题是不是很神奇,为什么呢?Eureka也是刚刚启动的,微服务注册也是第一次进来,它怎会已经注册进去了?
    
    //解释一下: 并发注册
    //客户端在注册的时候,会有个超时判断,如果长时间注册不成功,客户端会把该线程舍掉
    //客户端会开辟一个新的线程去注册,等你断点走到这里的时候,肯定已经注册成功了,但是,不是你注册的
    //我们打断点来搞这个,肯定是已经超时了
    Map<String, Lease<InstanceInfo>> gMap = 
        (Map)this.registry.get(registrant.getAppName());
    if(gMap == null){
        //这边我就不粘贴代码了,既然为空了,肯定是要put到数据结构  得到gMap
    }
    
    //由于上面,我们是把要注册的微服务已经放入到gMap数据结构中了
    //然后,我们就用我们传进来的InstanceInfo中的id从gMap中获取已经存在的Lease
    //existingLease 这个是已经存在数据结构中的微服务实例
    Lease<InstanceInfo> existingLease = (Lease)((Map)gMap).get(registrant.getId());
    //为什么会有这个?这边是有个冲突判断的的,就是上面我们说的这种情况,万一出现了,怎么办?
    //一般情况下, 这个冲突是不会存在的,但是还是怕万一
    //代码逻辑写的很是严谨,我们要向他们学习这种严谨的思维。
    if(existingLease != null && existingLease.getHolder() != null){
        //这个参数:已经存在的微服务实例最后的操作时间
        Long existingLastDirtyTimestamp = 
            ((InstanceInfo)existingLease.getHolder()).getLastDirtyTimestamp();
        //注册该微服务的时间戳
        Long registrationLastDirtyTimestamp = 
            registrant.getLastDirtyTimestamp();
        
        //这个判断 就是上面类似断点进来的那种情况,我们要保证数据结构中的微服务信息是最新的
        if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
            registrant = (InstanceInfo)existingLease.getHolder();
        }
    }else{
        
        
        // 这个else就是 服务注册 更新自我保护机制阈值的 地方
        
        //上面试有冲突的情况,这个else肯定就是没有冲突的时候
        //没有冲突的时候又干了什么呢?  自我保护阈值更新
        //有必要说一下,这个地方的代码了,为什么+2 啥的,一开始我是懵逼的
        //首先,我要说明一下,这边为什么会在else中?
        //if是解决冲突所在,不懂是啥冲突的,请再看一遍上面写的注释,既然是解决冲突的,
        //证明,这个微服务肯定是已经注册成功了,不能再走下面的流程了。
        
        
        //expectedNumberOfRenewsPerMin 期待每分钟心跳的数量
        //为何每来一次心跳续约要 +2?那是因为,心跳续约默认是的30S  既然这是每分钟了,肯定是要 +2
        //正常情况下,每分钟,肯定得来2个心跳续约,所以+2
        synchronized(this.lock) {
            if (this.expectedNumberOfRenewsPerMin > 0) {
                this.expectedNumberOfRenewsPerMin += 2;
                //每分钟,服务心跳预估值
                //这边这个值,会在自我保护机制用到《Eureka源码解析第三篇》
                this.numberOfRenewsPerMinThreshold = 
                    (int)(
                			(double)
                    			this.expectedNumberOfRenewsPerMin * 
                    			this.serverConfig.getRenewalPercentThreshold());
                		)
                    	
            }
        }
        
    }
    //看到这里是不是感觉很是诧异?在这个方法的入口,我们已经把传进来的微服务实例
    //放入到了数据结构中了,为什么这边又搞了一个Lease对象
    //说白了,就是要搞一个最新的对象,就是前面处理冲突的地方,拿到最新的微服务实例
    // 我觉得还是有必要讲解一下这个Lease对象的(下面代码块)
    Lease<InstanceInfo> lease = new Lease(registrant, leaseDuration);
    if (existingLease != null) {
        lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
    }
    ((Map)gMap).put(registrant.getId(), lease);
    //到这里,服务注册已经完成了
}
    
//讲解一下这个租债器对象
//它是一个工具类,并不是专门为微服务写的
//这个类中还是有很多其它方法的,下面都会写出来的
public class Lease<T> {
    
    public static final int DEFAULT_DURATION_IN_SECS = 90;
    //这个就是泛型,不仅仅可以是微服务对象,也可以是其它对象,只不过在服务注册就是一直我们
    //可能就是错误的理解为,它就是为了微服务对象诞生的,不是的,也可以是String,可以是很多东西
    private T holder;
    //服务剔除时间,当服务被剔除的时候,得把时间更新一下
    private long evictionTimestamp;
    //服务注册时间
    private long registrationTimestamp;
    //最后一次正常工作的时间
    //注册 心跳续约 服务下架 都算操作
    private long serviceUpTimestamp;
    //最后操作时间
    private volatile long lastUpdateTimestamp;
    private long duration;
    
    public void renew() {
        
        this.lastUpdateTimestamp = System.currentTimeMillis() + this.duration;
    }
    
    
    public void cancel() {
        
        if (this.evictionTimestamp <= 0L) {
            this.evictionTimestamp = System.currentTimeMillis();
        }
    }
        
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ftdjIzxH-1598332998422)(C:\Users\hunktimes\AppData\Roaming\Typora\typora-user-images\image-20200803145328368.png)]

心跳续约

这边先给出一个时间:

​ 心跳续约时间:30S

​ 最后操作时间:21:44:30

​ 续约前过期时间:21:45:00

​ 续约后的时间:21:45:00

​ 续约后最终过期时间:21:45:30

各个微服务会定时的发送请求给新注册中心,以确保服务还在正常工作。 心跳续约

所谓的心跳续约:就是把最后的操作时间 = 当前时间 + 服务过期时间

从此篇文章开头,第二张图片。

这个里面有个类 InstanceResource 这个是操作单个服务的类

private final PeerAwareInstanceRegistry registry;

@PUT
public Response renewLease(
    @HeaderParam("x-netflix-discovery-replication") String isReplication, 
	@QueryParam("overriddenstatus") String overriddenStatus, 
	@QueryParam("status") String status, 
	@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp){
    
    boolean isFromReplicaNode = "true".equals(isReplication);
    //这个是调用心跳续约的方法
    //其实这个又会跟踪到spring写的那个类中,又会发布监听
    boolean isSuccess = this.registry.renew(this.app.getName(), this.id,                                 isFromReplicaNode);
    
}


boolean renew(final String appName, final String serverId, boolean isReplication) {
    
    //这个又是到了spring自己写的那个类,发布一个监听器
    this.publishEvent(**********************);
    //这个是心跳续约的方法
    return super.renew(appName, serverId, isReplication);
}


PeerAwareInstanceRegistryImpl.java

boolean renew(String appName, String id, boolean isReplication) {
    
    if (super.renew(appName, id, isReplication)) {
        //如果心跳续约成功,那么就该同步集群了
        this.replicateToPeers(*************************);
    }
}


//其实心跳续约就是把最后的操作时间 = 当前时间 + 服务过期时间
//调到最后又到 Lease对象(租债器)
public void renew() {
    //更新最后操作时间了 = 当前时间 + 心跳续约时间  (这个是bug)
    //这个我觉得是Eureka的一个bug,从代码理解角度老说,确实是个bug
    //lastUpdateTimestamp 这个是最后操作时间
    //System.currentTimeMillis() + this.duration 这个是什么?是过期时间
    //如果让我写,我会这样写,
    //应该把当前时间赋值给最后操作时间
    //this.lastUpdateTimestamp = System.currentTimeMillis();
	this.lastUpdateTimestamp = System.currentTimeMillis() + this.duration;
}


//这个也是Lease中的方法,判断是否过期
//由于上面心跳续约有问题了,这边代码肯定是不对的
//System.currentTimeMillis() > this.lastUpdateTimestamp + this.duration + additionalLeaseMs
//这段代码的意思,当前时间 > 最后操作时间+心跳续约时间+集群同步损耗
//Eureka在源代码逻辑中,心跳续约中 已经加上了心跳续约时间,这边又加了一遍,相当于多加了一遍
public boolean isExpired(long additionalLeaseMs) {
    
    return
        this.evictionTimestamp > 0L ||
        System.currentTimeMillis() > this.lastUpdateTimestamp + this.duration +                   additionalLeaseMs;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值