Spring Cloud Ribbon源码分析---负载均衡实现
上一篇结合 Eureka 和 Ribbon 搭建了服务注册中心,利用Ribbon实现了可配置负载均衡的服务调用。这一篇我们来分析Ribbon实现负载均衡的过程。
从 @LoadBalanced入手
还记得前面配置 RestTemplate:
@Bean
@LoadBalanced
RestTemplate restTemplate() {
    return new RestTemplate();
}
在消费端使用Spring 提供的 RestTemplate 来发出请求,而Ribbon 在 RestTemplate 上添加了@LoadBalanced 注解就可以实现负载均衡,这是怎样做到的呢?
首先看 LoadBalanced 注解:
/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
该注解的作用是为了使用 LoadBalancerClient。
LoadBalancerClient 是一个接口,包含两个方法:
public interface LoadBalancerClient extends ServiceInstanceChooser {
	//针对来自LoadBalancer的特定的服务使用ServiceInstance来执行请求
    //根据 传入的 服务名 和 负载均衡请求,从 服务实例中选出一个实例发送请求
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
	//针对来自LoadBalancer的特定的服务使用ServiceInstance来执行请求
    //指定了服务实例,直接对该实例发送请求
	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
    //使用真实的主机和端口创建适当的URI,以供系统使用。某些系统使用带有逻辑服务名称的URI作为主机
    //即将我们在  RestTemplate 中写的 服务名换为真实的 ip + 端口 的形式
	URI reconstructURI(ServiceInstance instance, URI original);
}
LoadBalancerClient 接口只有一个实现类:RibbonLoadBalancerClient,点击查看这个类的调用,会看到在:RibbonAutoConfiguration 类中实例化bean:
@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
    @Bean
	@ConditionalOnMissingBean(LoadBalancerClient.class)
	public LoadBalancerClient loadBalancerClient() {
		return new RibbonLoadBalancerClient(springClientFactory());
	}
	@Configuration
	@ConditionalOnClass(HttpRequest.class)
	@ConditionalOnRibbonRestClient
	protected static class RibbonClientHttpRequestFactoryConfiguration {
		@Autowired
		private SpringClientFactory springClientFactory;
		@Bean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
			return restTemplate -> restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
		}
		@Bean
		public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
			return new RibbonClientHttpRequestFactory(this.springClientFactory);
		}
	}
    ......
    ......
    ......
}
在这里还实例化了RestTemplate。在类头部的配置信息中 有个注解:@AutoConfigureBefore,即在该类初始化之前需要初始化的类有两个:LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class。AsyncLoadBalancerAutoConfiguration LoadBalancerAutoConfiguration的异步实现版本,所以看一下 LoadBalancerAutoConfiguration 的实现即可:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();
	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
	}
	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
	}
	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
	}
	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryAutoConfiguration {
		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
			return new LoadBalancedRetryFactory() {};
		}
	}
	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {
		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
	}
}
在LoadBalancerAutoConfiguration类中,创建了一个LoadBalancerInterceptor拦截器,还维护了一个被@LoadBalanced修饰的RestTemplate列表,在初始化的时候,会为每个restTemplate实例添加LoadBalancerInterceptor拦截器。另外上面还能看到 Ribbon在请求失败使用的重试框架是Spring的Retry,重试的时候也会走配置好的负载均衡拦截器。继续跟踪 LoadBalancerInterceptor:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
	private LoadBalancerClient loadBalancer;
	private LoadBalancerRequestFactory requestFactory;
	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}
	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}
	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}
}
上面可以看到主要的逻辑在intercept方法中,从originalUri.getHost()中得到的就是我们在 restTemplate中填写的服务名,然后会调用LoadBalancerClient 的execute方法,即上面 LoadBalancerClient 的实现类: RibbonLoadBalancerClient,上面我们提了一嘴然后就跑到了LoadBalancerClient 的实例化逻辑里面去了,最终跟踪到现在,才发原来是在实例化LoadBalancerClient 的过程中塞入了一个拦截器。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
	private SpringClientFactory clientFactory;
	public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
		this.clientFactory = clientFactory;
	}
	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
				serviceId), serverIntrospector(serviceId).getMetadata(server));
		return execute(serviceId, ribbonServer, request);
	}
    protected ILoadBalancer getLoadBalancer(String serviceId) {
		return this.clientFactory.getLoadBalancer(serviceId);
	}
    ......
}
在execute方法中第一行返回了ILoadBalancer, ILoadBalancer 中定义了负载均衡器的操作的接口:
public interface ILoadBalancer {
	/**
	 *
	 * 可以用于服务器的初始列表。
	 * 同时也会将指定的服务器添加进服务器列表
	 * 同一逻辑服务器(host:port)本质上可以多次添加
	 *
	 * @param newServers new servers to add
	 */
	public void addServers(List<Server> newServers);
	/**
	 * 根据指定的key选择服务器
	 *
	 * @return server chosen
	 */
	public Server chooseServer(Object key);
	/**
	 * 由负载均衡器的客户端调用以通知服务器已关闭
	 * 否则,LB将认为它仍然有效直到下一个Ping周期
	 *(假设LB Impl执行ping操作)
	 *
	 * @param server Server to mark as down
	 */
	public void markServerDown(Server server);
	/**
	 *
	 * 已过期
	 * 获取服务器列表
	 *
	 * @param availableOnly if true, only live and available servers should be returned
	 */
	@Deprecated
	public List<Server> getServerList(boolean availableOnly);
	/**
	 * 获取可用的服务器列表
	 * @return Only the servers that are up and reachable.
     */
    public List<Server> getReachableServers();
    /**
     * 获取所有的服务器列表, 包含可用的和不可用的
     * @return All known servers, both reachable and unreachable.
     */
	public List<Server> getAllServers();
}
因为ILoadBalancer的实现类还是挺多的,我们先看一下完整的类图:

从上面的类图可以看到,ILoadBalancer 有三个实现类,他们之间是继承关系,最终的实现类是 ZoneAwareLoadBalancer。因为很多东西都是定义在BaseLoadBalancer中的,为了能看懂,我们一起先从 BaseLoadBalancer 中的 变量开始看:
public class BaseLoadBalancer extends AbstractLoadBalancer implements
        PrimeConnections.PrimeConnectionListener, IClientConfigAware {
    private final static IRule DEFAULT_RULE = new RoundRobinRule();
    private final static SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy();
    protected IRule rule = DEFAULT_RULE;
    protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY;
    protected IPing ping = null;
    @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>());
    private IClientConfig config; 
}
从上面的变量信息我们可以看出一些东西:
- 默认的 负载均衡策略是随机负载均衡;
 - 默认的Ping策略为串行化Ping;
 - 使用了一个list来保存所有的服务列表,一个list来保存当前所有的存活状态的服务列表;
 - 定义了客户端配置,用于初始化客户端以及负载均衡配置 。
 
接着看 BaseLoadBalancer 默认的无参构造函数做了什么:
public BaseLoadBalancer() {
    this.name = DEFAULT_NAME;
    this.ping = null;
    setRule(DEFAULT_RULE);
    setupPingTask();
    lbStats = new LoadBalancerStats(DEFAULT_NAME);
}
- 将负载均衡的默认规则设置为随机;
 - 设置Ping 定时任务;
 - 负载均衡的一些状态设置,为下次执行负载均衡提供参考。
 
setupPingTask() 方法里面还是有一些东西的,我们一起看一下:
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();
}
首先是设置了一个定时任务,任务的执行间隔是每 10S 执行一次。
跟进到PingTask类中:
//    protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY;
class PingTask extends TimerTask {
    public void run() {
        try {
            new Pinger(pingStrategy).runPinger();
        } catch (Exception e) {
            logger.error("LoadBalancer [{}]: Error pinging", name, e);
        }
    }
}
//默认Ping 的方式
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;
    }
}
设置Ping的策略为:DEFAULT_PING_STRATEGY,即上面提到的串行化 Ping的方式。
在 runPinger() 方法中会拿到当前 allServerList 中 所有的节点去Ping,在allServerList 中标记其存活状态,用当前Ping的存活状态 和 上次的 准或状态比对,如果 状态不同 ,发出通知 这些节点状态有改变;如果状态为存活,那么将 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;
                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"));
                    }
					//如果是存活状态,添加进list
                    if (isAlive) {
                        newUpList.add(svr);
                    }
                }
                upLock = upServerLock.writeLock();
                upLock.lock();
                upServerList = newUpList;
                upLock.unlock();
				//发送状态变化通知消息
                notifyServerStatusChangeListener(changedServers);
            } finally {
                pingInProgress.set(false);
            }
        }
    }
