【微服務】Ribbon工作原理源碼解析

Ribbon: 主要爲微服務名解析並提供負載均衡的功能,可理解爲 DNS + Nginx的組合體

1. Ribbon 基本概念

  • RestTemplate: 提供完整Http功能訪問API,提供interceptors嵌入點,Ribbon通過loadBalancerInterceptor實現服務名轉真實服務地址,MetricsClientHttpRequestInterceptor實現接口指標監控
  • loadBalancerInterceptor:負載均衡攔截器,主要爲RestTemplate與Ribbon結合提供切入點,RestTemplate藉助loadBalancerInterceptor引入Ribbon組件,提供對服務名的真實解析
  • RibbonLoadBalancerClient:Ribbon負載均衡器客戶端,主要用以協調原始請求在Ribbon框架處理流程,類似Mybatis Executor角色
  • IClientConfig: 客戶端配置,可用於負載均衡器、Http客戶端等配置,可理解爲配置緩存
  • IRule: 負載均衡規則
  • ILoadBalancer: 負載均衡器根據IRule規則選擇最合適的服務請求處理服務器地址,內部維護服務地址列表(PollingServerListUpdater 定時任務維護30s刷新1次)和上線服務地址列表(PingTask: 定時任務維護(Timer調度) 10s)

IRule: 規則均衡規則

  1. BestAvailableRule: 最大可用規則,取 ActiveRequestsCount 最小的服務器,即併發請求壓力最下的服務器認爲是最合適的服務器
  2. AvailabilityFilteringRule: 可用性過濾規則,過濾條件:1. 連續不斷的連接或讀取失敗 2. 活躍鏈接數超過閾值,默認:Integer.MAX_VALUE
  3. WeightedResponseTimeRule: 權重響應時間規則,即響應時間越長權重越低,後續被選中的概率越低
  4. RetryRule:重試規則,如若第1次獲取不成功,則一致重試,到截至時間時不在重試,跳出重試機制有定時任務InterruptTask設置線程中斷位置判斷,默認重試時間500毫秒,若沒設置subRule,默認輪詢RoundRobinRule
  5. RoundRobinRule:輪詢,AtomicInteger保證線程併發安全性存儲下1次目標服務器索引,計算規則: ( 當前索引 + 1 ) % (服務地址列表數量)
  6. RandomRule: 服務地址列表隨機獲取1個
  7. ZoneAvoidanceRule(默認規則): 綜合區域和可用性判斷,(ZoneAvoidancePredicate:過濾掉監控指標達到閾值的區域,AvailabilityPredicate:過濾服務斷路或服務器連接太多的服務地址)

ILoadBalancer: 負載均衡器
核心接口:

  • addServers: 添加服務器列表,主要爲負載均衡進行合適服務節點選取時提供原料
  • chooseServer: 跪在均衡器選取合適請求服務目標節點
  • markServerDown:標識目標服務器下線(手動調度),默認通過PingTask定時調度
  • getServerList: 獲取服務地址列表
  • getReachableServers: 獲取可用服務地址列表(BaseLoadBalancer.upServerList 緩存維護)
  • getAllServers:獲取所有服務地址列表(BaseLoadBalancer.allServerList 緩存維護)

實現:

  1. NoOpLoadBalancer: 空實現
  2. BaseLoadBalancer: 基礎實現
  3. DynamicServerListLoadBalancer:重寫initWithNiwsConfig方法,藉助PollingServerListUpdater實現服務列表定時(30s)動態刷新
  4. ZoneAwareLoadBalancer: 區域感知負載均衡器,根據區域活躍請求數來判斷,避免區域請求過於集中(ZoneAvoidanceRule規則判斷)

2. Ribbon 默認裝配組件

核心配置類:RibbonAutoConfiguration、RibbonClientConfiguration

RibbonAutoConfiguration: Ribbon自動配置

  • configurations:RibbonClientSpecification ribbon客戶端配置,主要由@RibbonClient、@RibbonClients
  • ribbonEagerLoadProperties: Ribbon服務餓加載配置:默認Ribbon服務列表啓動的時候不會加載,通過ribbon.eager-load-enabled和ribbon.eager-load-clients配置需提前加載的服務地址信息,便於應用啓動第一次訪問服務可無延遲啓動
  • SpringClientFactory(重點):Ribbon Client、Loadbalancer、IClientConfig創建工廠,藉助父類NamedContextFactory 實現1個服務1個IOC容器,服務之間通過容器隔離管理,維護在NamedContextFactory.contexts 緩存
  • LoadBalancerClient:Ribbon負載均衡器客戶端,負責整個負載均衡流程調度,例如:轉義服務名爲真實服務節點地址,默認實現RibbonLoadBalancerClient
  • LoadBalancedRetryPolicyFactory: 重試策略,依賴於spring-retry 組件
  • RibbonApplicationContextInitializer:Ribbon應用上下文初始化器,監聽ApplicationReadyEvent事件,可以提前加載ribbonEagerLoadProperties配置的服務地址列表
  • ConditionalOnRibbonRestClient: Ribbon客戶端條件選擇器,通過ribbon.http.client.enabled或ribbon.restclient.enabled開啓,默認不開啓
  • PropertiesFactory: 屬性工廠維護的Ribbon配置,用於服務自定義Ribbon配置(ribbon客戶端名稱 + ribbon + classNameProperty(類名),例如:NFLoadBalancerPingClassName 定時ping服務列表決定是否上下線)

PropertiesFactory用例:

  • eureka-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule: eureka-provider 服務負載均衡規則採用RandomRule (EurekaRibbonClientConfiguration實現)

RibbonClientConfiguration: Ribbon 客戶端設置,前面是可選,最後是默認HttpClientRibbonConfiguration

  • HttpClientConfiguration:默認引入DefaultApacheHttpClientConnectionManagerFactory、DefaultApacheHttpClientFactory(Http客戶端連接管理器工廠、Http客戶端創建工廠),可選支持OkHttp工具包
  • OkHttpRibbonConfiguration: OKHttp 框架支持,通過ribbon.okhttp.enabled開啓,藉助OKHttp實現底層Http交互
  • RestClientRibbonConfiguration: OverrideRestClient重寫RestTempalte,通過ribbon.http.client.enabled或ribbon.restclient.enabled開啓
  • HttpClientRibbonConfiguration: 默認Ribbon客戶端配置,藉助HttpClientConfiguration引入Factory創建HttpClientConnectionManager、CloseableHttpClient、RibbonLoadBalancingHttpClient(提供負載均衡的HttpClient)、RetryableRibbonLoadBalancingHttpClient(RibbonLoadBalancingHttpClient基礎加重試功能)

  • IClientConfig: Ribbon客戶端配置,默認DefaultClientConfigImpl,可通過IClientConfig.Builder建造器自定義
  • IRule: 負載均衡規則,默認ZoneAvoidanceRule: 區域和可用性規則
  • IPing: ping 服務地址列表,判斷節點是否存活,默認DummyPing 總是存活,可通過 (${ribbon.client.name} + ribbon + className)規則全局設置
  • ServerList: 服務地址API,默認ConfigurationBasedServerList,用於獲取服務關聯的列表,支持Eureka、Zookeeper、Nacos等服務註冊中心
  • ServerListUpdater: 服務地址定時更新,默認PollingServerListUpdater,定時任務30s刷新1次
  • ILoadBalancer: ribbon 負載均衡器,默認ZoneAwareLoadBalancer
  • ZonePreferenceServerListFilter: 區域更偏愛的過濾器,例如:服務列表獲取有Localhost、Debian, 更偏愛本地Localhost環境,類似Spark近距離數據處理提高效率場景,工作流程: ServerList獲取上線列表 --> ZonePreferenceServerListFilter 過濾更偏愛的節點信息
  • RibbonLoadBalancerContext:Ribbon負載均衡器上下文,相當於運行環境,可用與指標監控,例如:更新服務節點(ServerStats)狀態:niws.loadbalancer.default.connectionFailureCountThreshold等指標
  • RetryHandler:重試處理器,例如:Ribbon客戶端請求發生異常,請求是否重試、重試的節點判斷等,可避免故障節點請求路由
  • ServerIntrospector: 服務器內省機制,用於獲取服務節點元數據、判斷端口是否安全 Https或Http請求(ServerIntrospectorProperties.securePorts 配置)

3. Ribbon 整合Eureka

自動配置類:RibbonEurekaAutoConfiguration

@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class): 引入Eureka Ribbon定製化配置 EurekaRibbonClientConfiguration

條件選擇器

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRibbonAndEurekaEnabledCondition.class)
@interface ConditionalOnRibbonAndEurekaEnabled {

}

//解釋: 只要引入EurekaClient 依賴,條件默認成立,OnRibbonAndEurekaEnabledCondition 即是否引入EurekaClient組件
private static class OnRibbonAndEurekaEnabledCondition extends AllNestedConditions {

    public OnRibbonAndEurekaEnabledCondition() {
        super(ConfigurationPhase.REGISTER_BEAN);
    }

    @ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
    @ConditionalOnBean(SpringClientFactory.class)
    @ConditionalOnProperty(value = "ribbon.eureka.enabled", matchIfMissing = true)
    static class Defaults {}
    
    @ConditionalOnBean(EurekaClient.class)
    static class EurekaBeans {}

    @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
    static class OnEurekaClientEnabled {}
}

EurekaRibbonClientConfiguration: Eureka Ribbon的個性化組件定製

  • IPing: Eureka定製服務地址ping策略:NIWSDiscoveryPing根據InstanceStatus狀態判斷,UP,DOWN,STARTING, OUT_OF_SERVICE,UNKNOWN
  • ServerList: 服務地址列表API DomainExtractingServerList裝飾DiscoveryEnabledNIWSServerList,由eurekaClient獲取服務地址列表
  • ServerIntrospector: 服務節點內省器,EurekaServerIntrospector實現

4. RestTemplate 模板定製

4.1 LoadBalancerInterceptor

作用: 藉助RestTemplate的Interceptor機制引入Ribbon功能- 服務名解析、服務節點負載均衡

配置:LoadBalancerAutoConfiguration

@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
        // 負載均衡請求工廠,相當於包裝原始的HttpRequest方便負載均衡器處理
		return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
	}

	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

        // LoadBalancerInterceptor: 自定義負載均衡攔截器
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return new RestTemplateCustomizer() {
				@Override
				public void customize(RestTemplate restTemplate) {
					List<ClientHttpRequestInterceptor> list = new ArrayList<>(
							restTemplate.getInterceptors());
					list.add(loadBalancerInterceptor);
                    // restTemplate 配置攔截器
					restTemplate.setInterceptors(list);
				}
			};
		}
	}

4.2 MetricsHandlerInterceptor

作用:Ribbon Http請求監控

自動配置:MetricsInterceptorConfiguration

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(WebMvcConfigurerAdapter.class)
static class MetricsWebResourceConfiguration extends WebMvcConfigurerAdapter {

    // 指標攔截器    
    @Bean
    MetricsHandlerInterceptor servoMonitoringWebResourceInterceptor() {
        return new MetricsHandlerInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(servoMonitoringWebResourceInterceptor());
    }
}

// 藉助BeanPostProcessor聲明週期追加到restTemplate
private static class MetricsInterceptorPostProcessor
				implements BeanPostProcessor, ApplicationContextAware {
			private ApplicationContext context;
			private MetricsClientHttpRequestInterceptor interceptor;

			@Override
			public Object postProcessBeforeInitialization(Object bean, String beanName) {
				return bean;
			}

			@Override
			public Object postProcessAfterInitialization(Object bean, String beanName) {
				if (bean instanceof RestTemplate) {
					if (this.interceptor == null) {
						this.interceptor = this.context
								.getBean(MetricsClientHttpRequestInterceptor.class);
					}
					RestTemplate restTemplate = (RestTemplate) bean;
					// create a new list as the old one may be unmodifiable (ie Arrays.asList())
					ArrayList<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
					interceptors.add(interceptor);
                    // 追加指標攔截器
					interceptors.addAll(restTemplate.getInterceptors());
					restTemplate.setInterceptors(interceptors);
				}
				return bean;
			}

			@Override
			public void setApplicationContext(ApplicationContext context)
					throws BeansException {
				this.context = context;
			}
		}

5. Ribbon 客戶端服務列表地址涮新機制

調用棧:

DynamicServerListLoadBalancer.enableAndInitLearnNewServersFeature
PollingServerListUpdater.start

源碼:

@Override
public synchronized void start(final UpdateAction updateAction) {
    // CAS: 設置狀態位,開啓定時刷新服務節點列表信息
    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 {
                    // 策略接口: 取決於服務註冊中心類型
                    // Zookeeper: ZookeeperServerList
                    // Eureka: DiscoveryEnabledNIWSServerList
                    // ConfigurationBasedServerList: 手動配置
                    updateAction.doUpdate();
                    lastUpdated = System.currentTimeMillis();
                } catch (Exception e) {
                    logger.warn("Failed one update cycle", e);
                }
            }
        };
    
        // 定時任務調度刷新服務節點信息,默認 30s
        scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                wrapperRunnable,
                initialDelayMs,
                refreshIntervalMs,
                TimeUnit.MILLISECONDS
        );
    } else {
        logger.info("Already active, no-op");
    }
}

6. ILoadBalancer 節點列表信息維護

節點數據緩存:

  • BaseLoadBalancer.allServerList: 所有節點列表
  • BaseLoadBalancer.upServerList: 在線節點列表(可用節點列表)

心跳檢測調度: lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000); 默認10s

upServerList維護(PingTask):

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;

            // 根據Ping策略ping對應服務節點,具體Ping的方式IPing
            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();

            // ServerStatusChangeListener: 服務器狀態監聽器通知
            notifyServerStatusChangeListener(changedServers);
        } finally {
            pingInProgress.set(false);
        }
    }
}

7. RestTemplate 流程分析

案例代碼:restTemplate.getForEntity("http://spring-eureka-service/hello", String.class).getBody();

7.1 RestTemplate doExecute

核心代碼:ClientHttpRequest request = createRequest(url, method);

protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
        // 請求工廠: InterceptingClientHttpRequestFactory,裝飾默認工廠:SimpleClientHttpRequestFactory
        // 工作流程:InterceptingClientHttpRequestFactory構建負載均衡請求成簡單請求最後委託給SimpleClientHttpRequestFactory處理原始Http請求
		// InterceptingHttpAccessor.getRequestFactory:RestTemplate父類重寫 HttpAccessor 接口
        ClientHttpRequest request = getRequestFactory().createRequest(url, method);
		if (logger.isDebugEnabled()) {
			logger.debug("Created " + method.name() + " request for \"" + url + "\"");
		}
		return request;
	}

7.2 RibbonClientHttpRequestFactory 構建請求

接口:org.springframework.http.client.InterceptingClientHttpRequestFactory.createRequest

protected InterceptingClientHttpRequest(ClientHttpRequestFactory requestFactory,
			List<ClientHttpRequestInterceptor> interceptors, URI uri, HttpMethod method) {

    this.requestFactory = requestFactory; // 包裝工廠:SimpleClientHttpRequestFactory
    this.interceptors = interceptors; // 藍機器:LoadBalancerInterceptor、MetricsClientHttpRequestInterceptor
    this.method = method;
    this.uri = uri;
}

7.3 RestTemplate 執行請求

源碼:

org.springframework.web.client.RestTemplate.doExecute
response = request.execute();
org.springframework.http.client.AbstractClientHttpRequest.execute
org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal
org.springframework.http.client.InterceptingClientHttpRequest.executeInternal

InterceptingClientHttpRequest.executeInternal

@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();

    // 請求攔截器處理,類似Servlet FilterChain機制,責任鏈方式處理
    return requestExecution.execute(this, bufferedOutput);
}


private class InterceptingRequestExecution implements ClientHttpRequestExecution {

    private final Iterator<ClientHttpRequestInterceptor> iterator;

    public InterceptingRequestExecution() {
        this.iterator = interceptors.iterator();
    }

    @Override
    public ClientHttpResponse execute(HttpRequest request, final byte[] body) throws IOException {
        
        // RestTemplate配置攔截器,攔截器處理流程,設計很巧妙 nextInterceptor.intercept 將自身傳遞進去,然後回調execute方法,從而實現整個攔截鏈條的完整處理
        // 類似設計:FilterChain.doFilter(request, response); org.apache.catalina.core.ApplicationFilterChain.internalDoFilter
        if (this.iterator.hasNext()) {
            ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
            return nextInterceptor.intercept(request, body, this);
        }
        // 普通處理流程
        else {
            ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
            for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
                List<String> values = entry.getValue();
                for (String value : values) {
                    delegate.getHeaders().add(entry.getKey(), value);
                }
            }
            if (body.length > 0) {
                if (delegate instanceof StreamingHttpOutputMessage) {
                    StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
                    streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
                        @Override
                        public void writeTo(final OutputStream outputStream) throws IOException {
                            StreamUtils.copy(body, outputStream);
                        }
                    });
                }
                else {
                    StreamUtils.copy(body, delegate.getBody());
                }
            }
            return delegate.execute();
        }
    }
}

7.4 LoadBalancerInterceptor 負載均衡器攔截器

源碼:org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor.intercept

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
        final ClientHttpRequestExecution execution) throws IOException {
    final URI originalUri = request.getURI(); 
    String serviceName = originalUri.getHost(); // 獲取主機名 http://spring-eureka-service/hello,即spring-eureka-service 服務名
    Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
    return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}

7.5 LoadBalancerClient 負責任務調度

LoadBalancerClient默認注入:RibbonLoadBalancerClient(RibbonAutoConfiguration.loadBalancerClient)

接口:org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute

@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);
    }

    //服務器信息封裝類,包含服務ID,服務節點信息、是否HTTPS安全機制、節點服務器元數據信息
    RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
            serviceId), serverIntrospector(serviceId).getMetadata(server));

    return execute(serviceId, ribbonServer, request);
}

接着調用:org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute

@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
    Server server = null;
    if(serviceInstance instanceof RibbonServer) {
        server = ((RibbonServer)serviceInstance).getServer();
    }
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }

    //SpringClientFactory 緩存獲取或利用配置構建 RibbonLoadBalancerContext
    RibbonLoadBalancerContext context = this.clientFactory
            .getLoadBalancerContext(serviceId);

    // 構建Ribbon狀態記錄器,通知noteOpenConnection、noteRequestCompletion,用於請求狀態記錄
    RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

    try {
        // 請求處理
        T returnVal = request.apply(serviceInstance);

        // 記錄狀態
        statsRecorder.recordStats(returnVal);
        return returnVal;
    }
    // catch IOException and rethrow so RestTemplate behaves correctly
    catch (IOException ex) {
        statsRecorder.recordStats(ex);
        throw ex;
    }
    catch (Exception ex) {
        statsRecorder.recordStats(ex);
        ReflectionUtils.rethrowRuntimeException(ex);
    }
    return null;
}

7.6 請求底層處理邏輯

LoadBalancerRequest: 內部類

new LoadBalancerRequest<ClientHttpResponse>() {

    @Override
    public ClientHttpResponse apply(final ServiceInstance instance) throws Exception {
        
        // 請求包裝類,包含loadBalancer,用於後面解析服務名爲真實服務器IP
        HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);

        // 請求轉換器:允許應用轉換負載均衡請求,例如:添加自定義請求頭等
        if (transformers != null) {
            for (LoadBalancerRequestTransformer transformer : transformers) {
                serviceRequest = transformer.transformRequest(serviceRequest, instance);
            }
        }
        // 執行請求,回到 InterceptingRequestExecution.execute, 因爲前面攔截器執行完成,所以不在走攔截流程
        return execution.execute(serviceRequest, body);
    }

};
@Override
public ClientHttpResponse execute(HttpRequest request, final byte[] body) throws IOException {
     // 之前攔截器鏈條已經走完,這裏不走這段邏輯
    if (this.iterator.hasNext()) {
        ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
        return nextInterceptor.intercept(request, body, this);
    }
    // 真實請求處理,最終底層由org.springframework.http.client.SimpleBufferingClientHttpRequest.executeInternal 實現,很簡單的Http請求處理
    else {
        // 注意:這裏請求爲ServiceRequestWrapper,包含負載均衡器 request.getURI() 即 ServiceRequestWrapper.getURI (this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());)
        ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
        for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
            List<String> values = entry.getValue();
            for (String value : values) {
                delegate.getHeaders().add(entry.getKey(), value);
            }
        }
        if (body.length > 0) {
            if (delegate instanceof StreamingHttpOutputMessage) {
                StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
                streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
                    @Override
                    public void writeTo(final OutputStream outputStream) throws IOException {
                        StreamUtils.copy(body, outputStream);
                    }
                });
            }
            else {
                StreamUtils.copy(body, delegate.getBody());
            }
        }
        return delegate.execute();
    }
}
}

*** 至此整個Ribbon負載均衡處理流程完成 ***


8.0 組件詳細說明

8.0.1 負載均衡客戶端 LoadBalancerClient

作用:負責負載均衡請求流程調度

接口:

execute(String serviceId, LoadBalancerRequest<T> request) 執行請求
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)  執行請求(重載)
reconstructURI: 解析服務ID爲真實服務地址

服務地址解析:

  1. org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.reconstructURI
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
    Assert.notNull(instance, "instance can not be null");
    String serviceId = instance.getServiceId();
    RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);

    // 構造真實服務器地址信息
    Server server = new Server(instance.getHost(), instance.getPort());

    // 獲取RibbonClient客戶端配置
    IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
    ServerIntrospector serverIntrospector = serverIntrospector(serviceId);

    // 按需更新URI地址:例如:Https
    URI uri = RibbonUtils.updateToHttpsIfNeeded(original, clientConfig, serverIntrospector, server);

    // 翻譯URI爲真實服務器地址
    return context.reconstructURIWithServer(server, uri);
}
  1. com.netflix.loadbalancer.LoadBalancerContext.reconstructURIWithServer
public URI reconstructURIWithServer(Server server, URI original) {
    String host = server.getHost();
    int port = server.getPort();
    String scheme = server.getScheme();
    
    //請求主機地址 == 服務節點host
    if (host.equals(original.getHost()) 
            && port == original.getPort()
            && scheme == original.getScheme()) {
        return original;
    }
    if (scheme == null) {
        scheme = original.getScheme();
    }
    if (scheme == null) {
        scheme = deriveSchemeAndPortFromPartialUri(original).first();
    }

    // 路徑解析處理
    try {
        StringBuilder sb = new StringBuilder();
        sb.append(scheme).append("://");
        // url包含用戶信息解析:例如: http://zhangsan:[email protected] 格式
        if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
            sb.append(original.getRawUserInfo()).append("@");
        }
        // 拼接真實服務器IP
        sb.append(host);
        if (port >= 0) {
            sb.append(":").append(port);
        }
        // 拼接其他路徑
        sb.append(original.getRawPath());
        if (!Strings.isNullOrEmpty(original.getRawQuery())) {
            sb.append("?").append(original.getRawQuery());
        }
        if (!Strings.isNullOrEmpty(original.getRawFragment())) {
            sb.append("#").append(original.getRawFragment());
        }
        // 構建新的URI直接返回
        URI newURI = new URI(sb.toString());
        return newURI;            
    } catch (URISyntaxException e) {
        throw new RuntimeException(e);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章