Thursday, December 24, 2015

Spring Cloud Rest Client with Netflix Ribbon - Customizations

In an earlier blog post I had covered the basic configuration involved in making a REST call using Spring Cloud which utilizes Netflix Ribbon libraries internally to load balance calls, with basic configurations like setting read timeout, number of retries etc . Here I will go over some more customizations that can be done that will require going beyond the configuration files.

Use Case

My use case is very simple - I want to specify the URL(s) that a rest call is invoked against. This may appear straightforward to start with however there are a few catches to consider. By default if Spring Cloud sees Eureka related libraries in the classpath the behavior is to use Eureka to discover the instances of a service and loadbalance across the instances.

Approach 1 - Use a non-loadbalanced Rest Template

An approach that will work is to use an instance of RestTemplate that does not use Ribbon at all:

@Bean
public RestOperations nonLoadbalancedRestTemplate() {
    return new RestTemplate();
}

Now, where you need the Rest Template, you can inject this instance in, knowing that Spring Cloud also would have instantiated another instance that supports Eureka, so this injection will have to be done by name this way:

@Service("restTemplateDirectPongClient")
public class RestTemplateDirectPongClient implements PongClient {

    private final RestOperations restTemplate;

    @Autowired
    public RestTemplateDirectPongClient(@Qualifier("nonLoadbalancedRestTemplate") RestOperations restTemplate) {
        this.restTemplate = restTemplate;
    }

    ...
}

The big catch with the approach however is now that we have bypassed Ribbon all the features that Ribbon provides are lost - we would not have features like automatic retry, read and connect timeouts, loadbalancing in case we had multiple urls. So a better approach may be the following.

Approach 2 - Customize Ribbon based Rest Template

In the earlier blog post I had shown some basic customization of Ribbon which can be made using a configuration file:

samplepong:
  ribbon:
    DeploymentContextBasedVipAddresses: sample-pong
    ReadTimeout: 5000
    MaxAutoRetries: 2

All the customizations that you would normally do through a configuration file for Ribbon however do not carry over, in this specific instance I want to use a list of server instances that I specify instead of letting Ribbon figure out via a Eureka call. Using raw ribbon it is specified the following way:

samplepong.ribbon.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
samplepong.ribbon.listOfServers=127.0.0.1:8082

This specific configuration will not work with Spring Cloud however, the way to specify a list of servers is by specifying a configuration file along these lines:

package org.bk.noscan.consumer.ribbon;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ConfigurationBasedServerList;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PongDirectCallRibbonConfiguration extends RibbonClientConfiguration {

    @Bean
    @Override
    public ServerList<Server> ribbonServerList(IClientConfig clientConfig) {
        ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
        serverList.initWithNiwsConfig(clientConfig);
        return serverList;
    }

}

and telling Ribbon to use this configuration for the specific "named client" that we are concerned about:

@RibbonClients({
        @RibbonClient(name = "samplepongdirect", configuration = PongDirectCallRibbonConfiguration.class),
})

With this configuration in place, the list of servers can now be specified using configuration this way:

samplepongdirect:
  ribbon:
    DeploymentContextBasedVipAddresses: sample-pong
    listOfServers: localhost:8082
    ReadTimeout: 5000
    MaxAutoRetries: 2

One thing to note is that since the Ribbon Configuration is a normal Spring configuration it will likely get picked up as part of the @ComponentScan annotation, since this is very specific for Ribbon we would not want this configuration to be picked up this way. I have avoided that by specifying a package not in the normal classpath scan "org.bk.noscan.*" package!, I am not sure if there is another clean way to do this but this approach has worked well for me.

This approach is little more extensive than the first approach, however the advantage is that once this is in place all the features of Ribbon carry over.

Conclusion

This concludes the customizations involved in using Spring Cloud with Ribbon. If you are interested in exploring the code a little further I have this integrated in my github repo here.

References

Spring Cloud reference documentation has been an awesome source of information for all the details presented here. I had mistakenly opened a github issue thinking that it is a Spring Cloud issue and got some very useful information through the discussion here

Sunday, December 6, 2015

Spring Cloud Rest Client with Netflix Ribbon - Basics

In an earlier blog post I had covered the different options for a REST client in the Spring Cloud world. All the options wrap around a Netflix OSS based component called Ribbon which handles the aspects related to loadbalancing the calls across different instances hosting a service, handling failovers, timeouts etc. Here I will cover a few ways to customize the behavior of underlying Ribbon components when used with Spring Cloud and follow it up with more comprehensive customizations.

Creating a Rest Client


To recap, first consider a case where a simple service needs to be called:



A typical way to make this call using Spring is to inject in a RestTemplate and use it make this call, the following way:


public class RestTemplateBasedPongClient implements PongClient {

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public MessageAcknowledgement sendMessage(Message message) {
        String pongServiceUrl = "http://serviceurl/message";
        HttpEntity<Message> requestEntity = new HttpEntity<>(message);
        ResponseEntity<MessageAcknowledgement> response =  this.restTemplate.exchange(pongServiceUrl, HttpMethod.POST, requestEntity, MessageAcknowledgement.class, Maps.newHashMap());
        return response.getBody();
    }

}

There is nothing special here. When using Spring Cloud however the same code behaves differently, now the RestTemplate internally uses Netflix OSS Ribbon libraries to make the call. This helps as the typical call flow is to first find the instances running the service and then to loadbalance the calls across the instances and to maintain this state.

Rest Client With Ribbon


Let me digress a little to touch on Ribbon, Ribbon uses an abstraction called a "Named client" to control the behavior of a remote service call - the name by which the service has registered with Eureka, timeout for service calls, how many retries in case of failures etc. These are specified through configuration files, and the entries are typically along these lines, note that the "Named client" here is "samplepong" and the properties have this as a prefix:

samplepong.ribbon.MaxAutoRetries=2
samplepong.ribbon.MaxAutoRetriesNextServer=2
samplepong.ribbon.OkToRetryOnAllOperations=true
samplepong.ribbon.ServerListRefreshInterval=2000
samplepong.ribbon.ConnectTimeout=5000
samplepong.ribbon.ReadTimeout=90000
samplepong.ribbon.EnableZoneAffinity=false
samplepong.ribbon.DeploymentContextBasedVipAddresses=sample-pong
samplepong.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList


Coming back to Spring Cloud, it supports the concept of a "Named Client" in a very clever way through the Url hostname, so the RestTemplate call would now look like this:

ResponseEntity<MessageAcknowledgement> response =  this.restTemplate.exchange("http://samplepong/message", HttpMethod.POST, requestEntity, MessageAcknowledgement.class, Maps.newHashMap());

The "samplepong" in the url is the "Named client" and any customization for the behavior of the underlying Ribbon can be made by specifying the properties using this prefix. Since this is a Spring Cloud applications the properties can be specified cleanly in a yaml format along these lines:

samplepong:
  ribbon:
    DeploymentContextBasedVipAddresses: sample-pong
    ReadTimeout: 5000
    MaxAutoRetries: 2


Conclusion

This covers the basics of how Spring Cloud abstracts out the underlying the Ribbon libraries to provide a very intuitive facade to make remote service calls in the Cloud environment. There are some details that I have skimmed over on some of the customizations, I will cover these in a newer post. Here is my github repo with the code that I have used for the article.