• 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>());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    增加服务实例

    在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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    前面的三个重载方法,最终都是通过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();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    内部类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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    内部类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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    服务实例下线
    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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    触发下线通知。

    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);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    服务实例清单 对象

    在这里插入图片描述

    public interface ServerList<T extends Server> {
    	//获取初始化服务实例清单
        public List<T> getInitialListOfServers();
        //获取更新后的服务实例清单
        public List<T> getUpdatedListOfServers();   
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    默认配置
    //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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    获取服务实例清单

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    服务实例清单 更新器-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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    默认配置
    @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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    服务实例心跳检测

    通过一个定时任务来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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    而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);
              }
          }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    IPing接口

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

    public interface IPing {
        public boolean isAlive(Server server);
    }
    
    • 1
    • 2
    • 3

    在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) {
            }
    		
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    全局配置 OR 单独配置

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

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

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

  • 相关阅读:
    论文解读(soft-mask GNN)《Soft-mask: Adaptive Substructure Extractions for Graph Neural Networks》
    笔记(四)传统图机器学习的特征工程-连接
    Netty 系列之编解码器和 handler 的调用机制
    开发一个APP有多难?APP开发流程、开发成本揭秘
    Opencv——颜色模型+通道分离与合并
    在openSUSE-Leap-15.4-DVD-x86_64中使用佳能喷墨打印机ip2780
    MySQL数据库(五)
    AM@导数和微分的应用@弧微分和曲率
    spring boot+mybatis plus 实现动态数据源
    java计算机毕业设计ssm+vue企业后勤订餐管理系统
  • 原文地址:https://blog.csdn.net/hou_ge/article/details/112183573