I'm trying to use the FeignClient to send HTTP requests to target service.
The target service may have several instances grouped by different versions, so I want to write a class implemented IRule to choose a service instance based on a key that in the request parameters. And the 'IRule' implementation just like this:
public class MyLoadBalancerRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
List<Server> servers = getLoadBalancer().getReachableServers();
// choose and return a server from servers based on the key
}
}
but the key is always null.
LoadBalancerCommand passes its attribute loadBalancerKey as parameter to the choose method. And LoadBalancerCommand object is constructed in AbstractLoadBalancerAwareClient#executeWithLoadBalancer without setting the attribute loadBalancerKey.
So how to set/use the loadBalancerKey when I use FeignClient?
As far as I can see LoadBalancerCommand always has a null loadBalancerKey in all the out-of -the box usages, despite having a builder that allows it to be set. I guess you'd have to implement your own load balancer aware stack based on feign requests as a context/request object.
Thank for your response. I'm trying a solution to get the loadBalancerKey.
At first I write an annotation @LoadBalancerKey to specified an loadBalancerKey, then I can write the FeignClient interface like this:
@FeignClient(name = "UserService")
public interface UserFeignClient {
@RequestMapping(value = "/user", method = RequestMethod.GET)
ResponseEntity<User> getUser(@LoadBalancerKey @RequestParam("name") String name);
}
Secondly, I implement an AnnotatedParameterProcessor named LoadBalancerKeyParameterProcessor to process the @LoadBalancerKey. The implementation is very similar to RequestHeaderParameterProcessor. I add the loadBalancerKey to the request header, the key is loadBalancerKey. So I can get the loadBalancerKey from the request header.
Then I rewrite the FeignLoadBalancer.RibbonRequest constructor and AbstractLoadBalancerAwareClient#executeWithLoadBalancer:
// FeignLoadBalancer.RibbonRequest, set loadBalancerKey
RibbonRequest(Client client, Request request, URI uri) {
this.client = client;
setUri(uri);
this.request = toRequest(request);
setLoadBalancerKey(this.request.headers().get("loadBalancerKey"));
}
// AbstractLoadBalancerAwareClient
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri())
.withServerLocator(request.getLoadBalancerKey()) # set loadBalancerKey
.build();
....
}
Then I can get the custom loadBalancerKey in my implementation class of IRule .
+1
I had a similar requirement to implement a load balancer that uses an election hash for user stickiness based on a query parameter. I also end up re-writing AbstractLoadBalancerAwareClient#executeWithLoadBalancer . I wish we had an easy way to decide what to pass in Object key for the IRule
@jfgosselin wishing for something doesn't help. Describe where your key comes from and why you need it.
@spencergibb
I have a Zuul proxy that uses the query parameters from http request to determine which server to pick (in my specific case for user stickiness) . I want these parameters pass to Server choose(Object key) in IRule implementation.
The only way is to re-write AbstractLoadBalancerAwareClient ?
We certainly could support some kind of strategy interface or something akin to grab information out of the request. Would need to support all 4 implementations of AbstractLoadBalancerAwareClient and would probably require an abstract class that extends AbstractLoadBalancerAwareClient.
It's maybe easy to provide a protected method to build LoadBalancerCommand in AbstractLoadBalancerAwareClient. Then I can extends AbstractLoadBalancerAwareClient and just override the method. Secondly I think it's better to make the FeignLoadBalancer.RibbonRequest public.
@lowzj, I don't think so. We have 4 implementations of AbstractLoadBalancerAwareClient. Seems like we would need an extension point, otherwise you would have to extend all the ones you are using.
@spencergibb How about adding an annotation named @LoadBalancerKey and a class LoadBalancerKeyParameterProcessor to process it just like I wrote above?
I open a pr on project Netflix/ribbon to add loadBalancerKey when building a LoadBalancerCommand: https://github.com/Netflix/ribbon/pull/309
@spencergibb I submit a new commit to the PR: https://github.com/Netflix/ribbon/pull/309. It changes nothing except giving a chance to derived classes of AbstractLoadBalancerAwareClient to customize the LoadBalancerCommand object.
The derived classes of AbstractLoadBalancerAwareClient in sc-netflix are:
|-- AbstractLoadBalancingClient
| |-- OkHttpLoadBalancingClient
| | \__ RetryableOkHttpLoadBalancingClient
| \__ RibbonLoadBalancingHttpClient
| \__ RetryableRibbonLoadBalancingHttpClient
|-- FeignLoadBalancer
| \__ RetryableFeignLoadBalancer
\__ RestClient(in ribbon project)
\__ OverrideRestClient
\__ TestRestClient
So if that pr gets merged, I want to submit a pr to override the method customizeLoadBalancerCommandBuilder in these derived classes to set loadBalancerKey if existing: AbstractLoadBalancingClient, FeignLoadBalancer, OverrideRestClient. Is it OK?
Why am I doing this?
I have customized my own AbstractLoadBalancingClient and rewrite its derived classes(just copy and change their parent class) in sc-netflix since Brixton.SR4 to support this feature. Every time I upgrade spring-cloud's version, I have to compare the difference between the new and old version, and to change some existing codes carefully. Especially when I upgrading to Dalston.RELEASE, there are so many duplicated things I have to do.
Additionally, the loadBalancerKey is designed in ribbon in fact, but we don't use it. I don't know why, maybe a mistake.
So I think that it's better to support this feature in the official version.
@lowzj sounds good.
@spencergibb I'm so sorry for disturbing you again because that I have no idea how to get a response on the PR(https://github.com/Netflix/ribbon/pull/309) from the members of Ribbon project. I have been waiting for more than half a year and there was no any activity of Ribbon members in recent months.
Would you mind reminding someone who is the member of Ribbon to take a look at that PR? It really changes nothing status quo of Ribbon and will bring us a much more flexible routing strategy. Thanks.
I've pinged who I know @lowzj. Not much more I can do.
@spencergibb Thanks very much for your reply.
@spencergibb i think,you can add code in
AbstractLoadBalancerAwareClient.executeWithLoadBalancer:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
...
.withServerLocator(request)
.build();
...
}
The request to the user, allowing users to freely meet their own needs...
public class CustomRule extends RoundRobinRule {
@Override
public Server choose(Object o) {
return choose(getLoadBalancer(), o);
}
public Server choose(ILoadBalancer lb, Object key) {
((ClientRequest)key).getUri()..eg: take uri to do something..or get request header to do something..
}
}
This module has entered maintenance mode. This means that the Spring Cloud team will no longer be adding new features to the module. We will fix blocker bugs and security issues, and we will also consider and review small pull requests from the community.
Most helpful comment
Thank for your response. I'm trying a solution to get the loadBalancerKey.
At first I write an annotation
@LoadBalancerKeyto specified an loadBalancerKey, then I can write the FeignClient interface like this:Secondly, I implement an
AnnotatedParameterProcessornamedLoadBalancerKeyParameterProcessorto process the@LoadBalancerKey. The implementation is very similar toRequestHeaderParameterProcessor. I add theloadBalancerKeyto the request header, the key isloadBalancerKey. So I can get theloadBalancerKeyfrom the request header.Then I rewrite the FeignLoadBalancer.RibbonRequest constructor and AbstractLoadBalancerAwareClient#executeWithLoadBalancer:
Then I can get the custom
loadBalancerKeyin my implementation class ofIRule.