Ribbon学习笔记二

维护服务实例清单

服务实例清单的维护主要在抽象类BaseLoadBalancer中实现。在BaseLoadBalancer类中主要实现了:
1、维护了“up”状态的服务列表和全部的服务实例列表
2、通过IPing实现了设置服务实例列表的状态

服务实例清单
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections
        .synchronizedList(new ArrayList<Server>());
@Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> upServerList = Collections
        .synchronizedList(new ArrayList<Server>());
增加服务实例

在BaseLoadBalancer类中,有三个重载方法,如下所示:

@Override
public void addServers(List<Server> newServers) {
    if (newServers != null && newServers.size() > 0) {
        try {
            ArrayList<Server> newList = new ArrayList<Server>();
            newList.addAll(allServerList);
            newList.addAll(newServers);
            setServersList(newList);
        } catch (Exception e) {
            logger.error("LoadBalancer [{}]: Exception while adding Servers", name, e);
        }
    }
}

void addServers(Object[] newServers) {
    if ((newServers != null) && (newServers.length > 0)) {

        try {
            ArrayList<Server> newList = new ArrayList<Server>();
            newList.addAll(allServerList);

            for (Object server : newServers) {
                if (server != null) {
                    if (server instanceof String) {
                        server = new Server((String) server);
                    }
                    if (server instanceof Server) {
                        newList.add((Server) server);
                    }
                }
            }
            setServersList(newList);
        } catch (Exception e) {
            logger.error("LoadBalancer [{}]: Exception while adding Servers", name, e);
        }
    }
}

public void addServer(Server newServer) {
    if (newServer != null) {
        try {
            ArrayList<Server> newList = new ArrayList<Server>();

            newList.addAll(allServerList);
            newList.add(newServer);
            setServersList(newList);
        } catch (Exception e) {
            logger.error("LoadBalancer [{}]: Error adding newServer {}", name, newServer.getHost(), e);
        }
    }
}

前面的三个重载方法,最终都是通过setServersList()方法实现为all和up两个列表进行赋值。

public void setServersList(List lsrv) {
	//获取写锁
   Lock writeLock = allServerLock.writeLock();
   logger.debug("LoadBalancer [{}]: clearing server list (SET op)", name);
   //定义列表集合
   ArrayList<Server> newServers = new ArrayList<Server>();
   //加锁
   writeLock.lock();
   try {
       ArrayList<Server> allServers = new ArrayList<Server>();
       for (Object server : lsrv) {
           if (server == null) {//处理null的情况
               continue;
           }

           if (server instanceof String) {//如果存储的是service Id,则创建对应的server对象
               server = new Server((String) server);
           }

           if (server instanceof Server) {//添加到allServers变量,即把所有服务实例转换成server对象,并存储到了局部变量allServers中
               logger.debug("LoadBalancer [{}]:  addServer [{}]", name, ((Server) server).getId());
               allServers.add((Server) server);
           } else {
               throw new IllegalArgumentException(
                       "Type String or Server expected, instead found:"
                               + server.getClass());
           }

       }
       //判断服务实例清单,是否发生变化,如果发送变化,则触发对应的监听方法。
       boolean listChanged = false;
       if (!allServerList.equals(allServers)) {
           listChanged = true;
           if (changeListeners != null && changeListeners.size() > 0) {
              List<Server> oldList = ImmutableList.copyOf(allServerList);
              List<Server> newList = ImmutableList.copyOf(allServers);                   
              for (ServerListChangeListener l: changeListeners) {
                  try {
                      l.serverListChanged(oldList, newList);
                  } catch (Exception e) {
                      logger.error("LoadBalancer [{}]: Error invoking server list change listener", name, e);
                  }
              }
           }
       }
       if (isEnablePrimingConnections()) {
           for (Server server : allServers) {
               if (!allServerList.contains(server)) {
                   server.setReadyToServe(false);
                   newServers.add((Server) server);
               }
           }
           if (primeConnections != null) {
               primeConnections.primeConnectionsAsync(newServers, this);
           }
       }
       // 忽略之前的设置,重新初始化upServerList 变量
       allServerList = allServers;
       if (canSkipPing()) {
           for (Server s : allServerList) {
               s.setAlive(true);
           }
           upServerList = allServerList;
       } else if (listChanged) {//如果发送了变化,则调用forceQuickPing()进行验证服务实例清单
           forceQuickPing();
       }
   } finally {
       writeLock.unlock();
   }
}
public void forceQuickPing() {
    if (canSkipPing()) {
        return;
    }
    logger.debug("LoadBalancer [{}]:  forceQuickPing invoking", name);
    
    try {
    	new Pinger(pingStrategy).runPinger();
    } catch (Exception e) {
        logger.error("LoadBalancer [{}]: Error running forceQuickPing()", name, e);
    }
}

内部类Pinger中最终通过pingerStrategy.pingServers()方法实现了服务实例可用性验证,通过验证结果,重新设置upServerList列表。

class Pinger {

    private final IPingStrategy pingerStrategy;

    public Pinger(IPingStrategy pingerStrategy) {
        this.pingerStrategy = pingerStrategy;
    }

    public void runPinger() throws Exception {
        if (!pingInProgress.compareAndSet(false, true)) { 
            return; // Ping in progress - nothing to do
        }
        
        // we are "in" - we get to Ping

        Server[] allServers = null;
        boolean[] results = null;

        Lock allLock = null;
        Lock upLock = null;

        try {
            /*
             * The readLock should be free unless an addServer operation is
             * going on...
             */
            allLock = allServerLock.readLock();
            allLock.lock();
            allServers = allServerList.toArray(new Server[allServerList.size()]);
            allLock.unlock();

            int numCandidates = allServers.length;
            //内部类SerialPingStrategy
            results = pingerStrategy.pingServers(ping, allServers);

            final List<Server> newUpList = new ArrayList<Server>();
            final List<Server> changedServers = new ArrayList<Server>();

            for (int i = 0; i < numCandidates; i++) {
                boolean isAlive = results[i];
                Server svr = allServers[i];
                boolean oldIsAlive = svr.isAlive();

                svr.setAlive(isAlive);

                if (oldIsAlive != isAlive) {
                    changedServers.add(svr);
                    logger.debug("LoadBalancer [{}]:  Server [{}] status changed to {}", 
                		name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
                }

                if (isAlive) {
                    newUpList.add(svr);
                }
            }
            upLock = upServerLock.writeLock();
            upLock.lock();
            upServerList = newUpList;
            upLock.unlock();

            notifyServerStatusChangeListener(changedServers);
        } finally {
            pingInProgress.set(false);
        }
    }
}

内部类SerialPingStrategy :

private static class SerialPingStrategy implements IPingStrategy {

    @Override
    public boolean[] pingServers(IPing ping, Server[] servers) {
        int numCandidates = servers.length;
        boolean[] results = new boolean[numCandidates];

        logger.debug("LoadBalancer:  PingTask executing [{}] servers configured", numCandidates);

        for (int i = 0; i < numCandidates; i++) {
            results[i] = false; /* Default answer is DEAD. */
            try {

                if (ping != null) {
                    results[i] = ping.isAlive(servers[i]);
                }
            } catch (Exception e) {
                logger.error("Exception while pinging Server: '{}'", servers[i], e);
            }
        }
        return results;
    }
}
服务实例下线
public void markServerDown(Server server) {
    if (server == null || !server.isAlive()) {
        return;
    }

    logger.error("LoadBalancer [{}]:  markServerDown called on [{}]", name, server.getId());
    server.setAlive(false);
    // forceQuickPing();

    notifyServerStatusChangeListener(singleton(server));
}

触发下线通知。

private void notifyServerStatusChangeListener(final Collection<Server> changedServers) {
    if (changedServers != null && !changedServers.isEmpty() && !serverStatusListeners.isEmpty()) {
        for (ServerStatusChangeListener listener : serverStatusListeners) {
            try {
                listener.serverStatusChanged(changedServers);
            } catch (Exception e) {
                logger.error("LoadBalancer [{}]: Error invoking server status change listener", name, e);
            }
        }
    }
}

服务实例清单 对象

在这里插入图片描述

public interface ServerList<T extends Server> {
	//获取初始化服务实例清单
    public List<T> getInitialListOfServers();
    //获取更新后的服务实例清单
    public List<T> getUpdatedListOfServers();   

}
默认配置
//EurekaRibbonClientConfiguration.java

@Configuration(proxyBeanMethods = false)
public class EurekaRibbonClientConfiguration {
	@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 作为服务实例清单
		DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
				config, eurekaClientProvider);
		DomainExtractingServerList serverList = new DomainExtractingServerList(
				discoveryServerList, config, this.approximateZoneFromHostname);
		return serverList;
	}
}
获取服务实例清单

