Spring Cloud——Ribbon以及源码分析

本文详细解析了Spring Cloud Ribbon如何与RestTemplate集成,实现客户端负载均衡,包括Ribbon的原理、Eureka服务注册、负载均衡策略和使用示例。重点讲解了@LoadBalanced注解的工作原理及Eureka服务列表的刷新机制。
摘要由CSDN通过智能技术生成

一.RestTemplate简介

RestTemplate是Spring Resources中一个访问第三方RestFul API接口的网络请求框架。RestTemplate的设计原则和其他的Srping Template类似。都是为执行复杂任务提供一个具有默认行为的简单方法。

RestTemplate是用来消费Rest服务的,所以RestTemplate的主要方法都与Rest的Http协议的一些方法紧密相连。例如HEAD,GET,POST,DELETE等方法。这些方法在RestTemplate类对应的方法为headForHeaders(),getForObject(),postForObject()等。下面以GET请求为例:

在RestTemplate中,对GET请求可以通过以下两个方法调用实现

  • getForEntity:该方法返回的是ResponseEntity,该对象是对HTTP请求响应的封装,其中主要存储了HTTP的几个重要元素,比如HTTP请求状态码的枚举对象HttPStatus,在其父类的HttpEntity中还存储着HTTP请求的头信息对象等。这个方法有重载实现。

  • getForObject:是对getForEntity的进一步封装,它通过HttpMessageConverterExtractor对HTTP请求响应体body内容进行对象转化,实现请求直接返回包装好的对象内容。当不需要关注请求响应除body外的其他内容时,该方法就很好用,可以少一个从Response中获取body的步骤。该方法有重载实现。

二.负载均衡

负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式

  • 集中式负载均衡:在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(主要通过服务器节点之间按照专门用于负载均衡的设备F5,会维护一个下挂可用的服务端清单,当通过心跳检测来剔除故障的服务端节点以保证清单中的节点都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按照某种算法从维护的可用服务端取出一台服务端地址,然后进行转发),有软件的(Nginx)

  • 客户端负载均衡:将负载均衡逻辑以代码的形式封装到服务消费者的客户端上,服务消费者客户端维护了一分服务提供者的消息列表,有了信息列表,通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。

这两者最大的不同是服务清单存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端清单来自于服务注册中心。

三.Ribbon

3.1 简介

Ribbon是Netfilx公司开源的一个负载均衡的组件,属于上面讲的第二种方式,是将负载均衡逻辑封装在客户端,并且运行在客户端的进程中。Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,基于Netfilx的Ribbon实现。通过Spring Cloud的封装,可以让我们轻松将面向服务的REST模板自动转换成客户端负载均衡的服务调用。

Spring Cloud Ribbon虽然只是一个工具类框架,不像服务注册中心,配置中心,API网关那样需要独立部署,但是他几乎存在每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关请求转发等内容,几乎都是通过Ribbon实现。

有两种使用方式:

  • 结合RestTemplate

  • 结合Feign

3.2 Ribbon 模块

名  称说    明
ribbon-loadbalancer负载均衡模块,可独立使用,也可以和别的模块一起使用。
Ribbon内置的负载均衡算法都实现在其中。
ribbon-eureka基于 Eureka 封装的模块,能够快速、方便地集成 Eureka。
ribbon-transport基于 Netty 实现多协议的支持,比如 HTTP、Tcp、Udp 等。
ribbon-httpclient基于 Apache HttpClient 封装的 REST 客户端,集成了负载均衡模块,可以直接在项目中使用来调用接口。
ribbon-exampleRibbon 使用代码示例,通过这些示例能够让你的学习事半功倍。
ribbon-core一些比较核心且具有通用性的代码,客户端 API 的一些配置和其他 API 的定义。

3.3 使用RestTemplate和Ribbon来消费服务

这个的使用就在Eureka使用中使用过。

3.3.1 @LoadBalanced和LoadBalancerClient以及源码追踪(负载均衡器如何获取服务注册列表信息)

