文章目录
1. 概述
- 官网资料 :https://github.com/Netflix/ribbon/wiki/Getting-Started
- 已经进入维护阶段
1.1 Ribbon 是什么
- SpringCloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具
- 主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间服务处连接在一起。
- Ribbon 的客户端组件提供一系列完整的配置项,如:连接超时、重试等。简单的说就是在配置文件中列出 LoadBalance (LB:负载均衡)后面所有的机器,Ribbon 会自动帮你基于某种规则去连接这些机器
1.2 负载均衡(LB)
-
核心作用:就是将用户的请求平摊在多个服务器上,从而达到系统的 High Availability(HA :高可用)
-
常见的负载均衡软件有 Nginx、Lvs 等
-
Bubbo 和 SpringCloud 都提供了负载均衡,SpringCloud 的负载均衡算法可以自定义
-
负载均衡算法实现的方式:轮询、权重、随机
-
负载均衡的简单分类:
1、集中式在服务的 消费者 和 提供者之间使用独立的 LB 设施,如Nginx(反向代理服务器) ,由该设施负载把访问请求通过某种策略发送至服务的提供者
2、进程式
将LB逻辑集成到 消费者,消费者 从服务注册中心得到哪些地址可用,然后自己再从这些地址中选出一个合适服务器
Ribbon 就属于进程内 LB ,它只是一个类库,集成与消费者进程,消费者通过他来获取到服务提供者的地址
1.3 架构说明
- Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
- Ribbon 在工作时分成两步:
- 第一步 :先选择 Eureka Server ,它优先选择在同一个区域内负载较少的 Server。
- 第二步 :再根据用户指定的策略,再从 Server 取到的服务注册列表中 选择一个地址
2. 使用
2.1 RestTemplate 的使用
-
Ribbon 依赖
新版的 Eureka 会自动把 Ribbon 也一起导入,所以不需要额外引入依赖
-
getForObject / getForEntity 的使用
-
getForObject 之前的 cloud-consumer-order-80 模块中就已经有用过了
-
getForEntity ,这个方法返回的是一个对象
@GetMapping("/getForEntity/{id}") public CommonResult<Payment> getPaymentById2(@PathVariable("id") Long id){ ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/provider/getPaymentById/" + id, CommonResult.class); if (entity.getStatusCode().is2xxSuccessful()){ //这个 实体对象 中还包含其他很多信息 log.info("{状态码: " + entity.getStatusCode() + ", 头信息: " + entity.getHeaders() + "}"); // 如果成功 return entity.getBody(); }else { return new CommonResult<>(444, "【查询失败】"); } }
- postForObject / postForEntity 的使用
-
postForObject ,之前也写过
-
postForEntity
@GetMapping("/postForEntity") public CommonResult<Payment> addPayment2(Payment payment){ log.info("[=插入数据=]{"+ payment +"}"); ResponseEntity<CommonResult> entity = restTemplate.postForEntity(PAYMENT_URL + "/provider/addPayment", payment, CommonResult.class); if (entity.getStatusCode().is2xxSuccessful()){ //这个 实体对象 中还包含其他很多信息 log.info("{状态码: " + entity.getStatusCode() + ", 头信息: " + entity.getHeaders() + "}"); // 如果成功 return entity.getBody(); }else { return new CommonResult<>(444, "【查询失败】"); } }
2.2 负载均衡组件 - IRule
- IRule:根据特定算法从服务列表中选取一个要访问的服务
实现类 | 策略 |
---|---|
com.netflix.loadbalancer.RoundRobinRule | 轮询(默认) |
com.netflix.loadbalancer.RandomRule | 随机 |
com.netflix.loadbalancer.RetryRule | 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试 |
WeightedResponseTimeRule | 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择 |
BestAvailableRule | 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 |
AvailabilityFilteringRule | 先过滤掉故障实例,再选择并发较小的实例 |
ZoneAvoidanceRule | 复合判断server所在区域的性能和server的可用性选择服务器 |
- 修改负载均衡策略
-
修改 cloud-consumer-order-80 模块
-
注意
这个配置负载均衡的类 不能在主应用程序的上下文(也就是 主启动类的同级目录中),所以需要单独的创建一个包
-
新建一个包 myRule
-
新建一个规则类 :MyRule
@Configuration public class MySelfRule { @Bean public IRule myRule(){ return new RandomRule();//定义为随机 } }
-
主启动类添加 @RibbonClient
@EnableEurekaClient @SpringBootApplication @RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class) public class OrderMain80 { public static void main(String[] args) { SpringApplication.run(OrderMain80.class, args); } }
-
测试
启动两个 Provider ,一直发出查询请求,会在 8001、8002 随机选择
2.3 轮询原理
- 原理(轮询)
- RoundRobinRule 源码分析
2.4 自定义一个负载均衡策略
- 主要改造 cloud-consumer-order-80 模块
-
ApplicationContextBean去掉@LoadBalanced
-
在 8001、8002 的 Controller 中添加一个方法
/** 用于测试自定义负载均衡 */ @GetMapping(value = "/payment/lb") public String getPaymentLB(){ return serverPort; }
-
新建一个 lb 的包,并在该包下建一个接口:LoadBalancer
public interface LoadBalancer { /** 收集服务器总共有多少台能够提供服务的机器,并放到list里面 */ ServiceInstance instances(List<ServiceInstance> serviceInstances); }
-
为上面的接口写一个实现类
@Component public class MyLB implements LoadBalancer { // 用于统计当前的访问次数 private AtomicInteger atomicInteger = new AtomicInteger(0); /** * @return 当前是第几次访问 */ public final int getAndIncrement(){ int current, next; // 自旋锁 do { current = this.atomicInteger.get(); next = current >= 2147483647 ? 0 : current + 1; }while (!this.atomicInteger.compareAndSet(current, next)); System.out.println("【 当前访问次数 】 next = :"+next); return next; } @Override public ServiceInstance instances(List<ServiceInstance> serviceInstances) { // 通过取余,计算出当前应该是哪一个 Provider 提供服务 int index = getAndIncrement() % serviceInstances.size(); ServiceInstance instance = serviceInstances.get(index); System.out.println("当前是 【" + instance.getUri() + "】 提供服务"); return instance; } }
-
修改 cloud-consumer-order-80 的 Controller
/** 引入 自定义 的 LB */ @Autowired private LoadBalancer loadBalancer; @Autowired private DiscoveryClient discoveryClient; /** 用于测试自定义负载均衡 */ @GetMapping(value = "/payment/lb") public String getPaymentLB(){ // 根据 ServiceId 获取 Server 集合 List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); // 如果没有服务 if (instances == null || instances.size() <= 0){ return null; } // 获取 Server 实例 ServiceInstance serviceInstance = loadBalancer.instances(instances); // 通过实例获得该实例的 地址 URI uri = serviceInstance.getUri(); return restTemplate.getForObject(uri+"/provider/payment/lb",String.class); }
-
启动项目测试自己的轮询
-
控制台输出