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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取 2000+ 道 Java 面试题

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的接口实现类图如下:
20191123100274\_1.png
配置方式:

    #配置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;
                            }
                        });

                ...省略代码...
            }

来源:http://ddrv.cn/a/88268

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 深入理解Spring cloud源码篇之Ribbon源码

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