回顾这个用法,我们发现了在RestTemplate上加了@LoadBalanced注解,此时RestTemplate就结合Ribbon开启了负载均衡功能。那么是为什么呢?

查看@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 {

}

从@LoadBalanced注解码的注释中,可以知道该注解用来给RestTemplate标记,以使用负载均衡的客户端(LoadBalancerClient)来配置它。

LoadBalancerClient可以获取负载均衡的服务提供者的实例信息。这个从Eureka Client获取服务注册列表信息,并将服务注册信息缓存一份。在LoadBalancerCliet调用choose方法的时候,根据负载均衡策略选择一个服务实例的信息,从而进行负载均衡。也可以不从Eureka Client获取注册列表信息,这时候需要自己维护一份服务注册列表信息。

追踪LoadBalancerClient的源码,是一个接口类,继承了ServiceInstanceChooser,实现类为RibbonLoadBalanceClient。

public interface LoadBalancerClient extends ServiceInstanceChooser {

	/**
	 * Executes request using a ServiceInstance from the LoadBalancer for the specified
	 * service.
	 * @param serviceId The service ID to look up the LoadBalancer.
	 * @param request Allows implementations to execute pre and post actions, such as
	 * incrementing metrics.
	 * @param <T> type of the response
	 * @throws IOException in case of IO issues.
	 * @return The result of the LoadBalancerRequest callback on the selected
	 * ServiceInstance.
	 */
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	/**
	 * Executes request using a ServiceInstance from the LoadBalancer for the specified
	 * service.
	 * @param serviceId The service ID to look up the LoadBalancer.
	 * @param serviceInstance The service to execute the request to.
	 * @param request Allows implementations to execute pre and post actions, such as
	 * incrementing metrics.
	 * @param <T> type of the response
	 * @throws IOException in case of IO issues.
	 * @return The result of the LoadBalancerRequest callback on the selected
	 * ServiceInstance.
	 */
	<T> T execute(String serviceId, ServiceInstance serviceInstance,
			LoadBalancerRequest<T> request) throws IOException;

	/**
	 * Creates a proper URI with a real host and port for systems to utilize. Some systems
	 * use a URI with the logical service name as the host, such as
	 * http://myservice/path/to/service. This will replace the service name with the
	 * host:port from the ServiceInstance.
	 * @param instance service instance to reconstruct the URI
	 * @param original A URI with the host as a logical service name.
	 * @return A reconstructed URI.
	 */
	URI reconstructURI(ServiceInstance instance, URI original);

}

其中两个excute()方法用来执行请求,reconstructURI用于重构造Url。

LoadBalancerClient继承了ServiceInstanceChooser,这个接口有一格方法用于根据serviceId获取ServiceInstance,即通过服务名来选择服务实例。代码如下:

public interface ServiceInstanceChooser {

	/**
	 * Chooses a ServiceInstance from the LoadBalancer for the specified service.
	 * @param serviceId The service ID to look up the LoadBalancer.
	 * @return A ServiceInstance that matches the serviceId.
	 */
	ServiceInstance choose(String serviceId);

}

LoadBalancerClier的实现类为RibbonLoadBalancerClient,最终的负载均衡的请求处理都是由它执行。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.netflix.ribbon;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

public class RibbonLoadBalancerClient implements LoadBalancerClient {
    private SpringClientFactory clientFactory;

    public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }

    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);
        URI uri;
        Server server;
        if (instance instanceof RibbonLoadBalancerClient.RibbonServer) {
            RibbonLoadBalancerClient.RibbonServer ribbonServer = (RibbonLoadBalancerClient.RibbonServer)instance;
            server = ribbonServer.getServer();
            uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, ribbonServer);
        } else {
            server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
            IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
            ServerIntrospector serverIntrospector = this.serverIntrospector(serviceId);
            uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
        }

        return context.reconstructURIWithServer(server, uri);
    }

    public ServiceInstance choose(String serviceId) {
        return this.choose(serviceId, (Object)null);
    }

    public ServiceInstance choose(String serviceId, Object hint) {
        Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
        return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
    }

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        return this.execute(serviceId, (LoadBalancerRequest)request, (Object)null);
    }

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
        Server server = this.getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
            return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
        }
    }

    public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
        Server server = null;
        if (serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) {
            server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer();
        }

        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
            RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

            try {
                T returnVal = request.apply(serviceInstance);
                statsRecorder.recordStats(returnVal);
                return returnVal;
            } catch (IOException var8) {
                statsRecorder.recordStats(var8);
                throw var8;
            } catch (Exception var9) {
                statsRecorder.recordStats(var9);
                ReflectionUtils.rethrowRuntimeException(var9);
                return null;
            }
        }
    }

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

        return (ServerIntrospector)serverIntrospector;
    }

    private boolean isSecure(Server server, String serviceId) {
        IClientConfig config = this.clientFactory.getClientConfig(serviceId);
        ServerIntrospector serverIntrospector = this.serverIntrospector(serviceId);
        return RibbonUtils.isSecure(config, serverIntrospector, server);
    }

    protected Server getServer(String serviceId) {
        return this.getServer(this.getLoadBalancer(serviceId), (Object)null);
    }

    protected Server getServer(ILoadBalancer loadBalancer) {
        return this.getServer(loadBalancer, (Object)null);
    }

    protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
        return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
    }

    protected ILoadBalancer getLoadBalancer(String serviceId) {
        return this.clientFactory.getLoadBalancer(serviceId);
    }

    public static class RibbonServer implements ServiceInstance {
        private final String serviceId;
        private final Server server;
        private final boolean secure;
        private Map<String, String> metadata;

        public RibbonServer(String serviceId, Server server) {
            this(serviceId, server, false, Collections.emptyMap());
        }

        public RibbonServer(String serviceId, Server server, boolean secure, Map<String, String> metadata) {
            this.serviceId = serviceId;
            this.server = server;
            this.secure = secure;
            this.metadata = metadata;
        }

        public String getInstanceId() {
            return this.server.getId();
        }

        public String getServiceId() {
            return this.serviceId;
        }

        public String getHost() {
            return this.server.getHost();
        }

        public int getPort() {
            return this.server.getPort();
        }

        public boolean isSecure() {
            return this.secure;
        }

        public URI getUri() {
            return DefaultServiceInstance.getUri(this);
        }

        public Map<String, String> getMetadata() {
            return this.metadata;
        }

        public Server getServer() {
            return this.server;
        }

        public String getScheme() {
            return this.server.getScheme();
        }

        public String toString() {
            StringBuilder sb = new StringBuilder("RibbonServer{");
            sb.append("serviceId='").append(this.serviceId).append('\'');
            sb.append(", server=").append(this.server);
            sb.append(", secure=").append(this.secure);
            sb.append(", metadata=").append(this.metadata);
            sb.append('}');
            return sb.toString();
        }
    }
}

choose方法用于选择具体的服务实例。该方法通过getServer()方法去获取实例,经过源码追逐,最终交给ILoadBalancer类去选择服务实例。

ILoadBalancer是一个接口,该接口定义了一系列实现负载均衡的方法,源码如下:

public interface ILoadBalancer {
    void addServers(List<Server> var1);//用于添加一个Server集合

    Server chooseServer(Object var1);//根据key去获取Server

    void markServerDown(Server var1); //用于标记某个服务下线

    /** @deprecated */
    @Deprecated
    List<Server> getServerList(boolean var1); 

    List<Server> getReachableServers(); //获取可用Server集合

    List<Server> getAllServers(); //获得所有Server集合
}

ILoadBalancer的继承关系如下:

查看DynamicServerListLoadBalancer类的源码,发现需要配置IClientConfig,IRlue,IPing,ServerList,ServerListFilter和ILoadBalancaer。查看BaseLoadBalancer的源码,在默认的配置下,实现了下面配置:

1.IClientConfig:DefaultClientConfigImpl 用于配置负载均衡的客户端

2.IRlue:RoundRobinRule 用于配置负载均衡的客户端,有3个方法,其中choose是根据key来获取实例的,setLoadBalancer和getLoadBalancer是用来是设置和获取ILoadBalancer,源码如下

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

