Is it possible to define a static server list with ribbon (via feign) when Eureka is in use? - spring-cloud

Environment
Spring Boot 1.5.13.RELEASE
Spring Cloud Edgware.SR3
Java 8
Configuration
Eureka client is enabled and working correctly (I have tested and everything's working as I expect).
Some relevant properties from my configuration:
feign.hystrix.enabled=true
eureka.client.fetch-registry=true
spring.cloud.service-registry.auto-registration.enabled=true
service1.ribbon.listOfServers=https://www.google.com
Context
I have an application which speaks to 3 other services using feign clients. Two of these are discovered via Eureka service discovery. These are working well. The final service is an external one with a single static hostname and I do not want this resolved via Eureka. Since I do want Eureka for 2 of these services I would like to keep Eureka enabled.
Question
For the final service I tried adding service1.ribbon.listOfServers=https://www.google.com to the application.properties, however this cases the following error at runtime when invoking the feign client:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: Service1Client#test() failed and no fallback available.] with root cause
pricing_1 |
pricing_1 | com.netflix.client.ClientException: Load balancer does not have available server for client: service1
pricing_1 | at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) ~[ribbon-loadbalancer-2.2.5.jar!/:2.2.5]
My client is configured as follows:
#FeignClient("service1")
public interface Service1Client {
#GetMapping(value = "/")
String test();
}
Thanks in advance for any advice.
Consideration
Since the spirit of Ribbon as I understand it is to act as a client side load balancer and given in my case there is nothing to load balance (I have one fixed static hostname that returns a single A record in DNS). Ribbon actually feels like an unnecessary component - what I really wanted was the Feign client as I like the fact that it abstracts away the lower level HTTP request and object seralization. So I suppose an alternative follow up question is, can I use feign without ribbon - it seems the nice out of the box behaviour would be to use ribbon - even the Javadoc of the #FeignClient annotation says:
If ribbon is available it will be
used to load balance the backend requests, and the load balancer can be configured
using a #RibbonClient with the same name (i.e. value) as the feign client.
suggesting the two are quite closely related even if they are serving different purposes.

As you mentioned, there are two ways to solve your problem.
Use Feign without Ribbon
If you specify url attribute in #FeignClient annotation, it will work without Ribbon like the below.
#FeignClient(name = "service1", url = http://www.google.com)
public interface Service1Client {
#GetMapping(value = "/")
String test();
}
In this case, your other two Feign client will still work with Ribbon and Eureka.
Use Feign with Ribbon and without Eureka
What you are missing is in your configuration is NIWSServerListClassName.
Its default value is com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList and it will use Eureka to retrieve the list of server. If you set NIWSServerListClassName to ConfigurationBasedServerList for a ribbon client (feign client), only that client will work with listOfServers list without retrieving server list from Eureka. And other feign clients will still work with Eureka.
service1:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
listOfServers: http://www.google.com

Related

RestTemplate annotated by #LoadBalanced get wrong service address by service name from eureka sometimes

