How to loadbalance micro services via Ribbon (Not feign). I have 3 micro services "M1", "M2" and "M2_duplication", "M1" is communicating with "M2" via feign. I want if "M2" get too much traffic, the requests will be forwarded to the "M2_duplication". How is this possible via #ribbonclient ?
POM M1:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
The feign call in M1:
//name is taken from Eureka(service registry)
#FeignClient(name = "M1")
public interface M1ServiceClient {
#RequestMapping(method = RequestMethod.GET, value = "/getAllM2")
Map<String, String> getAllM2();
}
Application M1:
#EnableConfigurationProperties()
#SpringBootApplication
#EnableEurekaClient
#EnableFeignClients
public class PortefeuilleApplication {
public static void main(String[] args) {
SpringApplication.run(PortefeuilleApplication.class, args);
}
}
Your question is rather vague ... for example, you specifically state 'not feign' and then show a FeignClient. Nevertheless it looks like you are asking how to implement an availability pattern for your ribbon load balancer. To do this, you create a Ribbon Configuration class and then override the load balancer strategy rule. For example:
#Configuration
public class MyConfiguration {
#Bean
public IRule ribbonRule() {
return new RoundRobinRule();
}
}
There are a number of existing Ribbon Rule strategies around availability e.g. AvailabilityFilteringRule or BestAvailableFilter, but if none of these suit you could also write your own Rule. Once you have your class, amend your RibbonClient annotation to reference it, e.g:
#RibbonClient(name = "myClient", configuration = MyConfiguration.class)
You can find more information here: https://github.com/Netflix/ribbon/wiki/Working-with-load-balancers
Related
I've read whole documentation, tutorial [1], and spend several hours in sources, but I still do not understand how to configure loadbalancer, especially if I don't use magic annotations.
I have following configuration:
#Configuration
public class AppConfig {
public static final String SERVICE_ID = "service";
#Primary
#Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier() {
return ServiceInstanceListSuppliers.from(SERVICE_ID,
new DefaultServiceInstance(SERVICE_ID + "1", SERVICE_ID, "localhost", 8886, false),
new DefaultServiceInstance(SERVICE_ID + "2", SERVICE_ID, "localhost", 8887, false));
}
#Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
return new LoadBalancerClientFactory();
}
#Bean
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(LoadBalancerProperties properties) {
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerClientFactory(), properties);
}
}
and use bean loadBalancerExchangeFilterFunction as:
WebClient.builder()
.baseUrl("http://service/test-consumer")
.filter(lbFunction)
.build();
and it works. The problem is, that it works regardless of what hostname I use. So if I replace hostname "service" with whatever work I like, I will be still sending data to localhost:8886 or localhost:8887.
Can someone explain what is the role of serviceId and how this is paired to collection of DefaultServiceInstance?
(I want to understand the internals, what are the key components, their purpose and their interplay. I'm not primarily looking for magic annotation, but that one actually explained would be also great. Debugging it is really hard, I have several A4 with class diagrams and it still makes no sense at all).
Question: Is there a misconfiguration? What is the purpose of serviceId? It seems that none, as webclient using ReactorLoadBalancerExchangeFilterFunction will create roundrobin loadbalancer over configured ServiceInstances regardless of what is actual hostname used in given webclient.
Question2: how could I create 2 loadbalanced services and control to which service (not node) will request go? Do I need 2 separate webclients or some url pattern(like using serviceId in place of hostname) will do? If I need 2 webclients, how is pairing to DefaultServiceInstance done?
[1] https://spring.io/guides/gs/spring-cloud-loadbalancer/
EDIT:
after suggested update, the configuration looks like:
#Configuration
public class AppConfig {
#Bean
public ServiceInstanceListSupplier instanceSupplier(ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.build(context);
}
#Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
return new LoadBalancerClientFactory();
}
#Bean
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(LoadBalancerProperties properties) {
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerClientFactory(), properties);
}
}
application.properties contains:
spring.cloud.discovery.client.simple.instances.complicated[0].uri=http://localhost:8886
spring.cloud.discovery.client.simple.instances.complicated[1].uri=http://localhost:8887
webclient call to URL: http://localhost:8888/test-consumer (ie. hostname not matching serviceID) produces:
o.s.c.l.core.RoundRobinLoadBalancer : No servers available for service: localhost
eactorLoadBalancerExchangeFilterFunction : LoadBalancer does not contain an instance for the service localhost
webclient call to URL: http://complicated/test-consumer (ie. hostname matching serviceID) produces:
o.s.c.l.core.RoundRobinLoadBalancer : No servers available for service: complicated
eactorLoadBalancerExchangeFilterFunction : LoadBalancer does not contain an instance for the service complicated
The reason for this is that this.serviceId = environment.getProperty(PROPERTY_NAME); in DiscoveryClientServiceInstanceListSupplier(ReactiveDiscoveryClient,Environment) evaluates as null, thus even though I'm looking for some serviceId, delegate.getInstance is called with null, so no ServiceInstances are found. IF I removed #Bean instanceSupplier, and hope for autoconfiguration do it somehow magically, the this.serviceId = environment.getProperty(PROPERTY_NAME); is somehow magically set, serviceId is propagated correctly, and it works. For calls which leads elsewhere than configured serviceId, it fails saying, that this serviceId is not know, instead of making call.
SO it does mean, that if I configure loadbalancer, I cannot call anything else but (auto)configured services???
The LoadBalancer config should not be in a #Configuration-annotated class; instead, it should be a class passed for config via #LoadBalancerClient or #LoadBalancerClients annotation, as described here.
Also, the only bean you need to instantiate is the ServiceInstanceListSupplier (if you add spring-cloud-starter-loadbalancer, LoadBalancerClientFactory, and ReactorLoadBalancerExchangeFilterFunction will be instantiated by the starter).
So your LoadBalancer configuration class will look like so (without #Configuration):
public class AppConfig {
#Bean
public ServiceInstanceListSupplier instanceSupplier(ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.build(context);
}
}
and your actual #Configuration class (for example, where you configure other webflux-related beans), will have the following annotation: #LoadBalancerClients(defaultConfiguration = AppConfig.class).
Then, if you enable health-checks in the complicated instances, it should work without any problems.
Finally, able to resolve this issue with the below Configuration. not sure why it works only with Non blocking approach and when we pass the new RestTemplate
#Bean
public ServiceInstanceListSupplier instanceSupplier(ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withBlockingHealthChecks(new RestTemplate())//this change
.build(context);
}
Without introducing spring cloud contract, I customized the configuration of restdocs as below,
#Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
protected WebTestClient http;
#Autowired
private ApplicationContext context;
/**
* setup.
*/
#Before
public void before() {
this.http = WebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("http://theserver")
.filter(WebTestClientRestDocumentation
.documentationConfiguration(this.restDocumentation)
.operationPreprocessors()
.withRequestDefaults(prettyPrint())
.withResponseDefaults(prettyPrint())
)
.build();
}
However while using spring restdocs and cloud contract together, I have to use the annotation to enable rest docs and cloud contract,
#AutoConfigureRestDocs(uriHost = "theserver", uriPort = 80)
#AutoConfigureWebTestClient
public abstract class BaseTest {
Any advice how to generate pretty print docs while generating cloud contract stubs?
What you can do is not to use the #AutoConfigureRestDocs but use the API to pass to WebTestClientRestDocumentation.documentationConfiguration(...) the .snippets().withAdditionalDefaults(new WireMockSnippet()) line. That way by default you will start producing WireMock snippets and all of your previous configuration will not be discarded.
I am aware that we can force FeignClient to use OkHttp instead of Ribbon by providing the url Ex. #FeignClient(url="serviceId", name="serviceId")
I want the OkHttpClient to be used even when just the name is provided. Ex. #FeignClient(name="serviceId")
As per the spring cloud documentation "if Ribbon is enabled it is a LoadBalancerFeignClient, otherwise the default feign client is used."
How can I disable ribbon so that the default feign client will be used.
None of the solutions on the internet worked for me.
Simply setting an absolute url in the url portion resulted in loadbalancing exceptions
// this resulted in java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: localhost
#Lazy
#Configuration
#Import(FeignClientsConfiguration.class)
public class MyConfig {
#LocalServerPort
private int port;
#Bean
public MyClient myClient(final Decoder decoder, final Encoder encoder, final Client client) {
return Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.target(MyClient.class, "http://localhost:" + localServerPort);
}
}
setting spring.cloud.loadbalancing.ribbon.enabled=false resulted in application context problems. Additional settings needs to be disabled for this to work. I did not probe further
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'eurekaLoadBalancerClientConfiguration': Invocation of init method failed; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:416)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1788)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595)
...
...
My working solution
Finally, after inspecting the source code in org.springframework.cloud.openfeign.ribbon.DefaultFeignLoadBalancedConfiguration, I came up with this solution
#Lazy // required for #LocalServerPort to work in a #Configuration/#TestConfiguration
#TestConfiguration
#Import(FeignClientsConfiguration.class)
public class MyConfig {
#LocalServerPort
private int port;
#Bean
public MyClient myClient(Decoder decoder, Encoder encoder, Client client, Contract contract) {
return Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.target(MyClient.class, "http://localhost:" + localServerPort);
}
// provide a default `FeignClient` so that Spring will not automatically create their LoadBalancingFeignClient
#Bean
public Client feignClient(SpringClientFactory clientFactory) {
return new Client.Default(null, null);
}
}
I had the same question but my setup is a bit different and I did not get it working in my case (using spring-cloud-starter-openfeign with spring mvc style annotations).
FYI: I needed a custom client with an SSLSocketFactory and ended up just creating the bean for the client and keeping the url on #FeignClient
#Bean
public Client myClient() {
return new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
}
However, we do have projects using spring-cloud-starter-feign where the URL is not provided on the annotation. Not sure if the config below is complete (I did not set it up) but it might point you in the right direction...
dependencies
compile("org.springframework.cloud:spring-cloud-starter-feign") {
exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-ribbon'
exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-archaius'
}
config
#Configuration
#Import(FeignClientsConfiguration.class) // org.springframework.cloud.netflix.feign.FeignClientsConfiguration
public class MyConfig {
#Value("${client.url}")
private String url;
#Bean
public MyClient myClient(final Decoder decoder, final Encoder encoder, final Client client) {
return Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.target(MyClient.class, url);
}
}
It has nothing to do with Ribbon.
Check this:
feign:
httpclient:
enabled: false
This will disable the spring cloud autoconfigured httpclient, and will search a #Bean named httpClient in the context. So provide the definition of #Bean in a #Configuration class and that's all.
Check class FeignAutoConfiguration in spring cloud feign.
https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html
I'm trying to expose a REST service through OSGi (using Apache Felix). I'm using the osgi-jax-rs-connector to publish the resource. Here is the resource interface:
#Path("/bingo")
public interface BingoService {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/lottery")
List<Integer> getLottery();
}
The implementation uses DS annotation to obtain reference to a provided service in container:
#Component(
immediate = true,
service = BingoService.class,
properties = "jersey.properties")
public class Bingo implements BingoService {
#Reference
private RandomNumberGenerator rng;
#Activate
public void activateBingo() {
System.out.println("Bingo Service activated using " +
rng.getClass().getSimpleName());
}
#Override
public List<Integer> getLottery() {
List<Integer> result = new ArrayList<>();
for (int i = 5; i > 0; i--) {
result.add(rng.nextInt());
}
return result;
}
}
jersey.properties simply contains this line
service.exported.interfaces=*
When I deploy the bundle it starts and register the service correctly. But if I go to http://localhost:8181/services/bingo/lottery I get 404.
Could someone point me to the issue or give me some advice on where to look?
On reading the documentation for OSGi - JAX-RS Connector, it expects to find the annotations #Path or #Provider on the service instance object. You have placed them instead on an interface implemented by the component.
I'm not sure what the purpose of the BingoService interface is. This is not required for JAX-RS services. Normally you would register the resource class using its own type (e.g. service=Bingo.class) or simply java.lang.Object.
What I try to achieve is a routing for example:
http://zuul-host:8080/v1/foo/hello to my service foo-v1, resource hello
I'm trying out the regexmapper example described at http://cloud.spring.io/spring-cloud-netflix/spring-cloud-netflix.html
My problem is that I see that a service called foo-v1 gets mapped to /v1/foo in the PatternServiceRouteMapper but then I'm not able to call that route. It's also no visible at /mappings. Do I have to activate that route somewhere?
Setup
Foo Service
application.properties
server.port=9092
spring.application.name=foo-v1
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.instance.healthcheck.enable=true
Zuul
My configuration class Routings.java. I added some sysout log output for the service mapping and I get foo-v1 -> v1/foo in the log. Therefore this mapping should be active.
#Configuration
public class Routings {
#Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}") {
#Override
public String apply(final String serviceId) {
String route = super.apply(serviceId);
System.out.println(serviceId + " -> " +route);
return route;
}
};
}
}
My ZuulApplication.java
#SpringBootApplication
#EnableZuulProxy
#ComponentScan
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
#RefreshScope
#ConfigurationProperties("zuul")
public ZuulProperties zuulProperties() {
return new ZuulProperties();
}
}
Ok, found the solution.
Remove ignoredServices: '*' from the zuul config.
This happens if you work through the examples. They start with explicitly configured routes and ignore dynamic routings. It's in the documentation but made no sense to me at that point :-)
To skip having a service automatically added, set zuul.ignored-services to a list of service id patterns.
When using the regexmapper we start using services that get added automatically and that's the feature we disabled with ignoredServices: '*'