SpringCloud的入门学习之概念理解、Ribbon负载均衡入门

1、Ribbon负载均衡,Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端、负载均衡的工具。

   答:简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB,负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

2、Ribbon可以干什么呢?

  答:负载均衡Load Balancer,LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。常见的负载均衡有软件Nginx,LVS,硬件 F5等。相应的在中间件,例如:dubbo和SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。

3、负载均衡(Load Balance)的种类,如下所示。

  1)、集中式LB(偏向于硬件),即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方。
  2)、进程内LB(偏向于软件),将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

4、Ribbon负载均衡的初步使用。修改之前开发的消费端microservicecloud-consumer-dept-80。Ribbon的依赖包,需要和eureka进行整合。

 1 <project xmlns="http://maven.apache.org/POM/4.0.0"
 2     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
 4     http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6     <parent>
 7         <groupId>com.bie.springcloud</groupId>
 8         <artifactId>microservicecloud</artifactId>
 9         <version>0.0.1-SNAPSHOT</version>
10     </parent>
11     <artifactId>microservicecloud-consumer-dept-80</artifactId>
12 
13     <dependencies>
14         <!-- 自己定义的api -->
15         <dependency>
16             <groupId>com.bie.springcloud</groupId>
17             <artifactId>microservicecloud-api</artifactId>
18             <version>${project.version}</version>
19         </dependency>
20         <dependency>
21             <groupId>org.springframework.boot</groupId>
22             <artifactId>spring-boot-starter-web</artifactId>
23         </dependency>
24         <!-- 修改后立即生效,热部署 -->
25         <dependency>
26             <groupId>org.springframework</groupId>
27             <artifactId>springloaded</artifactId>
28         </dependency>
29         <dependency>
30             <groupId>org.springframework.boot</groupId>
31             <artifactId>spring-boot-devtools</artifactId>
32         </dependency>
33         <!-- 将微服务consumer客户端注册进eureka -->
34         <!-- eureka需要依赖如下两个依赖包 -->
35         <dependency>
36             <groupId>org.springframework.cloud</groupId>
37             <artifactId>spring-cloud-starter-eureka</artifactId>
38         </dependency>
39         <dependency>
40             <groupId>org.springframework.cloud</groupId>
41             <artifactId>spring-cloud-starter-config</artifactId>
42         </dependency>
43         <!-- Ribbon相关,需要和eureka进行整合 -->
44         <dependency>
45             <groupId>org.springframework.cloud</groupId>
46             <artifactId>spring-cloud-starter-ribbon</artifactId>
47         </dependency>
48 
49     </dependencies>
50 
51 </project>

修改配置文件application.yml。

1 server:
2   port: 80
3  
4 eureka:
5   client: # 客户端注册进eureka服务列表内
6     register-with-eureka: false # eureka客户端,自己不能注册
7     service-url:
8       defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集。@LoadBalanced注解是Ribbon的负载均衡配置。

 1 package com.bie.config;
 2 
 3 import org.springframework.cloud.client.loadbalancer.LoadBalanced;
 4 import org.springframework.context.annotation.Bean;
 5 import org.springframework.context.annotation.Configuration;
 6 import org.springframework.web.client.RestTemplate;
 7 
 8 /**
 9  * 
10  *
11  * @author biehl
12  * 
13  *         RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,
14  *         是Spring提供的用于访问Rest服务的客户端模板工具集
15  * 
16  */
17 @Configuration
18 public class ConfigBean {
19 
20     @Bean
21     @LoadBalanced // @LoadBalanced是Ribbon的负载均衡配置。
22     // Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端、负载均衡的工具。
23     public RestTemplate getRestTemplate() {
24         return new RestTemplate();
25     }
26 
27 }

修改客户端访问类。修改成微服务的名称,按照名称进行访问微服务。

 1 package com.bie.controller;
 2 
 3 import java.util.List;
 4 
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.web.bind.annotation.PathVariable;
 7 import org.springframework.web.bind.annotation.RequestBody;
 8 import org.springframework.web.bind.annotation.RequestMapping;
 9 import org.springframework.web.bind.annotation.RestController;
