深入理解Spring cloud源码篇之Ribbon源码

Ribbon简介

       Ribbon是一个客户端负载均衡器,是Netflix公司开源的项目,运行在客户端上,feign默认使用了Ribbon。

Ribbon在SpringCloud中的使用

Ribbon在Spring中三种使用方式:

①RestTemplate集成

    @Bean
    @LoadBalanced 
    RestTemplate RestTemplate() {
        SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
        simpleClientHttpRequestFactory.setReadTimeout(2000);
        simpleClientHttpRequestFactory.setConnectTimeout(2000);//设置超时时间
        return new RestTemplate(simpleClientHttpRequestFactory);
    }
    //通过以下方式调用
    String body = restTemplate.getForObject("http://servicename/", String.class);

②LoadBalancerClient

    @Autowired
    LoadBalancerClient loadbalancerClient; 
    //使用方法:
    loadbalancerClient.choose("serviceId").getUri();//获取url
    //用请求客户端请求url

③feign
       在https://blog.csdn.net/lgq2626/article/details/80392914文章中已经介绍了feign,feign中默认集成了ribbon。

Ribbon基本原理

       Nginx是服务端负载均衡器,nginx得负载均衡器具有这么几个特点:

  • 存在服务列表
  • 服务健康检测
  • 负载策略
    同理,ribbon客户端负载均衡器也有这几个重要的特点,下面一个一个分析。

服务列表:

       ribbon服务列表有两种方式来获取:
方式1:在配置文件中配置

ribbon.eureka.enabled=false
test-service.ribbon.listOfServers=http://www.baidu.com,http://www.jd.com,http://www.taobao.com

//使用方式:下面只说明使用feign也可以使用RestTemplate或者LoadBalancerClient去调用
//申明客户端,在bean里注入Test 直接调用即可
@FeignClient(name="test-service")
public interface Test {
    @RequestMapping("/")
    public String eee();
}

方式2:和eureka集成:
       在eureka客户端中初始化一个EurekaRibbonClientConfiguration类,中的ribbonServerList()方法,就是初始化服务列表的方法。每一个ribbon客户端都有一个RibbonClientConfiguration配置,初始化RibbonClientConfiguration配置的时候初始化了一些信息,包括是否ping,ribbonclient,还初始化了一个ILoadBalancer对象,调用到了DynamicServerListLoadBalancer方法的enableAndInitLearnNewServersFeature()开启了每30秒去更新一次eureka服务器的定时任务定时更新BaseLoadBalancer#upServerList对象。

服务健康检测机制:

       在ribbon负载均衡器中,提供了ping机制,每隔一段时间,就会去ping服务器,由 com.netflix.loadbalancer.IPing 接口去实现。单独使用ribbon,不会激活ping机制,默认采用DummyPing(在RibbonClientConfiguration中实例化),isAlive()方法直接返回true。ribbon和eureka集成,默认采用NIWSDiscoveryPing(在EurekaRibbonClientConfiguration中实例化的),只有服务器列表的实例状态为up的时候才会为Alive。
IPing的接口实现类图如下:
《深入理解Spring cloud源码篇之Ribbon源码》
配置方式:

#配置Ping操作的间隔
test-service.ribbon.NFLoadBalancerPingInterval=2
#配置IPing的实现类
test-service.ribbon.NFLoadBalancerPingClassName=com.test.PingTest

public class PingTest implements IPing{

    @Override
    public boolean isAlive(Server server) {
        System.out.println("我是ping机制");
        return false;
    }

}

