相关内容原文地址:

CSDN:Coding Farmer:Spring Cloud快速开发入门第四篇—客户端负载均衡Ribbon



一、Ribbon是什么

Ribbon是一个基于HTTP和TCP的客户端负载均衡器,当使用Ribbon对服务进行访问的时候,他会扩展Eureka客户端的服务发现功能,实现从Eureka注册中心获取服务端列表,并通过Eureka客户端来确定服务端是否已经启动。Ribbon在Eureka客户端服务发现的基础上,实现对服务实例的选择策略,从而实现对服务的负载均衡消费。负载均衡在系统架构中是一个非常重要的内容,因为负载均衡是对系统的高可用、网络的压力的缓冲和处理能力扩容的重要手段之一,我们通常说的负载均衡都是指的是服务端的负载均衡,其中分为硬件负载均衡和软件负载均衡。

  • 硬件负载均衡:主要通过服务器节点之间安装专门用于负载均衡的设备,比如F5,深信服,Array等。
  • 软件负载均衡:则是通过服务器上安装一些具有负载功能或模块的软件来完成请求分发工作,比如Nginx、LVS、HAProxy等。

硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡的设备时候,该设备按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端地址,然后进行转发。

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,是一个基于HTTP和TCP的客户端负载均衡工具。Spring Cloud对Ribbon做了二次封装,可以让我们使用 RestTemplate的服务请求,自动转换成客户端负载均衡的服务调用。Ribbon支持多种负载均衡算法,还支持自定义的负载均衡算法。Ribbon只是一个工具类框架,比较小巧, Spring Cloud对它封装后使用也非 常方便,它不像服务注册中心、配置中心、AP网关那样需要独立部署, Ribbon 只需要在代码直接使用即可。

Ribbon与 Nginx的区别:

  • 都是软负载
  • Ribbon是客户端负载均衡
  • Nginx是服务器段负载均衡

服务清单所存储的位置不同,在客户端负载均衡中,所有客户端节点下的服务端清单,需要自己从服务注册中心上获取,比如Eureka服务注册中心。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成,在SpringCloud实现的服务治理框架中,默认会创建针对各个服务治理框架到的Ribbon自动化整合配置,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,在实际使用的时候,我们可以通过查看这个类的实现,以找到他们的配置详情来帮助我们更好的使用它。

通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常的简单,只需要如下两步:

  1. 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心上
  2. 服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
  • 服务端的负载均衡是提前配置好的:Nginx
  • 客户端的负载均衡是从注册中心找的:Ribbon

在SpringCloud中,Ribbon主要与RestTemplate对象配合使用,Ribbon会自动化配置RestTemplate对象,通过@LoadBalance开启RestTemplate对象调用时的负载均衡,Ribbon所处的作用如图:

二、Ribbon实现客户端负载均衡

在微服务架构中使用客户端负载均衡调用:

  1. 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心上
  2. 服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。

复制服务提供者(springcloud-service-provider)并且命名为springcloud-service-provider-02,修改controlle响应结果内容,区别一服务提供者(springcloud-service-provider)内容。修改服务提供者(springcloud-service-provider-02)端口为8081

在消费者的RestTemplate中添加如下代码:

 //使用Ribbon实现负载均衡调用,默认是轮询
@LoadBalanced //加入ribbon的支持,那么在调用时,即可改为使用服务名称来访问
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}

查看Eureka的web页面显示提供者两个实例:

启动消费者,进行访问如图:





provider-01和provider-02交替出现,可以看出默认是轮询策略。

三、Ribbon负载均衡策略

Ribbon的负载均衡策略是由IRule接口定义,该接口由如下实现:



IRule实现类的负载策略含义

属性 含义
RandomRule 随机
RoundRobinRule 轮询
AvailabilityFilteringRule 先过滤掉由于多次访问故障的服务,以及并发连接数超过阀值的服务,然后对剩下的服务按照轮询策略进行访问
WeightedResponseTimeRule 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时间统计信息不足,,则使用RoundRobinRule策略,待统计信息足够,会切换到该WeightedResponseTimeRule策略
RetryRule 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定的时间内重试,如果不行的话,则分发到其他可用的服务
BestAvailableRule 先过滤掉由于多次访问的故障的服务,然后选择一个并发量最小的服务
ZoneAvoidanceRule 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务