I use springcloud to build the system, including many microservices。 For some interface calls, I use resttemplate annotated by #LoadBalance to implement load balancing, and use eureka as a registry center. However, when I call interfaces between different micro services, resttemplate sometimes will connect to wrong service. For example, I have service A, B, C, when service A call a service B's interface, resttemplate annotated by #LoadBalance will find the actual ip&port from eureka by service name first, and then build the actual url and send the request to target server, but sometimes, it will find the service C's ip&port when I call service B's interface, which cause a fail invoking. This case occurs infrequently but nerver disappear, I have been troubled for a long time, could anyone give me some suggestions? Thanks.
I learned why yesterday: it is a bug in spring cloud Dalston.RELEASE(https://github.com/spring-cloud/spring-cloud-commons/issues/224), and we happen to use this version. Spring cloud had fixed this bug in Dalston.SR2, and now it works fine

How to forward headers when using Zuul, Hystrix (and Feign) with Spring Cloud HATEOAS?

Context
My micro-services application is based on spring-cloud: a zuul gateway is configured in front of two micro-services: service-a and service-b.
One of my API requires that service-a requests service-b; I use feign for that.
Zuul send X-FORWARDED-* headers to the services, for them to rewrite the HATEOAS links correctly (when the services are configured with ForwardedHeaderFilter).
My problem is that the services communicate with each other using Feign, which relies on Hystrix.
Hystrix creates a new thread for each request (we don't use the SEMAPHORE config), so the request in Spring's RequestContextHolder is lost in the Feign request from service-a to service-b, I can't enrich the feign request with an feign interceptor anymore since the original request is lost.
Some potential solutions
Forwarding authorization token is now supported directly by Spring with the parameter hystrix.shareSecurityContext: true
There isn't any "out of the box" configuration to have Hystrix shares the request between threads.
A solution could be to implement my own HystrixConcurrencyStrategy, which is a class from netflix.hystrix.
My latest find is this Pull Request that has been sent to spring-cloud-netflix, but unfortunately not integrated.
I can try to copy the code of the Pull request, and create a bean, just as what "eacdy" wrote:
#Bean
public RequestAttributeHystrixConcurrencyStrategy hystrixRequestAutoConfiguration() {
return new RequestAttributeHystrixConcurrencyStrategy();
}
Is there an easier solution to forward the headers from Zuul with Hystrix?
I suppose that what I am trying to do is very standard when using Zuul, Hystrix, and HATEOAS micro-services that communicate with each other, so maybe there is something that exists already (and that I couldn't find)?
Thanks !
I thought it was quite a common thing to achieve, but after a lot of research, I couldn't find a way to forward the X-FORWARDED-* headers automatically with Feign and Hystrix.
So, I looked for another solution, which works and is quite clean:
In the Feign client from service-a to service-b, I declared a specific configuration "ServiceBFeignConfig", which, in addition to forward the token, also add the X-Forwarded-* headers corresponding to the gateway:
#Configuration
public class ServiceBFeignConfig {
#Autowired
private ApplicationProperties applicationProperties;
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
OAuth2AuthenticationDetails details =
(OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
if (applicationProperties.getFeign().getGatewayEnabled()) {
requestTemplate.header("X-Forwarded-Host", applicationProperties.getFeign().getGatewayHost());
requestTemplate.header("X-Forwarded-Port", applicationProperties.getFeign().getGatewayPort());
requestTemplate.header("X-Forwarded-Proto", applicationProperties.getFeign().getGatewayProtocol());
requestTemplate.header("X-Forwarded-Prefix", applicationProperties.getFeign().getServiceBPrefix());
}
}
};
}
}
You can see that the gateway host and port is configured in the properties files (that is served by Spring Cloud Config in my case). The service-b prefix is also set in these files.
These headers are only added if the "gatewayEnabled" property is set in the properties files.
You have to ignore this configuration from the component scan of Spring Boot, even if it needs the #Configuration annotation, so put it in a "ignorescan" package, and on your main Spring boot class, use:
#ComponentScan(basePackages = { "com.myservice" }, excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.myservice.ignorescan.*"))
At the end, the Forward headers will be added if you have the gatewayEnabled set to true, and the API call to the gateway get the correct HATEOAS links.

Not able to load balance using hardcoded urls in spring cloud zuul

I am testing spring zuul. I want to test round-robin requests forward using zuul routes. And not using eureka setup.
zuul.ignoredServices=*
ribbon.eureka.enabled=false
server.port=9000
zuul.routes.trackingv1.path=/tracking/v1/**
zuul.routes.trackingv1.stripPrefix=false
zuul.routes.trackingv1.serviceId=trackingv1
trackingv1.ribbon.listOfServers=http://localhost:8080/trackingv1,http://localhost:8081/trackingv1
But I am getting errors like Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: trackingv1
Any idea, what could be wrong?
It's same old problem with using properties. (extra space in value part of key). I had extra space in
zuul.routes.trackingv1.serviceId=trackingv1<space>
Now next problem is, from list of servers
trackingv1.ribbon.listOfServers=http://localhost:8080/trackingv1,http://localhost:8081/trackingv1 it is picking online host:port portion. How to add contextPath "trackingv1" ?

Does Feign retry require some sort of configuration?

I just tried to do a attempted a seamless upgrade of a service in a test setup. The service is being accessed by a Feign client. And naively I was under the impression that with multiple instances available of the service, the client would retry another instance if it failed to connect to one.
That, however, did not happen. But I cannot find any mention of how Feign in Spring Cloud is supposed to be configured to do this? Although I have seen mentions of it supporting it (as opposed to using RestTemplate where you would use something like Spring Retry?)
If you are using ribbon you can set properties similar to the following (substituting "localapp" for your serviceid):
localapp.ribbon.MaxAutoRetries=5
localapp.ribbon.MaxAutoRetriesNextServer=5
localapp.ribbon.OkToRetryOnAllOperations=true
ps underneath Feign has a Retryer interface, which was made to support things like Ribbon.
https://github.com/Netflix/feign/blob/master/core/src/main/java/feign/Retryer.java
see if property works - OkToRetryOnAllOperations: true
You can refer application ->
https://github.com/spencergibb/spring-cloud-sandbox/blob/master/spring-cloud-sandbox-sample-frontend/src/main/resources/application.yml
Spencer was quick...was late by few minutes :-)

Spring Cloud Feign Clients don't connect with a different Context Roots for different instances

I am in the process of evaluating Spring cloud with Eureka and the Feign Client with Ribbon.
My sample application I've been creating has a Eureka Server that receives the Clients just fine.
However, when using the FeignClient, the RequestMapping requires the Context Path to be included.
In my initial case, I had two services with the same VIP but different IDs.
- one service at localhost:8080/
- one service at localhost:7400/HelloWorld
The reason for the different context roots is due to potential deployment structure I can't easily change.
Is this something I need to configure within my Client? or Application or is this not possible?
Here is my #FeignClient
<code>
#Autowired
HelloClient client;
#FeignClient(value = "Hello-World")
interface HelloClient {
#RequestMapping(value = "/HelloWorld", method = RequestMethod.GET)
String hello();
}
</code>
Ideally, the RequestMapping would only be the value '/'