spring-cloud中定义了LoadBalancerClient作为负载均衡器的通用接口。并针对Ribbon实现了RibbonLoadBalancerClient,但是在具体实现客户端负载均衡时,是通过Ribbon的ILoadBalancer接口实现的。
AbstractLoadBalancer是ILoadBalancer接口的一个抽象实现,在此抽喜爱那个类中定义了一个关于服务实例的非组枚举类ServerGroup,它包含三种不同类型。
除此还实现了一个chooseServer()函数,这个函数通过调用接口中的chooseServer(Object key)实现,中参数key为null,表示在选择具体服务实例忽略key的条件判断。
最后,还定义了两个抽象函数
BaseLoadBalancer类是Ribbon负载均衡器的基础实现类,在该类中定义了黑多关于负载均衡器相关的基础内容。
DynamicServerListLoadBalancer继承了BaseLoadBalancer类,是对BaseLoadBalancer基础负载均衡器的扩展,其中实现了服务实例清单在运行期的动态更新能力。同时脊背对服务实例请按的过滤功能,也就是说我们可以通过过滤器来选择性的获取一批服务实例清单。
ServerList 代表了一个具体的服务实例的扩展类。其中具有两个抽象方法:
public List getInitialListOfServers();用于获取初始化的服务实例清单
public List getUpdatedListOfServers(); 用于获取更新的服务实例清单
在这个负载均衡器中需要实现服务实例的动态更新,那么则需要Ribbon具备访问Eureka来获取服务实例的能力。通过搜索整合Ribbon与Eureka的包可以找到配置类EurekaRibbonClientConfiguration可以找到创建serverList的实例源代码:创建了一个DomainExtractingServerList实例,从它的源码可以找到他的内部还定义了一个ServerList ,同时这个类中对getInitialListOfServers和getUpdatedListOfServers的具体实现,其实委托给了内部定义的ServerList 对象,而该对象通过创建DomainExtractingServerList时,由构造函数传入DiscoveryEnabledNIWSServerList实现的。而DiscoveryEnabledNIWSServerList通过类方法中的一个私有函数obtainServersViaDiscovery()通过服务发现机制来实现发现服务实例的获取的。obtainServersViaDiscovery的实现逻辑如下:主要是依靠eurekaClient从服务注册中心获取具体的服务实例InstanceInfo列表,然后对这些服务实例进行遍历将状态为UP的实力转换成DiscoveryEnabledServer对象,最后将这些实例组织成类表返回。源码:
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
logger.warn("EurekaClient has not been initialized yet, returning an empty list");
return new ArrayList<DiscoveryEnabledServer>();
}
EurekaClient eurekaClient = eurekaClientProvider.get();
if (vipAddresses!=null){
for (String vipAddress : vipAddresses.split(",")) {
// if targetRegion is null, it will be interpreted as the same region of client
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
for (InstanceInfo ii : listOfInstanceInfo) {
if (ii.getStatus().equals(InstanceStatus.UP)) {
if(shouldUseOverridePort){
if(logger.isDebugEnabled()){
logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
}
// copy is necessary since the InstanceInfo builder just uses the original reference,
// and we don't want to corrupt the global eureka copy of the object which may be
// used by other clients in our system
InstanceInfo copy = new InstanceInfo(ii);
if(isSecure){
ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
}else{
ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
}
}
DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
serverList.add(des);
}
}
if (serverList.size()>0 && prioritizeVipAddressBasedServers){
break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
}
}
}
return serverList;
}
在DiscoveryEnabledNIWSServerList中通过EurekaClient从服务注册中心获取到最新的服务实例清单后,返回list到DomainExtractingServerList类中,将继续通过setZones函数进行处理,setZones()函数将list类表中的元素,转换成内部定义的DiscoveryEnabledServer的子类对象DomainExtractingServer,在该对象爱你个的构造函数将为服务实例对象设置一些必要的属性信息。
private List<DiscoveryEnabledServer> setZones(List<DiscoveryEnabledServer> servers) {
List<DiscoveryEnabledServer> result = new ArrayList<>();
boolean isSecure = this.ribbon.isSecure(true);
boolean shouldUseIpAddr = this.ribbon.isUseIPAddrForServer();
for (DiscoveryEnabledServer server : servers) {
result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr,
this.approximateZoneFromHostname));
}
return result;
}
ServerListUpdater:Ribbon和Eureka整合后,这个对象实现的是ServerList的更新–服务更新器。
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
源码可以看出其内部定义了UpdateAction接口,UpdateAction()以匿名内部类的方式创建了一个具体实现,其中doUpdate实现的内容就是对ServerList的具体更新操作,除此之外,ServerListUpdater中还定义了一系列控制它和获取他的信息的操作。
public interface ServerListUpdater {
public interface UpdateAction {
void doUpdate();
}
//qidongfuwu gengxin
void start(UpdateAction updateAction);
void stop();
String getLastUpdate();
long getDurationSinceLastUpdateMs();
int getNumberMissedCycles();
int getCoreThreads();
}
ServerListUpdater的实现类主要有:EurekaNotificationServerListUpdater和PollingServerListUpdater两个实现类
作用:
默认实现:PollingServerListUpdater ,它首先创建一个Runnable的线程实现,调用updateAction。doUpdate(),最后在为这个Runnable线程启动类一个定时任务来执行。其中用于的启动定时任务的两个重要参数initialDelayMs、refreshIntervalMs默认定义非别为1000和30*1000毫秒。就是说更新服务实例在初始化延迟1秒后执行,周期为30秒重复执行,还有一些其他参数如最后更新实际那,是否存活等信息,同时实现了serverListUpdater中定义的一些其他操作内容。
ServerListFilter:在DynamicServerListLoadBalancer中实际实现的委托给了updateListOfServer函数。在获取服务可用实例的类表后引入了新的filter—ServerListFilter。这个接口中只有一个 List getFilteredListOfServers(List servers);抽象方法,主要用于实现对服务实例类表的过滤,通过传入的服务实例清单,根据一些规则返回过滤后的服务实例清单,[外链图片转存失败(img-ynupHTIk-1566362733553)(C:\Users\79182\AppData\Roaming\Typora\typora-user-images\1563511153283.png)]其中除了ZonePreferenceServerListFilter的实现是spring-cloud-Ribbon中对Netflix Ribbon的扩展实现外,其他均是Netflix Ribbon的原生实现类。
ZoneAwareLoadBalancer负载均衡器是对DynamicServerListLoadBalancer的扩展,在DynamicServerListLoadBalancer中并没有腹泻选择具体服务实例的chooseServer函数,所以依旧采用BaseLoadBelancer中实现的算法。使用RoundRobinRule规则,以线性轮询的方式来选择调用的服务实例,这个算法实现了简单并没有区域的概念,所以他会把所有实例视为一个Zone下的节点来看待,这样就会周期性的产生跨区域访问的情况,就会产生更高的延迟,会有一定的性能问题。而在ZoneAwareLoadBalancer中,没有复写setServersList说明实现服务实例清单的更新主逻辑没有修改,但是我们可以发现他复写了这个函数setServerListForZones根据区域Zone分组的实例列表,每一个区域对应着一个ZoneStats,用于存储每个Zone的一些状态和统计信息。源码:
@Override
protected void setServerListForZones(Map<String, List<Server>> zoneServersMap) {
super.setServerListForZones(zoneServersMap);
if (balancers == null) {
balancers = new ConcurrentHashMap<String, BaseLoadBalancer>();
}
for (Map.Entry<String, List<Server>> entry: zoneServersMap.entrySet()) {
String zone = entry.getKey().toLowerCase();
getLoadBalancer(zone).setServersList(entry.getValue());
}
// check if there is any zone that no longer has a server
// and set the list to empty so that the zone related metrics does not
// contain stale data
for (Map.Entry<String, BaseLoadBalancer> existingLBEntry: balancers.entrySet()) {
if (!zoneServersMap.keySet().contains(existingLBEntry.getKey())) {
existingLBEntry.getValue().setServersList(Collections.emptyList());
}
}
}
源码可以看出:在实现中创建了一个ConcurrentHashMap类型的balancers,用来存储每个Zone区域对应的负载均衡器,二聚体的负载均衡器的创建则通过在下面的第一个循环中getLoadBalancer来实现,同时创建的时候会创建它的规则,创建完成后调用setServersList函数为其设置对应的Zone区域的实例清单,第二个循环则是对Zone区域中的。实例清单进行检查,检查是否有Zone区域下已经没有实例了,是的话就将balancers中对应的Zone区域的实例列表清空,作用主要是为了后续选择节点时,防止过时的zone区域统计信息干扰具体实例的选择算法。
选择实例:
@Override
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
if (triggeringLoad == null) {
triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
}
if (triggeringBlackoutPercentage == null) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
}
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
}
只有当负载均衡器中维护的实例所属的zone区域的个数大于1的时候才会执行这里的选择策略,否则还是使用父类的实现。当zone区域的个数大于1的时候,实现的步骤为: