Eureka源码解析第二篇---集群同步、服务剔除

Eureka源码解析第二篇

集群同步

集群同步,这个东西怎么说呢?我觉得在看源码之前,我们必须要学会思考,集群同步到底什么时候执行?

一旦要集群同步了,讲道理,我们肯定是修改了集群,什么时候修改了集群呢,比如说服务注册,又比如说心跳续约,是不是,一旦服务注册了,心跳续约了,必然要同步集群。

所以,我们还是按照《Eureka源码解析第一篇》再走一遍代码,

我们先要进入Eureka中的一个类InstanceRegistery,在这个类中找到

在这个类中我们还是要找服务注册的方法,为什么找它?我们得验证上面的猜测啊,服务注册了,是否紧接着就是集群同步。

  //其实找到最后,你就会发现,在服务注册之后,紧接着就是集群同步的方法
//PeerAwareInstanceRegistryImpl.java  这个类做了集群同步的功能了  replicateToPeers()这个方法
//这个方法有两个很重要的参数, Action.Register   isReplication
//第一个参数:就是本次集群同步是服务注册还是心跳续约之类的,Action是个枚举
//第二个参数:isReplication 这个是个boolean类型,它很重要的  是否来着集群同步

//下面的图片,就是Eureka集群同步的过程

//我们在配置Eureka集群的时候,在application.yml中是怎么写的?
//server1: http://localhost:3002/eureka,http://localhost:3002/eureka
//server2: http://localhost:3001/eureka,http://localhost:3003/eureka
//server3: http://localhost:3001/eureka,http://localhost:3002/eureka

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5W3Jx026-1598433160791)(C:\Users\hunktimes\Desktop\springcloud\2\1)]

又得搞一波代码,心累,但是,我今年一定要坚持写完,以后不会再看spring源码了。

我们回顾一下,《Eureka源码解析第一篇》中,我们是怎么进行服务注册的,入口在什么地方?

一开始我们就说了,初始化了一个Jersey,类似springMVC框架的,拦截请求

如果是我们在写springMVC项目,初始化完成后,就得写Controller层了,不知道大家想起什么没有,如果还是没有,就要重新看看《Eureka源码解析第一篇》

这个时候是要找一下《Eureka源码解析第一篇》代码块的,有个传递了90S的心跳续约的地方,

我标注了一个方法 this.replicateToPeers() 这么个方法,这个是集群同步的方法,

下面 我就直接粘贴这个方法了:

