- 关于Ribbon
- Ribbon实现负载均衡
- 使用属性自定义Ribbon配置
- 负载均衡策略介绍
上篇已经实现了Eureka Server的高可用,理论上已经使得微服务更加完美,但依旧存在些许问题。例如,来自服务消费者的请求如何分摊到部署了多个实例的服务提供者?分摊规则如何取?本篇即揭开Ribbon面纱。
1、Ribbon简介
Ribbon是Netflix发布的负载均衡器,它可以控制HTTP和TCP客户端的行为规则。Ribbon支持多种负载均衡规则帮助服务消费者根据指定规则分摊请求到服务提供者,如轮询、随机、加权响应时间、区域感知轮询等。Ribbon配合Eureka使用更加的高效,配置也得到的极大的简化,大致架构图如下:
2、Ribbon实现负载均衡
由上面的架构图可以清晰的看出,Ribbon配置在服务消费者端。下面开始配置,pom中配置如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.simons.cn</groupId>
<artifactId>ticket-consumer-ribbon</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.22</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
<scope>provided</scope>
</dependency> <!--Eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<!-- 引入spring cloud的依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 添加spring-boot的maven插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类TicketConsumerRibbonApplication类中的RestTemplate上加上@LoadBalanced注解即可
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class TicketConsumerRibbonApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(TicketConsumerRibbonApplication.class,args);
}
}
修改TicketController类:
import com.simons.cn.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@Slf4j
@RestController
public class TicketController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/ticketpurchase")
public CommonResult purchaseTicket(@RequestParam(required = false, value = "name") String name) {
CommonResult result = restTemplate.getForObject("http://user-provider/" + "getuserinfo?name=" + name, CommonResult.class);
return result;
}
@GetMapping("/loginfo")
public void loginfo() {
ServiceInstance serviceInstance = loadBalancerClient.choose("user-provider");
log.info("host=" + serviceInstance.getHost() + ",port=" + serviceInstance.getPort() + ",serviceid=" + serviceInstance.getServiceId());
}
}
注意: user-provider是用户微服务实例中spring.application.name定义的服务提供者名称,一个名称可对应多个服务提供者实例
测试:
启动多个user-provider-eureka服务
启动discovery-eureka服务
启动ticket-consumer-ribbon服务,每个实例端口要不一致
浏览器多次访问: http://localhost:9000/loginfo
2018-07-20 10:17:44.290 INFO 7032 --- [nio-9000-exec-3] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:45.042 INFO 7032 --- [nio-9000-exec-4] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:45.546 INFO 7032 --- [nio-9000-exec-6] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:46.010 INFO 7032 --- [nio-9000-exec-8] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:46.386 INFO 7032 --- [io-9000-exec-10] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:46.709 INFO 7032 --- [nio-9000-exec-3] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:46.992 INFO 7032 --- [nio-9000-exec-4] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:47.218 INFO 7032 --- [nio-9000-exec-6] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:47.390 INFO 7032 --- [nio-9000-exec-8] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:47.545 INFO 7032 --- [io-9000-exec-10] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:47.718 INFO 7032 --- [nio-9000-exec-3] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:47.870 INFO 7032 --- [nio-9000-exec-4] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:48.024 INFO 7032 --- [nio-9000-exec-8] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:48.192 INFO 7032 --- [io-9000-exec-10] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:48.361 INFO 7032 --- [nio-9000-exec-3] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:48.521 INFO 7032 --- [nio-9000-exec-4] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:48.672 INFO 7032 --- [nio-9000-exec-5] c.simons.cn.controller.TicketController : host=10.200.121.49,port=8001,serviceid=user-provider
可以发现,每访问一次,idea编辑器控制台输出的日志信息中port,也就是每次请求均匀分配到了各个实例上,默认负载规则为轮询,上述代码已实现此功能。
3、使用属性配置自定义Ribbon配置
特定场景下,需要修改负载均衡规则,如不再使用轮询,改为随机,那么结合属性配置的方式这些将变得很简单
在项目ticket-consumer-ribbon中的application.yml中添加服务提供者的ribbon负载均衡规则
user-provider: #这里是用户微服务的节点名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
user-provider同上面的注意点的含义一致,这样便将负载均衡规则改为了随机。
测试步骤同上,依次访问,依次观察ticket-consumer-ribbon微服务实例控制台的log,可以发现负载规则已经不再是轮询,而是随机了,请求随机分配在两个user-provider实例上了。
4、Ribbon提供以下7种负载均衡策略(此表格借阅自他人博客)
策略名 | 策略声明 | 策略描述 | 实现说明 |
BestAvailableRule | public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server |
AvailabilityFilteringRule | public class AvailabilityFilteringRule extends PredicateBasedRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态 |
WeightedResponseTimeRule | public class WeightedResponseTimeRule extends RoundRobinRule | 根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。 | 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择server。 |
RetryRule | public class RetryRule extends AbstractLoadBalancerRule | 对选定的负载均衡策略机上重试机制。 | 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | public class RoundRobinRule extends AbstractLoadBalancerRule | roundRobin方式轮询选择server | 轮询index,选择index对应位置的server |
RandomRule | public class RandomRule extends AbstractLoadBalancerRule | 随机选择一个server | 在index上随机,选择index对应位置的server |
ZoneAvoidanceRule | public class ZoneAvoidanceRule extends PredicateBasedRule | 复合判断server所在区域的性能和server的可用性选择server | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。 |
项目的github:https://github.com/simonsfan/SpringCloud.git
引申阅读:脱离Eureka使用Ribbon