10 import org.springframework.web.client.RestTemplate;
11 
12 import com.bie.po.Dept;
13 
14 /**
15  * 
16  * RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问RESTFul服务模板类,
17  * 是Spring提供的用于访问Rest服务的客户端模板工具集
18  *
19  * 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url, requestMap,
20  * ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
21  * 
22  * 
23  * @author biehl
24  *
25  */
26 @RestController
27 public class DeptControllerConsumer {
28 
29     // private static final String REST_URL_PREFIX = "http://localhost:8001";
30     // 修改客户端访问类。修改成微服务的名称,按照名称进行访问微服务.
31     private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-PROVIDER-DEPT";
32 
33     @Autowired
34     private RestTemplate restTemplate;
35 
36     /**
37      * 
38      * @param dept
39      * @return
40      */
41     @RequestMapping(value = "/consumer/dept/add")
42     public boolean addDept(@RequestBody Dept dept) {
43 
44         return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
45     }
46 
47     /**
48      * 注意,从路径中获取到参数值,使用注解@PathVariable
49      * 
50      * @param id
51      * @return
52      */
53     @RequestMapping(value = "/consumer/dept/get/{id}")
54     public Dept getById(@PathVariable(value = "id") Long id) {
55 
56         return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
57     }
58 
59     /**
60      * 
61      * @return
62      */
63     @SuppressWarnings("unchecked")
64     @RequestMapping(value = "/consumer/dept/list")
65     public List<Dept> list() {
66 
67         return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
68     }
69 
70     // 测试@EnableDiscoveryClient,消费端可以调用服务发现
71     @RequestMapping(value = "/consumer/dept/discovery")
72     public Object discovery() {
73         return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery", Object.class);
74     }
75 
76 }

将consumer注册到Eureka Server注册中心。

 1 package com.bie;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
 6 
 7 @SpringBootApplication
 8 @EnableEurekaClient // 将consumer注册到Eureka Server注册中心
 9 public class MicroServiceCloudConsumerApplication {
10 
11     public static void main(String[] args) {
12         SpringApplication.run(MicroServiceCloudConsumerApplication.class, args);
13     }
14 
15 }

将三个节点的Eureka Server启动以后,再启动你的服务提供者、服务消费者。Ribbon和Eureka整合以后服务调用者,调用服务提供者提供的服务不需要再关心地址和端口号了。效果如下所示:

5、Ribbon的负载均衡。Ribbon的架构使用说明,如下所示:

  1)、Ribbon在工作时分成两步。

    a、第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server。
    b、第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

  2)、参考microservicecloud-provider-dept-8001,新建两个子模块工程,分别命名为microservicecloud-provider-dept-8002、microservicecloud-provider-dept-8003。然后参考cloudDB01新建两个数据库cloudDB02、cloudDB03。然后修改microservicecloud-provider-dept-8002、microservicecloud-provider-dept-8003子模块的配置文件application.yml。主要修改的是端口号,数据库链接,对外暴漏的统一的服务实例名称即spring.application.name这个名称要一致。

microservicecloud-provider-dept-8002子模块的配置文件application.yml,内容如下所示:

 1 server:
 2   port: 8002  
 3 
 4 mybatis:
 5   config-location: classpath:mybatis/mybatis.cfg.xml        # mybatis配置文件所在路径
 6   type-aliases-package: com.bie.po                          # 所有实体类别名类所在包
 7   mapper-locations:
 8   - classpath:mybatis/mapper/**/*.xml                       # mapper映射文件  
 9   
