在前文《深入理解 Eureka 注册中心的原理、服务的注册与发现》中,介绍了如何使用 Eureka 实现服务的注册与拉取,并且通过添加 @LoadBalanced
注解实现了负载均衡。这种自动化的背后隐藏着许多疑问:
本文旨在深入探讨使用 Eureka 实现负载均衡的原理,为我们理解微服务架构中服务调用的内部机制提供更清晰的认识。通过解答这些疑惑,我们将更好地理解服务发现、负载均衡的运作方式,为构建高性能、稳定的分布式系统打下坚实的基础。
Ribbon 是一个基于 HTTP 和 TCP 客户端的负载均衡器。在微服务架构中,服务的调用通常涉及到负载均衡的问题,即在多个服务提供方中选择一个进行调用。Ribbon 提供了一种简单而有效的负载均衡解决方案。
Ribbon 最初是 Netflix 公司开发的,后来成为 Spring Cloud 项目的一部分。它的主要作用是在服务消费者和提供者之间实现均衡的流量分发,确保每个服务提供者都能够得到适当的请求,避免出现服务过载或资源浪费的情况。
具体而言,Ribbon 实现了以下功能:
负载均衡算法: Ribbon 支持多种负载均衡算法,例如轮询、随机、权重轮询等,使得服务消费者可以根据实际场景选择适当的负载均衡策略。
服务实例的自动发现: Ribbon 与 Eureka 等服务注册中心集成,能够自动获取可用的服务实例列表。
故障转移和重试机制: Ribbon 具备故障转移和重试功能,可以在服务提供者发生故障时自动切换到其他健康的实例,提高系统的稳定性和可用性。
在 Spring Cloud 中,Ribbon 作为一个负载均衡的客户端组件,通过拦截微服务的调用请求,动态地选择目标服务实例,从而分配请求的负载,实现了对服务调用的细粒度控制。
下面是对这个流程的详细说明:
userservice
。这个流程确保了服务的请求能够被合理地分发到多个实例中,从而实现了负载均衡。
首先,Ribbon 实现负载均衡使用到的一个类叫做 LoadBalancerInterceptor
负载均衡拦截器,可以通过 IDEA 查看它的源码:
发现它实现了一个 ClientHttpRequestInterceptor
接口,即客户端 HTTP 请求拦截器:
它会拦截 RestTemplate
发生的 HTTP 请求,ClientHttpRequestInterceptor
是一个接口,并且其中包含了一个 intercept
方法,因此LoadBalancerInterceptor
作为实现这个接口的类也一定重写了 intercept
方法,此时我们可以在这个方法中设置一个断点进行调试,以追踪代码的运行:
3. 当找到了服务的名称之后,接下来要做的工作就是向 EurekaServer 去拉取对应的服务了,然后这个方法就把获取到的服务名交给了一个RibbonLoadBalancerClient
(Ribbon负载均衡客户端)进行处理。
4. 继续调试代码,进入execute
方法:
LoadBalancer
对象,:user-service
向 EurekaServer 中注册的服务。因此getLoadBalancer
方法的作用就是根据服务名称向 EurekaServer 中寻找服务列表。当找到了服务列表之后,我们就可以大胆的猜测,下一步所要做的工作就是进行负载均衡操作了。
8. 然后再进入chooseServer
方法,最后找到了rule.choose
方法:
此时查看 rule 对象,发现是一个接口:
既然是接口,那么就有实现类:
此时发现的实现类就是负载均衡的规则了。大致的规则有随机、轮询等等。
接下来就可以使用真正的IP和端口号去代替 userservice
,然后去访问指定了服务了。
以上就是 Ribbon 实现负载均衡的源码剖析,通过调试了方法深入探索了服务发现与负载均衡是实现流程,帮助我们更好的理解了服务发现、负载均衡的运作方式
通过上面的源码分析不难发现,Ribbon 的负载均衡规则是一个叫做 IRule 的接口来定义的,每一个子接口都是一种规则。
关于 Ribbon 的负载均衡策略可以总结如下表所示:
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对短路和并发数过高的服务器进行忽略 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器 |
RetryRule | 重试机制的选择逻辑 |
Ribbon 提供了这些内置的负载均衡规则,同时也支持自定义负载均衡规则。在实际应用中,根据业务特点选择合适的负载均衡策略是非常重要的。下面演示了 Ribbon 负载均衡策略的更改。
通过定义IRule实现可以修改负载均衡规则,有两种方式:
order-service
中的OrderApplication
启动类中,定义一个新的IRule
,并使用 @Bean
注解注册到 Spring 容器中:@Bean
public IRule randomRule(){
return new RandomRule();
}
order-service
的application.yml
文件中,添加新的配置也可以修改规则:# 修改 Ribbon 负载均衡策略
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
例如,下面是修改了负载均衡策略之后,再次使用 order-service
访问订单的结果。可以发现,现在不再是以轮询的方式挑选user-service
服务了,而是以随机的方式进行挑选了。
当我们重新启动 order-service
服务,然后在浏览器中进行订单访问,可以发现如下的现象:
当 order-service
服务启动后,第一次访问服务可以发现耗时需要三百多毫秒:
然后,再次访问多次,可以发现耗时都变成了十几毫米:
通过以上的现象就可以发现,Ribbon 默认采用的是懒加载模式,就像单例模式的懒汉模式一样,第一次访问的时候才会去创建LoadBalanceClient
实例,请求时间会很长。
为了解决上述懒加载的耗时问题,Ribbon 还提供了饥饿加载模式,饥饿加载则会在项目启动时创建,降低第一次访问的耗时。
通过下面配置开启饥饿加载:
此时重启 order-service
服务:
在启动服务的时候,就会发现日志变得更多了:
这个日志的内容就是加载LoadBalanceClient
实例所产生的日志。
再次首次访问 order-service
服务,就会发现消耗的时间变短了: