Eureka的InstanceInfoReplicator类(服务注册辅助工具)

关于服务注册

以下图片来自Netflix官方,图中显示Eureka Client会向注册中心发起Get Registry请求来获取服务列表:
在这里插入图片描述

以Spring Cloud的Edgware.RELEASE版本为例,Eureka client的注册动作是在com.netflix.discovery.DiscoveryClient类的initScheduledTasks方法中执行的,相关代码片段如下所示,请注意中文注释:

//略去不相关代码
...
//实例化InstanceInfoReplicator对象
instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize

            //监听器,用来监听作为Eureka client的自身的状态变化
            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId() {
                    return "statusChangeListener";
                }

                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                            InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
                        // log at warn level if DOWN was involved
                        logger.warn("Saw local status change event {}", statusChangeEvent);
                    } else {
                        logger.info("Saw local status change event {}", statusChangeEvent);
                    }
                    //状态变化时notify方法会被执行,此时上报最新状态到Eureka server
                    instanceInfoReplicator.onDemandUpdate();
                }
            };

            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                //注册监听器
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }
            //服务注册
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());

上述代码表明,将自身信息上报到Eureka server的工作是通过调用instanceInfoReplicator的api完成的;

InstanceInfoReplicator的作用

先看InstanceInfoReplicator源码的注释:

/**
 * A task for updating and replicating the local instanceinfo to the remote server. Properties of this task are:
 * - configured with a single update thread to guarantee sequential update to the remote server
 * - update tasks can be scheduled on-demand via onDemandUpdate()
 * - task processing is rate limited by burstSize
 * - a new update task is always scheduled automatically after an earlier update task. However if an on-demand task
 *   is started, the scheduled automatic update task is discarded (and a new one will be scheduled after the new
 *   on-demand update).
 *
 *   @author dliu
 */

我的理解:

  1. InstanceInfoReplicator是个任务类,负责将自身的信息周期性的上报到Eureka server;
  2. 有两个场景触发上报:周期性任务、服务状态变化(onDemandUpdate被调用),因此,在同一时刻有可能有两个上报的任务同时出现;
  3. 单线程执行上报的操作,如果有多个上报任务,也能确保是串行的;
  4. 有频率限制,通过burstSize参数来控制;
  5. 先创建的任务总是先执行,但是onDemandUpdate方法中创建的任务会将周期性任务给丢弃掉;

源码分析

以前面对注释的理解作为主线,去看源码:

  1. 先看构造方法,如下,中文注释位置需要注意:
InstanceInfoReplicator(DiscoveryClient discoveryClient, InstanceInfo instanceInfo, int replicationIntervalSeconds, int burstSize) {
        this.discoveryClient = discoveryClient;
        this.instanceInfo = instanceInfo;
		//线程池,core size为1,使用DelayedWorkQueue队列
        this.scheduler = Executors.newScheduledThreadPool(1,
                new ThreadFactoryBuilder()
                        .setNameFormat("DiscoveryClient-InstanceInfoReplicator-%d")
                        .setDaemon(true)
                        .build());

        this.scheduledPeriodicRef = new AtomicReference<Future>();

        this.started = new AtomicBoolean(false);
        //RateLimiter是个限制频率的工具类,用来限制单位时间内的任务次数
        this.rateLimiter = new RateLimiter(TimeUnit.MINUTES);
        this.replicationIntervalSeconds = replicationIntervalSeconds;
        this.burstSize = burstSize;
        //通过周期间隔,和burstSize参数,计算每分钟允许的任务数
        this.allowedRatePerMinute = 60 * this.burstSize / this.replicationIntervalSeconds;
        logger.info("InstanceInfoReplicator onDemand update allowed rate per min is {}", allowedRatePerMinute);
    }

从以上代码可见,构造方法中准备好了线程池和频率限制工具,再算好了每分钟允许的任务数;
2. 在com.netflix.discovery.DiscoveryClient类的initScheduledTasks方法中,通过调用instanceInfoReplicator.start方法启动了周期性任务,现在来看此方法:

public void start(int initialDelayMs) {
		//CAS操作,不但保证了只执行一次,多线程场景也能保证
		if (started.compareAndSet(false, true)) {
		        instanceInfo.setIsDirty();  // for initial register
		        //提交一个任务,延时执行,注意第一个参数是this,因此延时结束时,InstanceInfoReplicator的run方法会被执行
		        Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
		        //这个任务的Feature对象放在成员变量scheduledPeriodicRef中
		        scheduledPeriodicRef.set(next);
		}
}
  1. 延时时间到达时,会执行run方法:
public void run() {
        try {
            //更新信息,用于稍后的上报
            discoveryClient.refreshInstanceInfo();

            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {
                //上报
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
            //每次执行完毕都会创建一个延时执行的任务,就这样实现了周期性执行的逻辑
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            //每次创建的周期性任务,都要放入scheduledPeriodicRef,
            //如果外部调用了onDemandUpdate,就能通过onDemandUpdate取得当前要执行的任务
            scheduledPeriodicRef.set(next);
        }
    }
  1. 以上代码汇总起来,就完成了周期性任务的逻辑,接下来看看被外部调用的onDemandUpdate方法:
public boolean onDemandUpdate() {
        //没有达到频率限制才会执行
        if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
            //提交一个任务
            scheduler.submit(new Runnable() {
                @Override
                public void run() {
                    logger.debug("Executing on-demand update of local InstanceInfo");
                    //取出之前已经提交的任务
                    Future latestPeriodic = scheduledPeriodicRef.get();
                    //如果此任务未完成,就立即取消
                    if (latestPeriodic != null && !latestPeriodic.isDone()) {
                        logger.debug("Canceling the latest scheduled update, it will be rescheduled at the end of on demand update");
                        latestPeriodic.cancel(false);
                    }
					//通过调用run方法,令任务在延时后执行,相当于周期性任务中的一次
                    InstanceInfoReplicator.this.run();
                }
            });
            return true;
        } else {
            //如果超过了设置的频率限制,本次onDemandUpdate方法就提交任务了
            logger.warn("Ignoring onDemand update due to rate limiter");
            return false;
        }
    }

如上述代码所示,可见之前注释中提到的功能都已实现;

至此,InstanceInfoReplicator已分析完毕,可见这是个功能强大的辅助类,在应用信息上报到Eureka server时发挥了重要的作用,业务逻辑可以放心的提交上报请求,并发、频率超限等情况都被InstanceInfoReplicator处理好了;

©️2020 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值