在上一篇文章揭秘 Feign 调用机制:微服务通信的无缝集成中,我们了解到Feign调用机制的实现深度依赖Ribbon,本文将在此基础上深入探讨Ribbon是如何获取服务实例列表并执行负载均衡相关策略选择具体服务实例的。
一、Ribbon的重要性
在微服务架构中,通常会有多个服务实例同时运行以提供相同的服务。这就需要一种机制来确保客户端能够有效地选择合适的服务实例进行调用,同时还要保证负载能够均衡地分布在各个实例上,以提高系统的整体性能和可靠性。Ribbon 正是为了解决这个问题而诞生的。
二、获取服务实例列表
前文中,探究 Eureka 在 Spring Boot 中的配置注入与统一管理机制(下)——第四节提到,Eureka的源码中集成了Ribbon,那么本文继续以Eureka为例。其实,Ribbon 可以与多种服务注册中心集成。
Ribbon 会通过 EurekaClient 接口与 Eureka 服务器进行通信以获取服务实例。以下是核心方法:
- updateListOfServers方法
- 位置:DynamicServerListLoadBalancer类中。
- 作用:更新可用的服务实例列表
- 实现:调用 obtainServersViaDiscovery方法来获取可用的服务实例列表
- obtainServersViaDiscovery方法
- 位置:DiscoveryEnabledNIWSServerList类中。
- 作用:获取可用的服务实例
- 实现:调用getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion)的方法获取可用的服务实例列表。
- getInstancesByVipAddress(String vipAddress, boolean secure,@Nullable String region)方法
它是最核心的方法。- 位置: DiscoveryClient类中。
- 作用:负责根据虚拟主机名获取实例列表
- 实现
public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure,
@Nullable String region) {
……
Applications applications;
// 获取applications
if (instanceRegionChecker.isLocalRegion(region)) {
applications = this.localRegionApps.get();
} else {
applications = remoteRegionVsApps.get(region);
if (null == applications) {
logger.debug("No applications are defined for region {}, so returning an empty instance list for vip "
+ "address {}.", regio……n, vipAddress);
return Collections.emptyList();
}
}
// 根据虚拟主机名获取与之关联的实例列表。
if (!secure) {
return applications.getInstancesByVirtualHostName(vipAddress);
} else {
return applications.getInstancesBySecureVirtualHostName(vipAddress);
}
}
通过1-3步,我们知晓了Ribbon是如何获取可用的服务实例列表的。
那么,最新的服务实例列表是如何更新的呢。答案是可调度线程。
通常情况下,DynamicServerListLoadBalancer 使用一个后台任务(通过 PollingServerListUpdater)定期调用 updateListOfServers 方法,以保持服务实例列表的最新状态。
public PollingServerListUpdater(long initialDelayMs, long refreshIntervalMs) {
this.isActive = new AtomicBoolean(false);
this.lastUpdated = System.currentTimeMillis();
this.initialDelayMs = initialDelayMs;
this.refreshIntervalMs = refreshIntervalMs;
}
这个过程通常是由 PollingServerListUpdater 类中的 start() 方法启动的,该方法会定期触发更新操作。
this.scheduledFuture = getRefreshExecutor()