spring Cloud 2021 Alibaba OpenFeign 使用 Ribbon 的负载均衡

引入依赖

<!--        nacos 注册与发现-->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 由于 spring Cloud 2021 移除了ribbon 作为负载均衡器,
取而代之的是 springLoadbalancer所以需要引入该依赖,而openFei也随之默认支持了
springLoadbalancer -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-loadbalancer</artifactId>

</dependency>
<!-- ribbon 依赖 -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
	<version>2.2.10.RELEASE</version>
</dependency>
<!-- openFeign 依赖 -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>

</dependency>

至于为什么要引入 spring-cloud-loadbalancerspringCloud文档


spring-cloud-starter-openfeign支持spring-cloud-starter-loadbalancer。然而,作为一个可选的依赖项,如果你想使用它,你需要确保它已被添加到你的项目中。

openFeign 发起请求的流程

openFeign 会调用feign.SynchronousMethodHandler#executeAndDecode -> feign.Client#execute的方法,而feign.Client默认实现为 org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient,看一下 org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute方法

@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		final URI originalUri = URI.create(request.url());
		String serviceId = originalUri.getHost();
		Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
		String hint = getHint(serviceId);
		DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
			new RequestDataContext(buildRequestData(request), hint));
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
			.getSupportedLifecycleProcessors(
				loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
				RequestDataContext.class, ResponseData.class, ServiceInstance.class);
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
		ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
		org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
			instance);
		if (instance == null) {
			String message = "Load balancer does not contain an instance for the service " + serviceId;
			if (LOG.isWarnEnabled()) {
				LOG.warn(message);
			}
			supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
												 .onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
													 CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
			return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
				.body(message, StandardCharsets.UTF_8).build();
		}
		String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
		Request newRequest = buildRequest(request, reconstructedUrl);
		LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
		return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
														  supportedLifecycleProcessors, loadBalancerProperties.isUseRawStatusCodeInResponseData());
	}

主要关注 第14行的 ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
这里的loadBalancerClient::LoadBalancerClient是通过 spring 注入进来的 而 loadBalancerClient::LoadBalancerClient默认实现是 org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClientorg.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration#blockingLoadBalancerClient
所以我们只需要替换掉 LoadBalancerClientbean 为Ribbon 的loadLoadBalancerClient即可

/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.openfeign.loadbalancer;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Set;

import feign.Client;
import feign.Request;
import feign.Response;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.CompletionContext;
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;

import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData;
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing;

/**
* A {@link Client} implementation that uses {@link LoadBalancerClient} to select a
* {@link ServiceInstance} to use while resolving the request host.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
	public class FeignBlockingLoadBalancerClient implements Client {

		private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class);

		private final Client delegate;

		private final LoadBalancerClient loadBalancerClient;

		private final LoadBalancerClientFactory loadBalancerClientFactory;

		/**
* @deprecated in favour of
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory)}
*/
		@Deprecated
		public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
											   LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
			this.delegate = delegate;
			this.loadBalancerClient = loadBalancerClient;
			this.loadBalancerClientFactory = loadBalancerClientFactory;
		}

		public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
											   LoadBalancerClientFactory loadBalancerClientFactory) {
			this.delegate = delegate;
			this.loadBalancerClient = loadBalancerClient;
			this.loadBalancerClientFactory = loadBalancerClientFactory;
		}

		@Override
		public Response execute(Request request, Request.Options options) throws IOException {
			final URI originalUri = URI.create(request.url());
			String serviceId = originalUri.getHost();
			Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
			String hint = getHint(serviceId);
			DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
				new RequestDataContext(buildRequestData(request), hint));
			Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
				.getSupportedLifecycleProcessors(
					loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
					RequestDataContext.class, ResponseData.class, ServiceInstance.class);
			supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
			ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
			org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
				instance);
			if (instance == null) {
				String message = "Load balancer does not contain an instance for the service " + serviceId;
				if (LOG.isWarnEnabled()) {
					LOG.warn(message);
				}
				supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
													 .onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
														 CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
				return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
					.body(message, StandardCharsets.UTF_8).build();
			}
			String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
			Request newRequest = buildRequest(request, reconstructedUrl);
			LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
			return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
															  supportedLifecycleProcessors, loadBalancerProperties.isUseRawStatusCodeInResponseData());
		}

		protected Request buildRequest(Request request, String reconstructedUrl) {
			return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
								  request.charset(), request.requestTemplate());
		}

		// Visible for Sleuth instrumentation
		public Client getDelegate() {
			return delegate;
		}

		private String getHint(String serviceId) {
			LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId);
			String defaultHint = properties.getHint().getOrDefault("default", "default");
			String hintPropertyValue = properties.getHint().get(serviceId);
			return hintPropertyValue != null ? hintPropertyValue : defaultHint;
		}

	}

实现 ribbon的 loadLoadBalancerClient

事实上ribbon 是有实现的 LoadBalancerClient接口的,并注入的 ,见[org.springframework.cloud.client.loadbalancer.LoadBalancerClient]
但可能是 因为 netflix不更新后spring重写了LoadBalancerClient接口 而springCloud2021的choose和原来的的不太一样所以我们需要先继承 ribbon的 org.springframework.cloud.client.loadbalancer.LoadBalancerClient重写 choose方法

package com.tuling.orderopenfeignnacos.entity;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.Server;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.netflix.ribbon.*;

public class OpenFeignCompatibleRibbonLoadBalancerClient extends RibbonLoadBalancerClient {
    private SpringClientFactory clientFactory;
    public OpenFeignCompatibleRibbonLoadBalancerClient(SpringClientFactory clientFactory) {
        super(clientFactory);
        this.clientFactory = clientFactory;
    }

    private ServerIntrospector serverIntrospector(String serviceId) {
        ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
                ServerIntrospector.class);
        if (serverIntrospector == null) {
            serverIntrospector = new DefaultServerIntrospector();
        }
        return serverIntrospector;
    }

    private boolean isSecure(Server server, String serviceId) {

        IClientConfig config = this.clientFactory.getClientConfig(serviceId);
        ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
        return RibbonUtils.isSecure(config, serverIntrospector, server);
    }

    /**
     * Chooses a ServiceInstance from the LoadBalancer for the specified service and
     * LoadBalancer request.
     *
     * @param serviceId The service ID to look up the LoadBalancer.
     * @param request   The request to pass on to the LoadBalancer
     * @return A ServiceInstance that matches the serviceId.
     */

    @Override
    public <T> ServiceInstance choose(String serviceId, Request<T> request) {
        Server server = getServer(getLoadBalancer(serviceId), request);
        if (server == null) {
            return null;
        }
        return new RibbonServer(serviceId, server, isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));
    }
}

注册 OpenFeignCompatibleRibbonLoadBalancerClient

package com.tuling.orderopenfeignnacos.config;

import com.tuling.orderopenfeignnacos.entity.OpenFeignCompatibleRibbonLoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
 * openfeign springCloud 2021 ribbon 兼容适配
 */
@Configuration
public class OpenFeignRibbonCompatibleConfigure {
    @Resource
    private SpringClientFactory springClientFactory;

    /**
     * 参见 @see @link org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient()
     * @return
     */
    @Bean
    public LoadBalancerClient loadBalancerClient() {
        return new OpenFeignCompatibleRibbonLoadBalancerClient(springClientFactory);
    }
}

posted @ 2023-01-11 11:07  Epiphanyi  阅读(304)  评论(0)    收藏  举报