Ribbon源码分析

1、@LoadBalanced

/**

 * 给RestTemplate做标记,以使用负载均衡的客户端来配置它

 * 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 {

}

 

2、接下来看下 负载均衡的客户端 LoadBalancerClient

org.springframework.cloud.client.loadbalancer.LoadBalancerClient(接口)
RibbonLoadBalancerClient是其一个实现类
LoadBalancerClient的三个接口:

public interface LoadBalancerClient extends ServiceInstanceChooser {

// 执行请求, 对于RibbonLoadBalancerClient的这个方式最终也是调用下面的execute方法

<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

// 执行请求

<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

// 为系统构建一个合适的host:port形式的URI. 业务中,将使用的逻辑服务名构建URI

URI reconstructURI(ServiceInstance instance, URI original);

}

 

3、接着看RibbonLoadBalancerClient

分析:看源码的目的是为了看ribbon如何实现负载均衡的,就是ribbon如何选择服务端

RibbonLoadBalancerClient重要方法:

public class RibbonLoadBalancerClient implements LoadBalancerClient {

。。。。。

// 选择服务实例

public ServiceInstance choose(String serviceId) {

   Server server = getServer(serviceId);

   if (server == null) {

      return null;

   }

   return new RibbonServer(serviceId, server, isSecure(server, serviceId),

         serverIntrospector(serviceId).getMetadata(server));

}

// 使用加载器去获取server, 于是问题变成了ribbon如何选择加载器的问题

protected Server getServer(String serviceId) {

   return getServer(getLoadBalancer(serviceId));

}

// 默认的加载器是default

protected Server getServer(ILoadBalancer loadBalancer) {

   if (loadBalancer == null) {

      return null;

   }

   return loadBalancer.chooseServer("default"); // TODO: better handling of key

}

。。。。。

}

--------------------当然是继续跟踪getLoadBalancer方法啊-------------------------------

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

// ILoadBalancer就是com.netflix.loadbalancer.ILoadBalancer

public ILoadBalancer getLoadBalancer(String name) {

   return getInstance(name, ILoadBalancer.class);

}

}

分析:最终getServer()方法去获取实例,经过源码跟踪,最终交给了ILoadBalancer类去选择服务实例

 

总结:虽然Spring Cloud中定义了LoadBalancerClient作为负载均衡的通用接口,并且针对Ribbon实现了RibbonLoadBalancerClient,但是在具体的实现客户端负载均衡时,是通过Ribbon实现的 ILoadBalancer接口实现的。

下面根据ILoadBalancer接口的实现类看看到底是如何实现负载均衡的!

 

二、IloadBalancer接口

com.netflix.loadbalancer.ILoadBalancer(接口)

package com.netflix.loadbalancer;

public interface ILoadBalancer {

    void addServers(List<Server> var1);

    Server chooseServer(Object var1);

    void markServerDown(Server var1);

    /** @deprecated */

    @Deprecated

    List<Server> getServerList(boolean var1);

    List<Server> getReachableServers();

    List<Server> getAllServers();

}

 


其中,addServers()方法是添加一个Server集合;chooseServer()方法是根据key去获取Server;markServerDown()方法用来标记某个服务下线;getReachableServers()获取可用的Server集合;getAllServers()获取所有的Server集合。

其实现类:

 

先看下这些类、接口之间的关系:

 

分析:上面说到,实现客户端负载均衡最终是通过Ribbon实现的 ILoadBalancer接口实现的。那么实现负载均衡都需要实现哪些工作呢,或者说应该实现哪些工作?

负载均衡无外乎也就是选择一个服务器进行调用(进行通讯),只不过有个服务实例选择的算法。那么通过算法选择服务实例一定可以使用吗?肯定不是!当然最好进行下服务检活的测试。

根据这些想法,实现客户端负载均衡的类应该包括哪些东西呢?应该包含:服务实例的列表,选择算法,服务连通性的检测 等。当然看这个类主要还是关注它是如何实现IloadBalancer定义的方法的!

下面去具体的类中验证。

 

2.1 AbstractLoadBalancer

