一、Ribbon简介
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。
- 客户端负载均衡
负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们通常所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。
硬件负载均衡主要通过在服务器节点之间按照专门用于负载均衡的设备,比如F5等;
而软件负载均衡则是通过在服务器上安装一些用于负载均衡功能或模块等软件来完成请求分发工作,比如Nginx等。不论采用硬件负载均衡还是软件负载均衡,只要是服务端都能以类似下图的架构方式构建起来:
而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端端清单来自于服务注册中心,比如Eureka服务端。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,默认会创建针对各个服务治理框架的Ribbon自动化整合配置,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。
在实际使用的时候,我们可以通过查看这两个类的实现,以找到它们的配置详情来帮助我们更好地使用它。
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
▪️服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
▪️服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。
文档地址:
https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html
二、Maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
三、application.properties配置
server.port=8002
eureka.register.port=8761
#服务实例名
#eureka.instance.hostname=goods-service
#服务名,如果没有则为 unknown
spring.application.name=goods-service
eureka.register.host=localhost
eureka.client.serviceUrl.defaultZone=http\://${eureka.register.host}\:${eureka.register.port}/eureka/
#显示IP
eureka.instance.prefer-ip-address=true
# 注2.0 必须为ip-address
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则)
eureka.instance.lease-renewal-interval-in-seconds =3
四、服务调用
- Ribbon配置
@Configuration
public class RibbonConfig {
@Bean // 初始化 Bean
@LoadBalanced // 实现负载均衡
RestTemplate restTemplate(){
return new RestTemplate();
}
}
- 创建控制器
@RestController
@Slf4j
public class GoodsController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/getGoods")
public String getGoods() {
String message = restTemplate.getForObject("http://USER-SERVICE/getUser", String.class);
log.info(message);
return message;
}
}
五、负载均衡测试
-
启动多个服务实例,端口分别为8001 8005 8006
启动之前先将服务提供,启动设置单例关掉
-
消费测试
六、负载均衡器
虽然Spring Cloud 中定义了LoadBalancerClient作为负载均衡器的通用接口,并且针对Ribbon实现了RibbonLoadBalancerClient,但是它在具体体实现客户端负载均衡时,是通过Ribbon的ILoadBalancer接口实现的。
- AbstractLoadBalancer 抽象类
AbstractLoadBalancer是ILoadBalancer接口的抽象实现。在该抽象类中定义了一个关于服务实例的分组枚举类ServerGroup,它包含三种不同类型。
ALL :所有服务实例。
SATUS_UP:正常服务的实例。
SATUS_NOT_UP:停止服务的实例。
另外,还实现了一个chooseServer()函数,该函数通过调用接口中的chooseServer(Object key)实现,其中参数key为null,表示在选择具体服务实例时忽略key的条件判断。
最后,还定义了两个抽象函数。
getServerList(ServerGroup serverGroup):定义了根据分组类型来获取不同的服务实例的列表。
getLoadBalancerStats对象存储负载均衡器中各个服务实例当前的属性和统计信息。 - BaseLoadBalancer 实现类
BaseLoadBalancer类是Ribbon负载均衡器的基础实现类,在该类中定义了很多关于负载均衡器相关的基础内容。
定义并维护了两个存储服务实例Server对象的列表。一个用户存储所有服务实例的清单,一个用户存储正常服务的实例清单。
@Monitor(name=PREFIX + "AllServerList",type=DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>());
@Monitor(name=PREFIX + "UpServerList",type=DataSurceType.INFORMATIONAL)
protected volatile List<Server> upServerList = Collections.synchronizedList(new ArrayList<Server>());
定义了之前我们提到的用来存储负载均衡器各服务实例属性和统计信息的LoadBalancerStats对象。
定义了检查服务实例是否正常服务的IPing对象,在BaseLoadBalancer中默认为null,需要在构造时注入它的具体实现。
定义了检查服务实例操作的执行策略对象IPingStrategy,在BaseLoadBalancer中默认使用了该类中定义的静态内部类SerialPingStrategy实现。
定义了负载均衡的处理规则IRule对象。从BaseLoadBalancer中chooseServer(Object key)的实现源码,可以知道,负载均衡实际将服务实例选择人物委托给了IRule实例中的choose函数来实现。在这里,默认初始化了RoundRobinRule为IRule的实现对象。RouleRobinRule实现了最基本且常用的线性负载均衡规则。
启动ping任务。
实现了ILoadBalancer接口定义的负载均衡器应具备的一系列操作。
- DynamicServerListLoadBalancer
DynamicServerListLoadBalancer类继承于BaseLoadBalancer类,它是对基础负载均衡器的扩展。在该负载均衡器中,实现了服务清单在运行期的动态更新功能;同时,它还具备了对服务实例清单的过滤功能,也就是说,我们可以通过过滤器来选择性获取一批服务实例清单。
七、负载均衡策略
Ribbon中实现了非常多的选择策略(负载均衡中的服务实例选择策略),其中包括的RoundRobinRule和ZoneAvoidanceRule。
- 自动化配置:
由于Ribbon中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有一定的依赖关系,使得Ribbon开发者很难上手。Spring Cloud Ribbon中的自动化配置能够解决这种问题。就能自动化构架下面这些接口的实现。
IClientConfig:Ribbon的客户端配置,默认采用com.netflix.client.config.DefaultClientConfigImpl实现。
IRule:Ribbon的负载均衡策略,默认采用com.netflix.loadbalancer.ZoneAvoidanceRule实,该策略能够在多区域环境下选出最佳区域的实例进行访问。
IPing:Ribbon的实例检查策略,默认采用com.netflix.loadbalancer.NoOpPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。
ServerList:服务实例清单的维护机制,默认采用com.netflix.loadbalancer.ConfigurationBasedServerList实现。
ServerListFilter:服务实例清单过滤机制,默认采用org.springframework.cloud.netflix.ribbon.ZonePrefenceServerListFilter实现,该策略能够优先过滤出与请求调用方处于同区域的服务实例。
ILoadBalancer:负载均衡器,默认采用com.netflix.loadbalancer.ZoneAwareLoadBalancer实现,它具备了区域感知的能力。
通过自动化配置的实现,可以轻松地实现客户端负载均衡。同时,针对一些个性化需求,也可以方面替换上面的默认实现。只需要在Spring Boot应用中创建对应的实现实例就能覆盖这些默认的配置实现。
另外,也可以通过使用@RibbonClient注解来实现更细粒度的客户端配置。
com.netflix.client.config.IClientConfig:Ribbon的客户端配置,默认采用com.netflix.client.config.DefaultClientConfigImpl实现。
com.netflix.loadbalancer.IRule:Ribbon的负载均衡策略,默认采用com.netflix.loadbalancer.ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。
com.netflix.loadbalancer.IPing:Ribbon的实例检查策略,默认采用com.netflix.loadbalancer.NoOpPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。
com.netflix.loadbalancer.ServerList:服务实例清单的维护机制,默认采用com.netflix.loadbalancer.ConfigurationBasedServerList实现。
com.netflix.loadbalancer.ServerListFilter:服务实例清单过滤机制,默认采E用org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter,该策略能够优先过滤出与请求方处于同区域的服务实例。
com.netflix.loadbalancer.ILoadBalancer:负载均衡器,默认采用com.netflix.loadbalancer.ZoneAwareLoadBalancer实现,它具备了区域感知的能力。
- Ribbon提供的主要负载均衡策略
简单轮询负载均衡(RoundRobin)
以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
加权响应时间负载均衡 (WeightedResponseTime)
随机负载均衡 (Random)
Ribbon自带负载均衡策略比较:
策略名 | 策略声明 | 策略描述 | 实现说明 |
---|---|---|---|
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。 |
- 手动配置示例
通过自动化配置的实现,可以轻松的实现客户端的负载均衡。同时,针对一些个性化需求,我们可以方便的替换上面的这些默认实现,只需要在springboot应用中创建对应的实现实例就能覆盖这些默认的配置实现。
@Configuration
public class MyRibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
这样就会使用P使用了RandomRule实例替代了默认的com.netflix.loadbalancer.ZoneAvoidanceRule。
也可以使用@RibbonClient注解实现更细粒度的客户端配置
八、参数配置
对于Ribbon的参数通常有二种方式:全局配置以及指定客户端配置
全局配置的方式很简单
只需要使用ribbon.<key>=<value>格式进行配置即可。其中,<key>代表了Ribbon客户端配置的参数名,<value>则代表了对应参数的值。比如,我们可以想下面这样配置开启Ribbon的饥饿加载模式:
#开启Ribbon的饥饿加载模式
ribbon.eager-load.enabled=true
# 指定需要饥饿加载的客户端名称、服务名
ribbon.eager-load.clients=goods-service, user-service
通过上面的配置完成之后,我们尝试重启一下服务消费者,这个时候我们会发现,我们没有开始调用服务接口,但是上面初始化负载均衡的日志就已经打印出来了。这就说明我们对ribbon的饥饿加载模块设置已经生效了。
全局配置可以作为默认值进行设置,当指定客户端配置了相应的key的值时,将覆盖全局配置的内容
指定客户端的配置方式
<client>.ribbon.<key>=<value>的格式进行配置.<client>表示服务名,比如没有服务治理框架的时候(如Eureka),我们需要指定实例清单,可以指定服务名来做详细的配置。
# 2.01.5 已取消
user-service.ribbon.listOfServers=localhost:8080,localhost:8081,localhost:8082
九、常见错误:
- 找不到服务提供实例
检查服务提供者的配置:
spring.application.name=user-service
- 不能启动多个实例
1)启动之前先将服务提供,启动设置单例关掉
2)去掉依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
十、详解 RestTemplate
RestTemplate定义了36个与REST资源交互的方法,其中的大多数都对应于HTTP的方法。
其实,这里面只有11个独立的方法,其中有十个有三种重载形式,而第十一个则重载了六次,这样一共形成了36个方法。
delete() 在特定的URL上对资源执行HTTP DELETE操作
exchange()
在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中
映射得到的
execute() 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
getForObject() 发送一个HTTP GET请求,返回的请求体将映射为一个对象
postForEntity()
POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得
到的
postForObject() POST 数据到一个URL,返回根据响应体匹配形成的对象
headForHeaders() 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头
optionsForAllow() 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息
postForLocation() POST 数据到一个URL,返回新创建资源的URL
put() PUT 资源到特定的URL
RestTemplate 的get方法有以上几个,可以分为两类: getForEntity() 和 getForObject()
- getForEntity
服务提供:
服务消费:
- getForObject
服务提供:
服务消费:
测试:
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/get/balance")
public String getBalance() {
ServiceInstance instance = loadBalancerClient.choose("USER-SERVICE");
log.info(instance.getScheme());
log.info("主机:", instance.getHost());
log.info("实例ID:", instance.getInstanceId());
log.info("服务ID:", instance.getServiceId());
log.info("端口号:{}", instance.getPort());
log.info("地址:{}", instance.getUri());
return instance.getPort()+"";
}