天天看点

Spring Cloud Alibaba之负载均衡组件 - Ribbon

我们都知道在微服务架构中,微服务之间总是需要互相调用,以此来实现一些组合业务的需求。例如组装订单详情数据,由于订单详情里有用户信息,所以订单服务就得调用用户服务来获取用户信息。要实现远程调用就需要发送网络请求,而每个微服务都可能会存在有多个实例分布在不同的机器上,那么当一个微服务调用另一个微服务的时候就需要将请求均匀的分发到各个实例上,以此避免某些实例负载过高,某些实例又太空闲,所以在这种场景必须要有负载均衡器。

目前实现负载均衡主要的两种方式:

1、服务端负载均衡;例如最经典的使用Nginx做负载均衡器。用户的请求先发送到Nginx,然后再由Nginx通过配置好的负载均衡算法将请求分发到各个实例上,由于需要作为一个服务部署在服务端,所以该种方式称为服务端负载均衡。如图:

Spring Cloud Alibaba之负载均衡组件 - Ribbon

2、客户端侧负载均衡;之所以称为客户端侧负载均衡,是因为这种负载均衡方式是由发送请求的客户端来实现的,也是目前微服务架构中用于均衡服务之间调用请求的常用负载均衡方式。因为采用这种方式的话服务之间可以直接进行调用,无需再通过一个专门的负载均衡器,这样能够提高一定的性能以及高可用性。以微服务A调用微服务B举例,简单来说就是微服务A先通过服务发现组件获取微服务B所有实例的调用地址,然后通过本地实现的负载均衡算法选取出其中一个调用地址进行请求。如图:

Spring Cloud Alibaba之负载均衡组件 - Ribbon

我们来通过Spring Cloud提供的DiscoveryClient写一个非常简单的客户端侧负载均衡器,借此直观的了解一下该种负载均衡器的工作流程,该示例中采用的负载均衡策略为随机,代码如下:

什么是Ribbon:

Ribbon是Netflix开源的客户端侧负载均衡器

Ribbon内置了非常丰富的负载均衡策略算法

Ribbon虽然是个主要用于负载均衡的小组件,但是麻雀虽小五脏俱全,Ribbon还是有许多的接口组件的。如下表:

Spring Cloud Alibaba之负载均衡组件 - Ribbon

Ribbon默认内置了八种负载均衡策略,若想自定义负载均衡策略则实现上表中提到的IRule接口或AbstractLoadBalancerRule抽象类即可。内置的负载均衡策略如下:

Spring Cloud Alibaba之负载均衡组件 - Ribbon

默认的策略规则为ZoneAvoidanceRule

Ribbon主要有两种使用方式,一是使用Feign,Feign内部已经整合了Ribbon,因此如果只是普通使用的话都感知不到Ribbon的存在;二是配合RestTemplate使用,这种方式则需要添加Ribbon依赖和@LoadBalanced注解。

这里主要演示一下第二种使用方式,由于项目中添加的Nacos依赖已包含了Ribbon所以不需要另外添加依赖,首先定义一个RestTemplate,代码如下:

然后使用RestTemplate调用其他服务的时候,只需要写服务名即可,不需要再写ip地址和端口号。如下示例:

如果不太清楚RestTemplate的使用,可以参考如下文章:

微服务之间的通信的方式

在实际开发中,我们可能会遇到默认的负载均衡策略无法满足需求,从而需要更换其他的负载均衡策略。关于Ribbon负载均衡的配置方式主要有两种,在代码中配置或在配置文件中配置。

Ribbon支持细粒度的配置,例如我希望微服务A在调用微服务B的时候采用随机的负载均衡策略,而在调用微服务C的时候采用默认策略,下面我们就来实现一下这种细粒度的配置。

1、首先是通过代码进行配置,编写一个配置类用于实例化指定的负载均衡策略对象:

然后再编写一个用于配置Ribbon客户端的配置类,该配置类的目的是指定在调用user-center时采用RibbonConfig里配置的负载均衡策略,这样就可以达到细粒度配置的效果:

需要注意的是RibbonConfig应该定义在主启动类之外,避免被Spring扫描到,不然会产生父子上下文扫描重叠的问题,从而导致各种奇葩的问题。而在Ribbon这里就会导致该配置类被所有的Ribbon客户端共享,即不管调用user-center还是其他微服务都会采用该配置类里定义的负载均衡策略,这样就会变成了一个全局配置了,违背了我们需要细粒度配置的目的。所以需要将其定义在主启动类之外:

Spring Cloud Alibaba之负载均衡组件 - Ribbon

关于这个问题可以参考官方文档的描述:

https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_customizing_the_ribbon_client

2、使用配置文件进行配置就更简单了,不需要写代码还不会有父子上下文扫描重叠的坑,只需在配置文件中增加如下一段配置就可以实现以上使用代码配置等价的效果:

两种配置方式对比:

Spring Cloud Alibaba之负载均衡组件 - Ribbon

关于优先级:细粒度配置文件配置 > 细粒度代码配置 > 全局配置文件配置 > 全局代码配置

最佳实践总结:

尽量使用配置文件配置,配置文件满足不了需求的情况下再考虑使用代码配置

在同一个微服务内尽量保持单一性,例如统一使用配置文件配置,尽量不要两种方式混用,以免增加定位问题的复杂度

以上介绍的是细粒度地针对某个特定Ribbon客户端的配置,下面我们再演示一下如何实现全局配置。很简单,只需要把注解改为@RibbonClients即可,代码如下:

Ribbon默认是懒加载的,所以在第一次发生请求的时候会显得比较慢,我们可以通过在配置文件中添加如下配置开启饥饿加载:

以上小节基本介绍完了负载均衡及Ribbon的基础使用,接下来的内容需要配合Nacos,若没有了解过Nacos的话可以参考以下文章:

Spring Cloud Alibaba之服务发现组件 - Nacos

在Nacos Server的控制台页面可以编辑每个微服务实例的权重,服务列表 -> 详情 -> 编辑;默认权重都为1,权重值越大就越优先被调用:

Spring Cloud Alibaba之负载均衡组件 - Ribbon

权重在很多场景下非常有用,例如一个微服务有很多的实例,它们被部署在不同配置的机器上,这时候就可以将配置较差的机器上所部署的实例权重设置得比较低,而部署在配置较好的机器上的实例权重设置得高一些,这样就可以将较大一部分的请求都分发到性能较高的机器上。

但是Ribbon内置的负载均衡策略都不支持Nacos的权重,所以我们就需要自定义实现一个支持Nacos权重配置的负载均衡策略。好在Nacos Client已经内置了负载均衡的能力,所以实现起来也比较简单,代码如下:

然后在配置文件中配置一下就可以使用该负载均衡策略了:

思考:既然Nacos Client已经有负载均衡的能力,Spring Cloud Alibaba为什么还要去整合Ribbon呢?

个人认为,这主要是为了符合Spring Cloud标准。Spring Cloud Commons有个子项目 spring-cloud-loadbalancer ,该项目制定了标准,用来适配各种客户端负载均衡器(虽然目前实现只有Ribbon,但Hoxton就会有替代的实现了)。 Spring Cloud Alibaba遵循了这一标准,所以整合了Ribbon,而没有去使用Nacos Client提供的负载均衡能力。

在Spring Cloud Alibaba之服务发现组件 - Nacos一文中已经介绍过集群的概念以及作用,这里就不再赘述,加上上一小节中已经介绍过如何自定义负载均衡策略了,所以这里不再啰嗦而是直接上代码,实现代码如下:

同样的,想要使用该负载均衡策略的话,在配置文件中配置一下即可:

在以上两个小节我们实现了基于Nacos权重的负载均衡策略及同一集群下优先调用的负载均衡策略,但在实际项目中,可能会面临多版本共存的问题,即一个微服务拥有不同版本的实例,并且这些不同版本的实例之间可能是互不兼容的。例如微服务A的v1版本实例无法调用微服务B的v2版本实例,只能够调用微服务B的v1版本实例。

而Nacos中的元数据就比较适合解决这种版本控制的问题,至于元数据的概念及配置方式已经在Spring Cloud Alibaba之服务发现组件 - Nacos一文中介绍过,这里主要介绍一下如何通过Ribbon去实现基于元数据的版本控制。

举个例子,线上有两个微服务,一个作为服务提供者一个作为服务消费者,它们都有不同版本的实例,如下:

服务提供者有两个版本:v1、v2

服务消费者也有两个版本:v1、v2

v1和v2是不兼容的。服务消费者v1只能调用服务提供者v1;消费者v2只能调用提供者v2。如何实现呢?下面我们来围绕该场景,实现微服务之间的版本控制。

综上,我们需要实现的主要有两点:

优先选择同集群下,符合metadata的实例

如果同集群下没有符合metadata的实例,就选择其他集群下符合metadata的实例

首先我们得在配置文件中配置元数据,元数据就是一堆的描述信息,以k - v形式进行配置,如下:

然后就可以写代码了,和之前一样,也是通过负载均衡策略实现,具体代码如下:

继续阅读