1.Ribbon是什么
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端,一套基于HTTP和TCP的客户端负载均衡工具。
Ribbon是Netflix发布的开源项目,通过Spring Cloud的封装,可以轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。
Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括Feign,它也是基于Ribbon实现的工具。
2.Ribbon的作用(负载均衡LB介绍)
如上所说,微服务间的调用通过Ribbon来实现,可以实现客户端负载均衡的服务调用,即负载均衡LB(Load Balance)
负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。通常所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。
服务端负载均衡(集中式LB)
服务端负载均衡也可以叫做集中式LB
就是在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;
- 硬件负载均衡
主要通过在服务器节点之间按照专门用于负载均衡的设备,比如F5
- 软件负载均衡
通过在服务器上安装一些用于负载均衡功能或模块等软件来完成请求分发工作,比如Nginx
上面所说两种类型的负载均衡设备都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端端地址,然后进行转发。
客户端负载均衡(进程内LB)
客户端负载均衡也可以叫做进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
所以!!
客户端负载均衡和服务端负载均衡最大的不同是:服务清单所存储的位置。
在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端端清单来自于服务注册中心,即Eureka服务注册中心或者Zookeper等等。
同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性。默认会创建针对各个服务治理框架的Ribbon自动化整合配置。
比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration。
Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
▪️服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
▪️服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。
3.Ribbon的初步实践
pom.xml
<!-- Ribbon相关 -->
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
修改application.yml 追加eureka的服务注册地址 Ribbon的使用依赖Eureka
与Eureka整合
实现进程内LB下挂可用的服务端清单
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
对ConfigBean进行新注解@LoadBalanced 获得Rest时加入Ribbon的配置
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
为了能和Eureka Server服务注册中心保持心跳维护服务端清单的健康性,要注册启动类成为eurekaClient
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
//在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效,对应的是微服务名,即spring.application.name
@RibbonClient(name="MICROSERVICECLOUD-DEPT")
public class DeptConsumer80_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptConsumer80_App.class, args);
}
}
开始时间客户端访问类
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class DeptController_Consumer
{
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/dept/add")
public boolean add(Dept dept)
{
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add", dept, Boolean.class);
}
@RequestMapping(value="/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id)
{
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id, Dept.class);
}
@SuppressWarnings("unchecked")
@RequestMapping(value="/consumer/dept/list")
public List<Dept> list()
{
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list", List.class);
}
}
GET请求
在RestTemplate中,对GET请求可以通过两个方法进行调用实现。
- getForEntity函数。
该方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装,其中主要存储了HTTP的几个重要元素,比如HTTP请求状态码的枚举对象HttpStatus(也就是我们常说的404、500这些错误码)、在它的父类HttpEntity中还存储着HTTP请求的头信息对象HttpHeaders以及泛型类型的请求体对象
getForEntity(String url, Class responseType,Object... urlVariables);
getForEntity(String url, Class responseType, Map urlVariables);
getForEntity(URI url, Class responseType)
- getForObject函数。
该方法可以理解为对getForEntity的进一步封装,它通过HttpMessageConverterExtractor对HTTP的请求响应体body内容进行对象转换,实现请求直接返回包装好的对象内容。 当不需要关注请求响应除body外的其他内容时,可以使用该函数,可以少一个从Response中获取body的步骤。
▪️getForObject(String url, Class responseType, Object ... urlVariables)
▪️getForObject(String url, Class responseType, Map urlVariables)
▪️getForObject(URI url, Class responseType)
POST 请求
RestTemplate中,对POST请求时可以通过三个方法调用实现。
- postForEntity函数
该方法同GET请求中的getForEntity类似,会在调用后返回ResponseEntity对象,其中T为请求响应的body类型。
postForEntity(String url, Object request, Class responseType, Object... uriVariables)
postForEntity(String url, Object request, Class responseType, Map uriVariables)
postForEntity(URI url, Object request, Class responseType)
-
postForObject函数。
该方法也跟getForObject的类型类似,它的作用就是简化postForEntity的后续处理。通过直接将请求响应的body内容包装成对象来返回使用,
postForObject(String url, Object request, Class responseType, Object... uriVariables)
postForObject(String url, Object request, Class responseType, Map uriVariables)
postForObject(URI url, Object request, Class responseType)
- postForLocation函数。
该方法实现了以POST请求提交资源,并返回新的资源的URI。
▪️postForLocation(String url, Object request, Object... uriVariables)
▪️postForLocation(String url, Object request, Map uriVariables)
▪️postForLocation(URI url, Object request)
由于postForLocation函数会返回新资源的URI,该URI就相当于指定了返回类型
Ribbon负载均衡
Ribbon负载均衡要求服务提供者提供集群搭建才能看到效果,而Ribbon只要注册在了Eureka,服务提供者使用统一的spring.application.name。即能看到效果。
@LoadBalanced
该注解用来给RestTemplate标记,以使用负载均衡的客户端(LoadBalancerClient)来配置它。
使用添加@LoadBalanced注解后的RestTemplate调用服务提供者的接口时,可以使用虚拟IP替代真实IP地址。
Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心ip地址和端口号
Ribbon自带负载均衡策略比较
Ribbon提供的主要负载均衡策略介绍,其中Ribbon默认使用简单轮询的负载均衡策略
1:简单轮询负载均衡(RoundRobin)
以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
2:随机负载均衡 (Random)
随机选择状态为UP的Server
3:加权响应时间负载均衡 (WeightedResponseTime)
根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。
4:区域感知轮询负载均衡(ZoneAvoidanceRule)
复合判断server所在区域的性能和server的可用性选择server
策略名 | 策略声明 | 策略描述 | 实现说明 |
---|---|---|---|
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。 |
切换Ribbon的负载均衡机制
首先要知道Ribbon的IRule是一个接口类,是Ribbon负载均衡器负责选择怎样的负载均衡算法
它的实现类有下面至少这些
那么怎么切换负载均衡算法呢,只需要在配置文件里面配置好IRule已经实现好的负债均衡算法,就可以切换算法。
格式如下
{服务提供者名称}:
ribbon:
NFLoadBalancerRuleClassName:{IRule的实现类}
当然,你也可以自定义一个负载均衡算法,指定调用某个服务的时候使用这种算法,具体可看此文章link