美文网首页Spring Cloud Java 杂谈阿里云
Ribbon与Spring cloud整合源码分析

Ribbon与Spring cloud整合源码分析

作者: 良辰美景TT | 来源:发表于2018-07-05 18:16 被阅读7次

    简价

    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

    相关文章

      网友评论

        本文标题:Ribbon与Spring cloud整合源码分析

        本文链接:https://www.haomeiwen.com/subject/geiuuftx.html