SpringCloud实战-Ribbon 的基本架构和实现原理

Netflix Ribbon 基本架构
作为一款客户端负载均衡工具,要做的事情无非就是两件:第一件事情是获取注册中心中的服务器列表;第二件事情是在这个服务列表中选择一个服务进行调用

Netflix Ribbon 中的核心类

  1. ILoadBalancer
public interface ILoadBalancer {
  //添加后端服务
  //这里抽象成添加服务列表,因为Ribbon是一个工具,可以用来集成不限于Eureka
  //1
  public void addServers(List<Server> newServers);
 
  //选择一个后端服务
  //2
  public Server chooseServer(Object key); 
 
	//标记一个服务不可用
  public void markServerDown(Server server);
 
	//获取当前可用的服务列表
  public List<Server> getReachableServers();
	 
	//获取所有后端服务列表
   public List<Server> getAllServers();
}

  1. IRule
    IRule 接口是对负载均衡策略的一种抽象,可以通过实现这个接口来提供各种适用的负载均衡算法
public interface IRule{
    public Server choose(Object key);
    public void setLoadBalancer(ILoadBalancer lb);
    public ILoadBalancer getLoadBalancer();
}

  1. IPing
    IPing 接口判断目标服务是否存活
public interface IPing {
    public boolean isAlive(Server server);
}

4.LoadBalancerStats
LoadBalancerStats 类记录负载均衡的实时运行信息,用来作为负载均衡策略的运行时输入

BaseLoadBalancer 中ILoadBalancer 接口中 chooseServer 方法的实现

public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
            	//调用Irule接口
                return rule.choose(key);
            } catch (Exception e) {
                 return null;
            }
        }
}

Spring Cloud Netflix Ribbon
对于 Netflix Ribbon 组件而言,我们首先需要明确它提供的只是一个辅助工具,这个辅助工具的目的是让你去集成它,而不是说它自己完成所有的工作。而 Spring Cloud 中的 Spring Cloud Netflix Ribbon 就是就专门针对 Netflix Ribbon 提供了一个独立的集成实现。

如果让我们集成Netflix Ribbon,该怎么样做?
对于集成Eureka来说,可以通过Eureka客户端提供的的服务发现接口获取指定的服务列表,再看看上面的ILoadBalancer 接口,它有一个addServers方法,获取服务列表后就可以采用负载均衡策略调用某一个服务了。
在这里插入图片描述
@LoadBalanced 注解
为什么通过 @LoadBalanced 注解创建的 RestTemplate 就能自动具备客户端负载均衡的能力?

事实上,在 Spring Cloud Netflix Ribbon 中存在一个自动配置类——LoadBalancerAutoConfiguration 类。而在该类中,维护着一个被 @LoadBalanced 修饰的 RestTemplate 对象的列表。在初始化的过程中,对于所有被 @LoadBalanced 注解修饰的 RestTemplate,调用 RestTemplateCustomizer 的 customize 方法进行定制化,该定制化的过程就是对目标 RestTemplate 增加拦截器 LoadBalancerInterceptor。

@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 restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
}

重点在这个LoadBalancerInterceptor,

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
 
    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;
 
    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }
 
    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this(loadBalancer, new LoadBalancerRequestFactory(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();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    }
}

上面代码的重点是LoadBalancerClient,它在Spring-Cloud-Commons jar包下,是一个Spring Cloud关于负载均衡的抽象

public interface LoadBalancerClient extends ServiceInstanceChooser {
 
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
 
    <T> T execute(String serviceId, ServiceInstance serviceInstance,
           LoadBalancerRequest<T> request) throws IOException;
 
    URI reconstructURI(ServiceInstance instance, URI original);
}

其中一个实现类RibbonLoadBalancerClient 位于 spring-cloud-netflix-ribbon 工程中。这样我们的代码流程就从应用程序转入到了 Spring Cloud Netflix Ribbon 中

在 LoadBalancerClient 接口的实现类 RibbonLoadBalancerClient 中,choose 方法最终调用了如下所示的 getServer 方法:

protected Server getServer(ILoadBalancer loadBalancer) {
        if (loadBalancer == null) {
            return null;
        }
        return loadBalancer.chooseServer("default"); 
}

这里的 loadBalancer 对象就是前面介绍的 Netflix Ribbon 中的 ILoadBalancer 接口的实现类。这样,我们就把 Spring Cloud Netflix Ribbon 与 Netflix Ribbon 的整体协作流程串联起来。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值