com.netflix.loadbalancer.AbstractLoadBalancer, 是IloadBalancer的直接实现类,是个抽象类

 

2.2 BaseLoadBalancer

com.netflix.loadbalancer.BaseLoadBalancer是IloadBalancer(负载均衡器)的基础实现类,该类中定义了很多关于负载均衡相关的内容。当然看这个类主要还是关注它是如何实现IloadBalancer定义的方法的!

/**

 * A basic implementation of the load balancer where an arbitrary list of

 * servers can be set as the server pool. A ping can be set to determine the

 * liveness of a server. Internally, this class maintains an "all" server list

 * and an "up" server list and use them depending on what the caller asks for.

 *

 * @author stonse

 *

 */

public class BaseLoadBalancer extends AbstractLoadBalancer implements

        PrimeConnections.PrimeConnectionListener, IClientConfigAware {

// 定义了两个集合,分别存储 所有服务实例的清单 和 正常服务的清单

// 注意这两个集合做了线程安全的处理

@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)

protected volatile List<Server> allServerList = Collections

        .synchronizedList(new ArrayList<Server>());

@Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)

protected volatile List<Server> upServerList = Collections

        .synchronizedList(new ArrayList<Server>());

 

 

// 存储负载均衡器各个服务实例属性和统计信息的LoadBalancerStats对象

protected LoadBalancerStats lbStats;

// 检查服务实例是否正常的IPing对象,默认为空,需要在构造时注入它的实现

protected IPing ping = null;

// 检查服务实例操作的执行策略对象,默认使用该类内定义的静态内部类SerialPingStrategy的实现,检查所有服务是否可以ping通

protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY;

// 负载均衡的处理规则对象

protected IRule rule = DEFAULT_RULE;

private final static IRule DEFAULT_RULE = new RoundRobinRule();

 

 

 

}

 

BaseLoadBalancer解析:

1、静态内部类SerialPingStrategy判断server是否可以ping通的策略是,顺序遍历server列表,存在问题就是如果IPing速度不理想,或者Server列表比较大,可能会影响性能。这个时候需要重写

IPingStrategy的pingServers(IPing ping, Server[] servers)函数去扩展ping的执行策略

2、BaseLoadBalancer选择服务:chooseServer(Object key)实现

public Server chooseServer(Object key) {

    if (counter == null) {

        counter = createCounter();

    }

    counter.increment();

    if (rule == null) {

        return null;

    else {

        try {

            return rule.choose(key);

        catch (Exception e) {

            logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);

            return null;

        }

    }

}

说明:发现BaseLoadBalancer选择server是通过IRule来实现的, BaseLoadBalancer使用的默认IRule的策略是RoundRobinRule, 其底层实现了最基本且常用的线性负载均衡规则,即从服务列表中线性的选择服务进行Ping, 返回第一个可用的服务。关于IRule的实现下面介绍。

3、BaseLoadBalancer添加服务:public void addServers(List<Server> newServers);

将向负载均衡器中 新添加的服务实例 添加到原有维护着的 所有服务实例清单allServerList 中。并通过调用setServerList方法新创建的服务列表进行处理,用新的列表覆盖旧的列表。

4、BaseLoadBalancer标记某个服务实例暂停服务:public void markServerDown(Server server);

5、BaseLoadBalancer定时的服务实例的检测

在BaseLoadBalancer的默认构造函数中,会直接启动一个用于定时检查Server是否健康的任务,改任务的默认执行时间间隔为10s

/**

 * Default constructor which sets name as "default", sets null ping, and

 * {@link RoundRobinRule} as the rule.

 * <p>

 * This constructor is mainly used by {@link ClientFactory}. Calling this

 * constructor must be followed by calling {@link #init()} or

 * {@link #initWithNiwsConfig(IClientConfig)} to complete initialization.

 * This constructor is provided for reflection. When constructing

 * programatically, it is recommended to use other constructors.(以编程方式构造时,建议使用其他构造函数。)

 */

public BaseLoadBalancer() {

    this.name = DEFAULT_NAME;

    this.ping = null;

    setRule(DEFAULT_RULE);

    setupPingTask();

    lbStats = new LoadBalancerStats(DEFAULT_NAME);

}

 

 

void setupPingTask() {

    if (canSkipPing()) {

        return;

    }

    if (lbTimer != null) {

        lbTimer.cancel();

    }

    lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,

            true);

    // 10s

    lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);

    forceQuickPing();

}