10 spring:
11   application:
12     name: microservicecloud-provider-dept                   # 微服务的名称,多节点名称要一致。
13   datasource:
14     type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
15     driver-class-name: org.gjt.mm.mysql.Driver              # mysql驱动包
16     url: jdbc:mysql://localhost:3306/cloudDB02              # 数据库名称
17     username: root
18     password: 123456
19     dbcp2:
20       min-idle: 5                                           # 数据库连接池的最小维持连接数
21       initial-size: 5                                       # 初始化连接数
22       max-total: 5                                          # 最大连接数
23       max-wait-millis: 200                                  # 等待连接获取的最大超时时间
24 
25 eureka:
26   client: # 客户端注册进eureka服务列表内
27     service-url:
28       # defaultZone: http://localhost:7001/eureka
29       defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
30   instance: 
31     instance-id: microservicecloud-provider-dept8002 # 将eureka-server注册中心的服务,显示你想看的名称
32     prefer-ip-address: true # 访问路径可以显示IP地址    
33 
34      
35              
36 info: # 微服务info内容显示详细信息
37   app.name: microservicecloud-provider-dept # 应用名称
38   company.name: www.baidu.com # 公司地址
39   build.artifactId: $project.artifactId$  # 构建项目artifactId
40   build.version: $project.version$ # 构建项目版本号
41       

microservicecloud-provider-dept-8003子模块的配置文件application.yml,内容如下所示:

 1 server:
 2   port: 8003
 3 
 4 mybatis:
 5   config-location: classpath:mybatis/mybatis.cfg.xml        # mybatis配置文件所在路径
 6   type-aliases-package: com.bie.po                          # 所有实体类别名类所在包
 7   mapper-locations:
 8   - classpath:mybatis/mapper/**/*.xml                       # mapper映射文件  
 9   
10 spring:
11   application:
12     name: microservicecloud-provider-dept                   # 微服务的名称
13   datasource:
14     type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
15     driver-class-name: org.gjt.mm.mysql.Driver              # mysql驱动包
16     url: jdbc:mysql://localhost:3306/cloudDB03                # 数据库名称
17     username: root
18     password: 123456
19     dbcp2:
20       min-idle: 5                                           # 数据库连接池的最小维持连接数
21       initial-size: 5                                       # 初始化连接数
22       max-total: 5                                          # 最大连接数
23       max-wait-millis: 200                                  # 等待连接获取的最大超时时间
24 
25 eureka:
26   client: # 客户端注册进eureka服务列表内
27     service-url:
28       # defaultZone: http://localhost:7001/eureka
29       defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
30   instance: 
31     instance-id: microservicecloud-provider-dept8003 # 将eureka-server注册中心的服务,显示你想看的名称
32     prefer-ip-address: true # 访问路径可以显示IP地址    
33 
34      
35              
36 info: # 微服务info内容显示详细信息
37   app.name: microservicecloud-provider-dept # 应用名称
38   company.name: www.baidu.com # 公司地址
39   build.artifactId: $project.artifactId$  # 构建项目artifactId
40   build.version: $project.version$ # 构建项目版本号
41       

  3)、最后启动3个节点的Eureka Server注册中心集群,启动3个节点的服务提供者,测试三个服务提供者都可以正常访问数据库。启动服务的消费者,客户端通过Ribbon完成负载均衡并访问三个节点的服务提供者,主要观察每次返回的结果,各不相同,完成负载均衡的效果。

启动3个节点的服务提供者,测试三个服务提供者都可以正常访问数据库。如下所示:

启动3个节点的Eureka Server注册中心集群,如下所示:

启动服务的消费者,客户端通过Ribbon完成负载均衡并访问三个节点的服务提供者,主要观察每次返回的结果,各不相同,完成负载均衡的效果。

  

  4)、总结,Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所请求的客户端结合使用,和Eureka结合只是其中的一个实例。

6、Ribbon的核心组件IRule,IRule根据特定算法中从服务列表中选取一个要访问的服务。七种默认算法如下所示:

  1)、RoundRobinRule轮询算法。一个节点一次。
  2)、RandomRule随机算法。随机选择一个节点。
  3)、AvaliabilityFilteringRule会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阀值的服务,然后对剩余的服务列表按照轮询策略进行访问。
  4)、WeightedResponseTimeRule根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动的时候如果统计信息不足,则使用  5)、RoundRobinRule策略,等统计信息足够。会切换到WeightedResponseTimeRule。
  6)、RetryRule先按照RoundRobinRule的策略的获取服务,如果获取服务失败,则在指定的时候内会重试,获取可用的服务。
  7)、BestAvaliableRule会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。
  8)、ZoneAvoidanceRule默认规则,复合判断server所在区域的性能和server的可用性选择服务器。

在消费端,可以在初始化配置文件里面,将默认的轮询算法,修改为自己想使用的默认算法。

 1 package com.bie.config;
 2 
 3 import org.springframework.cloud.client.loadbalancer.LoadBalanced;
 4 import org.springframework.context.annotation.Bean;
 5 import org.springframework.context.annotation.Configuration;
 6 import org.springframework.web.client.RestTemplate;
 7 
 8 import com.netflix.loadbalancer.IRule;
 9 import com.netflix.loadbalancer.RetryRule;
10 
11 /**
12  * 
13  *
14  * @author biehl
15  * 
16  *         RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,
17  *         是Spring提供的用于访问Rest服务的客户端模板工具集
18  * 
19  */
20 @Configuration
21 public class ConfigBean {
22 
23     @Bean
24     @LoadBalanced // @LoadBalanced是Ribbon的负载均衡配置。
25     // Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端、负载均衡的工具。
26     public RestTemplate getRestTemplate() {
27         return new RestTemplate();
28     }
29 
30     // 默认是轮询算法,这里可以选择其他算法,方法如下所示:
31     public IRule iRule() {
32         // 使用随机算法替换默认的轮询算法。
33         // return new RandomRule();
34 
35         // 先按照RoundRobinRule的策略的获取服务,如果获取服务失败,则在指定的时候内会重试,获取可用的服务。
36         return new RetryRule();
37     }
38 
39 }

7、自定义Ribbo的负载均衡策略,不再使用七种默认的算法。使用自定义的算法。

  注意:官方文档明确给出了警告,这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类会被所有的Ribbon客户端所共享,也就是说我们达不到特殊定制的目的了。

开始修改主启动类,添加@RibbonClient注解,指定服务提供者的项目名称,以及提供自定义的算法策略,如下所示:

 1 package com.bie;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
 6 import org.springframework.cloud.netflix.ribbon.RibbonClient;
 7 
 8 import com.iRule.RibbonSelfRule;
 9 
10 /**
11  * 官方文档明确给出了警告,这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
12  * 否则我们自定义的这个配置类会被所有的Ribbon客户端所共享,也就是说我们达不到特殊定制的目的了。
13  *
14  * @author biehl
15  *
16  */
17 @SpringBootApplication
18 @EnableEurekaClient // 将consumer注册到Eureka Server注册中心
19 @RibbonClient(name = "MICROSERVICECLOUD-PROVIDER-DEPT", configuration = RibbonSelfRule.class)
20 // 向启动类添加@RibbonClient。在启动该微服务的时候就能加载我们自定义的Ribbon配置类,从而使配置生效。
21 public class MicroServiceCloudConsumerApplication {
22 
23     public static void main(String[] args) {
24         SpringApplication.run(MicroServiceCloudConsumerApplication.class, args);
25     }
26 
27 }

