Ribbon与Spring cloud整合源码分析

简价

Ribbon是一种客户端的负载均衡器。提供了多种负载均衡的算法,支持多种协议(HTTP,TCP,UDP),并提供了故障容错的能力。官方网址为:https://github.com/Netflix/ribbon

Ribbon版Hello World

我们再使用的时候需要指定Server对象也就是可以做为负载的服务器,以及负载的Rule规则,默认采用的是轮询的规则。
代码如下,里面有详细的注释:

package com.ivan.ribbon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;

import com.google.common.collect.Lists;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.LoadBalancerBuilder;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
import com.netflix.loadbalancer.reactive.ServerOperation;

import rx.Observable;

public class RibbonClient {
   public static void main(String[] args) throws Exception {
       // 提供服务的服务器列表, 这里可以根据具体的测试url提供多个url。
       List<Server> servers = Lists.newArrayList(new Server("localhost", 8000), new Server("localhost", 8001));
       // 负载均衡器, 这里可以设置rule
       BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(servers);
       //这个可以提交具体的执行命令逻辑。需要传入具体的负载均衡器
       LoadBalancerCommand<String> command = LoadBalancerCommand.<String> builder().withLoadBalancer(loadBalancer).build();
       //连续执行10次,这样使可以看到具体的效果了。
       for (int i = 0; i < 10; i++) {
           command.submit(new ServerOperation<String>() {
               public Observable<String> call(Server server) {
                   URL url;
                   //这里的path是能够访问的url
                   String path = "/provider";
                   InputStream inputStream = null;
                   InputStreamReader inputStreamReader = null;
                   BufferedReader reader = null;
                   try {
                       url = new URL("http://" + server.getHost() + ":" + server.getPort() + path);
                       HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                       inputStream = conn.getInputStream();
                       inputStreamReader = new InputStreamReader(inputStream);
                       reader = new BufferedReader(inputStreamReader);
                       String tempLine = null;
                       StringBuffer resultBuffer = new StringBuffer();
                       while ((tempLine = reader.readLine()) != null) {
                           resultBuffer.append(tempLine);
                       }
                       String data = resultBuffer.toString();
                       System.out.println("data : " + data);
                       return Observable.just(data);
                   } catch (Exception e) {
                       return Observable.error(e);
                   } finally {
                       if (reader != null) {
                           try {
                               reader.close();
                           } catch (IOException e) {
                           }
                       }

                       if (inputStreamReader != null) {
                           try {
                               inputStreamReader.close();
                           } catch (IOException e) {
                           }
                       }

                       if (inputStream != null) {
                           try {
                               inputStream.close();
                           } catch (IOException e) {
                           }
                       }

                   }
               }
           }).toBlocking().first();

       }

   }
}

负载均衡规则

Ribbon 提供了若干个内置的负载规则如下图所示:


image.png
  • RoundRobinRule: 系统默认的规则, 通过简单的轮询服务列表来选择服务器
  • AvailabilityFilteringRule: 该规则会忽略以下服务器:
    1)无法连接的服务器: 在默认情况下, 如果 3 次连接失败, 该服务器将会被置为
    “ 短路” 的状态, 该状态将持续 30 秒, 如果再次连接失败, “ 短路” 状态的持
    续 时 间 将 会 以 几 何 级 增 加 。 可 以 通 过 修 改
    niws.loadbalancer.<clientName>.connectionFailureCountThreshold 属性, 来
    配置连接失败的次数。
    2) 并发数过高的服务器: 如果连接到该服务器的并发数过高, 也会被这个规则忽
    略, 可以通过修改<clientName>.ribbon.ActiveConnectionsLimit 属性来设定最
    高并发数。
  • WeightedResponseTimeRule: 为每个服务器赋予一个权重值, 服务器的响应时间
    越长, 该权重值就是越少, 这个规则会随机选择服务器, 这个权重值有可能会决定
    服务器的选择。
  • ZoneAvoidanceRule: 该规则以区域、 可用服务器为基础, 进行服务器选择。 使用
    Zone 对服务器进行分类, 可以理解为机架或者机房。
  • BestAvailableRule: 忽略“ 短路” 的服务器, 并选择并发数较低的服务器。
  • RandomRule: 顾名思义, 随机选择可用的服务器。
  • RetryRule: 含有重试的选择逻辑, 如果使用 RoundRobinRule 选择服务器无法连
    接, 那么将会重新选择服务器。