疑问:默认的ping是null, 会导致这个pingTask不会执行其中的定时任务。

可以通过指定的方式创建的构造器:

public BaseLoadBalancer(String name, IRule rule, LoadBalancerStats stats,

        IPing ping, IPingStrategy pingStrategy) {

 

    logger.debug("LoadBalancer [{}]:  initialized", name);

     

    this.name = name;

    this.ping = ping;

    this.pingStrategy = pingStrategy;

    setRule(rule);

    setupPingTask();

    lbStats = stats;

    init();

}

 

总结:至此,我们看到了负载均衡器BaseLoadBalancer,对服务列表的维护,包括列表结构、定时检测服务存活效果等,如何实现接口IloadBalancer中的一些方法(没写出来的方法实现比较简单),至于如何选择服务,或者说是如何根据选择算法选择服务,这部分还要继续追踪IRule里面的实现。

2.3 DynamicServerListLoadBalancer

继承了BaseLoadBalancer类,对基础负载均衡器的扩展,实现了服务实例清单在运行期的动态更新能力;同时,还具备了对服务实例清单的过滤功能,也就是可以通过过滤器来选择性的获取一批服务实例清单。

public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {

// 新增的关于服务列表的操作对象,元素是个Server类的子类,代表一个具体的服务实例的扩展类

volatile ServerList<T> serverListImpl;

 

volatile ServerListFilter<T> filter;

}

而ServerList接口中的方法:

package com.netflix.loadbalancer;

public interface ServerList<T extends Server> {

    // 获取初始化的服务实例清单

    List<T> getInitialListOfServers();

    // 用户获取更新的服务实例清单

    List<T> getUpdatedListOfServers();

}

接口ServerList也有多个实现类:

 

ServerList<T>:

既然要实现服务实例的动态更新,肯定是从Eureka服务中心获取服务实例,就要有Ribbon和Eureka整合的部分,于是正好有个包 ribbon-eureka, 通过探索其中的类,可以发现创建ServerList实例的内容。(往后的内容太复杂了。。。。)

最后会看到,这两个方法是通过DiscoveryEnabledNIWSServerList类一个私有函数obtainServersViaDiscovery通过服务发现机制实习服务实例获取的。

ServerListFilter<T>:

知道了获取服务实例清单,那么它又是如何触发向Eureka Server去获取服务实例清单?如何在获取服务实例清单后更新本地服务实例清单呢?

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {

    @Override

    public void doUpdate() {

        updateListOfServers();

    }

};

总结:

RestTemplate是如何和Ribbon结合的
最后,回答问题的本质,为什么在RestTemplate加一个@LoadBalance注解就可可以开启负载均衡呢?

@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}

全局搜索ctr+shift+f @LoadBalanced有哪些类用到了LoadBalanced有哪些类用到了, 发现LoadBalancerAutoConfiguration类,即LoadBalancer自动配置类。

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

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
}
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}


@Configuration
@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 new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}

}

在该类中,首先维护了一个被@LoadBalanced修饰的RestTemplate对象的List,在初始化的过程中,通过调用customizer.customize(restTemplate)方法来给RestTemplate增加拦截器LoadBalancerInterceptor。

而LoadBalancerInterceptor,用于实时拦截,在LoadBalancerInterceptor这里实现来负载均衡。LoadBalancerInterceptor的拦截方法如下:

@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, requestFactory.createRequest(request, body, execution));
}

 

总结
综上所述,Ribbon的负载均衡,主要通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,并默认10秒一次向EurekaClient发送“ping”,进而检查是否更新服务列表,最后,得到注册列表后,ILoadBalancer根据IRule的策略进行负载均衡

而RestTemplate 被@LoadBalance注解后,能过用负载均衡,主要是维护了一个被@LoadBalance注解的RestTemplate列表,并给列表中的RestTemplate添加拦截器,进而交给负载均衡器去处理。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值