Ribbon - 初始化中提到了,@LoadBalanced
註解的RestTemplate
會注入攔截器LoadBalancerInterceptor
,咱們看看LoadBalancerInterceptor
是怎麼作的。git
這裏主要是經過URL把serviceId取出來,而後調用LoadBalancerClient
的execute
方法。github
@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, this.requestFactory.createRequest(request, body, execution)); }
主要是獲取ILoadBalancer
和Server
,經過Server進行遠程調用。這個server已是有對應的IP和端口了。spring
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { // 獲取ILoadBalancer ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // 經過ILoadBalancer獲取Server Server server = getServer(loadBalancer, hint); 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); }
RibbonLoadBalancerClient#getLoadBalancer會調用SpringClientFactory#getLoadBalancer,SpringClientFactory的注入能夠看上一章。SpringClientFactory#getLoadBalancer調用父類的NamedContextFactory#getInstance方法,NamedContextFactory#getInstance會判斷以前是否建立了serviceId對應的AnnotationConfigApplicationContext,若是沒有則建立。segmentfault
這裏關注是createContext方法,他註冊了RibbonEurekaAutoConfiguration和RibbonClientConfiguration,註冊後就調用refresh方法。這兩個值是怎麼來的,能夠參考上一章SpringClientFactory的建立。這兩個有前後順序,因此會先加載RibbonEurekaAutoConfiguration後加載RibbonEurekaAutoConfiguration,而IPing、ServerList都有@ConditionalOnMissingBean
註解,因此優先實例化RibbonEurekaAutoConfiguration的IPing、ServerList。負載均衡
protected AnnotationConfigApplicationContext getContext(String name) { // 若是沒有,則調用createContext建立 if (!this.contexts.containsKey(name)) { synchronized (this.contexts) { if (!this.contexts.containsKey(name)) { this.contexts.put(name, createContext(name)); } } } //返回 return this.contexts.get(name); } protected AnnotationConfigApplicationContext createContext(String name) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); if (this.configurations.containsKey(name)) { for (Class<?> configuration : this.configurations.get(name) .getConfiguration()) { context.register(configuration); } } // 註冊org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration for (Map.Entry<String, C> entry : this.configurations.entrySet()) { if (entry.getKey().startsWith("default.")) { for (Class<?> configuration : entry.getValue().getConfiguration()) { context.register(configuration); } } } // 註冊org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType); context.getEnvironment().getPropertySources().addFirst(new MapPropertySource( this.propertySourceName, Collections.<String, Object>singletonMap(this.propertyName, name))); if (this.parent != null) { // Uses Environment from parent as well as beans context.setParent(this.parent); // jdk11 issue // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101 context.setClassLoader(this.parent.getClassLoader()); } context.setDisplayName(generateDisplayName(name)); context.refresh(); return context; }
refresh加載比較重要的有如下幾個,代碼就不貼了,這幾個類就是在上面註冊的RibbonEurekaAutoConfiguration和RibbonClientConfiguration裏面。ide
這幾個bean加載的時候,都有相似如下的判斷,這個主要是判斷是否有自定義配置,若是沒有,則取默認,有就取自定義的。函數
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) { return this.propertiesFactory.get(ILoadBalancer.class, config, name); }
下面主要看ZoneAwareLoadBalancer的構造函數,他會調用父類的BaseLoadBalancer#initWithConfig方法和DynamicServerListLoadBalancer#restOfInit方法。this
這個方法主要是開啓定時任務ping,也就是檢查其餘服務是不是存活的。spa
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) { // 其餘略 // 設置ping的時間,默認30秒,也就是說服務掛了最多30秒就發現了 setPingInterval(pingIntervalTime); // 設置最大次數 setMaxTotalPingTime(maxTotalPingTime); // 設置IRule,這裏會把當前的ILoadBalancer賦值給IRule setRule(rule); // 開啓定時任務ping setPing(ping); // 其餘略 }
定時任務的調用過程簡略爲:PingTask#run-->BaseLoadBalance.Pinger#runPinger-->BaseLoadBalance.SerialPingStrategy#pingServers-->NIWSDiscoveryPing#isAlive,NIWSDiscoveryPing#isAlive是判斷。.net
public void runPinger() throws Exception { // 其餘略 // 經過BaseLoadBalance.SerialPingStrategy#pingServers調用NIWSDiscoveryPing#isAlive, // 主要是判斷服務的狀態是否是UP,若是是UP,就是存活。 results = pingerStrategy.pingServers(ping, allServers); final List<Server> newUpList = new ArrayList<Server>(); final List<Server> changedServers = new ArrayList<Server>(); // 後面代碼主要是把存活的存入到newUpList再到upServerList,把變化的存入changedServers,並調用監聽通知 // 其餘略 }
這個方法主要有兩個功能,一個是調用DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature開啓定時任務獲取Eureka的註冊表,一個是調用DynamicServerListLoadBalancer#updateAllServerList方法獲取Eureka的註冊表。因此主要看看DynamicServerListLoadBalancer#updateListOfServers。
public void updateListOfServers() { List<T> servers = new ArrayList<T>(); if (serverListImpl != null) { // 獲取Eureka的註冊表 servers = serverListImpl.getUpdatedListOfServers(); if (filter != null) { // 過濾zone爲defaultZone servers = filter.getFilteredListOfServers(servers); LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); } } // 賦值allServerList updateAllServerList(servers); }
咱們回到RibbonLoadBalancerClient#execute來,getLoadBalancer方法已經把該加載的加載了,獲取到ILoadBalancer後,咱們就已經獲取到被調用的服務列表了。如今就要獲取某一個服務來作遠程調用了,因爲注入的是ZoneAvoidanceRule,因此默認的就是輪詢來獲取Server。獲取到Server後,就能夠進行遠程調用了。
private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextIndex.get(); int next = (current + 1) % modulo; if (nextIndex.compareAndSet(current, next) && current < modulo) return current; } }
ribbon在調用以前,會獲取到Eureka的註冊信息,並開啓定時任務去更新Eureka的註冊信息,以及檢測是否存活,默認都是30秒。調用的時候,經過serviceId,獲取服務列表(此時已轉爲IP+端口),再經過負載均衡策略,獲取某個服務進行遠程調用。