[享学Netflix] 四十三、Ribbon的LoadBalancer五大组件之:ServerList服务列表

调试一个初次见到的代码比重写代码要困难两倍。因此,按照定义,如果你写代码非常巧妙,那么没有人足够聪明来调试它。

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

前言

继上文讲述了LoadBalancer五大组件之一的IPIng后,本文继续讲解它的ServerList组件。


正文

ServerList

该接口定义了获取服务器列表的方法。

// T并不规定必须是Server,也可以是它的子类
// 但实际情况是:一般并不会自己去继承Server来玩,因为Server已经够抽象了
public interface ServerList<T extends Server> {
	// 返回初始状态的服务器列表。比如初试配置了10台那它永远是10个Server
	public List<T> getInitialListOfServers();
	// 返回更新后的服务列表。它会周期ping过后,返回活着的
	public List<T> getUpdatedListOfServers();
}

它的继承图谱如下(Spring Cloud环境下多了个实现):

在这里插入图片描述
在这里插入图片描述


AbstractServerList

该抽象实现有且提供一个public方法:提供loadBalancer使用的过滤器AbstractServerListFilter

此处疑问:获取ServerListFilter的方法为何写在ServerList里呢?八竿子打不着好吗?不知道作者咋想的呢?因此本处先一笔略过

public abstract class AbstractServerList<T extends Server> implements ServerList<T>, IClientConfigAware {

	public AbstractServerListFilter<T> getFilterImpl(IClientConfig niwsClientConfig) throws ClientException{
		...
	}
}

还好它有个实现类:ConfigurationBasedServerList,基于配置的ServerList。


ConfigurationBasedServerList

它可以从Configuration配置中加载服务器列表的实用工具实现类。属性名的定义格式如下:

<clientName>.<nameSpace>.listOfServers=<comma delimited hostname:port strings>

依赖的IClientConfig配置管理去获取配置值,所以没有ClientName获取的就是全局的,但是那又有什么意义呢???

// 这里挺有意思,到这里却确定了泛型类型是Server,搞怪~
public class ConfigurationBasedServerList extends AbstractServerList<Server>  {
	private IClientConfig clientConfig;
	
	// 在此处两个方法的实现效果是一模一样的。
	@Override
	public List<Server> getInitialListOfServers() {
	    return getUpdatedListOfServers();
	}
	@Override
	public List<Server> getUpdatedListOfServers() {
		// key是:listOfServers(请注意:首字母l是小写)
        String listOfServers = clientConfig.get(CommonClientConfigKey.ListOfServers);
        // derive->逗号分隔 -> new Server(s.trim())
        // 也就是说s是个id
        return derive(listOfServers);
	}
	
	protected List<Server> derive(String value) { ... }
}

该实现类需要引起重视,因为在Spring Cloud中,脱离eureka使用ribbon的经典配置:

# 禁用ribbon在eureka里使用
ribbon.eureka.enabled=false
# 配置服务提供者的地址
account.ribbon.listOfServers=localhost:8080,localhost:8081

这样配置后,它就是使用ConfigurationBasedServerList来加载服务器列表的。


代码示例
  • API方式配置
@Test
public void fun2(){
    // 准备配置
    IClientConfig config = new DefaultClientConfigImpl();
    // config.set(CommonClientConfigKey.valueOf("listOfServers"), "www.baidu.com,http://yourbatman.com:8080");
    config.set(CommonClientConfigKey.ListOfServers, "    www.baidu.com,http://yourbatman.com:8080    ");

    ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
    serverList.initWithNiwsConfig(config);

    serverList.getInitialListOfServers().forEach(server -> {
        System.out.println(server.getId());
        System.out.println(server.getHost());
        System.out.println(server.getPort());
        System.out.println(server.getHostPort());
        System.out.println(server.getScheme());
        System.out.println("-----------------------------");
    });
}

运行程序,控制台输出:

www.baidu.com:80
www.baidu.com
80
www.baidu.com:80
null
-----------------------------
yourbatman.com:8080
yourbatman.com
8080
yourbatman.com:8080
http
-----------------------------
  • 配置文件方式:

config.properties配置内容为:

account.ribbon.listOfServers=    www.baidu.com,http://yourbatman.com:8080    

编写测试代码:

@Test
public void fun100(){
    DefaultClientConfigImpl config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("account");

    ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
    serverList.initWithNiwsConfig(config);

    serverList.getInitialListOfServers().forEach(server -> {
        System.out.println(server.getId());
        System.out.println(server.getHost());
        System.out.println(server.getPort());
        System.out.println(server.getHostPort());
        System.out.println(server.getScheme());
        System.out.println("-----------------------------");
    });
}

运行程序,结果完全同上。


小bug