结合Ribbon负载均衡,默认的是轮询,重新注入IRule可以实现负载均衡的其他策略。

四、Rest请求模板类解读

当我们从服务消费端去调用服务提供者的服务的时候,使用了一个极其方便的对 象叫 RestTemplate,当时我们只使用了 Rest Template中最简单的一个功能 getForEntity发起了一个get请求去调用服务端的数据,同时,我们还通过配置@ Loadbalanced注解开启客户端负载均衡, RestTemplate的功能非常强大, 那么接下来就来详细的看一下 RestTemplate中几种常见请求方法的使用。 在日常操作中,基于Rest的方式通常是四种情况,它们分表是

  • GET请求-查询数据
  • POST请求-添加数据
  • PUT请求-修改数据
  • DELETE-删除数据

4.1 RestTemplate的GET请求

Get请求可以有两种方式:

第一种:getForEntity(…)

该方法返回一个ResponseEntity对象,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,比如响应码,contentType,contentLength,响应消息体等。

 ResponseEntity<String> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/hello", String.class);
String body = forEntity.getBody();
HttpStatus statusCode = forEntity.getStatusCode();
int statusCodeValue = forEntity.getStatusCodeValue();
HttpHeaders headers = forEntity.getHeaders(); System.out.println(body);
System.out.println(statusCode);
System.out.println(statusCodeValue);
System.out.println(headers);

getForEntity方法第—个参数为要调用的服务的地址,即服务提供者提供的http://SPRINGCLOUD-SERVICE-PROVIDER/provider/hello接口地址,注意这里是通过服务名调用而不是服务地址,如果改为服务地址就无法使用 Ribbon实现客户端负载均衡了。 getForEntity方法第二个参数 String.class表示希望返回的body类型是 String 类型,如果希望返回一个对象,也是可以的,比如User对象。

    /**
* 调用get请求,返回一个User对象
* @return
*/
@RequestMapping("/user")
public User user(){
//逻辑判断省略
ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/user", User.class);
System.out.println(forEntity.getBody().getId()+""+forEntity.getBody().getName()+""+forEntity.getBody().getPhone());
return forEntity.getBody();
}

另外两个重载方法:

第一个重载方法:

        @Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
} @Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
}

比如:

 /**
* 给服务传参数Get请求
* @return
*/
@RequestMapping("/getUser")
public User getUser(){
//逻辑判断省略
String [] arr={"2","xxx","4545645456"};
Map<String,Object> map=new HashMap<>();
map.put("id",1);
map.put("name","wwwwww");
map.put("phone","1213213213123"); //ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={0}&name={1}&phone={2}", User.class,arr);
//ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={id}&name={name}&phone={phone}", User.class,map);
/*
* restTemplate.getForObject在getForObject在getForEntity在次封装,直接获取返回值类型,相当于ResponseEntity中的getBody
*/
User user1 = restTemplate.getForObject("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={id}&name={name}&phone={phone}", User.class, map); //System.out.println(forEntity.getBody().getId()+""+forEntity.getBody().getName()+""+forEntity.getBody().getPhone());
System.out.println(user1.getId()+""+user1.getName()+""+user1.getPhone());
return user1;
}

可以用一个数字做占位符,最后是一个可变长度的参数,来来替换前面的占位符

也可以前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值

第二种:getForObject(…)

与 getForEntity使用类似,只不过 getForobject是在 getForEntity基础上进行了再次封装,可以将http的响应体body 信息转化成指定的对象,方便我们的代码开发: 当你不需要返回响应中的其他信息,只需要body体信息的时候,可以使用这个更方便; 它也有两个重载的方法,和 getForEntity相似。

        @Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
} @Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
} @Override
@Nullable
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}

4.2 RestTemplate的POST请求

restTemplate.postForEntity();
restTemplate.postForObject();
restTemplate.postForLocation();