可以先使用Ribbon提供的默认算法,测试通过这种方法,是否可以使用自定义算法策略。可以自己进行测试,这里省略。

 1 package com.iRule;
 2 
 3 import org.springframework.context.annotation.Bean;
 4 import org.springframework.context.annotation.Configuration;
 5 
 6 import com.netflix.loadbalancer.IRule;
 7 import com.netflix.loadbalancer.RandomRule;
 8 
 9 /**
10  * 
11  *
12  * @author biehl
13  * 
14  *         官方文档明确给出了警告,
15  * 
16  *         这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
17  * 
18  *         否则我们自定义的这个配置类会被所有的Ribbon客户端所共享,
19  * 
20  *         也就是说我们达不到特殊定制的目的了。
21  *
22  * 
23  */
24 @Configuration
25 public class RibbonSelfRule {
26 
27     @Bean
28     public IRule iRule() {
29         // Ribbon默认是轮询,这里自定义是随机。
30         return new RandomRule();
31     }
32 
33 }

 可以参考官网的案例,进行升级和改造,如下所示:

  1 package com.iRule;
  2 
  3 import java.util.List;
  4 import java.util.Random;
  5 
  6 import com.netflix.client.config.IClientConfig;
  7 import com.netflix.loadbalancer.AbstractLoadBalancerRule;
  8 import com.netflix.loadbalancer.ILoadBalancer;
  9 import com.netflix.loadbalancer.Server;
 10 
 11 /**
 12  * 
 13  *
 14  * @author biehl
 15  * 
 16  *         随机算法,从官方网址拷贝的。
 17  *
 18  */
 19 public class RandomRule_ZDY extends AbstractLoadBalancerRule {
 20 
 21     // 创建一个Random对象
 22     Random rand;
 23 
 24     public RandomRule_ZDY() {
 25         rand = new Random();
 26     }
 27 
 28     /**
 29      * Randomly choose from all living servers
 30      * 
 31      * 返回给具体服务的那一个服务器
 32      * 
 33      */
 34     // @edu.umd.cs.findbugs.annotations.SuppressWarnings(value =
 35     // "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
 36     public Server choose(ILoadBalancer lb, Object key) {
 37         // 判断如果是null,就返回null
 38         if (lb == null) {
 39             return null;
 40         }
 41         // 定义服务器Server类
 42         Server server = null;
 43 
 44         // 循环判断server是否等于null,server初始值是null
 45         while (server == null) {
 46             // 判断线程是否中断,如果中断返回null
 47             if (Thread.interrupted()) {
 48                 return null;
 49             }
 50             // 获取到活的可达的服务器节点。活着的可以提供服务的服务器。
 51             List<Server> upList = lb.getReachableServers();
 52             // 获取到所有的服务器节点
 53             List<Server> allList = lb.getAllServers();
 54 
 55             // 获取到所有服务器节点的个数
 56             int serverCount = allList.size();
 57             // 如果获取到的所有服务器节点的个数等于0,就返回null
 58             if (serverCount == 0) {
 59                 /*
 60                  * No servers. End regardless of pass, because subsequent passes only get more
 61                  * restrictive.
 62                  */
 63                 return null;
 64             }
 65 
 66             // 如果获取到的所有服务器节点不为0,就随机获取一个索引。
 67             int index = rand.nextInt(serverCount);
 68             // 将获取到的index从活的可达的服务器中获取到这个服务器
 69             server = upList.get(index);
 70 
 71             // 如果获取到的这个服务器为null
 72             if (server == null) {
 73                 /*
 74                  * The only time this should happen is if the server list were somehow trimmed.
 75                  * This is a transient condition. Retry after yielding.
 76                  */
 77                 // 暂停当前正在执行的线程对象,并执行其他线程,就是进入就绪状态
 78                 Thread.yield();
 79                 continue;
 80             }
 81             
 82             // 如果是活的可以提供服务的,就返回该服务器。
 83             if (server.isAlive()) {
 84                 return (server);
 85             }
 86             
 87             // Shouldn't actually happen.. but must be transient or a bug.
 88             server = null;
 89             Thread.yield();
 90         }
 91 
 92         return server;
 93 
 94     }
 95 
 96     @Override
 97     public Server choose(Object key) {
 98         return choose(getLoadBalancer(), key);
 99     }
100 
101     @Override
102     public void initWithNiwsConfig(IClientConfig clientConfig) {
103         // TODO Auto-generated method stub
104 
105     }
106 }

作者:别先生

博客园:https://www.cnblogs.com/biehongli/

如果您想及时得到个人撰写文章以及著作的消息推送,可以扫描上方二维码,关注个人公众号哦。

猜你喜欢

转载自www.cnblogs.com/biehongli/p/11910050.html