看完初始化bean过程之后,我们接着看 ILoadBalancer 接口中的几个方法在这里的实现:
addServers 方法:
@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);
        }
    }
}
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) {
                continue;
            }
            if (server instanceof String) {
                server = new Server((String) server);
            }
			//这里保存当前所有的server信息
            if (server instanceof Server) {
                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;
        //如果server信息有变化,给各个监听事件发送监听信息
        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);
            }
        }
        // This will reset readyToServe flag to true on all servers
        // regardless whether
        // previous priming connections are success or not
        //这里直接将以前的 serverList 用现在的覆盖掉, 是没有做唯一性校验的 注意*************
        allServerList = allServers;
        //下面接着去执行 Ping
        if (canSkipPing()) {
            //如果当前没有默认的Ping策略,默认将所有的节点存活状态设置为可用
            for (Server s : allServerList) {
                s.setAlive(true);
            }
            upServerList = allServerList;
        } else if (listChanged) {
            //执行默认的Ping 策略
            forceQuickPing();
        }
    } finally {
        writeLock.unlock();
    }
}
直接将 newServers 添加进 allServerList ,这里是没有做唯一性过滤的。
chooseServer方法:
public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            return rule.choose(key);
        } catch (Exception e) {
            logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
            return null;
        }
    }
}
这里的代码很简单,最主要的代码在:rule.choose(key)这一句,rule 为 默认的 RoundRobinRule,点进去 choose()方法,你就会进入到IRule接口,这里就是负载均衡规则的路由类, 后面我专门列一个章节讲所有的负载均衡算法,这里就先跳过。
markServerDown方法:
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));
}
getReachableServers方法:
@Override
public List<Server> getReachableServers() {
    return Collections.unmodifiableList(upServerList);
}
直接返回可用的服务list。
getAllServers方法:
@Override
public List<Server> getAllServers() {
    return Collections.unmodifiableList(allServerList);
}
直接返回所有的服务list。
分析完 BaseLoadBalancer ,DynamicServerListLoadBalancer 和 ZoneAwareLoadBalancer 基本大同小异:
- DynamicServerListLoadBalancer :使用动态源的服务器, 即服务器列表可能是在运行时更改。 通过一些Filter函数来动态的过滤掉指定的服务器列表;
 - ZoneAwareLoadBalancer :这个负载均衡器适用于异地多机房的情况,在选择服务器的时候可以避免整个区域。LoadBalancer将计算并检查所有可用区域的区域统计信息。如果任何区域的“平均活动请求数”已达到配置的阈值,则该区域将从活动服务器列表中删除。如果多个区域已达到阈值,则将删除每台服务器上最活跃请求的区域。一旦删除了最坏的区域,将在其余区域中选择一个区域,其概率与其实例数成正比。服务器将从具有指定规则的选定区域返回。对于每个请求都将重复上述步骤,也就是说,每个区域相关的负载平衡决策都是在最新统计信息的帮助下实时做的。
 
以上,总结一下Ribbon的负载均衡如何实现:
在 LoadBalancerClient 类中 配置了LoadBalancerInterceptor,拦截器主要调用 ILoadBalancer 来进行 负载均衡逻辑处理。在 ILoadBalancer 中配置了 Ping 策略,负载均衡策略,维护当前可用的服务端列表信息。 RestTemplate 因为被 @LoadBalance 修饰以后,实例信息被添加进 LoadBalancerClient 的缓存中,然后被 RestTemplateCustomizer 包装,里面封装了拦截器信息所以能够进行负载均衡配置。
Spring Cloud Ribbon源码分析---负载均衡实现的更多相关文章
- Spring Cloud Ribbon 源码分析---负载均衡算法
		