IRule有很多的默认类,这些实现类根据不同的算法和逻辑来处理负载均衡的策略。策略也可以自定义。

  • BestAvaliableRule:选择最小请求数
  • ClientConfigEnableRoundRobinRule:轮询
  • RandomRule:随机选择
  • RoundRobinRule:轮询选择Server
  • RetryRule:根据轮询的方式重试
  • WeightResponseTimeRule:根据响应时间去分配一个weight,weight越低,被选择的可能性就越低
  • ZoneAvoidanceRule:根据server的zone区域和可用性来轮询选择

3.IPing:DummyPing ,用于向Server发送"ping",来判断server是否有响应,从而判断server是否可用。


public interface IPing {
    boolean isAlive(Server var1);
}

子类:

  • PingUrl:真实地去ping某个URL,判断其是否可用
  • PingConstant:固定返回某服务是否可用,默认返回true,即可用
  • NoOping:不去ping,直接返回true
  • DummyPing:直接返回true,并实现了initWithNiwsConfig方法
  • NiWsDiscoveryPing:根据DiscoveryEnableServer的InstanceInfo的InstanceStatus去判断,如果InstanceStatus.UP,则可用

4.ServerList:ConfigurationBasedServerList,获取所有的server的注册列表信息接口

5.ServerListFilter:ZonePreferenceServerListFilter,定义了可配置去过滤或者特性动态地获取符合条件的serverl列表的方法。

6.ILoadBalancer:ZoneAwareLoadBalancer

查看DynamicServerListLoadBalancer源码:DynamicServerListLoadBalancer的构造函数中有一个initWithNiwsConfig方法,在该方法中最终执行了restOfInit方法,该方法中有一个updateListOfServers方法,用来获取所有的ServerList的。

 public DynamicServerListLoadBalancer(IClientConfig clientConfig) {
        this.isSecure = false;
        this.useTunnel = false;
        this.serverListUpdateInProgress = new AtomicBoolean(false);

        class NamelessClass_1 implements UpdateAction {
            NamelessClass_1() {
            }

            public void doUpdate() {
                DynamicServerListLoadBalancer.this.updateListOfServers();
            }
        }

        this.updateAction = new NamelessClass_1();
        this.initWithNiwsConfig(clientConfig);
    }

public void initWithNiwsConfig(IClientConfig clientConfig) {
        try {
            super.initWithNiwsConfig(clientConfig);
            String niwsServerListClassName = clientConfig.getPropertyAsString(CommonClientConfigKey.NIWSServerListClassName, "com.netflix.loadbalancer.ConfigurationBasedServerList");
            ServerList<T> niwsServerListImpl = (ServerList)ClientFactory.instantiateInstanceWithClientConfig(niwsServerListClassName, clientConfig);
            this.serverListImpl = niwsServerListImpl;
            if (niwsServerListImpl instanceof AbstractServerList) {
                AbstractServerListFilter<T> niwsFilter = ((AbstractServerList)niwsServerListImpl).getFilterImpl(clientConfig);
                niwsFilter.setLoadBalancerStats(this.getLoadBalancerStats());
                this.filter = niwsFilter;
            }

            String serverListUpdaterClassName = clientConfig.getPropertyAsString(CommonClientConfigKey.ServerListUpdaterClassName, "com.netflix.loadbalancer.PollingServerListUpdater");
            this.serverListUpdater = (ServerListUpdater)ClientFactory.instantiateInstanceWithClientConfig(serverListUpdaterClassName, clientConfig);
            this.restOfInit(clientConfig);
        } catch (Exception var5) {
            throw new RuntimeException("Exception while initializing NIWSDiscoveryLoadBalancer:" + clientConfig.getClientName() + ", niwsClientConfig:" + clientConfig, var5);
        }
    }

    void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        this.setEnablePrimingConnections(false);
        this.enableAndInitLearnNewServersFeature();
        this.updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections().primeConnections(this.getReachableServers());
        }

        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

追踪updateListOfServers,最终由.serverListImpl.getUpdatedListOfServers()获取所有服务列表,代码如下。

 @VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList();
        if (this.serverListImpl != null) {
            servers = this.serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
            if (this.filter != null) {
                servers = this.filter.getFilteredListOfServers((List)servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
            }
        }

        this.updateAllServerList((List)servers);
    }

serverListImpl是ServerList接口的具体实现类。追逐源码,ServerList的实现类是DiscoveryEnabledNIWSServerList。这个类有下面两个方法:

   public List<DiscoveryEnabledServer> getInitialListOfServers() {
        return this.obtainServersViaDiscovery();
    }

    public List<DiscoveryEnabledServer> getUpdatedListOfServers() {
        return this.obtainServersViaDiscovery();
    }

继续追逐源码,obtainServersViaDiscovery方法是根据eurekaClientProvider.get方法获取EurekaClient的,然后根据EurekaClient获取服务注册列表信息,代码如下:

 private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
        List<DiscoveryEnabledServer> serverList = new ArrayList();
        if (this.eurekaClientProvider != null && this.eurekaClientProvider.get() != null) {
            EurekaClient eurekaClient = (EurekaClient)this.eurekaClientProvider.get();
            if (this.vipAddresses != null) {
                String[] var3 = this.vipAddresses.split(",");
                int var4 = var3.length;

                for(int var5 = 0; var5 < var4; ++var5) {
                    String vipAddress = var3[var5];
                    List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);
                    Iterator var8 = listOfInstanceInfo.iterator();

                    while(var8.hasNext()) {
                        InstanceInfo ii = (InstanceInfo)var8.next();
                        if (ii.getStatus().equals(InstanceStatus.UP)) {
                            if (this.shouldUseOverridePort) {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Overriding port on client name: " + this.clientName + " to " + this.overridePort);
                                }

                                InstanceInfo copy = new InstanceInfo(ii);
                                if (this.isSecure) {
                                    ii = (new Builder(copy)).setSecurePort(this.overridePort).build();
                                } else {
                                    ii = (new Builder(copy)).setPort(this.overridePort).build();
                                }
                            }

                            DiscoveryEnabledServer des = this.createServer(ii, this.isSecure, this.shouldUseIpAddr);
                            serverList.add(des);
                        }
                    }

                    if (serverList.size() > 0 && this.prioritizeVipAddressBasedServers) {
                        break;
                    }
                }
            }

            return serverList;
        } else {
            logger.warn("EurekaClient has not been initialized yet, returning an empty list");
            return new ArrayList();
        }
    }

其中eurekaProvider的实现类是LeagacyEurekaClientProvider,这是一个获取EurekaClient的实例的类, 代码如下:
 

class LegacyEurekaClientProvider implements Provider<EurekaClient> {
    private volatile EurekaClient eurekaClient;

    LegacyEurekaClientProvider() {
    }

    public synchronized EurekaClient get() {
        if (this.eurekaClient == null) {
            this.eurekaClient = DiscoveryManager.getInstance().getDiscoveryClient();
        }

        return this.eurekaClient;
    }
}

EurekaClient的实现类是DiscoverClient,这个类具有服务注册,获取服务注册列表等功能。

负载均衡器是从Eureka Client获取服务列表信息,并根据IRule的策略去路由,根据IPing去判断服务可用性。

3.3.2 负载均衡器每隔多长时间从Eureka Client获取注册信息

在BaseLoadBalancer中,构造方法开启了一个PingTask任务:

  public BaseLoadBalancer(String name, IRule rule, LoadBalancerStats stats, IPing ping, IPingStrategy pingStrategy) {
       //省略代码
        this.setupPingTask();
      
    }

在setupPingTask的具体代码逻辑里,开启了ShutdownEnableTimer的PingTask任务,在默认情况下,变量pingIntervalSeconds的值为10,即每10秒向EurekaClient发送一次“Ping”

