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