上一篇分析了Ribbon如何发送出去一个自带负载均衡效果的HTTP请求,本节就重点分析各个算法都是如何实现. 负载均衡整体是从IRule进去的: public interface IRule{ /* ...
 - SOFA 源码分析 — 负载均衡和一致性 Hash
		
前言 SOFA 内置负载均衡,支持 5 种负载均衡算法,随机(默认算法),本地优先,轮询算法,一致性 hash,按权重负载轮询(不推荐,已被标注废弃). 一起看看他们的实现(重点还是一致性 hash) ...
 - Spring Cloud Eureka源码分析之服务注册的流程与数据存储设计!
		
Spring Cloud是一个生态,它提供了一套标准,这套标准可以通过不同的组件来实现,其中就包含服务注册/发现.熔断.负载均衡等,在spring-cloud-common这个包中,org.sprin ...
 - Spring Cloud Eureka源码分析 --- client 注册流程
		
Eureka Client 是一个Java 客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的.使用轮询负载算法的负载均衡器. 在应用启动后,将会向Eureka Serve ...
 - 【spring cloud】源码分析(一)
		
概述 从服务发现注解 @EnableDiscoveryClient入手,剖析整个服务发现与注册过程 一,spring-cloud-common包 针对服务发现,本jar包定义了 DiscoveryCl ...
 - spring cloud ribbon源码解析(一)
		
我们知道spring cloud中restTemplate可以通过服务名调接口,加入@loadBalanced标签就实现了负载均衡的功能,那么spring cloud内部是如何实现的呢? 通过@loa ...
 - spring cloud ribbon源码解析(二)
		
在上一篇文章中主要梳理了ribbon的执行过程,这篇主要讲讲ribbon的负载均衡,ribbon的负载均衡是通过ILoadBalancer来实现的,对ILoadBalancer有以下几个类 1.Abs ...
 - Spring Cloud Eureka源码分析---服务注册
		
本篇我们着重分析Eureka服务端的逻辑实现,主要涉及到服务的注册流程分析. 在Eureka的服务治理中,会涉及到下面一些概念: 服务注册:Eureka Client会通过发送REST请求的方式向Eu ...
 - Spring Cloud Eureka源码分析之三级缓存的设计原理及源码分析
		
Eureka Server 为了提供响应效率,提供了两层的缓存结构,将 Eureka Client 所需要的注册信息,直接存储在缓存结构中,实现原理如下图所示. 第一层缓存:readOnlyCache ...
 
随机推荐
- js中函数的参数为函数的情况即回调函数
			
js中函数的参数可以是数组对象也可以是函数,当参数为函数时我们叫做回调函数 //定义回调函数function B() { console.log("函数B")setTimeout( ...
 - python3常用的内置函数
			
数学相关 abs(a) : 求取绝对值.abs(-1) max(list) : 求取list最大值.max([1,2,3]) min(list) : 求取list最小值.min([1,2,3]) su ...
 - 【Idea】idea中spring框架配置文件,无法自动提示spring配置
 - Python之数据分析
			
什么是数据分析? 运用不同行业中,专门从事行业数据搜集.整理.分析,并依据数据做出行业研究.评估和预测的专业人员. 熟悉行业知识.公司业务及流程,最好有自己独到的见解,若脱离行业认知和公司业务背景,分 ...
 - 自动化渗透测试工具(Cobalt Strike)3.1 最新破解版
			
自动化渗透测试工具(Cobalt Strike)3.1 最新破解版[附使用教程] Cobalt Strike是一款专业的自动化渗透测试工具,它是图形化.可视化的,图形界面非常友好,一键傻瓜化使用MSF ...
 - CentOS7.5搭建NFS(Network File System)
			
NFS(Network File System)即网络文件系统,是由Sun公司开发的一种通过网络方式共享文件系统的通用共享解决方案.可以将远程Linux系统上的文件共享资源挂载到本地主机(Linux客 ...
 - discuz x3.3门户出现关键词和描述显示“首页”的解决方法
			
Discuz社区在后台设置好门户标题.关键字.描述,更新缓存,发现用户登录状态下,门户首页的关键字和描述正常显示:但在游客状态下不显示,在某工具中查看到的情况是只显示首页,这对SEO是致命打击. 找到 ...
 - Spring Cloud 之 Consul 知识点:服务注册与发现(类似工具:Eureka、ZooKeeper、Etcd)
			
资料 网址 springcloud(十三):注册中心 Consul 使用详解 http://ityouknow.com/springcloud/2018/07/20/spring-cloud-cons ...
 - shell 编程生成日期文件;Server虚拟机上进行Web服务器配置
			
shell 编程生成日期文件 1. 请编写一个脚本,命名为sh01.sh,其功能是: 键盘输入文件名(要求使用名字全拼作为文件名). 自动创建3个文件. 1个为系统当天日期(CCYYMMDD). 1个 ...
 - async await 总结
			
1.async await成对出现,await再async定义的函数内 2.async定义的是函数 3.async 返回一个Promise 4.async 函数中 return 的结果将作为回调的参数 ...