【深入浅出SpringCloud源码探究】「Netflix系列之Ribbon+Fegin」微服务化的负载均衡组件源码剖析与实战开发全流程(Ribbon篇)

本文主要是介绍【深入浅出SpringCloud源码探究】「Netflix系列之Ribbon+Fegin」微服务化的负载均衡组件源码剖析与实战开发全流程(Ribbon篇),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

微服务化的负载均衡组件源码剖析与实战开发全流程

  • 什么是负载均衡
    • 负载均衡的种类
      • 服务器端负载均衡(S-LB)
      • 客户端负载均衡(C-LB)
        • 注解@LoadBalanced
        • LoadBalancerAutoConfiguration类
          • LoadBalancerClient类
            • 源码分析
          • ServiceInstanceChooser类
      • 内置负载均衡策略的介绍
        • IRule
          • `IRule`的源码
          • `IRule`接口定义了3个方法
          • 主要方法是
        • IRule的实现类
          • 八种常见的负载均衡策略
          • 负载均衡的自定义
            • 通过代码实现 - 配置类
          • 同时使用2种以上的不同策略算法
            • 移除@Configuration 注解
            • 操作处理方式
            • 通过配置配置文件实现不同的算法
      • 如何对负载均衡策略进行扩展
        • 继承 AbstractLoadBalancerRule 类
        • 通过配置文件
    • 总结分析

什么是负载均衡

负载均衡是通过将请求流量分发到多个服务器来实现资源分配的一种策略。它可以确保各个服务器在处理请求方面的负载均衡,并能够更高效地利用系统资源。负载均衡的主要目标是避免服务器过载,并通过在不同的服务器之间分发负载,提高系统的可伸缩性和可用性。

负载均衡的种类

通过负载均衡,我们可以在服务器集群中有效地分配请求,从而实现更快的响应时间和更好的用户体验。目前负载均衡的方式分类主要有两种:服务器端负载均衡(nginx)和客户端负载均衡(Ribbon)
在这里插入图片描述

服务器端负载均衡(S-LB)

服务器端负载均衡(如Nginx)是一种将请求流量分发到多个服务器的方法,以提高系统的性能和可靠性。通过将请求分发到不同的服务器,负载均衡可以避免单个服务器的过载,并能够更均衡地分配请求负载,从而提高整体的响应能力。
在这里插入图片描述

客户端负载均衡(C-LB)

客户端负载均衡(如Ribbon)是在客户端层面上进行的负载均衡。当客户端发起请求时,通过负载均衡算法,Ribbon可以选择最合适的服务器来处理请求。这种方式使得客户端可以根据实际情况选择最佳的服务器,提高了系统的可扩展性和容错性。
在这里插入图片描述

注解@LoadBalanced

作用:识别应用名称,并进行负载均衡。

在Spring Cloud应用中,使用RestTemplate进行服务间的通讯时,我们可以添加@LoadBalanced注解来开启负载均衡。

在一个微服务架构中,通常一个服务会有多个实例,并且这些实例部署在不同的服务器上。为了保证服务的高可用性,我们需要在这些实例之间进行负载均衡。Spring Cloud提供了@LoadBalanced注解,它可以在RestTemplate中实现负载均衡:

@Configuration
public class RestTemplateConfiguration {@LoadBalanced  // 使用 @LoadBalanced 注解实现负载均衡@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}}

在RestTemplate添加了@LoadBalanced注解之后,我们就可以直接使用服务名来调用其他服务,而不再需要关心具体的IP和端口号。

@Autowired
private RestTemplate restTemplate;public String runTaskExecute() {return restTemplate.getForObject("http://serviceName/execute", String.class);
}

当发起请求时,Ribbon会自动从服务注册中心获取serviceName的所有实例,然后根据负载均衡策略选择一个实例进行调用,这样就实现了负载均衡。默认负载均衡策略是基于轮询算法,平均分配请求给每个服务实例。

LoadBalancerAutoConfiguration类

Ribbon的负载均衡自动配置需满足两个条件:

  1. RestTemplate类必须在当前项目的环境中可用,@ConditionalOnClass(RestTemplate.class)
  2. Spring的Bean工厂中必须存在LoadBalancerClient的实现类的Bean实例。@ConditionalOnBean(LoadBalancerClient.class)
    在这里插入图片描述

下面是LoadBalancerAutoConfiguration的源码:

/*** Auto configuration for Ribbon (client side load balancing).* @author Spencer Gibb* @author Dave Syer* @author Will Tran*/
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) {return new SmartInitializingSingleton() {@Overridepublic void afterSingletonsInstantiated() {for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {for (RestTemplateCustomizer customizer : customizers) {customizer.customize(restTemplate);}}}};}
}
LoadBalancerClient类

