小言_互联网的博客

[享学Ribbon] 二十五、Ribbon和Eureka的整合(一):ribbon-eureka工程详解

628人阅读  评论(0)

一个好的程序员是那种过单行线马路都要往两边看的人。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

Ribbon作为客户端负载均衡器,有一个必要的基础条件就获取到ServerList服务器列表,以及后续的动态更新服务列表。通过前面学习知道,服务列表它可以来自任何地方,比如默认实现ConfigurationBasedServerList它表示服务列表可以来自于配置(文件)。实际生产中,我们不可能把ServerList地址写死在配置里,实际的方式是把Ribbon同注册中心整合从而从注册中心里获取到列表,并且动态的去sync服务列表,本文“A哥”就带你领略一番。

服务注册中心有多种,本文将讲述它和自家产品Eureka做整合,以Eureka为代表进行说明即可,其它的举一反三。另需要说明的是:虽说eureka1.x目前也已经处在停更维护状态,但在Spring Cloud体系注册中心方面它依旧坚挺。为了便于整合,Ribbon官方提供了专门的整合工程:ribbon-eureka(基于Eureka 1.x)。

在阅读本文之前,建议/要求你已对Eureka有一定的认识了。关于Netflix各组件的学习,A哥非常用心的专门汇总了一篇文章,供以参考:Netflix OSS套件一站式学习驿站


正文

该工程是Ribbon旗下的一个子模块,所以GAV和Ribbon保持一致:

<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-eureka</artifactId>
    <version>2.3.0</version>
</dependency>

它的间接依赖截图如下:


说明:关于版本的使用上,请参照本专栏第一篇文章的版本声明部分,详细介绍了为何本系列依旧使用2.3.0版本。Eureka版本我们是可以单独升级的,本处约定使用其1.9.13版本(保持和Spring Cloud Hoxton.SR1版本内置的Eureka版本一致)。

因为ribbon-eureka仅依赖Eureka的核心API,因此只要大版本号不变,核心API必定是向下兼容的


为何Ribbon需要Eureka?

Ribbon 维护了一个服务器列表,如果服务器有宕机现象,Ribbon 能够自行将其剔除(内部有探活机制),没毛病;但如果该服务器故障排除,重新启动,或者增加新的负载节点,那么我们需要手工调用 Ribbon 的接口将其动态添加/移除进Ribbon 的服务器列表才能正常work,这样明显不够尽如人意。

我们想,如何能够在服务节点启动时,自行动态的添加/减少服务列表呢?答案那就是注册中心,也就是本文要说的Eureka。Eureka 提供了 Application Service 客户端的自行注册的功能。此外,Eureka 的缓存机制能够防止大规模宕机带来的灾难性后果。因此Ribbon和它整合,便可以让本地服务列表实现动态化。

Eureka和Ribbon因为都是Netflix自家产品,所以整合起来是比较方便的。若你想整合其它注册中心,可以使用其它相关整合包


ribbon-eureka工程详解

该整合工程由Netflix官方提供,是Ribbon主动去整合Eureka的(从命名上你也能看得到主次)。ribbon-eureka这个工程的内容并不多,截图如下:


针对这个工程的详解,主要会分两大部分展开:

  1. 工程内各类的源码解释
  2. 手工代码示例,领略Ribbon整合Eureka后是如何工作的

DiscoveryEnabledServer:扩展Server实现

它扩展自Server,代表该实例来自于注册中心(服务发现),所以它额外扩展了包含InstanceInfo形式的元数据。

说明:InstanceInfo是eureka里面的一个实例info信息,包含如:instanceId、appName、appGroupName、ipAddr、port、securePort、homePageUrl、healthCheckUrl...非常非常多的属性

public class DiscoveryEnabledServer extends Server{

	private final InstanceInfo instanceInfo;
	// com.netflix.loadbalancer.Server.MetaInfo服务元信息,基础数据均来自于InstanceInfo 
	private final MetaInfo serviceInfo;

	// 构造器:通过InstanceInfo构造出一个DiscoveryEnabledServer实例
    public DiscoveryEnabledServer(final InstanceInfo instanceInfo, boolean useSecurePort) {
        this(instanceInfo, useSecurePort, false);
    }
    // useIpAddr:是否使用IP地址
    public DiscoveryEnabledServer(final InstanceInfo instanceInfo, boolean useSecurePort, boolean useIpAddr) {
    	// 如果允许使用ip地址,并且判断该注册中心实例的port的是允许的,那就设置上
    	if(useSecurePort && instanceInfo.isPortEnabled(PortType.SECURE)) {
    		super.setPort(instanceInfo.getSecurePort());
    	}
		
		this.instanceInfo = instanceInfo;

		// MetaInfo的实现,全部委托给instanceInfo实例信息
		this.serviceInfo = new MetaInfo() {
			...
            @Override
            public String getAppName() {
                return instanceInfo.getAppName();
            }
            @Override
            public String getServiceIdForDiscovery() {
                return instanceInfo.getVIPAddress();
            }
            ...
		};
    }
    
    // 属性get方法
    public InstanceInfo getInstanceInfo() {
        return instanceInfo;
    }
    // 这可是复写了父类的方法哦
    // 父类的MetaInfo实现几乎为空实现:大都返回null
    @Override
    public MetaInfo getMetaInfo() {
        return serviceInfo;
    }
}

该Server实例的ip、端口等其它信息均来自于注册中心的实例info:InstanceInfo,因此对Eureka中的InstanceInfo的理解就显得很有必要,还好我有准备,请参考:Eureka的最核心概念:InstanceInfo实例信息


LegacyEurekaClientProvider

Legacy:遗赠,遗产。

一个通过静态方法,单例模式提供EurekaClient的遗留类,不建议再使用

class LegacyEurekaClientProvider implements Provider<EurekaClient> {

    private volatile EurekaClient eurekaClient;

    @Override
    public synchronized EurekaClient get() {
        if (eurekaClient == null) {
            eurekaClient = DiscoveryManager.getInstance().getDiscoveryClient();
        }

        return eurekaClient;
    }
}

因为DiscoveryManager已经被标记为@Deprecated,自然而然的本(工具)类也就不再推使用了(容易出错)。


NIWSDiscoveryPing:通过实例状态探活

对于IPing接口,Ribbon内置的几个实现如NoOpPing、DummyPing等其实均没有实际意义,空实现而已(永远返回true)。而对于本处集成了Eureka注册中心的话,定时ping这个动作显得就非常的有意义了。

本处给的实现,就是一个结合注册中心实例状态来实现探活的:

public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {

	@Override
	public boolean isAlive(Server server) {
	    boolean isAlive = true;
	    if (server!=null && server instanceof DiscoveryEnabledServer){
	           DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;	            
	           InstanceInfo instanceInfo = dServer.getInstanceInfo();
	           if (instanceInfo!=null){	                
	               InstanceStatus status = instanceInfo.getStatus();
	               if (status!=null){
	                   isAlive = status.equals(InstanceStatus.UP);
	               }
	           }
	       }
	    return isAlive;
	}
	...
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

它并不会进行远成访问。Server的活与否,完全由注册中心(本地)实例InstanceInfo.status属性来决定:

  • InstanceStatus.UP状态表示Server是活着的(isAlive=true)
  • 其它状态如InstanceStatus.DOWN/STARTING/OUT_OF_SERVICE/UNKNOWN都会认为Server已死(暂时不可用),从而最终就会被T出去

还记得IPing多久执行一次吗?

突然被灵魂拷问的感觉有木有,这在前面讲IPing这个组件的时候可没少啰嗦,这里只是“复习”一下。答案是:30s(默认值),详见BaseLoadBalancer#PingTask

当然喽:此值必须是可配置的啊。通过ribbon.NFLoadBalancerPingInterval = xxx来指定,单位秒。


DefaultNIWSServerListFilter:具有区域意识的服务过滤器

默认的NIWS筛选器——处理基于zone区域关联性和其他相关属性的筛选服务器。

public class DefaultNIWSServerListFilter<T extends Server> extends ZoneAffinityServerListFilter<T> {
}

直接继承自ZoneAffinityServerListFilter的“空”实现,关于它的讲解前面A哥也没少啰嗦。


总结

关于Ribbon和Eureka的整合第一部分就先讲解到这,你会发现ribbon-eureka工程还有两个API没有涉及到,因为那哥俩才是重头戏,为了分离关注,A哥把它放在了下篇文章单独、重点叙述,请您移步。

声明


转载:https://blog.csdn.net/f641385712/article/details/105031383
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场