private void replicateToPeers(
    							PeerAwareInstanceRegistryImpl.Action action,
    							String appName, 
    							String id, 
    							InstanceInfo info, 
    							InstanceStatus newStatus, 
    							boolean isReplication){
    
    //说一下,这个判断的作用,这个就是Eureka集群同步的时候最重要的地方
    //前面我们说了,所有的微服务注册都会和上面说的一模一样,都会调用到Eureka中的一个类
    //InstanceRegistery   这个类中有个register方法,服务注册
    //isReplication 我前面有说过,这个标志就是为了判断集群同步到底是server之间的同步
    //还是客户端注册时候的同步
    
    //如果是客户端的注册,你应该明白一点儿,所有的微服务都会走上面的注册,
    //都会走同样的集群同步,这样搞会不会有问题呢?当然会了
    //所以这边 加了一个判断,如果peerEurekaNodes 这个就是在application.yml配置集群的时候
    //如果是空的,证明是单机的,isReplication 为fasle就是注册的
    //直接返回,不让进行集群同步
    
    //如果是server之间的同步,这个isReplication得是true
    if (this.peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
        return;
    }
    
    //下面还有个知识点
    server1: http://localhost:3002/eureka,http://localhost:3002/eureka
    //比如,我们在配置server1的时候,为什么不用加上 http://localhost:3001/eureka
    //就是下面的代码进行了判断
    if (this.peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
        //判断地址,如果是当前地址,直接continue  所以,不用配置当前的http://localhost:3001/eureka
        continue;
    }
    //这个就是真正的搞集群同步的地方了,
    //我要get的点已经get到了,  这个里面会有个调用HTTP的那个模板, template之类的东西
    //这个方法暂时没有深入研究了
    this.replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
                              

服务剔除

服务剔除 服务下架,这是两个概念,一个是主动 一个是被动

服务剔除:我的服务端好久没有接受到客户端的心跳续约了,证明它已经挂了,我会主动的剔除它

​ 定时的去清理很久都没有心跳续约的微服务(定时器)

服务下架:可以理解为客户端直接自己不用了, 自己主动下架了服务中心

//EurekaServerInitializerConfiguration.java
//还记得这个类吗?初始化Eureka上下文环境的这个类,参考《Eureka源码解析第一篇》开头那边

//在这个类中, 为啥start()方法要开启一个线程去启动
//我想要说的是,这段代码很棒,以后自己在写中间件的时候,如果有相同的业务,也要这么写
//解释一下:  还记得springcloud是怎么样诞生的吗?springcloud整合了一大堆插件来解决
//微服务之间的调用问题,什么雪崩了,降级啊等等,其实springcloud什么也没有做,只是把市面上这些成熟的技术
//整合到springboot中,组装成了springcloud,和 联想公司一样,是个组装为主的破玩意儿

//那,我们不可能因为其中一个模块有问题,就让整个springboot都起不来啊,是不是
//所以,我们要开启一个线程去启动这个东西,起不起的来,有什么关系呢?反正是异步,不影响我springcloud启动就行

//这个就是,Eureka就算有问题,springcloud也能正常启动,因为组件不仅仅就一个Eureka

//还要说一下这个类实现的两个接口   这两个接口会在《Spring源码解析》中根据案例进行详细讲解
//ServletContextAware
	// ServletContextAware 这个就很重要了,凡是什么Aware 说白了,就是要拿到Spring环境下的容器
	//等Spring初始化完毕后,实现这样的接口就是为了拿到Spring环境

//SmartLifecycle
	//这个接口里面有很多方法 比如说start() stop()方法
	//start() 这个就是Spring在启动的时候调用的方法
	//stop() 容器在停止的时候调用的方法	
	//这个有什么用?比方说,你想着Spring启动的时候,加载点儿热数据啥的  就可以用这个接口
	
    public class EurekaServerInitializerConfiguration implements ServletContextAware,  		 	SmartLifecycle, Ordered {
    
    //实现接口ServletContextAware  拿到servletContext容器
    private ServletContext servletContext;
    public void setServletContext(ServletContext servletContext){
        this.servletContext = servletContext;
    }
    
    public void start() {
        (new Thread(new Runnable() {
            //初始化了环境   发布了两个监听器
            this.eurekaServerBootstrap.contextInitialized(this.servletContext);
            this.publish(new EurekaRegistryAvailableEvent(this.getEurekaServerConfig()));
            this.publish(new EurekaServerStartedEvent(this.getEurekaServerConfig()));

        })).start();

        }
    }
}

contextInitialized(this.servletContext){
    
    //这个就是初始化Eureka本地配置  就是yml文件中的东西
    this.initEurekaEnvironment();
    //这个就是初始化了EurekaServer的一些信息
    this.initEurekaServerContext();
}

protected PeerAwareInstanceRegistry registry;

initEurekaServerContext(){
    
    //同步集群的地方
    int registryCount = this.registry.syncUp();
    this.registry.openForTraffic(this.applicationInfoManager, registryCount);
}

public int syncUp() {
    
    int count = 0;//这个count会用来记录同步了多少微服务,在自我保护机制会用到的
    
    //private int registrySyncRetries = 0; 重试次数  默认是0
    //private long registrySyncRetryWaitMs = 30000L; 重试等待时间
    //这个可以在配置文件中配置的
    for(int i = 0; i < this.serverConfig.getRegistrySyncRetries() && count == 0; ++i) {
        
         if (i > 0) {
             Thread.sleep(this.serverConfig.getRegistrySyncRetryWaitMs());
         }
    }
}


public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
    
    //EurekaServer初始化的时候,更新自我保护阈值
    //《Eureka源码解析第三篇》中,会说到,Eureka几种更新自我保护阈值的地方
    
    //这边为什么会乘以2?
    //count Eureka初始化的时候,count是服务注册成功的个数。30S一次
    //syncUp() 请认真看上面的方法的代码,这边这个东西不好解释,只能靠自己的理解了
    //我这边,统计的是每分钟,所以要*2
    this.expectedNumberOfRenewsPerMin = count * 2;
    //这个是自我保护机制的阈值
    this.numberOfRenewsPerMinThreshold = 
        (int)((double)this.expectedNumberOfRenewsPerMin * 				
              this.serverConfig.getRenewalPercentThreshold());
    
    super.postInit(); //这个里面启动了定时器去做 服务剔除
    
    //这个里面是封装了一个定时器,定时去清理微服务  JDK得那个定时器 Timer,不和任何框架去耦合
    //既然是定时器,肯定得有时间。  yml配置文件中:eviction-interval-timer-in-ms 这个时间可以配置的
}

//这个就是定时器中的方法
public void evict(long additionalLeaseMs) {
    
    //这个就是触发了自我保护机制的
    //一旦触发了,就不会清理了
    if (!this.isLeaseExpirationEnabled()) {
        logger.debug("DS: lease expiration is currently disabled.");
    }else{
        
        //然后,就该看一下了,看看 Eureka是怎样做 服务剔除的
        
        //首先定义了一个  需要被剔除的服务的集合
        List<Lease<InstanceInfo>> expiredLeases = new ArrayList();
        
        //还记得服务注册时候的那个map集合吗?
        //ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
        //我上面说过,服务剔除,剔除的是 没有收到心跳续约的微服务
        //那,我是不是得遍历registry,还记得我在《Eureka源码解析第一篇》说的那个Lease对象吗?
        //里面还有个判断微服务是否过期的方法
        
        //遍历我的集群对象
        Iterator var4 = this.registry.entrySet().iterator();
        while(true) {
            
            int registrySize = (int)this.getLocalRegistrySize();//计算出当前有多少个微服务
            //RenewalPercentThreshold  这个值默认的是0.85D
            //计算出这么一个阈值
            int registrySizeThreshold = (int)((double)registrySize * 
            	this.serverConfig.getRenewalPercentThreshold());
            //总数量 - 阈值 = 服务剔除的限制
            int evictionLimit = registrySize - registrySizeThreshold;
            //有多少服务已经过期、服务剔除的一个limit  取小值
            int toEvict = Math.min(expiredLeases.size(), evictionLimit);
            //这里涉及到了  自我保护机制了,为什么要两者取小值  看《Eureka源码解析第三篇》的开头
            //就是为了防止剔除的微服务数量超过了总数量的15%  这个值可以配置的,就是上面的 0.85D
            
            //Eureka集群的高可用就在这里展示了,85%的集群高可用率
            //反正这个剔除是定时!,比方说,我这次需要剔除30个,实际上我只能剔除15个
            //下次再剔除剩下的15个,谁知道这是网络波动引起的还是Eureka出问题了,还是什么出问题了呢
            //剩下的15个可以在下个循环里面剔除,说不定下次剔除的时候,这几个服务又可以用了呢!
            //这样的话,就不需要剔除了嘛,皆大欢喜啊
            if (toEvict > 0) {
                
                //这里是服务剔除的地方
                //这里绝对不可以轮循的剔除,直接把集合里的元素排序剔除,那肯定不行
                //1、减轻压力,剔除不同服务上的微服务
                	//这边用了一个“洗牌算法”
                	//next  生成这么一个随机数,Collections.swap(expiredLeases, i, next);
                	//swap() 这个方法的作用是,把集合expiredLeases的next元素和i位置元素交换
                	//那样的话,根据,for循环,我剔除的就是这个 随机的next位置上的元素了
                //2、第二个原因才是最重要的, 比如说我有个user微服务集群,它都挂了,轮循剔除的话,一下子
                	//全部剔除了,这就坏事了啊,那你整个项目都不能用了
                	//我这么说,你可能会有疑问,都调用不通了,为何还要保留,有什么用呢?
                	//服务可用的本质是什么?是能调通,而不是以能否返回正确值为准则的
                	//用springcloud的时候,肯定会用到Hystrix 服务容错机制
              		//我们都会在微服务中写 容错机制的,保证微服务可以调用
                for(int i = 0; i < toEvict; ++i) {
                    int next = i + random.nextInt(expiredLeases.size() - i);
                    Collections.swap(expiredLeases, i, next);
                    Lease<InstanceInfo> lease = (Lease)expiredLeases.get(i);
                    String appName = ((InstanceInfo)lease.getHolder()).getAppName();
                    String id = ((InstanceInfo)lease.getHolder()).getId();
                    EurekaMonitors.EXPIRED.increment();
                    //Eureka贼JB懒,这个internalCancel这个方法是服务下架的方法
                    //这个代码是服务下架,也可以说成服务剔除   反正都是这个代码逻辑
                    //这边也会更新 自我保护机制的阈值
                    this.internalCancel(appName, id, false);
                }
            }
            
            //反正这个是最终的,判断一下 微服务是否超期
            if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
            	expiredLeases.add(lease);
            }
        }
    }
    
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值