专栏首页BAT的乌托邦[享学Netflix] 四十三、Ribbon的LoadBalancer五大组件之:ServerList服务列表

[享学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里这两个方法也是一样的)。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 6. 二十不惑,ObjectMapper使用也不再迷惑

    各位好,我是YourBatman。从本文起,终于要和Jackson的“高级”部分打交道了,也就是数据绑定jackson-databind模块。通过接触它的高级A...

    YourBatman
  • Java二进制和位运算,这一万字准能喂饱你

    本号正在连载Jackson深度解析系列,虽然目前还只讲到了其流式API层面,但已接触到其多个Feature特征。更为重要的是我在文章里赞其设计精妙,处理优雅,因...

    YourBatman
  • 你知道@RequestMapping的name属性有什么用吗?带你了解URI Builder模式(UriComponents/UriComponentsBuilder)【享学Spring MVC】

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    YourBatman
  • springboot vue整合websocket

    compile 'org.springframework.boot:spring-boot-starter-websocket'

    似水的流年
  • Java8 Lambda表达式.md什么是λ表达式λ表达式的类型λ表达式的使用其它相关概念

    为了支持函数式编程,Java 8引入了Lambda表达式. 在Java 8中采用的是内部类来实现Lambda表达式.具体实现代码,可以通过debug看, 同时...

    一个会写诗的程序员
  • java-reflection

    以上方法返回值的类型是一个 Class 类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即可以通过对象反射求出类的名称。

    Remember_Ray
  • 阿里大于短信服务_总结_01_短信验证码接入

    shirayner
  • 设计模式之 装饰器模式

    tanoak
  • 学过框架的必看—Java反射

    反射作为 Java 的高级特性,很多框架中都用到了反射的知识,如 Spring,Hibernate等,通过配置就可以动态干预程序的运行,那么什么是反射呢?

    Wizey
  • Java-函数

    方法重载:在同一个类中,出现了方法名相同 不能出现参数名以及参数条件一致的方法 特点:

    DataScience

扫码关注云+社区

领取腾讯云代金券