共计 7651 个字符,预计需要花费 20 分钟才能阅读完成。
1:Ribbon 是什么
Ribbon 是一个开源的客户端负载均衡器,由 Netflix 发布,是 SpringCloud-Netflix 中重要的一环,通过它将 Netflix 的中间层服务连接在一起。
Ribbon 客户端组件提供一系列完善的配置项,如连接超时、重试等。简单的说,就是在配置文件中列出 Load Balancer 后面所有的服务,Ribbon 会自动的基于某种规则(如简单轮询,随机连接等)去连接这些服务,也很容易实现自定义的负载均衡算法。
2:Ribbon 能干什么
Ribbon 是在客户端来实现负载均衡的访问服务,主要的功能点:
- 服务发现,发现依赖服务的列表
- 服务选择规则,在多个服务中如何选择一个有效服务
- 服务监听,检测失效的服务,高效剔除失效服务
3:代码实例
加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
官方的 HelloWorld 具体示例参看:
https://github.com/Netflix/ri…
4:参数配置
4.1:概述
对于 Ribbon 参数的 key 以及 value 类型的定义,可以通过 com.netflix.client.config.CommonClientConfigKey 查看
如果你没有配置任何属性,则 ribbon 会使用 com.netflix.client.config.DefaultClientConfigImpl 里的值
4.2:配置方式
Ribbon 的参数配置通常有二种方式:全局配置以及指定客户端配置
- 全局配置的方式很简单只需要使用 ribbon.<key>=<value> 格式进行配置即可。
其中,<key> 代表了 Ribbon 客户端配置的参数名,<value> 则代表了对应参数的值。
比如,可以向下面这样配置 Ribbon 的超时时间:
ibbon.ConnectTimeout=250
全局配置可以作为默认值进行设置,当指定客户端配置了相应的 key 的值时,将覆盖全局配置的内容
- 指定客户端的配置方式
<client>.ribbon.<key>=<value> 的格式进行配置
<client> 表示服务名,比如没有服务治理框架的时候(如 Eureka),需要指定实例清单,如:userService.ribbon.listOfServers=localhost:8080
4.3:配置示例 my.properties:
# Max number of retries on the same server (excluding the first try)
hello-client.ribbon.MaxAutoRetries=1
# Max number of next servers to retry (excluding the first server)
hello-client.ribbon.MaxAutoRetriesNextServer=1
# Whether all operations can be retried for this client
hello-client.ribbon.OkToRetryOnAllOperations=true
# Interval to refresh the server list from the source
hello-client.ribbon.ServerListRefreshInterval=2000
# Connect timeout used by Apache HttpClient
hello-client.ribbon.ConnectTimeout=3000
# Readtimeout used by Apache HttpClient
hello-client.ribbon.ReadTimeout=3000
# Initial list of servers, can be changed via Archaius dynamic property at runtime
#ribbon.listOfServers=localhost:8765
hello-client.ribbon.listOfServers=localhost:8765,localhost:8766
hello-client.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
ConfigurationManager.loadPropertiesFromResources("my.properties"); // 1
System.out.println(ConfigurationManager.getConfigInstance()
.getProperty("hello-client.ribbon.listOfServers"));
RestClient client = (RestClient) ClientFactory.getNamedClient("hello-client"); // 2
HttpRequest request = HttpRequest.newBuilder().uri(new URI("/userServiceProvider?name=cc5")).build(); // 3
for (int i = 0; i < 15; i++) {HttpResponse response = client.executeWithLoadBalancer(request); // 4
System.out.println("Status code for" + response.getRequestedURI() + ":" + response.getStatus());
}
4.4:启动查看日志:
5:Ribbon 核心组件
5.1:Ribbon 核心组件
- IRule:根据特定算法中从服务列表中选取一个要访问的服务
- IPing:后台运行线程,用来判断服务是否可用
- ServerList:服务列表,可以是静态的或动态的。如果它是动态的,后台线程将刷新并按一定的时间间隔过滤列表
- ServerListFilter:该接口允许过滤配置或动态获取的具有所需特性的服务器列表
- ServerListUpdater:被 DynamicServerListLoadBalancer 用于动态的更新服务列表
- IClientConfig:定义各种配置信息,用来初始化 ribbon 客户端和负载均衡器
- ILoadBalancer:定义软件负载平衡器操作的接口。动态更新一组服务列表及根据指定算法从现有服务器列表中选择一个服务
5.2:配置
这些组件可以以编程方式设置,也可以是客户端配置属性的一部分,并通过反射创建。
支持的属性如下所示,应以 <clientName>.ribbon. 为前缀:
- NFLoadBalancerClassName:要使用的 ILoadBalancer
- NFLoadBalancerRuleClassName:要使用的 Irule
- NFLoadBalancerPingClassName:要使用的 Iping
- NIWSServerListClassName:要使用的 ServerList
- NIWSServerListFilterClassName:要使用的 ServerListFilter
5.3:IRule,常用实现有以下几种:
都在 com.netflix.loadbalancer 包下面
1:RoundRobinRule:轮询规则
2:RandomRule:随机获取一个服务
3:AvailabilityFilteringRule 这个负载均衡器规则,会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
4:WeightedResponseTimeRule 根据平均响应时间计算所有服务的权重,响应时间越快,服务权重越大、被选中的概率越高。刚启动时,如果统计信息不足,则使用 RoundRobinRule 策略,等统计信息足够,会切换到 WeightedResponseTimeRule。
5:RetryRule 先按照 RoundRobinRule 的策略获取服务,如果获取服务失败,则在指定时间内会进行重试,获取可用的服务
6:BestAvailableRule 此负载均衡器会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
7:ZoneAvoidanceRule:默认规则, 复合判断 server 所在区域的性能和 server 的可用性选择 server
5.4:IPing,常用实现有以下几种:
都在 com.netflix.loadbalancer 包下面
1:DummyPing:默认实现,默认返回 true,即认为服务永远活着
2:NIWSDiscoveryPing 不执行真正的 ping。如果 Discovery Client 认为是在线,则程序认为本次心跳成功,服务活着
3:PingUrl 此组件会使用 HttpClient 调用服务的一个 URL,如果调用成功,则认为本次心跳成功,表示此服务活着。
4:NoOpPing:永远返回 true,即认为服务永远活着
ServerList,常用实现有以下几种:
都在 com.netflix.loadbalancer 包下面
1:ConfigurationBasedServerList,从配置文件中获取所有服务列表,也是静态获取服务列表,配置例子:sample-client.ribbon.listOfServers=
2:DiscoveryEnabledNIWSServerList 从 Eureka Client 中获取服务列表。此值必须通过属性中的 VipAddress 来标识服务器集群。DynamicServerListLoadBalancer 会调用此对象动态获取服务列表
3:DomainExtractingServerList:代理类,根据 ServerList 的值实现具体的逻辑
5.5:ServerListFilter,常用实现有以下几种:
都在 com.netflix.loadbalancer 包下面
1:ZoneAffinityServerListFilter 过滤掉所有的不和客户端在相同 zone 的服务,如果和客户端相同的 zone 不存在,才不过滤不同 zone 有服务。启用此配置使用以下配置 <clientName>.ribbon.EnableZoneAffinity=true
2:ServerListSubsetFilter ZoneAffinityServerListFilter 的子类。此过滤器确保客户端仅看到由 ServerList 实现返回的整个服务器的固定子集。它还可以定期用新服务器替代可用性差的子集中的服务器。要启用此过滤器,请指定以下属性:
myClient.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
// the server must register itself with Eureka server with VipAddress “myservice”
myClient.ribbon.DeploymentContextBasedVipAddresses=myservice
myClient.ribbon.NIWSServerListFilterClassName=com.netflix.loadbalancer.ServerListSubsetFilter
only show client 5 servers. default is 20.
myClient.ribbon.ServerListSubsetFilter.size=5
5.6:ServerListUpdater,常用实现有以下几种:
都在 com.netflix.loadbalancer 包下面
1:PollingServerListUpdater 默认的实现策略。此对象会启动一个定时线程池,定时执行更新策略
2:EurekaNotificationServerListUpdater 当收到缓存刷新的通知,会更新服务列表。
IClientConfig,常用实现有以下几种:
1:com.netflix.client.config.DefaultClientConfigImplIClientConfig 的默认实现
5.7:ILoadBalancer,常用实现有以下几种:
都在 com.netflix.loadbalancer 包下面
1:DynamicServerListLoadBalancer DynamicServerListLoadBalancer 组合 Rule、IPing、ServerList、ServerListFilter、ServerListUpdater 实现类,实现动态更新和过滤更新服务列表
2:ZoneAwareLoadBalancer 这是 DynamicServerListLoadBalancer 的子类,主要加入 zone 的因素。统计每个 zone 的平均请求的情况,保证从所有 zone 选取对当前客户端服务最好的服务组列表
6:结合 Eureka
6.1:代码示例(准备多个服务注册到 eureka):
1:先加入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
可以不用加入 Ribbon 依赖,因为 Eureka 的 starter 里面包含了 ribbon
2:写一个配置类,提供 RestTemplate,示例如下:
@Bean
@LoadBalanced
public RestTemplate rt(){return new RestTemplate();
}
3:写一个 Hello2 的 Controller,在方法里面使用 RestTemplate
@RestController
public class Hello2 {
@Autowired
private RestTemplate rt;
@Autowired
private LoadBalancerClient lbc;
@RequestMapping("/ribbon2")
public String ribbon2() {String s = rt.getForObject("http://userService/userServiceProvider?name=cc5", String.class);
System.out.println("s==="+s);
return "okok";
}
}
4:启动 SpringBoot 的启动类,然后就可以通过页面进行访问测试了
多次点击:
6.2:说明:
1:RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法,能够大大提高客户端的编写效率。
2:@LoadBalanced 表示 restTemplate 使用 LoadBalancerClient 执行请求,会自动使用负载均衡策略,它内部会在 RestTemplate 中加入 LoadBalancerInterceptor 这个拦截器,这个拦截器的作用就是使用负载均衡。这样在访问远程服务的时候,可以直接使用 Provider 向 Eureka Server 注册的虚拟名字,如:
rt.getForObject("http://userService/userServiceProvider?name=cc", String.class);
3:当 Eureka 与 Ribbon 结合使用(即两者都在类路径上)时,ribbonServerList 将被扩展为 DiscoveryEnabledNIWSServerList,扩展名为 Eureka 的服务器列表。它还用 NIWSDiscoveryPing 替换 IPing 接口,代理到 Eureka 以确定服务器是否启动。
4:可以通过代码来进行配置,比如配置 IRule、IPing 等
5:设置属性 ribbon.eureka.enabled = false 将明确禁用在 Ribbon 中使用 Eureka
7:重试机制
SpringCloud 通过整合 Spring retry 来增强 RestTemplate 的重试能力,对于开发者来说,只需要简单配置,即可完成重试策略,配置示例如下:
// 该参数用来开启重试机制,它默认是关闭的
spring.cloud.loadbalancer.retry.enabled=true
// 断路器的超时时间需要大于 Ribbon 的超时时间,不然不会触发重试
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
// 请求连接超时时间
userService.ribbon.ConnectTimeout=250
// 请求处理的超时时间
userService.ribbon.ReadTimeout=1000
// 对所有操作请求都进行重试
userService.ribbon.OkToRetryOnAllOperations=true
// 切换实例的重试次数
userService.ribbon.MaxAutoRetriesNextServer=2
// 对当前实例的重试次数
userService.ribbon.maxAutoRetries=1
8: @RibbonClient
注解 @RibbonClient,这样做的目的是为了在启动该微服务的时候就能去加载我们的自定义 Ribbon 配置类,从而使配置生效,形如:
@RibbonClient(name=”userService”,configuration=MyConf2.class)
1:这个 userService 不是随便写的,而是注册到 Eureka 发现组件上的微服务服务端,意思是要对所有工程名为 userService 的服务提供者进行负载均衡管理
2:官方文档明确给出了警告:这个测试配置类 MyConf2 不能放在 @ComponentScan 所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的 Ribbon 客户端所共享,也就是说我们达不到特殊化定制的目的了。