[Enjoy Netflix] Forty-three of Ribbon's Load Balancer five components: ServerList Service List

Debugging a code you first see is twice as difficult as rewriting it.So, by definition, if you write code very cleverly, no one is smart enough to debug it.

->Return to Column Directory <-
Code download address: https://github.com/f641385712/netflix-learning

Catalog

Preface

Following the description of IPIng, one of LoadBalancer's five components, this article continues with its ServerList component.

text

ServerList

This interface defines how to get a list of servers.

// T does not specify that it must be a Server or a subclass of it
// But the reality is that you don't normally inherit Server to play on your own, because Server is abstract enough
public interface ServerList<T extends Server> {
	// Returns a list of servers in their initial state.For example, if you initially configure 10 servers, it will always be 10 servers
	public List<T> getInitialListOfServers();
	// Return to the updated list of services.It will cycle ping and return to being alive
	public List<T> getUpdatedListOfServers();
}

Its inheritance map is as follows (multiple implementations in the Spring Cloud environment):


AbstractServerList

The Abstract implementation has and provides a public method that provides the filter AbstractServerListFilter used by loadBalancer.

Here's the question: Why is the way to get the ServerListFilter written in the ServerList?Can't you beat eight poles?Don't know what the author thought?So skip here first

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

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

Fortunately, it has an implementation class: ConfigurationBasedServerList, which is configuration-based ServerList.

ConfigurationBasedServerList

It can load utility implementation classes for server lists from the Configuration configuration.The attribute names are defined in the following format:

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

Depends on IClientConfig configuration management to get configuration values, so what is global without ClientName, but what does that mean?

// It's interesting here, but here you've determined that the generic type is Server, funny~
public class ConfigurationBasedServerList extends AbstractServerList<Server>  {
	private IClientConfig clientConfig;
	
	// The results of the two methods are identical here.
	@Override
	public List<Server> getInitialListOfServers() {
	    return getUpdatedListOfServers();
	}
	@Override
	public List<Server> getUpdatedListOfServers() {
		// key is: listOfServers (note that the first letter l is lowercase)
        String listOfServers = clientConfig.get(CommonClientConfigKey.ListOfServers);
        // Derive->comma-separated-> new Server (s.trim())
        // That is, s is an id
        return derive(listOfServers);
	}
	
	protected List<Server> derive(String value) { ... }
}

This implementation class deserves attention because in Spring Cloud, the ribbon classic configuration is used without eureka:

# Disable ribbon use in eureka
ribbon.eureka.enabled=false
# Configure the address of the service provider
account.ribbon.listOfServers=localhost:8080,localhost:8081

Once configured this way, it uses the ConfigurationBasedServerList to load the list of servers.

Code Samples
  • API mode configuration:
@Test
public void fun2(){
    // Prepare for configuration
    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("-----------------------------");
    });
}

Run the program, console output:

www.baidu.com:80
www.baidu.com
80
www.baidu.com:80
null
-----------------------------
yourbatman.com:8080
yourbatman.com
8080
yourbatman.com:8080
http
-----------------------------
  • Profile method:

The config.properties configuration is:

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

Write test code:

@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("-----------------------------");
    });
}

Run the program with the same results.

Small bug

A little partner responded to a case with me, and I'll make up for it here by the way.It does this (just changed Config):

@Test
public void fun101(){
    DefaultClientConfigImpl config = DefaultClientConfigImpl.getEmptyConfig();
    config.loadDefaultValues(); // Note: This sentence must show a call, otherwise there is no value in the configuration

    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("-----------------------------");
    });
}

Configg.properties content (global configuration used):

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

Run program output:

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

what?Output only one???Why only print one?

Before you understand the specific reason, make sure you are well aware of the principles of IClientConfig (don't know what you can do about it) Here ).ConfigurationBasedServerList uses clientConfig.get(CommonClientConfigKey.ListOfServers) to get attribute values, while the get() method relies on DefaultClientConfigImpl#getProperty(String key):

DefaultClientConfigImpl: 

    protected Object getProperty(String key) {
        if (enableDynamicProperties) {
        	// It looks for the properties of the specified clientName before looking for the global
        	// Seconds are here, and it uses DynamicProperty.getInstance(.).getString()
        	// This getString() method is ** output as is **, which is particularly important
        	...
        }
        return properties.get(key);
	}

The example only calls the Load Default method, so enableDynamicProperties is still false, so here's the properties.get(key) method, which is a Map get method with nothing to say but how does its value fit in?

DefaultClientConfigImpl: 

	public void loadDefaultValues() {
		...
		// Go back to the configuration file to load the default values
		putDefaultStringProperty(CommonClientConfigKey.ListOfServers, "");
	}
	protected void putDefaultStringProperty(IClientConfigKey propName, String defaultValue) {
		String value = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(propName), defaultValue);
		setPropertyInternal(propName, value);
	}

This AbstractConfiguration#getString() method is the culprit: when the value corresponding to the key is a set, only the first value collection.iterator().next() is returned.So there's only one value to put in properties, and there must be only one to get out properties.get(key).

Solving the problem is very simple. Do not use the config.loadDefaultValues() method, but do so, and the problem will be solved.

DefaultClientConfigImpl config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("noClient")

This "bug" does not matter by itself, as it was said that listOfServers prohibit global configuration.However, the principles reflected in this result can be further explored.There are actually more than one such minor bug in Ribbon, which will continue to be mentioned in subsequent articles.

Note: I personally think this minor problem is caused by Ribbon's incomplete consideration. Of course, in most cases it is harmless and can be avoided from "operation"~

StaticServerList

It is Spring Cloud's implementation of the ServerList interface and has been passed here since it is relatively simple.As the name implies, it represents a static List, which is too simple to implement.

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;
	}
}

This implementation class has never been used anywhere, and Spring Cloud has provided that if you have a dead Server address in your code, you can use it, but the probability that we need to do so is minimal.

Where to call?

The ServerList#getInitialListOfServers method is called anywhere. The only call to the ServerList#getUpdatedListOfServers method is at DynamicServerListLoadBalancer#updateListOfServers:

DynamicServerListLoadBalancer: 

	// Update the list of services.
	public void updateListOfServers() {
		// Get List
		servers = serverListImpl.getUpdatedListOfServers();
		// Filter a list
		servers = filter.getFilteredListOfServers(servers);
	
		// Set up the latest list
		pdateAllServerList(servers);
	}

summary

One of the five components of Ribbon's Load Balancer is ServerList, which is still fairly simple.This API is used to get a List of Servers, which can come from dead lists, configuration files, or even remote networks such as the eureka Configuration Center.In addition, you can see from the implementation that the getInitialListOfServers and getUpdatedListOfServers are completely equivalent (even in the implementation of eureka integration, com.netflix.niws.LoadBalancer.DiscoveryEnabled NIWSServerList).

statement

The original is not easy, the code is not easy. Thank you for your compliment, collection and attention.Sharing this article with your circle of friends is allowed, but you refuse to copy it.You can also invite you to join my family of Java engineers and architects to learn and communicate.

343 original articles were published, 505 were praised, 440,000 visits were received+
His message board follow

Tags: Apache Spring Attribute github

Posted on Sun, 15 Mar 2020 21:17:29 -0400 by gammaster