1. 概述

1.1 Ribbon是什么

SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端,是负载均衡的工具。

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件复杂均衡算法和服务调用。Ribbon客户端组件提供一系列完整的配置项如连接超时、重试等。简单的说,就是在配置文件中列出Load Balancer(负载均衡简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。也可以使用Ribbon实现自定义的负载均衡算法。

1.2 Ribbon能做什么

主要是负载均衡(LB):所谓负载均衡,简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(High Available高可用),常见的负载均衡有软件Nginx、LVS,硬件F5等。

Ribbon本地负载均衡客户端和Nginx服务端负载均衡的区别:

  • Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的。
  • Ribbon是本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

负载均衡又分为两类,分别可以对应于Nginx和Ribbon:

  • 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。
  • 进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器,Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

Ribbon实际上就是负载均衡 + RestTemplate调用

2. 使用案例

2.1 架构说明

Ribbon其实就是一个负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。Ribbon在工作的时候分两步:

  • 先选择EurekaServer,优先选择同一个区域内负载较少的Server

  • 根据用户指定的策略,从Server取到的服务注册列表中选择一个地址

其中Ribbon提供了多种的负载均衡策略,如轮询、随机和根据响应时间加强等。

2.2 pom.xml

在POM文件中我们引入了如下依赖:

<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

点开该依赖的源码,我们发现事实上该依赖内部已经引入了Ribbon,其引入Ribbon的源码如下:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.1.RELEASE</version>
<scope>compile</scope>
</dependency>

我们在Maven的依赖中也可以看到,在引入 spring-cloud-starter-netflix-eureka-client 的同时我们就已经引入了 **spring-cloud-starter-netflix-ribbon **,所以我们没必要单独添加Ribbon的依赖。

1.3 RestTemplate使用

RestTemplate官方说明可以在RestTemplate官方API查看,下面简要说明其主要方法

  • getForObject方法:返回对象为响应体数据转化成的对象,基本上可以理解为Json对象。
  • getForEntity方法:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {
@Resource
private RestTemplate restTemplate; // private static final String PAYMENT_URL = "http://localhost:8001";
private static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE"; @GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_SRV
+ "/payment/get/"
+ id, CommonResult.class);
} @GetMapping("/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity =
restTemplate.getForEntity(PAYMENT_SRV
+ "/payment/get/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()) {
log.info("===> " + entity.getStatusCode()
+ "\t" + entity.getHeaders());
return entity.getBody(); //返回请求体
} else {
return new CommonResult<>(444, "操作失败");
}
}
}

在后台控制台也输出了状态码和请求头的如下日志:

===> 200 OK	[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Thu, 28 Jan 2021 15:44:50 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]

3. Ribbon核心组件IRule接口

3.1 IRule理解

它可以根据特定算法从服务列表中选取一个要访问的服务

IRule是一个接口,其源码如下:

package com.netflix.loadbalancer;

/**
* Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of
* as a Strategy for loadbalacing. Well known loadbalancing strategies include
* Round Robin, Response Time based etc.
*
* @author stonse
*
*/
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/ public Server choose(Object key); public void setLoadBalancer(ILoadBalancer lb); public ILoadBalancer getLoadBalancer();
}

以下是IRule接口的部分实现,这些实现分别对应了若干负载均衡算法

以下简要说明7种主要的负载均衡算法,这些负载均衡算法均是抽象类com.netflix.loadbalancer.AbstractLoadBalancerRule 的实现,而给抽象类实现了IRule接口:

  • com.netflix.loadbalancer.RoundRobinRule:轮询,为默认的负载均衡算法
  • com.netflix.loadbalancer.RandomRule:随机
  • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule(轮询)的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
  • com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择。
  • com.netflix.loadbalancer.BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
  • com.netflix.loadbalancer.ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择服务器
3.2 如何替换负载均衡算法

服务消费者80添加轮询算法配置类

首先我们应该明确是服务消费方采用轮询算法来访问同一服务提供方的不同微服务实例,所以我们应该在服务消费方80方的微服务中添加轮询算法配置类。

在添加配置类时,有必须要注意的点,就是官方文档明确给出了警告:这个自定义的轮询算法配置类不能放在@ComponentScan注解所扫描的当前包下以及子包下,否则自定义的这个配置类就会被所有Ribbon客户端所共享,就达不到特殊化定制的目的了。换句话说,如果这个配置类我们能够被@ComponentScan注解扫描到,那么访问所有的微服务提供方的具体实例时,我们都会采取配置类中的算法,如果要特殊化定制 - 即指定访问某些微服务提供方时采用配置的轮询算法,那么我们就应该使这个配置类让@ComponentScan注解扫描不到,我们知道在主启动类的@SpringBootApplication注解中,其实这个注解包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan这三个注解,所以我们写的轮询算法配置类不能和主启动类在同一个包下,所以我们需要建新的包,实现定制轮询算法的配置类:

package com.polaris.myrule;

/**
* @author polaris
*/
@Configuration
public class MySelfRule { @Bean
public IRule myRule() {
return new RandomRule(); //定义随机负载均衡算法
}
}

包结构的内容如下,我们可以看到,轮询算法配置类在主启动类的@ComponentScan扫描不到的包下:

服务消费者80主启动类中添加@RibbonClient注解

@SpringBootApplication
@EnableEurekaClient
//访问的微服务为CLOUD-PAYMENT-SERVICE,采用配置文件中的轮询算法
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class);
}
}

测试

测试发现我们用服务消费方访问服务提供方的微服务时,8001和8002不再交替轮询访问,而是随机访问。

4. Ribbon负载均衡算法

4.1 默认负载均衡算法(轮询)原理

轮询负载均衡算法原理:Rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标, 每次服务重启后Rest接口计数从1开始。

List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE")

根据服务方的服务名,获取其所有实例,如有以下实例:

List[0] List[1]
服务名 payment8001 payment8002
服务地址 127.0.0.1:8001 127.0.0.1:8002

这两个实例组合成一个集群,共2台机器,集群总数为2,按照轮询负载均衡算法原理:

  • 请求总数为1时,1 % 2 = 1,对应下标位置是1,获得服务地址127.0.0.1:8001

  • 请求总数为2时,2 % 2 = 0,对应下标位置是0,获得服务地址127.0.0.1:8002

  • 请求总数为3时,3 % 2 = 1,对应下标位置是1,获得服务地址127.0.0.1:8001

  • ...

4.2 轮询源码分析

com.netflix.loadbalancer.RoundRobinRule源码的负载均衡算法部分分析如下(代码中标注了中文注释):

package com.netflix.loadbalancer;

/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
*/
public class RoundRobinRule extends AbstractLoadBalancerRule { //... public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} Server server = null;
int count = 0;
while (server == null && count++ < 10) {
//获得还活着的健康的服务实例(机器)即可达的,也就是Status为up的实例
List<Server> reachableServers = lb.getReachableServers();
//获取所有服务实例,无论是死是活,只要注册进服务中心即可
List<Server> allServers = lb.getAllServers();
//Status为up的服务实例数量
int upCount = reachableServers.size();
//所有服务实例的数量,对应上述原理分析中的服务器集群总数量
int serverCount = allServers.size(); //如果没有可达的服务实例的话,直接报警告
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
} //调用服务器位置下标 = incrementAndGetModulo(服务器集群总数)
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);//根据下标获取服务实例 if (server == null) {
/* Transient. */
Thread.yield();
continue;
} if (server.isAlive() && (server.isReadyToServe())) {
return (server);
} // Next.
server = null;
} if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
} /**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
}
4.3 自己实现轮询负载均衡算法

首先我们将服务注册中心(7001/7002构成集群)启动,然后在服务提供方8001/8002中的Controller中添加功能,用来一会儿测试服务消费方80来轮询访问CLOUD-PAYMENT-SERVICE服务:

@GetMapping("/payment/lb")
public String getPaymentLB(){
return serverPort;
}

服务提供方的这个方法就是简单的在页面输出自己的端口号,也就是我们可以在页面区分访问的CLOUD-PAYMENT-SERVICE服务到底对应的是8001实例还是8002实例。

启动8001/8002,将两个服务实例注册进服务注册中心后,我们再改造服务消费方80服务,分为以下四步:

  • 首先我们先让RestTemplate失去Ribbon中的负载均衡能力,取消掉@LoadBalanced注解即可:
@Configuration
public class ApplicationContextConfig {
@Bean
// @LoadBalanced//使用该注解赋予RestTemplate负载均衡的能力
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
//applicationContext.xml <bean id="" class="">
  • 然后编写自己的负载均衡接口:

给接口定义了方法instances用于在服务提供方服务的所有服务实例中选择一个具体实例。

public interface LoadBalancer {
/**
* 从服务列表中用负载均衡算法选择出具体的实例
* @param serviceInstances 服务列表
* @return
*/
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
  • 用轮询负载均衡算法实现负载均衡接口:

