上次我们了解了eureka(参见Greenwich.SR2版本的Spring Cloud Eureka实例),里面的服务消费方我们其实已经用到了ribbon。在pom里我们引入了
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
在主类中我们通过RestTemplate的@LoadBalanced注解启动了ribbon
@LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
因此,ribbon实例我们上次其实已经实现了,只要我们启动两个服务方实例(如何启动多个实例参见IDEA同一项目启动多个实例),不停的刷新浏览器中消费方的请求链接(http://localhost:8763/sayHello?name=world),你会发现端口号是顺序变化的:
这是因为ribbon默认的负载均衡策略就是轮询。
Spring Cloud Ribbon是一个客户端负载均衡工具,与传统的服务端负载均衡(如Nginx)不同。首先服务端负载均衡需要一个独立的服务器来路由客户端请求到服务端去,另外客户端负载均衡需要客户端自己去维护服务实例的列表。或者可以这么理解,客户端里集成了一个服务端的负载均衡就成了客户端负载均衡了。那么客户端如何查询并维护(心跳)这份服务列表呢?它只能去找注册中心Eureka求助了。Spring Cloud微服务之间的调用不是rpc而是http,我们的客户端调服务端(或者说服务的消费方调提供方)是通过REST模板请求来进行的,因此我们的客户端负载均衡也离不开REST。
那么ribbon的客户端负载均衡是如何通过REST实现的呢?首先从服务列表中根据负载均衡策略选出一个指定的服务实例,再根据服务实例进行http调用。我们看下ribbon给我提供的负载均衡策略都有哪些:
策略名 | 策略描述 |
BestAvailableRule | 选择一个最小的并发请求的服务实例 |
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为熔断的服务实例,并过滤掉那些高并发超过一定阈值的服务实例 |
WeightedResponseTimeRule | 根据响应时间分配一个权重区间,响应时间越长,权重区间越窄,被选中的可能性越低 |
RetryRule | 对选定的负载均衡策略机上重试机制 |
RoundRobinRule | 方式轮询选择服务实例 |
RandomRule | 随机选择一个服务实例 |
ZoneAvoidanceRule | 复合判断服务实例所在区域的性能和可用性进行服务实例选择 |
如果你想定制自己的负载均衡策略也是可以的。接下来我们看下,ribbon如果脱离注册中心和REST如何实现负载均衡。
1、application新增如下配置:
#关闭eureka ribbon.eureka.enabled=false #配置服务实例a-bootiful-client的服务列表 a-bootiful-client.ribbon.listOfServers=localhost:8762,localhost:8772 #配置服务实例a-bootiful-client的负载均衡策略 a-bootiful-client.ribbon.NFLoadBalanceRuleClassName=com.netflix.loadbalancer.RandomRule
2、去掉主类的REST的@LoadBalance注解
// @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
3、获取负载均衡处理后选中的服务实例,并解析服务实例ip和port进行调用
@Autowired private LoadBalancerClient loadBalancerClient; @Override public String call(String name) { // 获取负载均衡策略选择后的实例 ServiceInstance instance = loadBalancerClient.choose("a-bootiful-client"); // 不再根据服务实例名调用,而是通过ip和端口调用 ResponseEntity resultResponseEntity = restTemplate.postForEntity("http://" + instance.getHost() + ":" + instance.getPort() + "/hello?name=" + name, null, String.class); if (resultResponseEntity != null && resultResponseEntity.getBody() != null) { return name + " says: " + resultResponseEntity.getBody().toString(); } return null; }
原文地址:https://www.cnblogs.com/wuxun1997/p/11212997.html