void setupPingTask() {
        if (!this.canSkipPing()) {
            if (this.lbTimer != null) {
                this.lbTimer.cancel();
            }

            this.lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + this.name, true);
            this.lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0L, (long)(this.pingIntervalSeconds * 1000));
            this.forceQuickPing();
        }
    }

PingTask创建了一个Pinger对象,并执行runPinger方法

class PingTask extends TimerTask {
        PingTask() {
        }

        public void run() {
            try {
                (BaseLoadBalancer.this.new Pinger(BaseLoadBalancer.this.pingStrategy)).runPinger();
            } catch (Exception var2) {
                BaseLoadBalancer.logger.error("LoadBalancer [{}]: Error pinging", BaseLoadBalancer.this.name, var2);
            }

        }
    }

查看这个方法,最终根据pingStarategy.pingServers(ping,allServers)来获取服务可用性,如果该返回结果与之前相同,则不向EurekaClient获取注册列表,如果不同,则通知ServerStatusChangerListener服务注册列表信息发生改变,进行更新或者重新拉取,代码如下:

  public void runPinger() throws Exception {
            if (BaseLoadBalancer.this.pingInProgress.compareAndSet(false, true)) {
                Server[] allServers = null;
                boolean[] results = null;
                Lock allLock = null;
                Lock upLock = null;

                try {
                    allLock = BaseLoadBalancer.this.allServerLock.readLock();
                    allLock.lock();
                    allServers = (Server[])BaseLoadBalancer.this.allServerList.toArray(new Server[BaseLoadBalancer.this.allServerList.size()]);
                    allLock.unlock();
                    int numCandidates = allServers.length;
                    boolean[] resultsx = this.pingerStrategy.pingServers(BaseLoadBalancer.this.ping, allServers);
                    List<Server> newUpList = new ArrayList();
                    List<Server> changedServers = new ArrayList();

                    for(int i = 0; i < numCandidates; ++i) {
                        boolean isAlive = resultsx[i];
                        Server svr = allServers[i];
                        boolean oldIsAlive = svr.isAlive();
                        svr.setAlive(isAlive);
                        if (oldIsAlive != isAlive) {
                            changedServers.add(svr);
                            BaseLoadBalancer.logger.debug("LoadBalancer [{}]:  Server [{}] status changed to {}", new Object[]{BaseLoadBalancer.this.name, svr.getId(), isAlive ? "ALIVE" : "DEAD"});
                        }

                        if (isAlive) {
                            newUpList.add(svr);
                        }
                    }

                    upLock = BaseLoadBalancer.this.upServerLock.writeLock();
                    upLock.lock();
                    BaseLoadBalancer.this.upServerList = newUpList;
                    upLock.unlock();
                    BaseLoadBalancer.this.notifyServerStatusChangeListener(changedServers);
                } finally {
                    BaseLoadBalancer.this.pingInProgress.set(false);
                }
            }

LoadBalancerClient是在初始化时向Eureka获取服务注册列表信息,并每隔10秒向EurekaClient发送“ping”,来判断服务的可用性。如果服务的可用性发生了改变或者服务的数量与之前不同,则更新或者重新拉取。LoadBalancerClient有了这些服务注册列表信息,就可用根据之前的IRule来进行负载均衡了。

3.3.3 现在回到之前的问题,为什么加了@LoadBalanced就可以使用负载均衡呢。

全局搜索@LoadBalanced找到了LoadBalancerAutoConfiguration 。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = 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);
				}
			}
		});
	}

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

	@Configuration(proxyBeanMethods = false)
	@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);
			};
		}

	}

	/**
	 * Auto configuration for retry mechanism.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
			return new LoadBalancedRetryFactory() {
			};
		}

	}

	/**
	 * Auto configuration for retry intercepting mechanism.
	 */
	@Configuration(proxyBeanMethods = false)
	@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类中,首先维护了一个被@LoadBalanced修饰的RestTemplate对象的list。在初始化过程中,调用customizer.customize(restTemplate)给RestTemplate增加拦截器LoadBalancerIntereceptor。LoadBalancerIntereceptor用于实时拦截,实现了负载均衡的方法。

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

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值