该文为Ribbon与Eureka整合源码分析系列博文的第一篇,主要解答如下问题:
基于RibbonEurekaAutoConfiguration自动配置类,开启Ribbon与Eureka的整合之路。该配置类定义如下:
通过RibbonClients注解的defaultConfiguration属性,为所有客户端提供一份默认配置,即EurekaRibbonClientConfiguration。在该配置类中,用于指定IRule、IPing等接口的默认实现。关于该配置类,后续博文,会有详细介绍。
调试过Ribbon代码的朋友,应该会发现,这些配置类,基于延迟加载的方式装配的。其中所涉及的Factory等Bean,是基于该配置文件进行配置。
针对于服务选择等操作,Spring提供了LoadBalancerClient接口。该接口,后续会工作于LoadBalancerInterceptor拦截器中,正是这个拦截器,让用户在无感知的状态中,使用到Ribbon提供的诸多功能。也是基于这个配置类,完成该接口配置。
关于该接口以及实现,会在后续博文给出详细介绍。
基于该配置类,提供了客户端关于Ribbon中IPing、ServerList等默认配置。
这两个配置,分别用于服务状态更新、服务拉取操作。其详细内容,会在该博文后续内容中,进行详细介绍。
在该配置类中,提供了诸如IRule、ServerListUpdater、ILoadBalancer等接口的配置操作,分别用于负载均衡算法、服务实例状态更新等操作。其中部分配置,依赖于EurekaRibbonClientConfiguration配置类(或者说被EurekaRibbonClientConfiguration提供的配置,所覆盖)
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);
}
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();
}
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");
}
}
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完成服务实例更新操作。
在RibbonClientConfiguration配置类中,在DynamicServerListLoadBalancer构造方法处(ZoneAwareLoadBalancer的父类),通过restOfInit方法(最终调用DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery方法,通过EurekaClient,获取服务实例),获取服务实例。
在DynamicServerListLoadBalancer构造方法调用的restOfInit方法内,通过enableAndInitLearnNewServersFeature方法,完成ServerListUpdater Bean的start方法,开启服务实例更新动作。而ServerListUpdater Bean,基于RibbonClientConfiguration完成配置,默认使用PollingServerListUpdater,基于现行轮询的方式实现服务实例更新。