LoadBalancerClient的接口的定义,该接口在Spring Cloud中被用来实现客户端的负载均衡。它是一个Spring Cloud特有的接口,扩展自ServiceInstanceChooser接口。这个接口定义了如何从服务注册中心获取服务实例并对其进行负载均衡。

import java.io.IOException;
import java.net.URI;/*** Represents a client side load balancer.* * @author Spencer Gibb*/
public interface LoadBalancerClient extends ServiceInstanceChooser {/*** Execute request using a serviceInstance from the LoadBalancer for the specified service.** @param serviceId the service id to look up the LoadBalancer* @param request allows implementations to execute pre and post actions such as incrementing metrics* @return the result of the LoadBalancerRequest callback on the selected ServiceInstance */<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;/*** Execute request using a ServiceInstance from the LoadBalancer for the specified service.** @param serviceId the service id to look up the LoadBalancer* @param serviceInstance the service to execute the request to* @param request allows implementations to execute pre and post actions such as incrementing metrics* @return the result of the LoadBalancerRequest callback on the selected serviceInstance */<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;/*** Create a proper URI with a real host and port for systems to utilize.* Some systems use a URI with the logical service name as the host, such as http://myservice/path/to/service.* This will replace the service name with the host:port from the serviceInstance.** @param instance* @param original a URI with the host as a logical service name* @return a reconstructed URI*/URI reconstructURI(ServiceInstance instance, URI original);
}
源码分析

这些方法主要在客户端向服务端发起请求时使用,以实现负载均衡的效果:

  • execute(String serviceId, LoadBalancerRequest<T> request) throws IOException:负载均衡器选择的服务实例执行请求。serviceId是要查找负载均衡器的服务的id,request参数让实现类在服务调用前后执行一些操作,如度量增量等。

  • execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException:增加了serviceInstance参数,该参数表示要执行请求的服务,是在已经选择了服务实例后执行请求。

  • reconstructURI(ServiceInstance instance, URI original):一些系统使用的是逻辑服务名作为主机的URI,如http://myservice/path/to/service,这种URI中的主机名不是真实的网络地址,而是服务名。

在基于服务名进行路由的微服务系统,为了获取实际的网络地址(host:port),就需要这个转换方法,把逻辑服务名替换为从服务实例获取的真实主机名和端口

ServiceInstanceChooser类

接口ServiceInstanceChooser,该接口在Ribbon中被用作负载均衡策略的接口,应用可以实现这个接口来自定义自己的负载均衡策略。在这个接口中定义了一个choose方法,该方法用于从负载均衡器中为特定服务选择一个服务实例。

/*** Implemented by classes which use a load balancer to choose a server to* send a request to.** @author Ryan Baxter*/
public interface ServiceInstanceChooser {/*** Choose a ServiceInstance from the LoadBalancer for the specified service.** @param serviceId the service id to look up the LoadBalancer* @return a ServiceInstance that matches the serviceId*/ServiceInstance choose(String serviceId);
}

负载均衡器具备以下几个主要职能:
在这里插入图片描述

  1. 能够根据特定的服务ID从负载均衡器中选取一个合适的服务实例。

  2. 能够利用选取的服务实例执行特定的任务或请求。

  3. 能够为系统生成一个有效的“主机名:端口号”格式的URI,以便于系统的其他部分使用。


内置负载均衡策略的介绍

IRule

IRule是Ribbon客户端内置负载均衡策略的接口定义,所有Ribbon内建策略或者自定义策略都需要实现这个接口。它主要的决定了服务选择的策略,即根据什么样的规则从一组服务中选取一个有效的服务实例。