有小伙伴跟我反应了一种case,在此处顺便我给补上。它是这么做的(仅仅改了Config而已):

@Test
public void fun101(){
    DefaultClientConfigImpl config = DefaultClientConfigImpl.getEmptyConfig();
    config.loadDefaultValues(); // 注意:这句必须显示调用,否则配置里无值

    ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
    serverList.initWithNiwsConfig(config);

    serverList.getInitialListOfServers().forEach(server -> {
        System.out.println(server.getId());
        System.out.println(server.getHost());
        System.out.println(server.getPort());
        System.out.println(server.getHostPort());
        System.out.println(server.getScheme());
        System.out.println("-----------------------------");
    });
}

config.properties内容(使用的全局配置):

ribbon.listOfServers=www.baidu.com,http://yourbatman.com:8080

运行程序输出:

www.baidu.com:80
www.baidu.com
80
www.baidu.com:80
null
-----------------------------

what?只输出一个???为何只会打印一个呢?

在了解具体原因之前,确保你已经对IClientConfig的原理非常了解了(不了解可以点这里)。ConfigurationBasedServerList使用的是clientConfig.get(CommonClientConfigKey.ListOfServers)去获取属性值,而get()方法依赖的是DefaultClientConfigImpl#getProperty(String key)

DefaultClientConfigImpl:

    protected Object getProperty(String key) {
        if (enableDynamicProperties) {
        	// 它里面会先找指定clientName的属性,再找全局
        	// 秒就秒在这里,它使用的是DynamicProperty.getInstance(..).getString()
        	// 这个getString()方法为**原样输出**,这点特别重要
        	...
        }
        return properties.get(key);
	}

例子中只调用了加载默认值方法,所以enableDynamicProperties仍旧为false,所以此处会走properties.get(key)方法,该方法就是一Map的get方法没啥可说的,重点是它的值是如何放进去的?

DefaultClientConfigImpl:

	public void loadDefaultValues() {
		...
		// 回去配置文件加载默认值放进去
		putDefaultStringProperty(CommonClientConfigKey.ListOfServers, "");
	}
	protected void putDefaultStringProperty(IClientConfigKey propName, String defaultValue) {
		String value = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(propName), defaultValue);
		setPropertyInternal(propName, value);
	}

这个AbstractConfiguration#getString()方法就是罪魁祸首:当key对应的value值是集合时,只会返回第一个值collection.iterator().next()。所以放进properties时只有一个值,properties.get(key)取出来肯定就只有一个喽。

解决问题非常的简单,不要用config.loadDefaultValues()方法即可,而是这么做,问题就得到解决。

DefaultClientConfigImpl config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("noClient")

这个“bug”本身并不重要,因为说过listOfServers禁止使用全局配置。但是该结果反映出来的原理还是可以深究的。实际上Ribbon里的这种小bug不止一处,后续文章还会继续提到。

说明:这种小问题我个人觉得是Ribbon的考虑不够全面导致的,当然喽绝大多数情况下都无伤大雅,可以从“操作”上规避它~


StaticServerList

它是Spring CloudServerList接口的实现,因为比较简单,因此在此处一并带过。顾名思义它表示静态的List,这种实现太简单了。

public class StaticServerList<T extends Server> implements ServerList<T> {

	private final List<T> servers;
	public StaticServerList(T... servers) {
		this.servers = Arrays.asList(servers);
	}
	
	@Override
	public List<T> getInitialListOfServers() {
		return servers;
	}
	@Override
	public List<T> getUpdatedListOfServers() {
		return servers;
	}
}

该实现类没有任何地方被使用到过,Spring Cloud提供出来是若你有写死Server地址在代码里的时候,可以使用它,只是我们需要这么做的概率极小极小而已。


哪里调用?

ServerList#getInitialListOfServers方法无任何地方调用,ServerList#getUpdatedListOfServers方法的唯一调用处是:DynamicServerListLoadBalancer#updateListOfServers处:

DynamicServerListLoadBalancer:

	// 更新服务列表。
	public void updateListOfServers() {
		// 拿到列表
		servers = serverListImpl.getUpdatedListOfServers();
		// 把列表过滤一把
		servers = filter.getFilteredListOfServers(servers);
	
		// 设置最新列表
		pdateAllServerList(servers);
	}

总结

关于Ribbon的LoadBalancer五大组件之:ServerList就介绍到这了,本篇内容依旧比较简单。该API用于获取Server列表,它可以来自于写死的List、配置文件、甚至是远程网络(如eureka配置中心)。另外从实现中你可以看到:getInitialListOfServers和getUpdatedListOfServers两方法是完全对等的(哪怕和eureka集成的实现com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList里这两个方法也是一样的)。
分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了362 篇原创文章 · 获赞 531 · 访问量 48万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104885950