与Spring Cloud整合

1:先在pom.xml文件里加入需要的依赖, 关键信息如下:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
    </dependencies>

2:写启动类,需要加入EnableDiscoveryClient人注解。代码如下:

package com.ivan.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * 
 * 功能描述: 
 * 
 * @version 2.0.0
 * @author zhiminchen
 */

@SpringBootApplication
@EnableDiscoveryClient
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
    }
}

3:写个自定义负载的规则,也可以没有这个类,这样默认就是轮询的规则。代码如下:

package com.ivan.consumer.rule;

import java.util.List;

import org.apache.commons.lang.math.RandomUtils;
import org.springframework.stereotype.Component;

import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;

/**
 * 
 * 功能描述: 自定义Rule,80%的概率选第一台服务器,20%的概率选第二台服务器
 * 
 * @version 2.0.0
 * @author zhiminchen
 */
@Component
public class MyRule implements IRule {

    private ILoadBalancer lb;

    @Override
    public Server choose(Object key) {
        List<Server> allServer = lb.getAllServers();
        int value = RandomUtils.nextInt(10);
        Server server = null;
        if (value > 8) {
            server = allServer.get(0);
        } else {
            server = allServer.get(1);
        }
        System.out.println("port is : " + server.getPort());
        return server;
    }

    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        this.lb = lb;
    }

    @Override
    public ILoadBalancer getLoadBalancer() {
        return this.lb;
    }

}

4:编写服务调用者,代码如下:

package com.ivan.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Configuration
public class ConsumerController {
    
    //这个值会自动注入的噢
    @Autowired
    private LoadBalancerClient client;
    
    //这个值也会自动注入的噢
    @Autowired
    private SpringClientFactory factory;

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

    @RequestMapping(value = "/consumer", method = RequestMethod.GET)
    public String consumer() {
        RestTemplate template = getRestTemplate();
        // 根据应用名称调用服务
        String json = template.getForObject("http://provider/provider", String.class);
        return json;
    }
}

代码效果应该是大部分请求会调用到Server为零的服务器上,也就是说我们自定义的Rule起作用了,同时可以看到控制台有相应的输出记录。

源码分析

上面的代码我们会有两个疑问:

  • 为什么我们自定义的Rule,只加上了@Component注解,这个规则便能起作用。
  • SpringClientFactory 与 LoadBalancerClient 这两个类是如何注入到我们自定义的Controller里的。
    因为Spring Cloud是基与Spring Boot进行构建的, 之所以上面的类能够起作用,核心还是因为Spring Boot 的SpringFactoriesLoader 机制在起作用。我们可以找到spring-cloud-netflix-cord的jar包,里面有个spring.factories文件,在这个文件里,我们可以看到会自动加载RibbonAutoConfiguration类。代码如下图所示:


    image.png

    在RibbonAutoConfiguration类里,我们可以看到定义了SpringClientFactory 与 LoadBalancerClient 这两个Bean, 这就解释了为什么我们的应用代码可以注入这两个类了。代码截图如下图所示:


    image.png

    至于我们自定义的Rule能够起作用,是因为我们的Spring容器会扫描当前录与子录的代码,将Component注释的类自动注入到Spring IOC容器类, 如果我们没有配置相应的Rule, Spring Cloud会为我们默认加载一个Rule类,这个类在RibbonClientConfiguration里定义,代码如下图:
    image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 断断续续看Ribbon的源码差不多也有7-8天了,总算告一段落。本文记录了这些天对源码的阅读过程与一些分析理解,如...
    程序猿DD阅读 6,541评论 6 11
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,799评论 6 342
  • 简介 Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix ...
    Chandler_珏瑜阅读 251,007评论 22 183
  • 软件是有生命的,你做出来的架构决定了这个软件它这一生是坎坷还是幸福。 本文不是讲解如何使用Spring Cloud...
    Bobby0322阅读 22,637评论 3 166