比如:

 /**
* 调用POST请求
* @return
*/
@RequestMapping("/addUser")
public User addUser(){
//逻辑判断省略
String [] arr={"2","xxx","4545645456"};
//不能使用map传递参数
Map<String,Object> map=new HashMap<>();
map.put("id",1);
map.put("name","wwwwww");
map.put("phone","1213213213123"); /**
*要传的表单信息,参数数据(很坑人)
*/
MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>();
multiValueMap.add("id",1);
multiValueMap.add("name","xxxxx");
multiValueMap.add("phone","000000000"); //使用jdk中的map传参数,接收不到
ResponseEntity<User> userResponseEntity = restTemplate.postForEntity(
"http://SPRINGCLOUD-SERVICE-PROVIDER/provider/addUser", multiValueMap, User.class); System.out.println(userResponseEntity.getBody().getId()+""+userResponseEntity.getBody().getName()+""+userResponseEntity.getBody().getPhone());
return userResponseEntity.getBody();
}

4.3 RestTemplate的PUT请求

 restTemplate.put();

比如:

 /**
* 调用PUT请求
* @return
*/
@RequestMapping("/updateUser")
public String updateUser(){
//逻辑判断省略
String [] arr={"2","xxx","4545645456"};
//不能使用map传递参数
Map<String,Object> map=new HashMap<>();
map.put("id",1);
map.put("name","wwwwww");
map.put("phone","1213213213123"); /**
*要传的表单信息,参数数据(很坑人)
*/
MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>();
multiValueMap.add("id",1);
multiValueMap.add("name","xxxxx");
multiValueMap.add("phone","000000000"); //使用jdk中的map传参数,接收不到
restTemplate.put("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/updateUser", multiValueMap); return "SUCCESS";
}

4.4 RestTemplate的DELETE请求

restTemplate.delete();
/**
* 调用DELETE请求
* @return
*/
@RequestMapping("/deleteUser")
public String deleteUser(){
//逻辑判断省略
String [] arr={"2","xxx","4545645456"};
Map<String,Object> map=new HashMap<>();
map.put("id",1);
map.put("name","wwwwww");
map.put("phone","1213213213123"); /**
*要传的表单信息,参数数据(很坑人),只有post,PUT请求采用这种map传参数
*/
/* MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>();
multiValueMap.add("id",1);
multiValueMap.add("name","xxxxx");
multiValueMap.add("phone","000000000"); */ //使用jdk中的map传参数,接收不到,不能使用MultiValueMap,接收不到参数
restTemplate.delete("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/deleteUser?id={id}&name={name}&phone={phone}", map); return "SUCCESS";
}