IRule的源码
package com.netflix.loadbalancer;/** The class that will be used by clients of Ribbon API to pick a server from* the already filtered list of servers. The responsibility of implementations* will be to spread the load of request traffic among the list of servers.*/
public interface IRule {/** set load balancer** @param lb*/public void setLoadBalancer(ILoadBalancer lb);/** get load balancer** @return*/public ILoadBalancer getLoadBalancer();/** Choose a server from load balancer.** @param key An object that the load balancer may use to determine*            which server to return. key is defined by client, and*            can be anything.  load balancer implementations may*            choose to return a server based on key or not.** @return server chosen*/public Server choose(Object key);
}
IRule接口定义了3个方法
  1. setLoadBalancer(ILoadBalancer lb): 用来设置负载均衡器。
  2. getLoadBalancer(): 用来获取负载均衡器。
  3. choose(Object key): 用来从已经过滤过的服务列表中选择合适的服务。key参数是由客户端定义的,可以是任何对象。实现此接口的类可以选择根据key选择服务,也可以忽略key
主要方法是
public abstract Server choose(Object key);
  • 出参: Server:这是Ribbon定义的一个类,代表了一个可以达到的、执行某个服务的物理或者虚拟的实例。
  • 入参: choose(Object key):此方法根据传入的key(key的具体含义根据实现类的解读可能有所不同),选择并返回一个Server。
IRule的实现类

当你实现IRule接口时,你可以自己定义服务选择的规则,比如你可以根据服务的实际情况(如服务器负载、网络延迟等信息)来选择最符合你需求的服务,创建出定制化的负载均衡策略。

八种常见的负载均衡策略

在这里插入图片描述
八种常见的负载均衡策略(BestAvailableRule、AvailabilityFilteringRule等)都是IRule的实现类,每种策略都有自己独特的服务选择规则。

策略名称描述
BestAvailableRule该策略选择并发请求最少的server。若某个Server处于熔断状态,将忽略该Server。
AvailabilityFilteringRule过滤掉连续连接失败被标记为熔断的Server,以及并发连接高的Server。
ZoneAvoidanceRule复合判断Server所在区域的性能和Server的可用性来选择Server,剔除不可用的区域的所有Server和连接数过多的Server。
RandomRule随机策略,会在所有可用的Server中进行随机选择。
RetryRule为选定的负载均衡策略添加重试机制。在配置的时间段内若无法成功选择Server,将会持续重试。
RoundRobinRule轮询策略,每次请求会轮询选择一个Server。此为默认策略。
WeightedResponseTimeRule根据响应时间分配权重,响应时间较长的Server权重越小,被选中的可能性越低。
ResponseTimeWeightedRule与WeightedResponseTimeRule一致,是其旧版本名称。
负载均衡的自定义
通过代码实现 - 配置类

仅需两个简洁的配置,即可确定轮询的策略。

@Configuration
public class RandomRuleConfig {@Beanpublic IRule randomRule() {return new RandomRule();}
}@Configuration
@RibbonClient(name = "client-balance-provider", configuration = RandomRuleConfig.class)
public class ProviderConfiguration {
}

注意,如果在RandomRuleConfig类中添加了@Configuration注解,所有的负载均衡策略将被覆盖。这是因为如果RandomRuleConfig类被SpringContext扫描到,这会导致所有的策略被@RibbonClients共享,从而实现覆盖。

同时使用2种以上的不同策略算法
移除@Configuration 注解

以下是整理和优化后的代码:

// Define the round robin rule configuration.
public class RoundRobinRuleConfig {@Beanpublic IRule roundRobinRule() {return new RoundRobinRule();}
}

RoundRobinRuleConfig类定义了roundRobinRule方法,该方法生成了RoundRobinRule的实例,表示采用轮询负载均衡策略。

// The configuration class for the Ribbon client.
@Configuration
@RibbonClient(name = "spring-cloud-provider2", configuration = RoundRobinRuleConfig.class)
public class Provider2Configuration {}

Provider2Configuration类则是spring-cloud-provider2服务的Ribbon配置类,该类指定在调用spring-cloud-provider2服务时,使用RoundRobinRuleConfig类中定义的轮询策略。

操作处理方式

需要新增RoundRobinRuleConfig类,并移除其中的@Configuration注解。同时,也要移除RandomRuleConfig类中的@Configuration注解。

  1. 可以通过指定@ComponentScan的扫描路径实现(默认情况下,扫描路径为主类所在的所有文件夹)。

  2. RoundRobinRuleConfig类移至主类之外,防止主程序进行扫描,一定要确保SpringContext无法扫描到这些类。

通过配置配置文件实现不同的算法

通过配置文件,可以让应用来灵活的选择不同的负载均衡策略。设置格式为:<application-name>.ribbon.NFLoadBalancerRuleClassName=<fully-qualified-class-name>

