Ribbon与Eureka整合分析(一)、服务实例数据获取以及更新

服务实例数据获取以及更新

该文为Ribbon与Eureka整合源码分析系列博文的第一篇,主要解答如下问题:

  1. 基于哪些配置类,完成整套操作?
  2. 如何从Eureka处,获取服务实例?
  3. 如何同步服务实例?

一、使用哪些配置类?

RibbonEurekaAutoConfiguration配置类

基于RibbonEurekaAutoConfiguration自动配置类,开启Ribbon与Eureka的整合之路。该配置类定义如下:
在这里插入图片描述
通过RibbonClients注解的defaultConfiguration属性,为所有客户端提供一份默认配置,即EurekaRibbonClientConfiguration。在该配置类中,用于指定IRule、IPing等接口的默认实现。关于该配置类,后续博文,会有详细介绍。

RibbonAutoConfiguration配置类

调试过Ribbon代码的朋友,应该会发现,这些配置类,基于延迟加载的方式装配的。其中所涉及的Factory等Bean,是基于该配置文件进行配置。
针对于服务选择等操作,Spring提供了LoadBalancerClient接口。该接口,后续会工作于LoadBalancerInterceptor拦截器中,正是这个拦截器,让用户在无感知的状态中,使用到Ribbon提供的诸多功能。也是基于这个配置类,完成该接口配置。
在这里插入图片描述
关于该接口以及实现,会在后续博文给出详细介绍。

EurekaRibbonClientConfiguration配置类

基于该配置类,提供了客户端关于Ribbon中IPing、ServerList等默认配置。
在这里插入图片描述
在这里插入图片描述
这两个配置,分别用于服务状态更新、服务拉取操作。其详细内容,会在该博文后续内容中,进行详细介绍。

RibbonClientConfiguration配置类

在该配置类中,提供了诸如IRule、ServerListUpdater、ILoadBalancer等接口的配置操作,分别用于负载均衡算法、服务实例状态更新等操作。其中部分配置,依赖于EurekaRibbonClientConfiguration配置类(或者说被EurekaRibbonClientConfiguration提供的配置,所覆盖)

二、如何从Eureka处获取服务?

Ribbon基于Server,用于定义服务。

public class Server {

    /**
     * Additional meta information of a server, which contains
     * information of the targeting application, as well as server identification
     * specific for a deployment environment, for example, AWS.
     */
    public static interface MetaInfo {
        /**
         * @return the name of application that runs on this server, null if not available
         */
        public String getAppName();

        /**
         * @return the group of the server, for example, auto scaling group ID in AWS.
         * Null if not available
         */
        public String getServerGroup();

        /**
         * @return A virtual address used by the server to register with discovery service.
         * Null if not available
         */
        public String getServiceIdForDiscovery();

        /**
         * @return ID of the server
         */
        public String getInstanceId();
    }

    public static final String UNKNOWN_ZONE = "UNKNOWN";
    private String host;
    private int port = 80;
    private String scheme;
    private volatile String id;
    private volatile boolean isAliveFlag;
    private String zone = UNKNOWN_ZONE;
    private volatile boolean readyToServe = true;

	……省略其他方法

同时提供了ServerList接口,用于获取Server列表。

public interface ServerList<T extends Server> {

    public List<T> getInitialListOfServers();
    
    /**
     * Return updated list of servers. This is called say every 30 secs
     * (configurable) by the Loadbalancer's Ping cycle
     * 
     */
    public List<T> getUpdatedListOfServers();   

}

针对于ServerList,整个继承体系如下图所示:
在这里插入图片描述
在RibbonClientConfiguration配置类中,使用ZoneAwareLoadBalancer作为ILoadBalancer借口的配置类,其所需的ServerList,通过EurekaRibbonClientConfiguration提供。
ServerList部分配置源码如下

	@Bean
	@ConditionalOnMissingBean
	public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
		if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
			return this.propertiesFactory.get(ServerList.class, config, serviceId);
		}
		DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
				config, eurekaClientProvider);
		DomainExtractingServerList serverList = new DomainExtractingServerList(
				discoveryServerList, config, this.approximateZoneFromHostname);
		return serverList;
	}

选择DomainExtractingServerList作为配置类,同时将DiscoveryEnabledNIWSServerList 作为构造方法参数传递。正是这个list,完成从Eureka拉取服务操作。该动作通过ZoneAwareLoadBalancer触发。
ILoadBalancer配置代码如下

@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

ILoadBalancer继承体系如下所示
在这里插入图片描述

  1. DynamicServerListLoadBalancer实现类,是的服务实例清单拥有动态更新能力,同时增加服务实例过滤的动作。
  2. ZoneAwareLoadBalancer增加了区域概念,用以解决请求跨区而导致的高延迟问题。
    关于负载均衡算法,会在后续博文中,进一步介绍。
    接回上面的话题,在ZoneAwareLoadBalancer构造方法处,通过DynamicServerListLoadBalancer构造方法,调用restOfInit方法,在该方法中,通过updateListOfServers方法,调用DiscoveryEnabledNIWSServerList对象的getUpdatedListOfServers方法获取服务实例清单。
public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
            servers = serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);

            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
        updateAllServerList(servers);
    }

现在进入到DiscoveryEnabledNIWSServerList.getUpdatedListOfServers方法

public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
        return obtainServersViaDiscovery();
    }

    private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
        List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();

        if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
            logger.warn("EurekaClient has not been initialized yet, returning an empty list");
            return new ArrayList<DiscoveryEnabledServer>();
        }

        EurekaClient eurekaClient = eurekaClientProvider.get();
        if (vipAddresses!=null){
            for (String vipAddress : vipAddresses.split(",")) {
                // if targetRegion is null, it will be interpreted as the same region of client
                List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
                for (InstanceInfo ii : listOfInstanceInfo) {
                    if (ii.getStatus().equals(InstanceStatus.UP)) {

                        if(shouldUseOverridePort){
                            if(logger.isDebugEnabled()){
                                logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
                            }

                            // copy is necessary since the InstanceInfo builder just uses the original reference,
                            // and we don't want to corrupt the global eureka copy of the object which may be
                            // used by other clients in our system
                            InstanceInfo copy = new InstanceInfo(ii);

                            if(isSecure){
                                ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
                            }else{
                                ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
                            }
                        }

                        DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
                        serverList.add(des);
                    }
                }
                if (serverList.size()>0 && prioritizeVipAddressBasedServers){
                    break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
                }
            }
        }
        return serverList;
    }

通过这段代码发现,其服务实例是通过EurekaClient.getInstancesByVipAddresst获取。

三、如何更新服务实例

Ribbon提供ServerListUpdater接口,用于启动服务实例更新操作。其中start、stop方法,用于开启服务更新和暂停更新操作。同时,增加UpdateAction内部类接口,服务实例更新操作,正是基于该接口完成。

public interface ServerListUpdater {

    /**
     * an interface for the updateAction that actually executes a server list update
     */
    public interface UpdateAction {
        void doUpdate();
    }


    /**
     * start the serverList updater with the given update action
     * This call should be idempotent.
     *
     * @param updateAction
     */
    void start(UpdateAction updateAction);

    /**
     * stop the serverList updater. This call should be idempotent
     */
    void stop();

    /**
     * @return the last update timestamp as a {@link java.util.Date} string
     */
    String getLastUpdate();

    /**
     * @return the number of ms that has elapsed since last update
     */
    long getDurationSinceLastUpdateMs();

    /**
     * @return the number of update cycles missed, if valid
     */
    int getNumberMissedCycles();

    /**
     * @return the number of threads used, if vaid
     */
    int getCoreThreads();
}

ServerListUpdater接口,存在如下两种实现类
在这里插入图片描述

  1. PollingServerListUpdater,基于定时任务的方式,进行服务实例更新操作。
public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };

            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }
  1. EurekaNotificationServerListUpdater,基于Eureka的事件通知的方式,完成服务实例更新。
if (isActive.compareAndSet(false, true)) {
            this.updateListener = new EurekaEventListener() {
                @Override
                public void onEvent(EurekaEvent event) {
                    if (event instanceof CacheRefreshedEvent) {
                        if (!updateQueued.compareAndSet(false, true)) {  // if an update is already queued
                            logger.info("an update action is already queued, returning as no-op");
                            return;
                        }

                        if (!refreshExecutor.isShutdown()) {
                            try {
                                refreshExecutor.submit(new Runnable() {
                                    @Override
                                    public void run() {
                                        try {
                                            updateAction.doUpdate();
                                            lastUpdated.set(System.currentTimeMillis());
                                        } catch (Exception e) {
                                            logger.warn("Failed to update serverList", e);
                                        } finally {
                                            updateQueued.set(false);
                                        }
                                    }
                                });  // fire and forget
                            } catch (Exception e) {
                                logger.warn("Error submitting update task to executor, skipping one round of updates", e);
                                updateQueued.set(false);  // if submit fails, need to reset updateQueued to false
                            }
                        }
                        else {
                            logger.debug("stopping EurekaNotificationServerListUpdater, as refreshExecutor has been shut down");
                            stop();
                        }
                    }
                }
            };
            if (eurekaClient == null) {
                eurekaClient = eurekaClientProvider.get();
            }
            if (eurekaClient != null) {
                eurekaClient.registerEventListener(updateListener);
            } else {
                logger.error("Failed to register an updateListener to eureka client, eureka client is null");
                throw new IllegalStateException("Failed to start the updater, unable to register the update listener due to eureka client being null.");
            }
        } else {
            logger.info("Update listener already registered, no-op");
        }

同样的,在DynamicServerListLoadBalancer构造方法处,在restOfInit方法内,调用enableAndInitLearnNewServersFeature方法,开启服务更新操作。其中基于匿名类方式,调用updateListOfServers方法(服务拉取,也是调用此方法),来更新服务。

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();
        }
    };

在RibbonClientConfiguration配置类中,官方选择PollingServerListUpdater完成服务实例更新操作。
在这里插入图片描述

总结

  1. 服务拉取

在RibbonClientConfiguration配置类中,在DynamicServerListLoadBalancer构造方法处(ZoneAwareLoadBalancer的父类),通过restOfInit方法(最终调用DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery方法,通过EurekaClient,获取服务实例),获取服务实例。

  1. 服务更新

在DynamicServerListLoadBalancer构造方法调用的restOfInit方法内,通过enableAndInitLearnNewServersFeature方法,完成ServerListUpdater Bean的start方法,开启服务实例更新动作。而ServerListUpdater Bean,基于RibbonClientConfiguration完成配置,默认使用PollingServerListUpdater,基于现行轮询的方式实现服务实例更新。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要做个有钱人2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值