Ribbon源码分析LoadBalancerInterceptor

158 阅读1分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

6Ribbon源码分析LoadBalancerInterceptor

LoadBalancerInterceptor拦截器是如何将一个普通的RestTemplate变成客户端负载均衡的呢

import java.io.IOException;
import java.net.URI;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;

/**
 * @author Spencer Gibb
 * @author Dave Syer
 */
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

   private LoadBalancerClient loadBalancer;

   public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
      this.loadBalancer = 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();
      return this.loadBalancer.execute(serviceName,
            new LoadBalancerRequest<ClientHttpResponse>() {

               @Override
               public ClientHttpResponse apply(final ServiceInstance instance)
                     throws Exception {
                  HttpRequest serviceRequest = new ServiceRequestWrapper(request,
                        instance);
                  return execution.execute(serviceRequest, body);
               }

            });
   }

   private class ServiceRequestWrapper extends HttpRequestWrapper {

      private final ServiceInstance instance;

      public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance) {
         super(request);
         this.instance = instance;
      }

      @Override
      public URI getURI() {
         URI uri = LoadBalancerInterceptor.this.loadBalancer.reconstructURI(
               this.instance, getRequest().getURI());
         return uri;
      }

   }

}

通过LoadBalancerAutoConfiguration我们可以看到在拦截器中注入了LoadBalancerClient的实现。当@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求时,会被LoadBalancerInterceptor类的intercept方法拦截。由于我们在使用RestTemplate时采用了服务名作为host,所以直接从HttpRequest的URI对象中通过getHost()方法拿到服务名,然后调用execute方法根据服务名来选择实例并发起实际的请求。

LoadBalancerClient只是一个抽象的负载均衡器接口,具体实现类为RibbonLoadBalancerClient

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);
   }
   RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
         serviceId), serverIntrospector(serviceId).getMetadata(server));

   RibbonLoadBalancerContext context = this.clientFactory
         .getLoadBalancerContext(serviceId);
   RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

   try {
      T returnVal = request.apply(ribbonServer);
      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;
}

protected Server getServer(ILoadBalancer loadBalancer) {
		if (loadBalancer == null) {
			return null;
		}
		return loadBalancer.chooseServer("default"); // TODO: better handling of key
	}

可以看到第一步就是通过getServer根据传入的服务名serviceId获得具体的服务实例并没有使用LoadBalancerClient的choose方法,而是使用了ILoadBalancer接口。