DiscoveryEnabledNIWSServerList 类的核心方法,通过EurekaClient 对象,实现与EurekaServer交互,进而来获取服务实例清单。

@Override
public List<DiscoveryEnabledServer> getInitialListOfServers(){
    return obtainServersViaDiscovery();
}

@Override
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(",")) {//遍历应用集合
           // 使用eurekaClient 获取对应的InstanceInfo集合
           List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
           //遍历InstanceInfo实例集合
           for (InstanceInfo ii : listOfInstanceInfo) {
           		//获取up状态的服务实例
               if (ii.getStatus().equals(InstanceStatus.UP)) {
					//是否启用重写接口,默认不启用
                   if(shouldUseOverridePort){
                       if(logger.isDebugEnabled()){
                           logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
                       }

                       //使用重写接口设置服务实例的接口
                       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 对象,并添加到serverList集合
                   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;
}

//创建DiscoveryEnabledServer 对象
protected DiscoveryEnabledServer createServer(final InstanceInfo instanceInfo, boolean useSecurePort, boolean useIpAddr) {
    DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, useSecurePort, useIpAddr);

    // Get availabilty zone for this instance.
    EurekaClientConfig clientConfig = eurekaClientProvider.get().getEurekaClientConfig();
    String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
    String instanceZone = InstanceInfo.getZone(availZones, instanceInfo);
    server.setZone(instanceZone);

    return server;
}

服务实例清单 更新器-ServerListUpdater

在这里插入图片描述
任何实例都可能存在开发者主动的上下线行为,或者一些网络故障以及自身硬件的意外导致实例处于不同的状态,所以服务实例清单也是处于一个不断变化的过程。为了维护这个清单,Netflix提供了ServerListUpdater接口,通过它可以及时更新服务实例清单。而有两个ServerListUpdater接口的实现类,分别是EurekaNotificationServerListUpdater和PollingServerListUpdater。

public interface ServerListUpdater {

    public interface UpdateAction {
        void doUpdate();
    }

    void start(UpdateAction updateAction);

    void stop();

    String getLastUpdate();

    long getDurationSinceLastUpdateMs();
    
    int getNumberMissedCycles();

    int getCoreThreads();
}
默认配置
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
		RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
	@Bean
	@ConditionalOnMissingBean
	public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
		return new PollingServerListUpdater(config);
	}
}
start()方法
@Override
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,//延时执行时间,默认1s
                refreshIntervalMs,//刷新时间间隔,默认30s
                TimeUnit.MILLISECONDS//单位毫秒
        );
    } else {
        logger.info("Already active, no-op");
    }
}

服务实例心跳检测

通过一个定时任务来ping服务器,这个任务是每隔pingIntervalSeconds秒执行一次,这样Eureka的客户端就可以得到最新服务的状态了。而这里的定时任务的执行内容是通过PingTask类来实现的

//BaseLoadBalancer.java


void setupPingTask() {
   if (canSkipPing()) {
       return;
   }
   if (lbTimer != null) {
       lbTimer.cancel();
   }
   lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
           true);
   lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
   //初始化时执行一次
   forceQuickPing();
}

而PingTask 中又通过内部类Pinger的runPinger()方法实现。

class PingTask extends TimerTask {
      public void run() {
          try {
          	new Pinger(pingStrategy).runPinger();
          } catch (Exception e) {
              logger.error("LoadBalancer [{}]: Error pinging", name, e);
          }
      }
  }
public void forceQuickPing() {
   if (canSkipPing()) {
       return;
   }
   logger.debug("LoadBalancer [{}]:  forceQuickPing invoking", name);
   
   try {
   	new Pinger(pingStrategy).runPinger();
   } catch (Exception e) {
       logger.error("LoadBalancer [{}]: Error running forceQuickPing()", name, e);
   }
}

IPing接口

具体执行心跳监测服务实例的是IPing接口。
在这里插入图片描述

public interface IPing {
    public boolean isAlive(Server server);
}

在Spring Boot工程中,如果没有配置Eureka服务发现的客户端,则使用DummyPing,它将恒定返回true。如果配置了Eureka服务发现的客户端,则使用NIWSDiscoveryPing,它将通过和Eureka服务治理中心通信的机制来判定。其他的实现,NoOpPing是恒定返回true;PingConstant是设置一个布尔值(boolean),让isAlive方法恒定返回这个布尔值;PingUrl是通过配置具体的url请求来断定服务是否可用。

NIWSDiscoveryPing
public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {
	        
		BaseLoadBalancer lb = null; 
		
		public NIWSDiscoveryPing() {
		}
		
		public BaseLoadBalancer getLb() {
			return lb;
		}

		public void setLb(BaseLoadBalancer lb) {
			this.lb = lb;
		}

		public boolean isAlive(Server server) {
		    boolean isAlive = true;
		    //如果断定服务实例是Eureka服务发现的实例,就采用服务实例的状态判断是否可用。
		    if (server!=null && server instanceof DiscoveryEnabledServer){
	            DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;	            
	            InstanceInfo instanceInfo = dServer.getInstanceInfo();
	            if (instanceInfo!=null){	                
	                InstanceStatus status = instanceInfo.getStatus();
	                if (status!=null){
	                    isAlive = status.equals(InstanceStatus.UP);
	                }
	            }
	        }
		    return isAlive;
		}

        @Override
        public void initWithNiwsConfig(
                IClientConfig clientConfig) {
        }
		
}

全局配置 OR 单独配置

全局配置是指在服务消费者配置的内容对所有服务提供者有效。单独配置是指服务消费者可以配置对某个服务提供者有效,而对其他服务提供者无效。

局部定义
  • <clientName>.ribbon.NFLoadBalancerClassName:负载均衡类,需实现。ILoadBalancer接口。
  • <clientName>.ribbon.NFLoadBalancerRuleClassName:负载均衡策略,需实现IRule接口。
  • <clientName>.ribbon.NFLoadBalancerPingClassName:心跳监测类,需实现IPing接口。
  • <clientName>.ribbon.NIWSServerListClassName:服务实例清单类,需实现ServerList接口。
  • <clientName>.ribbon.NIWSServerListFilterClassName:服务实例清单过滤类,需实现ServerListFilter接口。

Spring Cloud还提供了@RibbonClient和@RibbonClients。针对单个微服务配置类使用@RibbonClient,针对多个微服务配置使用@RibbonClients。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Ribbon组合框是一种常见的图形用户界面控件,常用于显示和选择多个选项中的一个。它通常与Ribbon界面风格相结合,使用户能够在菜单和工具栏之外获得更多的操作选项。 Ribbon组合框具有以下几个主要特点。首先,它具有下拉列表的功能,用户可以通过点击或下拉箭头来打开下拉菜单,从中选择一个选项。其次,它支持文本和图标显示,可以根据需要显示选项的文本描述或使用图标来进行直观展示。再次,它可以自动完成用户的输入,即根据用户键入的信息来筛选和匹配可选项,提供更加智能的交互体验。 Ribbon组合框的应用场景非常广泛。例如,在表单设计中,可以使用Ribbon组合框来选择性别、部门、职位等信息;在电商网站中,可以使用Ribbon组合框来选择商品的分类、品牌或价格范围;在音乐播放器中,可以使用Ribbon组合框来选择歌曲的艺术家、专辑或流派。总之,无论是选择固定选项,还是根据输入内容进行筛选,Ribbon组合框都是一种非常便捷和直观的控件。 在使用Ribbon组合框时,需要注意以下几点。首先,要保持选项的清晰和有序,避免选项数量过多或杂乱无章;其次,要根据界面整体布局和风格设计,选择合适的颜色、尺寸和样式;再次,要为组合框提供适当的提示和帮助信息,如下拉菜单的简洁说明或自动完成的操作提示。 总之,Ribbon组合框是一种强大的用户界面控件,提供了丰富的选项选择和交互功能,在各种应用场景下都能发挥其作用。通过合理使用和设计,可以营造出更加方便和高效的用户体验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

姠惢荇者

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

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

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

打赏作者

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

抵扣说明:

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

余额充值