这样就可以根据应用的实际需要,选择最适合的负载均衡策略。

#设置策略 
spring-cloud-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
#spring-cloud-provider2.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

如何对负载均衡策略进行扩展

当内置的负载均衡策略不满足业务需求的时候,我们就需要自定义 Ribbon 的负载策略。

继承 AbstractLoadBalancerRule 类

扩展了AbstractLoadBalancerRule`抽象类,用于定义自定义的负载均衡规则。这个规则是,从所有可用的服务实例(upList)中,选择端口为7779的服务实例为提供服务的实例。如果没有找到,或者选中的实例已经不可用,则重新选择。如果因为线程中断导致获取服务实例错误,直接返回null。

/*** Custom Ribbon load balancing strategy.*/
public class RoncooCustomRule extends AbstractLoadBalancerRule {private Server choose(ILoadBalancer lb, Object key) {if (lb == null) {return null;}Server server = null;while (server == null) {if (Thread.interrupted()) {return null;}// Available service instancesList<Server> upList = lb.getReachableServers();// Only fetch service instance with port: 7779for (Server s : upList) {if (s.getPort() == 7779) {server = s;break;}}if (server == null) {Thread.yield();continue;}if (server.isAlive()) {return server;}server = null;Thread.yield();}return server;}@Overridepublic Server choose(Object key) {return choose(getLoadBalancer(), key);}@Overridepublic void initWithNiwsConfig(IClientConfig clientConfig) {// No-Operation}
}
通过配置文件
spring-cloud-provider.ribbon.NFLoadBalancerRuleClassName=com.roncoo.education.configuration.RoncooCustomRule

总结分析

Ribbon是Netflix开发的一款基于HTTP和TCP的客户端负载均衡器。它在Spring Cloud环境中广泛使用于执行HTTP请求的负载均衡,其主要知识点和技术特性包括:

  1. 客户端负载均衡:Ribbon是一个客户端的负载均衡器,这意味着它会在客户端运行,并在发起请求时对一组服务实例进行选择。

  2. 灵活的负载均衡策略:Ribbon内置了多种负载均衡策略,如轮询、随机、响应时间加权等。用户也可以自定义策略以满足特殊要求。

  3. 故障转移:在访问某个服务实例失败时,Ribbon可以自动选择另一个实例进行访问,以提高系统的可用性。

  4. 扩展性良好:Ribbon的结构让它可以方便地扩展和定制,以支持各种各样的需求。
    在这里插入图片描述

这篇关于【深入浅出SpringCloud源码探究】「Netflix系列之Ribbon+Fegin」微服务化的负载均衡组件源码剖析与实战开发全流程(Ribbon篇)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/499692

相关文章

Spring boot整合dubbo+zookeeper的详细过程

《Springboot整合dubbo+zookeeper的详细过程》本文讲解SpringBoot整合Dubbo与Zookeeper实现API、Provider、Consumer模式,包含依赖配置、... 目录Spring boot整合dubbo+zookeeper1.创建父工程2.父工程引入依赖3.创建ap

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

MySQL 多列 IN 查询之语法、性能与实战技巧(最新整理)

《MySQL多列IN查询之语法、性能与实战技巧(最新整理)》本文详解MySQL多列IN查询,对比传统OR写法,强调其简洁高效,适合批量匹配复合键,通过联合索引、分批次优化提升性能,兼容多种数据库... 目录一、基础语法:多列 IN 的两种写法1. 直接值列表2. 子查询二、对比传统 OR 的写法三、性能分析

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

创建Java keystore文件的完整指南及详细步骤

《创建Javakeystore文件的完整指南及详细步骤》本文详解Java中keystore的创建与配置,涵盖私钥管理、自签名与CA证书生成、SSL/TLS应用,强调安全存储及验证机制,确保通信加密和... 目录1. 秘密键(私钥)的理解与管理私钥的定义与重要性私钥的管理策略私钥的生成与存储2. 证书的创建与

浅析Spring如何控制Bean的加载顺序

《浅析Spring如何控制Bean的加载顺序》在大多数情况下,我们不需要手动控制Bean的加载顺序,因为Spring的IoC容器足够智能,但在某些特殊场景下,这种隐式的依赖关系可能不存在,下面我们就来... 目录核心原则:依赖驱动加载手动控制 Bean 加载顺序的方法方法 1:使用@DependsOn(最直

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr