[enjoy learning Netflix] 39. Source code analysis of Ribbon core API: detailed explanation of Ribbon coreiclientconfig

Any fool can write code that can be understood by machines. Only good programmers can write code that can be understood by humans.

- > return to the column directory
Code download address: https://github.com/f641385712/netflix-learning

Catalog

Preface

How important configuration is to a program is needless to say. Each library has its own configuration management mode, such as Spring has the environment abstraction, etc.

This article is about to introduce a frequently used and very important interface in the Ribbon: IClientConfig, which is responsible for the configuration management of the Ribbon, including the maintenance of all default values, as well as providing its reading and writing capabilities.

text

All configurations of the Ribbon are managed by IClientConfig and provide a unified entry and exit. Other core components such as IClient and Iload balancer will read this configuration to control their behavior.

In a word, the configuration management of Ribbon depends on the Netflix Archaius library. To learn it, you can jump here: Netflix Archaius full text

IClientConfig

Define various API s to initialize client configuration of IClient or Iload balancer and method execution.

public interface IClientConfig {

	// Such as account, user
	public String getClientName();
	// The default is ribbon
	public String getNameSpace();
	
	// Load the properties for the given client / load balancer (named clinetName). This method is important, important, important
	// The default is loadDefaultValues() method
	public void loadProperties(String clientName);
	// Load the Configuration defaults and put them into the global Configuration. Not related to clientName, public
	public void loadDefaultValues();

	@Deprecated
	public void setProperty(IClientConfigKey key, Object value);
    @Deprecated
	public Object getProperty(IClientConfigKey key);
    @Deprecated
	public Object getProperty(IClientConfigKey key, Object defaultVal);
	// Please use these three methods with generics to replace the above three methods without forced conversion
	// The input parameter is IClientConfigKey, which has been explained above
	public <T> IClientConfig set(IClientConfigKey<T> key, T value);
	public <T> T get(IClientConfigKey<T> key);
	public <T> T get(IClientConfigKey<T> key, T defaultValue);
	... // Omit getPropertyAsInteger getPropertyAsString getPropertyAsBoolean method


	// Whether a key is included in the configuration
	public boolean containsProperty(IClientConfigKey key);
	//Returns the applicable virtual address ("vip") used by this Client configuration.
	//Will rely on VipAddressResolver for resolution
	public String resolveDeploymentContextbasedVipAddresses();
}

The interface has a unique implementation class, DefaultClientConfigImpl, which manages the default values corresponding to the common property key and implements all interface methods.

DefaultClientConfigImpl

Load the default client configuration for the property from the ConfigurationManager of Archaius. You can also load IClientConfig programmatically.

You can define properties in the classpath file or as system properties. If it is the former, the ConfigurationManager.loadPropertiesFromResources()API should be called to load files into the global Configuration configuration. Most of the configurations will not be displayed. These default values are managed by DefaultClientConfigImpl:

public class DefaultClientConfigImpl implements IClientConfig {

	// ===================Defaults==================
	public static final Boolean DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS = Boolean.TRUE;
	public static final String DEFAULT_NFLOADBALANCER_PING_CLASSNAME = "com.netflix.loadbalancer.DummyPing";
	// By default, it will not be retried on a single Server
    public static final int DEFAULT_MAX_AUTO_RETRIES = 0;
    // By default, this one doesn't work, only the next one will be retried (if you have more than one machine, others won't be tried)
    public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1;
    public static final int DEFAULT_READ_TIMEOUT = 5000;
    public static final int DEFAULT_CONNECT_TIMEOUT = 2000;
	...
	// Default nameSpace value
	public static final String DEFAULT_PROPERTY_NAME_SPACE = "ribbon";
	// all connections idle for 30 secs
	public static final int DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS = 30000; 
}

The above is the defined public static default values. Each default value corresponds to the common keys managed by CommonClientConfigKey. This correspondence is also assigned to DefaultClientConfigImpl, which then places k-v in the global configuration:

DefaultClientConfigImpl: 

	// Properties loads all properties k-v. note that key is like ConnectTimeout, not ribbon.ConnectTimeout
	protected volatile Map<String, Object> properties = new ConcurrentHashMap<>();
	// Dynamic properties: compared with properties, it only loads the properties k-v that support dynamic
	// So you can see that its v is dynamicstring property, which is dynamic
	private final Map<String, DynamicStringProperty> dynamicProperties = new ConcurrentHashMap<String, DynamicStringProperty>();
	// This variable has no effect. It feels like a member variable that the author forgot to delete
	protected Map<IClientConfigKey<?>, Object> typedProperties = new ConcurrentHashMap<>();

	private String clientName = null;
	// Used to resolve vipAddress (resolution below)
	private VipAddressResolver resolver = null;
	// Whether dynamic attributes are allowed? Note: the value here is true
	// But refer to the following constructor: the constructor will change this value to false
	private boolean enableDynamicProperties = true;
	// The default namespace is ribbon, please do not change it
	// Note: all configurations are bound to NameSpace
	private String propertyNameSpace = DEFAULT_PROPERTY_NAME_SPACE;

	... // Omit get methods for all properties
    public void setClientName(String clientName){
        this.clientName  = clientName;
    }
    @Override
	public String getClientName() {
        return clientName;
    }

	// =======There are only two constructors that need special attention=======
	// During construction, the enableDynamicProperties will be changed to false, indicating that the property value is not allowed to be dynamic
    public DefaultClientConfigImpl() {
        this.dynamicProperties.clear();
        this.enableDynamicProperties = false;
    }
    public DefaultClientConfigImpl(String nameSpace) {
    	this();
    	this.propertyNameSpace = nameSpace;
    }

When constructing a DefaultClientConfigImpl instance, enableDynamicProperties will be set to false, that is, dynamic properties will not be supported. The instance has been built, but the "Mount" of the default value has not been completed. Let's continue to see.

Attribute loading

Property's [configuration format]

Any configuration loading needs to have a format, which is also reflected in the code:

DefaultClientConfigImpl: 

	// Default value, global value format
    String getDefaultPropName(String propName) {
        return getNameSpace() + "." + propName;
    }
    // Specify the format of clientName
    public String getInstancePropName(String restClientName, String key) {
        return restClientName + "." + getNameSpace() + "." + key;
    }

	// Get the key corresponding to the specified propName, which reflects the priority
	// If there is a clientName, take the specified clientName, otherwise take the global
    private String getConfigKey(String propName) {
        return (clientName == null) ? getDefaultPropName(propName) : getInstancePropName(clientName, propName);
    }

It specifies that the loading format is:

// By default, nameSpace=ribbon, and probably no one will change it
<clientName>.<nameSpace>.<propertyName>=<value>

This configuration is bound to clientName: specify that Client is exclusive. Most of the time, we need a global public configuration, which is the so-called default value. If a property lacks clientName, it will be interpreted as a property applicable to all clients:

<nameSpace>.<propertyName>=<value>

// As follows, it is the common property of all client s
ribbon.ReadTimeout=5000

loadDefaultValues() load defaults

When building the DefaultClientConfigImpl, it does not immediately load the default value into the global Configuration configuration Configuration, but needs to display the called.

DefaultClientConfigImpl: 

	// It's an interface method
	@Override
    public void loadDefaultValues() {
        putDefaultIntegerProperty(CommonClientConfigKey.MaxHttpConnectionsPerHost, getDefaultMaxHttpConnectionsPerHost());
        putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalHttpConnections, getDefaultMaxTotalHttpConnections());
		...
		putDefaultStringProperty(CommonClientConfigKey.VipAddressResolverClassName, getDefaultVipaddressResolverClassname()); 
		...
		putDefaultStringProperty(CommonClientConfigKey.ListOfServers, "");
	}


	// 1. First, go to Configuration to find whether there are configured global property values (for example, configured in config.properties or system properties)
	// 2. If not, use defaultValue, which is the constant value of this class as the default value
	// 3. Put in setPropertyInternal(propName, value);
    protected void putDefaultStringProperty(IClientConfigKey propName, String defaultValue) {
        String value = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(propName), defaultValue);
        setPropertyInternal(propName, value);
    }
    protected void setPropertyInternal(final String propName, Object value) {
    	// Record all k-v key value pairs into properties
    	// Note that its key is propertyName, such as ConnectTimeout, not ribbon.ConnectTimeout
		String stringValue = (value == null) ? "" : String.valueOf(value);
        properties.put(propName, stringValue);
		// It's very critical here: very critical: very critical
		// If enableDynamicProperties=false, i.e. dynamic properties are not supported, return
		// That is, callback functions will not be registered to support dynamic properties
        if (!enableDynamicProperties) {
            return;
        }

		// ======If dynamic property support is enabled======
		
		// clientName.ribbon.xxx or ribbon.xxx
		String configKey = getConfigKey(propName);
		// See if the value has been configured in Configuration
		DynamicStringProperty prop = DynamicPropertyFactory.getInstance().getStringProperty(configKey, null);
		// Dynamic property callback: used to synchronously update the values in properties
		prop.addCallback(() -> {
			// Note: this get method is dynamic~~~~~~~
			String value = prop.get();
            if (value != null) {
                properties.put(propName, value);
            } else {
                properties.remove(propName);
            }
		});
		dynamicProperties.put(propName, prop);
    }

By calling this method, all default k-v are installed in map < string, Object > properties, and dynamic monitoring is enabled for properties (if enableDynamicProperties=true), making them dynamic.

loadProperties(clientName) load the specified Client value

In our development process, most of the time we want to get all the configurations owned by the specified Client. This method loads the properties of the given Client. It first loads the default values for all properties, as well as any properties that have been defined with the Archaius ConfigurationManager.

DefaultClientConfigImpl: 

	// It's an interface method
    @Override
	public void loadProperties(String restClientName){
		// Important: important: important: This is the only place to change it to true
		enableDynamicProperties = true;
		// By the way, it also helps you assign a value to the member property clientName
		setClientName(restClientName);
		// Load defaults (Note: they are all dynamic at this time)
		loadDefaultValues();
		// Here we get a new Configuration by using the subset method 
		// A short answer: take all the ones that start with "account." (set restClientName=account here)
		Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
		// Traverse and put the values in Configuration into the global member properties
		for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
			...
			// If it's ribbon.xxx, cut it. Make sure the latter part is the real key
			if (prop.startsWith(getNameSpace())){
				prop = prop.substring(getNameSpace().length() + 1);
			}
			// As mentioned above, the important thing here is the getStringValue() method
			setPropertyInternal(prop, getStringValue(props, key));
		}
	}

	// This is to solve the problem of AbstractConfiguration by default, which automatically converts comma separated strings into arrays
	// We know that in AbstractConfiguration, if you configure 1,2,3, the final storage is in the form of array [1,2,3]
	// This method is to restore it. The specific logic is relatively simple, so we will not elaborate on it
	protected static String getStringValue(Configuration config, String key) {
		...
	}
  1. Only when this method is called to load the configuration of the specified client can enableDynamicProperties be set to true to support dynamic properties
    1. Implication: dynamic attributes are not supported under pure default management
  2. The getStringValue() method can ensure that the value value you configured is original
    1. For example, if you configure 1,2,3, make sure that the one stored in properties is 1,2,3, not [1,2,3]
    2. Note that the processing here is different from putDefaultStringProperty(). It depends on abstractconfiguration ා getstring(), which only returns collection.iterator().next(), that is, if you are comma separated, only the first value will be returned (this difference is particularly important. Do not configure comma separated form in all default values, otherwise it will be a bug )
  3. . please make sure that restClientName cannot be null (it is a small bug without judgment in the program), otherwise it will be thrown incorrectly

In most cases, we do not need to call loadDefaultValues() actively, but only use the loadProperties(restClientName) method to complete the configuration loading.

Attribute acquisition

After the properties are loaded, it is easier to get them.

DefaultClientConfigImpl: 

	// Interface method: get all attributes k-v
	// Note: the instance itself is returned here, not the copy. You can add and delete values to it
    @Override
	public Map<String, Object> getProperties() {
        return properties;
    }
    // Although it has been marked as out of date: use the get() method instead, but there are many people using it, so take a look
    @Override
	public Object getProperty(IClientConfigKey key){
        String propName = key.key();
        Object value = getProperty(propName);
        return value;
    }
    protected Object getProperty(String key) {
    	if (enableDynamicProperties) {
    		DynamicStringProperty dynamicProperty = dynamicProperties.get(key);
    		// Find the properties of the specified Client first
    		dynamicValue = DynamicProperty.getInstance(getConfigKey(key)).getString();
    			// We can't find it. We can't find it
    			dynamicValue = DynamicProperty.getInstance(getDefaultPropName(key)).getString();
    	}
    	return properties.get(key);
    }

Execution logic of getProperty() method:

  1. If the dynamic property enableDynamicProperties=true is enabled, go to the dynamic property first
    1. First find the configuration of the specified Client (clientName.ribbon.xxx)
    2. Global configuration (ribbon.xxx) if not found
  2. If you find wood, go to the global properties
DefaultClientConfigImpl: 

	@Override
    public <T> T get(IClientConfigKey<T> key) {
    	// It also depends on the getProperty() method mentioned above
    	Object obj = getProperty(key.key());
        if (obj == null) {
            return null;
        }
        Class<T> type = key.type();
    	... // Do data conversion according to type, omitted. This is the benefit of generics
    }

	// =============Of course, there are some shortcuts==========
	    @Override
    public int getPropertyAsInteger(IClientConfigKey key, int defaultValue) { ... }
    @Override
    public String getPropertyAsString(IClientConfigKey key, String defaultValue) { ... }
    @Override
    public boolean getPropertyAsBoolean(IClientConfigKey key, boolean defaultValue) { ... }


	// =========Judgment method=========
    @Override
	public boolean containsProperty(IClientConfigKey key){
        Object o = getProperty(key);
        return o != null ? true: false;
    }

Case construction

IClientConfig is the most frequently used interface, so Ribbon also provides us with many ways to build this instance.

Example 1: static method

We know that the only implementation class of IClientConfig is DefaultClientConfigImpl, so it provides several static methods for you to easily build its instance:

DefaultClientConfigImpl: 

	// Empty. Because none of the load methods has been called, the properties property is empty
	// At this time, if you get property, the results are all null
	public static DefaultClientConfigImpl getEmptyConfig() {
	    return new DefaultClientConfigImpl();
	}
	
	// DefaultClientConfigImpl instance with clientName, recommended
	// It calls config.loadProperties(clientName) internally, so it is a complete config
	public static DefaultClientConfigImpl getClientConfigWithDefaultValues(String clientName) {
		return getClientConfigWithDefaultValues(clientName, DEFAULT_PROPERTY_NAME_SPACE);
	}
	// The name of ClientName is default, which is generally not recommended
	public static DefaultClientConfigImpl getClientConfigWithDefaultValues() {
        return getClientConfigWithDefaultValues("default", DEFAULT_PROPERTY_NAME_SPACE);
    }
    // You don't need to specify clientName or nameSpace
	public static DefaultClientConfigImpl getClientConfigWithDefaultValues(String clientName, String nameSpace) {
	    DefaultClientConfigImpl config = new DefaultClientConfigImpl(nameSpace);
	    config.loadProperties(clientName);
		return config;
	}

This method is recommended, especially the method with ClientName. What you get is a flesh and blood instance that can provide services immediately.

Example 2: Builder mode

There is an internal class com.netflix.client.config.IClientConfig.Builder in the IClientConfig interface even in the Builder mode.

IClientConfig: 
	
	public static class Builder {
		
		private IClientConfig config;
		
		// The instance it builds is also DefaultClientConfigImpl
        public static Builder newBuilder() {
            Builder builder = new Builder();
            builder.config = new DefaultClientConfigImpl();
            return builder;
        }
        // It is recommended to use it instead of the empty structure above
        public static Builder newBuilder(String clientName) {
            Builder builder = new Builder();
            builder.config = new DefaultClientConfigImpl();
            builder.config.loadProperties(clientName);
            return builder;
        }
        // Of course, you can also specify a specific Class implementation, but we never do this
        public static Builder newBuilder(Class<? extends IClientConfig> implClass, String clientName) {
            Builder builder = new Builder();
            try {
                builder.config = implClass.newInstance();
                builder.config.loadProperties(clientName);
            } catch (Exception e) {
                throw new IllegalArgumentException(e);
            }
            return builder;
        }
        ...
        // =======Provide some quick setting methods=======
        public Builder withMaxAutoRetries(int value) {
            config.set(CommonClientConfigKey.MaxAutoRetries, value);
            return this;
        }
        ...
        public Builder withConnectTimeout(int value) {
            config.set(CommonClientConfigKey.ConnectTimeout, value);
            return this;
        }
		...
        public IClientConfig build() {
            return config;
        }
	}

The builder is easy to use in the construction process with a little complexity.

Example 3: new mode

Not to mention, it is to use the constructor for new. It should be noted that for the IClientConfig instance just released from new, enableDynamicProperties is false.

Code example

public void fun5() {
    // Static method construction
    IClientConfig config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("YourBatman");
    System.out.println(config.getClientName());
    System.out.println(config.get(CommonClientConfigKey.ConnectTimeout));
    System.out.println("-----------------------");
    // Builder build
    config = IClientConfig.Builder.newBuilder("YourBatman")
            .withConnectTimeout(8000)
            .withReadTimeout(10000)
            .build();
    System.out.println(config.getClientName());
    System.out.println(config.get(CommonClientConfigKey.ConnectTimeout));
    System.out.println("-----------------------");

    // new way to build
    config = new DefaultClientConfigImpl();
    System.out.println("load Front:");
    System.out.println(config.getClientName());
    System.out.println(config.get(CommonClientConfigKey.ConnectTimeout));
	
	config.loadProperties("YourBatman");
    System.out.println("load Later:");
    System.out.println(config.getClientName());
    System.out.println(config.get(CommonClientConfigKey.ConnectTimeout));
}

Run program, print:

YourBatman
2000
-----------------------
YourBatman
8000
-----------------------
load Front:
null
null
load Later:
YourBatman
2000

summary

This is how IClientConfig, the configuration management interface of Ribbon, is introduced. The content of this article is quite important, but it is not difficult. I hope the readers can learn something.

statement

It's not easy to create and code. Thank you for your praise, collection and attention. Sharing this article with your circle of friends is allowed, but no plagiarism is allowed. You can also [scan the code on the left / add wx: fsx641385712] to invite you to join my Java senior engineer and Architect Series family for learning and communication.

339 original articles published, 504 praised, 440000 visitors+
His message board follow

Tags: Apache Attribute Spring hot update

Posted on Sat, 14 Mar 2020 21:42:42 -0700 by svihas