Ribbon
Spring Cloud Ribbon是基于Netlix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。
概述
负载均衡(LB,Load Balance)是什么
-
就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)
-
常见的负载均衡有软件Nginx、LVS、硬件F5等。
-
分为集中式LB,与进程内LB
集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;
进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一 个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon本地负载均衡客户端与Nginx服务端负载均衡区别
-
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。
-
Ribbon是本地负载均衡,在调用微服务接口时候,会在注册中心上获取到注册信息服务列表,之后将其缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
架构说明
Ribbon在工作时分成两步
- 先选择EurekaServer ,它优先选择在同一个区域内负载较少的server.
- 再根据户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
pom
Eureka Client依赖自带了Ribbon,不需要再额外添加依赖。
再谈RestTemplate
@GetMapping(value = "/consumer/payment/createForEntity")
public CommonResult<Payment> create2(Payment payment) {
// 返回对象为ResponseEntity对象,与getForEntity效果一样。
return restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class).getBody();
}
@GetMapping(value = "/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
// 返回对象为ResponseEntity对象, 包含了响应中的一些重要信息,如响应头、响应状态码、响应体等。
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
} else {
return new CommonResult(444, "操作失败");
}
}
Ribbon核心组件IRule
实现类 | 作用 |
---|---|
com.netflix.loadbalancer.RoundRobinRule | 轮询 |
com.netflix.loadbalancer.RandomRule | 随机 |
com.netflix.loadbalancer.RetryRule | 先按照RoundRobinRule的策略获取服务,如果获取服务失败,在指定时间内会进行重试 |
WeightedResponseTimeRule | 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择 |
BestAvailableRule | 会先过滤掉由于多次访问故障,而处于断路器跳闸状态的服务,然后选择一个并 发量最小的服务 |
AvailabilityFilteringRule | 先过滤掉故障实例,再选择并发较小的实例 |
ZoneAvoidanceRule | 默认规则,复合判断server所在区域的性能和server的可用性选择服务器 |
Ribbon负载规则替换
1.修改cloud-consumer-order80
2.新建package
com.indi.rule
3.为其创建MySelfRule规则类
package com.indi.rule;
@Configuration
public class MySelfRule{
@Bean
public IRule myRule(){
return new RandomRule(); // 定义为随机
}
}
4.主启动类添加@RibbonClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE" , configuration = MySelfRule.class)
5.测试
http://localhost/consumer/payment/get/1
Ribbon负载均衡算法
原理
/**
* 负载均衡算法:
* 实际调用的服务器下标 = 第几次Rest请求 % 服务器集群数量
* 每次服务重新启动后,rest接口计数从1开始
*
* 按照轮询算法原理:
* 第1次Rest请求时: 1 % 2 = 1对应下标位置为1,则获得服务地址为127.0.0.1:8001
* 第2次Rest请求时: 2 % 2 = 0对应下标位置为0,则获得服务地址为127.0.0.1:8002
* 第3次Rest请求时: 3 % 2 = 1对应下标位置为1,则获得服务地址为127.0.0.1:8001
* 第4次Rest请求时: 4 % 2 = 0对应下标位置为0,则获得服务地址为127.0.0.1:8002
* 往后的以此类推....
*/
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); // 内含8001/8002两台服务器,它们组合成为集群
instances.get(0); // 127.0.0.1:8002
instances.get(1); // 127.0.0.1:8001
int numOfRequest = 1; // 第几次Rest请求
int numOfClusters = 2; // 服务器集群数量
int index = numOfRequest % numOfClusters; // 实际调用的服务器下标 = 第几次Rest请求 % 服务器集群数量
手写轮询算法
1.cloud-provider-payment8001/8002改造
PaymentController.java
返回端口信息
@GetMapping("/payment/lb")
public String getPaymentLB(){
return serverPort;
}
2.cloud-consumer-order80改造
关闭自动负载均衡
ApplicationContextConfig.java
创建获取服务器的接口
LoadBalancer.java
public interface LoadBalancer {
/**
* 获取要轮询的服务器实例
* @param serviceInstances
* @return
*/
public ServiceInstance getServiceInstance(List<ServiceInstance> serviceInstances);
}
创建其实现类
MyLB.java
@Component // 可以让容器扫描到这个类
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0); // compareAndSet()的实现类
/**
* 使用自旋锁获取当前是第几次
*
* @return
*/
public final int getAndIncrement() {
int current;
int next;
do {
current = this.atomicInteger.get();
next = current > 2147483647 ? 0 : current + 1;
// compareAndSet:如果内存里的值与current不一样,则修改失败,返回false,继续循环;如果一样,则修改成功,返回true
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("*****第" + next + "次访问");
return next;
}
@Override
public ServiceInstance getServiceInstance(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
使用自定义负载均衡
OrderController.java
@Resource
private DiscoveryClient discoveryClient;
@Resource
private LoadBalancer loadBalancer;
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB() {
// 获取集群列表
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0) {
return null;
}
// 获取服务器实例
ServiceInstance serviceInstance = loadBalancer.getServiceInstance(instances);
// 获取访问地址
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
3.测试