负载策略:

       在ribbon负载机制中包括随机/轮询/连接数最少…

  • RoundRobinRule(轮询):当eureka列表为10个的话,list为10的话,取服务的顺序为: 1234567890;这是使用取模来实现。// int next = (current + 1) % modulo;
  • RandomRule(随机):随机算法,比较简单// int index = rand.nextInt(serverCount);
  • BestAvailableRule(当前连接数最少):
    使用ribbon客户端负载均衡器的时候,选用了一个客户端,去请求的时候,会让一个线程安全的Integer自增一个值。在请求完毕之后在减少一个值。在feign和ribbon集成请求的代码中,在LoadBalancerCommand#submit()的
    final ServerStats stats = loadBalancerContext.getServerStats(server);是自增, loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler);是自减。
  • WeightedResponseTimeRule(权重分配):在feign请求得时候会得出一个请求时间,在DynamicServerWeightTask线程中会定时去计算这个服务相应得平均时间。总时间减去每个服务得平均时间就是每个服务得权重。权重越大越容易被选中。如果服务没有开始请求,权重为0的时候默认执行RoundRobinRule策略。
  • AvailabilityFilteringRule(服务状态):随机选一台服务,判断服务器的状态。

Ribbon和Feign集成

       在上一篇文章中介绍了feign的使用,但是仅仅只是介绍了feign请求带url的解析,并没有解释和ribbon的集成。下面介绍ribbon和feign的集成请求方式:
       在FeignClientFactoryBean#getObject()中:

    if (!StringUtils.hasText(this.url)) {
            String url;
            if (!this.name.startsWith("http")) {
                url = "http://" + this.name;
            }
            else {
                url = this.name;
            }
            url += cleanPath();
            return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, url));//如果请求没有带url,而是带了name
        }

    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
        Client client = getOptional(context, Client.class);//这里获取的Client是LoadBalancerFeignClient,在DefaultFeignLoadBalancedConfiguration/FeignRibbonClientAutoConfiguration(版本不同初始化类不同)类中初始化。
        if (client != null) {
            builder.client(client);
            Targeter targeter = get(context, Targeter.class);
            //这里不往下跟了,在上篇中都跟过了,直接跟到LoadBalancerFeignClient#execute()方法中
            return targeter.target(this, builder, context, target);
        }

        throw new IllegalStateException(
                "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
    }

比较简单的代码就直接过了,直接看重点LoadBalancerCommand#submit();

 public Observable<T> submit(final ServerOperation<T> operation) {

            ...省略代码...
            // Use the load balancer
            Observable<T> o = 
                                    //选择server,和feign默认的选择策略是ZoneAwareLoadBalancer
                    (server == null ? selectServer() : Observable.just(server))
                    .concatMap(new Func1<Server, Observable<T>>() {
                        @Override
                        // Called for each server being selected
                        public Observable<T> call(Server server) {
                            context.setServer(server);
                            final ServerStats stats = loadBalancerContext.getServerStats(server);

                            // Called for each attempt and retry
                            Observable<T> o = Observable
                                    .just(server)
                                    .concatMap(new Func1<Server, Observable<T>>() {
                                        @Override
                                        public Observable<T> call(final Server server) {
                                            context.incAttemptCount();
                                            loadBalancerContext.noteOpenConnection(stats);//做一些记录,包括把服务器的连接数+1,设置请求的开始时间,方便上文中讲到的策略选择

                                            if (listenerInvoker != null) {
                                                try {
                                                    listenerInvoker.onStartWithServer(context.toExecutionInfo());
                                                } catch (AbortExecutionException e) {
                                                    return Observable.error(e);
                                                }
                                            }

                                            final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start();
                                            //在这个call里面调用服务,调用到了FeignLoadBalancer#execute()/RetryableFeignLoadBalancer#execute(重试)去发起请求,并且返回值
                                            return operation.call(server).doOnEach(new Observer<T>() {
                                                ...省略代码...//处理返回值,包括把服务器连接数-1,状态变化,统计请求时间,方便策略选择
                                            });
                                        }
                                    });

                            if (maxRetrysSame > 0) 
                                o = o.retry(retryPolicy(maxRetrysSame, true));
                            return o;
                        }
                    });

            ...省略代码...
        }
    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/lgq2626/article/details/80481514
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