客户端负载均衡Ribbon的更多相关文章

  1. 【Dalston】【第二章】客户端负载均衡(Ribbon)

    对于大型应用系统负载均衡(LB:Load Balancing)是首要被解决一个问题.在微服务之前LB方案主要是集中式负载均衡方案,在服务消费者和服务提供者之间又一个独立的LB,LB通常是专门的硬件,如 ...

  2. 客户端负载均衡Ribbon之二:Loadbalance的源码

    Load Balance负载均衡是用于解决一台机器(一个进程)无法解决所有请求而产生的一种算法. 像nginx可以使用负载均衡分配流量,ribbon为客户端提供负载均衡,dubbo服务调用里的负载均衡 ...

  3. 客户端负载均衡Ribbon之一:Spring Cloud Netflix负载均衡组件Ribbon介绍

    Netflix:['netfliːks] ribbon:英[ˈrɪbən]美[ˈrɪbən]n. 带; 绶带; (打印机的) 色带; 带状物;v. 把…撕成条带; 用缎带装饰; 形成带状;     L ...

  4. Spring Cloud入门教程(二):客户端负载均衡(Ribbon)

    对于大型应用系统负载均衡(LB:Load Balancing)是首要被解决一个问题.在微服务之前LB方案主要是集中式负载均衡方案,在服务消费者和服务提供者之间又一个独立的LB,LB通常是专门的硬件,如 ...

  5. 服务注册发现Eureka之三:Spring Cloud Ribbon实现客户端负载均衡(客户端负载均衡Ribbon之三:使用Ribbon实现客户端的均衡负载)

    在使用RestTemplate来消费spring boot的Restful服务示例中,我们提到,调用spring boot服务的时候,需要将服务的URL写死或者是写在配置文件中,但这两种方式,无论哪一 ...

  6. 五、springcloud之客户端负载均衡Ribbon

    一.简介 在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的.Spring cloud有两种服务调用方式: 一种是ribbon+restTemplate, ...

  7. 003客户端负载均衡Ribbon & 短路器Hystrix

    1.POM配置 和普通Spring Boot工程相比,仅仅添加了Eureka.Ribbon.Hystrix依赖和Spring Cloud依赖管理 <dependencies> <!- ...

  8. spring cloud 之 客户端负载均衡 Ribbon

    一.负载均衡 负载均衡(Load Balance): 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性.其意 ...

  9. 0403-服务注册与发现-客户端负载均衡-Ribbon的基本使用

    一.概述 问题1.上一篇文章已说明如何注册微服务,但是调用方如何调用,以及如何防止硬编码.即电影微服务调用用户微服务 问题2.用户微服务多个节点,调用服务方如何负载均衡 二.实现负载均衡方式 2.1. ...

随机推荐

  1. 数据湖框架选型很纠结?一文了解Apache Hudi核心优势

    英文原文:https://hudi.apache.org/blog/hudi-indexing-mechanisms/ Apache Hudi使用索引来定位更删操作所在的文件组.对于Copy-On-W ...

  2. AgileConfig-如何使用AgileConfig.Client读取配置

    前面的文章(AgileConfig基于.NetCore的一个轻量级配置中心,AgileConfig轻量级配置中心 1.1.0 发布,支持应用间配置继承)都是介绍AgileConfig服务端已经控制台是 ...

  3. Azure Databricks 第二篇:pyspark.sql 简介

    pyspark中的DataFrame等价于Spark SQL中的一个关系表.在pyspark中,DataFrame由Column和Row构成. pyspark.sql.SparkSession:是Da ...

  4. BPOS关于“相邻库存查询”的调整

    "相邻库存查询"的应用场景:主要是实现门店间,相互查看商品库存状况,但出于公司对门店的查看权限控制要求,不能一次性查看到相关店铺的所有库存,所以产生了"相邻库存查询&qu ...

  5. MySQL 标识符到底区分大小写么——官方文档告诉你

    最近在阿里云服务器上部署一个自己写的小 demo 时遇到一点问题,查看 Tomcat 日志后定位到问题出现在与数据库服务器交互的地方,执行 SQL 语句时会返回 指定列.指定名 不存在的错误.多方查证 ...

  6. windows端口占用

    原文链接http://zhhll.icu/2020/04/08/windows/windows%E4%B9%8B%E7%AB%AF%E5%8F%A3%E5%8D%A0%E7%94%A8/ 1.查看当前 ...

  7. ssh信任 sftp用法 scp用法【转】

    为了进行批量关机工作,前提要配置好ssh的双机信任. A机192.168.1.241 B机192.168.1.212 在A机上获取一个pub密钥,即为公共密钥. 执行这个命令后:ssh-keygen  ...

  8. docker+mysql集群+读写分离+mycat管理+垂直分库+负载均衡

    依然如此,只要大家跟着我的步骤一步步来,100%是可以测试成功的 centos6.8已不再维护,可能很多人的虚拟机中无法使用yum命令下载docker, 但是阿里源还是可以用的 因为他的centos- ...

  9. 【Oracle】表空间配额问题

    由于需求,需要新建用户,但是新建的用户,会有相关的配额跟着,莫名其妙的问题让人很头疼 下面介绍下如何修改成不限制配额 select * from user_ts_quotas ; alter user ...

  10. 惠普电脑(HP PHILIPS系列)安装ubuntu后无法连接WIFI解决方案(手动安装8821CE驱动)

    一步一步来, 先说环境: 我的电脑是HP PHILIPS系列,ubuntu版本是16.04 背景: win10安装ubuntu后发现无法连接wifi(但win10系统可以连接WIFI),在ubuntu ...