RoundRobinRule源码中用for(;;)实现的自旋锁,这里我们用do{} while();实现自旋锁。

@Component
public class MyLB implements LoadBalancer { private AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncrement() {
int current;
int next;
//自旋锁
do {
current = this.atomicInteger.get(); //初始值为0
next = current >= 2147483647 ? 0 : current + 1; //最大整数
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("===> 访问次数next:" + next);
return next;
} /**
* 从服务列表中用轮询负载均衡算法选择出具体的实例
* Rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
*
* @param serviceInstances 服务列表
* @return
*/
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
  • 最后我们在80服务的Controller中添加方法:
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {
@Resource
private RestTemplate restTemplate; @Resource
private LoadBalancer loadBalancer; @Resource
private DiscoveryClient discoveryClient; @GetMapping("payment/lb")
public String getPaymentLB() {
//获取服务提供方所有的服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0) {
return null;
}
//采用自己实现的轮询负载均衡算法选择具体实例
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
}

在浏览器中输入http://localhost/consumer/payment/lb,也就是80端口的服务消费方采用我们自己编写的轮询负载均衡算法访问CLOUD-PAYMENT-SERVICE服务的具体实例,测试成功,在服务消费方80服务的后端控制台也输出了的日志。

四. Ribbon负载均衡服务调用的更多相关文章

  1. Ribbon负载均衡服务调用

    1.在听周阳老师讲解时,使用Ribbon核心组件IRule时是这样用的: ribbon版本 : 自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,项目结构如下 MySelfR ...

  2. 四. SpringCloud负载均衡与调用

    1. Ribbon概述 1.1 Ribbon是什么 SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端,是负载均衡的工具. Ribbon是Netflix发布的开源项 ...

  3. SpringCloud系列五:Ribbon 负载均衡(Ribbon 基本使用、Ribbon 负载均衡、自定义 Ribbon 配置、禁用 Eureka 实现 Ribbon 调用)

    1.概念:Ribbon 负载均衡 2.具体内容 现在所有的服务已经通过了 Eureka 进行了注册,那么使用 Eureka 注册的目的是希望所有的服务都统一归属到 Eureka 之中进 行处理,但是现 ...

  4. Ribbon负载均衡及Feign消费者调用服务

    微服务调用Ribbon 简介 前面讲了eureka服务注册与发现,但是结合eureka集群的服务调用没讲. 这里的话 就要用到Ribbon,结合eureka,来实现服务的调用: Ribbon是Netf ...

  5. SpringCloud之Ribbon负载均衡及Feign消费者调用服务

    目的: 微服务调用Ribbon Ribbon负载均衡 Feign简介及应用 微服务调用Ribbon Ribbon简介 1. 负载均衡框架,支持可插拔式的负载均衡规则 2. 支持多种协议,如HTTP.U ...

  6. SpringCloud 服务负载均衡和调用 Ribbon、OpenFeign

    1.Ribbon Spring Cloud Ribbon是基于Netflix Ribbon实现的-套客户端―负载均衡的工具. 简单的说,Ribbon是Netlix发布的开源项目,主要功能是提供客户端的 ...

  7. Ribbon负载均衡(四)

    一.Ribbon定义 spring cloud Ribbon是基于Netflix Ribbon实现的一套客户端,负载均衡工具 简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端 ...

  8. Spring Cloud微服务Ribbon负载均衡/Zuul网关使用

    客户端负载均衡,当服务节点出现问题时进行调节或是在正常情况下进行 服务调度.所谓的负载均衡,就是当服务提供的数量和调用方对服务进行 取舍的调节问题,在spring cloud中是通过Ribbon来解决 ...

  9. 小D课堂 - 新版本微服务springcloud+Docker教程_4-03 高级篇幅之Ribbon负载均衡源码分析实战

    笔记 3.高级篇幅之Ribbon负载均衡源码分析实战     简介: 讲解ribbon服务间调用负载均衡源码分析         1.完善下单接口         2.分析@LoadBalanced ...

随机推荐

  1. 高性能MySQL学习总结一

    一.MySQL逻辑架构 第一层的服务不是MySQL独有的,大多数是基于网络的客户端/服务端的工具,如连接处理.授权认证.安全等等. 第二层就是MySQL的核心功能,包括查询解析.分析.优化.缓存以及所 ...

  2. JavaDailyReports10_10

    1.4.2 键盘事件的处理 KeyListener  接口实现了处理键盘事件      KeyEvent 对象描述键盘事件的相关信息. KeyListener 接口有三个方法:KeyPressed K ...

  3. Java学习日报7.14

    package fabs;import java.util.Scanner;public class Fabs { public static void main(String args[]) { S ...

  4. 如何优雅的传递 stl 容器作为函数参数来实现元素插入和遍历?

    问题背景 开始正文之前,做一些背景铺垫,方便读者了解我的工程需求.我的项目是一个客户端消息分发中心,在连接上消息后台后,后台会不定时的给我推送一些消息,我再将它们转发给本机的其它桌面产品去做显示.后台 ...

  5. 手把手教你用C语言编写一个哈希表

    原文链接:https://www.swack.cn/wiki/001558681974020669b912b0c994e7090649ac4846e80b2000/001572849111298ae3 ...

  6. Angular入门到精通系列教程(10)- 指令(Directive)

    1. 摘要 2. 组件与指令之间的关系 2.1. 指令的种类 3. Angular 中指令的用途 4. 指令举例 4.1. 指令功能 4.2. Anuglar CLI生成基本文件 4.3. Direc ...

  7. 【LeetCode】365.水壶问题

    题目描述 解题思路 思路一:裴蜀定理-数学法 由题意,每次操作只会让桶里的水总量增加x或y,或者减少x或y,即会给水的总量带来x或y的变化量,转为数字描述即为:找到一对整数a,b使得下式成立: ax+ ...

  8. 【Flutter】容器类组件之变换

    前言 Transform可以在其子组件绘制时对其应用一些矩阵变换来实现一些特效. 接口描述 const Transform({ Key key, @required this.transform, t ...

  9. Linux echo和cat和grep和tr的基础用法

    Linux vim   搜索 echo  :   显示输出功能 echo oldboy>1.txtx cat 1.txtx >  重定向   文件内容覆盖 >> 追加重定向   ...

  10. java中如何踢人下线?封禁某个账号后使其会话立即掉线!

    需求场景 封禁账号是一个比较常见的业务需求,尤其是在论坛.社区类型的项目中,当出现了违规用户时我们需要将其账号立即封禁. 常规的设计思路是:在设计用户表时增加一个状态字段,例如:status,其值为1 ...