Difference in load balancer behavior - spring-cloud

I noticed that load balancer has different behavior pattern in certain scenario.
When using discovery with load balancer and one of the services is restarted on different port - then if services are running on Spring Boot 2.3.8-RELEASE, other service can find and communicate with restarted service, but if services are running on Spring Boot 2.7.8 it will only work if below setting is set to "true":
spring.cloud.loadbalancer.health-check.refetch-instances=true
I tried to create 3x brand new projects in first test using Spring Boot 2.3.8-RELEASE and in second test Spring Boot 2.7.8 to eliminate any specific setting or configuration or my actual project. I was thinking that maybe there is something specific in my actual project causing difference in behavior, but it turns out that vanilla plain setup has exactly this same problem.
In general my expectation was that setup using Spring Boot 2.3.8-RELEASE will have exactly this same behavior in case of restarting a service on different port as Spring Boot 2.7.8 - but that doesn't seems to be a case.
I noticed that load balancer has different behavior pattern in certain scenario.
When using discovery with load balancer and one of the services is restarted on different port - then if services are running on Spring Boot 2.3.8-RELEASE, other service can find and communicate with restarted service, but if services are running on Spring Boot 2.7.8 it will only work if below setting is set to "true":
spring.cloud.loadbalancer.health-check.refetch-instances=true
I tried to create 3x brand new projects in first test using Spring Boot 2.3.8-RELEASE and in second test Spring Boot 2.7.8 to eliminate any specific setting or configuration or my actual project. I was thinking that maybe there is something specific in my actual project causing difference in behavior, but it turns out that vanilla plain setup has exactly this same problem.
In general my expectation was that setup using Spring Boot 2.3.8-RELEASE will have exactly this same behavior in case of restarting a service on different port as Spring Boot 2.7.8 - but that doesn't seems to be a case.
Here are details of my tests.
My setup is 3x spring boot applications - all in this same version (first test with 2.3.8-RELEASE and second with 2.7.8):
Discovery service - Eureka Server
Test service 1 - "caller" - Eureka Client
Test service 2 - "receiver" - also Eureka Client
The difference in behavior can be reproduce following scenario:
Start Discovery service
Start "caller" application - wait for it to register in Discovery
Start "receiver" application - also wait for it to register in Discovery and also wait for "caller" application to fetch latest record from Discovery
Call GET endpoint in "caller" application which in turn calls GET endpoint in "receiver" application
At this stage everything works fine, "caller" can find and call "receiver"
Change server port in "receiver" application to different value and restart "receiver" application
Again, wait for "receiver" application to register in Discovery and "caller" application to fetch latest record from Discovery
At this stage there is a difference in the way how Spring Boot 2.3.8-RELEASE application stack behaves and 2.7.8
What happens is 2.3.8-RELEASE will work fine after changing port in "receiver" application but 2.7.8 will ONLY be able to communicate with "receiver" if below setting is set to "true":
spring.cloud.loadbalancer.health-check.refetch-instances=true
if it's not (and that is the default setting) the 2.7.8 will not be able to ever communicate with "receiver" unless "receiver" port will be reverted to original value and both applications restarted.
Here are some more significant elements of my configuration for Spring Boot 2.3.8-RELEASE:
Discovery service
Main class:
#SpringBootApplication
#EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
application.yml:
spring:
application:
name: eureka
eureka:
client:
registerWithEureka: false
fetchRegistry: false
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.abednarski79</groupId>
<artifactId>eureka-old</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka</name>
<description>Eureka service</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Caller service
Rest template configuration:
#Configuration
public class RestTemplateConfig {
#Bean
#LoadBalanced
#Qualifier("loadBalanced Rest Template")
RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
#Bean
#Qualifier("rest Template")
RestTemplate restTemplate() {
return new RestTemplate();
}
}
Controller:
#RestController
public class CallerController
{
private Rest Template restTemplate;
public CallerController (#Qualifier("loadBalancedRestTemplate") RestTemplate restTemplate)
{
this.restTemplate= restTemplate;
}
#GetMapping(value = "/call")
public String call()
{
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<String> (headers);
restTemplate.exchange("http://receiver/receive", HttpMethod. GET, entity, String.class);
System.out.println("Called.");
return "success";
}
}
pom.xml - only difference vs discovery service is load balancer is added:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
application.yml:
spring:
application:
name: caller
cloud:
loadbalancer:
ribbon:
enabled: false
eureka:
instance:
prefer-ip-address: true
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://localhost:8001/eureka/
Receiver application
Controller:
#RestController
public class ReceiverController
{
#GetMapping(value = "/receive")
public String receive()
{
System.out.println("Received.");
return "success";
}
}
application.yml:
spring:
application:
name: receiver
eureka:
instance:
prefer-ip-address: true
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://localhost:8001/eureka/
And here are some more significant elements of my configuration for Spring Boot 2.7.8: Discovery service
pom.xml:
<parent>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.abednarski79</groupId>
<artifactId>eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka</name>
<description>Eureka service</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Caller service
pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.abednarski79</groupId>
<artifactId>caller</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>caller</name>
<description>Caller service</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Rest template configuration:
#Configuration
#LoadBalancerClient(value = "receiver", configuration = LoadBalancerConfig.class)
public class RestTemplateConfig {
#Bean
#LoadBalanced
#Qualifier("loadBalanced RestTemplate")
RestTemplate loadBalanced RestTemplate() {
return new RestTemplate();
}
#Bean
#Qualifier("rest Template")
Rest Template restTemplate() {
return new RestTemplate();
}
and load balancer configuration:
public class LoadBalancerConfig
{
#Bean
ServiceInstanceListSupplier serviceInstanceListSupplier(ConfigurableApplicationContext context, #Qualifier ("restTemplate") RestTemplate restTemplate) {
return ServiceInstanceListSupplier
.builder()
.withBlockingDiscoveryClient()
.withBlockingHealthChecks(restTemplate)
.build(context);
}
}
application.yml:
spring:
application:
name: caller
cloud:
loadbalancer:
health-check:
refetch-instances: false
eureka:
instance:
prefer-ip-address: true
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://localhost:9001/eureka/

Related

How to avoid creation of Multiple Method Interceptors for the method with cache annotations when using spring data jpa and spring data redis

In my application I am using two data modules (spring-boot-starter-data-jpa and spring-boot-starter-data-redis).
I have a method annotated with #CachPut to store in cache. this method is called once but the actual CachePut operation is happening twice. when I debug there are two proxies created which intercepts the same method.
When I use only one module spring-boot-starter-data-redis it works as expected because there is only one proxy created which intercepts the method having #CachPut annotation.
But as per our requirement our application need to use both the data modules (spring-boot-starter-data-jpa for DB related stuff and spring-boot-starter-data-redis for handling some cache related stuff). If I add spring-boot-starter-data-jpa then the cache operation is getting executed twice (due to multiple proxies).
Is there any way to disable proxy creation with #EnableCaching for jpa module but enable only for redis module.
Code Snippets
Configuration class:
#Configuration
#EnableCaching
public class MyConfiguration {
#Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, #NotNull RedisCacheProperties properties) {
....
}
}
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>
CacheProcessor (Interface and Implementation classes)
public interface MyCacheProcessor {
MyData store(final MyData myData);
}
public class MyCacheProcessorImpl implements MyCacheProcessor {
#CachePut(cacheNames = {"my-data"}, key = "#myData.id")
public MyData store(final MyData myData) {
log.debug("storing in redis"); // This is printed only once (but actual cache put operation is happening twice )
return myData;
}
}
yaml configs
spring:
redis:
timeout: 500
host: devserver
port: 26379
datasource:
url: jdbc:h2:mem:testdb;Mode=Oracle;DB_CLOSE_ON_EXIT=FALSE
platform: h2
redis.cache.enabled: true
application:
redis:
cache:
my-data:
ttl: 15m
serializer:
type: org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
handles: com.my.sample.model.MyData
I expect the cache operation should be executed only once even If I use both the data modules. Currently the cache operation is executed twice.
Any guidance would be appreciated.

Spring Cloud Connector and MongoDB Replica Set

I have developed a micro service in Spring boot and it is deployed in Cloud Foundry. MongoDB is a service created in PCF and it is a replica set type service. The mongodb service is bound to the micro service in PCF. I am using Spring cloud connector to automatically fetch the connection string for the mongodb service when deployed in cloud using the following code.
#Configuration
#Profile("cloud")
public class CloudFoundryDatabaseConfig extends AbstractCloudConfig{
#Bean
public Cloud cloud() {
return new CloudFactory().getCloud();
}
#Bean
public MongoDbFactory mongoFactory() {
return connectionFactory().mongoDbFactory();
}
}
This code is working perfectly fine when the mongoDB service is a standalone type. However if it is a replica set, i get a unknown host exception. Since the mongodb URI contains comma separated host names, it seems to be unresolved.
An example of MongoDB URI below.
"mongodb://username:password#101.23.65.41:28000,101.23.65.43:28000,101.23.65.45:28000/default?authSource=admin"
Error:
com.mongodb.MongoSocketException: mongod-node-0-310d0fd1.mongodb.internal: Name or service not known}, caused by {java.net.UnknownHostException
Pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-cloudfoundry-connector</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-spring-service-connector</artifactId>
</dependency>
Please help.

Turbine Dashboard Is not Loading for Aggregation in Spring Cloud Microservice

I am trying to develop a spring cloud microservice using spring MVC and spring boot framework. And Eureka server , Zuul , Ribbon , hystrix and Turbine using for spring cloud. I already developed a microservice and implemented only hystrix dashboard. I am able to take hystrix dashboard. Now I am implementing more services. So I choosed turbine for aggregation of monitoring. But it not getting the dashboard.I implemented turbine in separate spring boot project.
My pom.xml containing,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
And My main class containing,
#SpringBootApplication
#EnableHystrixDashboard
#EnableCircuitBreaker
#EnableTurbine
public class ZTurbineClientApplication {
public static void main(String[] args) {
SpringApplication.run(ZTurbineClientApplication.class, args);
}
}
And my Turbine project application.properties file containing,
server.port=8085
spring.application.name=espace-Turbine
eureka.client.serviceUrl.defaultZone=http://localhost:8071/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
turbine:
aggregator:
clusterConfig: APPCLUSTER
app-config: espaceService1,espaceService2
instanceUrlSuffix.APPCLUSTER: /hystrix.stream
And My previous first services's application.properties file like
eureka.client.serviceUrl.defaultZone=http://localhost:8071/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
spring.application.name=espaceService1
server.port=8080
eureka:
instance:
prefer-ip-address: true
leaseRenewalIntervalInSeconds: 3
leaseExpirationDurationInSeconds: 3
metadata-map:
cluster: APPCLUSTER
And second service's application property file contains,
eureka.client.serviceUrl.defaultZone=http://localhost:8071/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
spring.application.name=espaceService2
server.port=8081
eureka:
instance:
prefer-ip-address: true
leaseRenewalIntervalInSeconds: 3
leaseExpirationDurationInSeconds: 3
metadata-map:
cluster: APPCLUSTER
these are my implementation details.
After I took URL "http://localhost:8085/hystrix.dashboard". And pasted "http://localhost:8085/turbine.stream?cluster=APPCLUSTER". But getting error like "Unable to connect to Command Metric Stream". Adding screenshots below.
You need to remove space from comma separated service names
turbine.aggregator.cluster-config=espace-Second_Microservice,espaceFirst_Microservice
You cannot aggregate streams from different cluster names, either use one cluster name in both espace-Second_Microservice and espace-First_Microservice or don't use cluster at all.
To define one cluster name use below config
eureka:
instance:
prefer-ip-address: true
leaseRenewalIntervalInSeconds: 3
leaseExpirationDurationInSeconds: 3
metadata-map:
cluster: APPCLUSTER
Use below mentioned config for turbine
turbine:
aggregator:
clusterConfig: APPCLUSTER
app-config: espace-Second_Microservice,espace-First_Microservice
instanceUrlSuffix.APPCLUSTER: /hystrix.stream
Use http://{turbine host}:{turbine Port}/turbine.stream?cluster=APPCLUSTER in Hystrix Dashboard

Spring Boot admin not showing any client applications in dashboard

I have been following this tutorial for Spring boot Admin, but when I run the admin application I can't see any spring boot client application for monitoring. I have checked that both the admin and client apps are running. Admin is running on port 8080 and client application is running on port 9010
This is what I have done in my Admin application
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<version>1.5.3</version>
</dependency>
Added these annotations
#Configuration
#SpringBootApplication
#EnableAdminServer
public class SpringBootAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAdminApplication.class, args);
}
}
This is what I have added in my client application
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>1.5.3</version>
</dependency>
and added this in my properties file (YAML)
spring:
boot:
admin:
url:
- http://localhost:8080
management:
security:
enabled: false
This is what my Dashboard looks like, as you can see there are no applications to monitor.

Config server not getting discovered in a consul first setup

I am using a consul first setup. For this ,I am using a consul docker image with port 8500 mapped to 8500 so I can successfully use it on local set up.I can access consul at http://localhost:8500 .
I have the following configuration for the config server -
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Application.yml
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/xxxxxx/spring-config-repo.git
consul:
host: consul
port: 8500
enabled: true
discovery:
enabled: true
register: true
service-name: configservice
bootstrap.yml
spring:
application:
name: config-server
ConfigServerApplication.java
#EnableConfigServer
#EnableDiscoveryClient
#SpringBootApplication
public class ConfigServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServiceApplication.class, args);
}
}
When I run this jar, it is successful. I can see the config server in consul ui.
Following are the details of the config server as seen from consul-
Request - http://localhost:8500/v1/catalog/services
Response -
{
"configservice": [],
"consul": []
}
Request - http://localhost:8500/v1/catalog/service/configservice
Response -
[
{
"ID": "f6ac953c-07b9-4097-974e-3ea9cd39bec2",
"Node": "a0954c644062",
"Address": "127.0.0.1",
"TaggedAddresses": {
"lan": "127.0.0.1",
"wan": "127.0.0.1"
},
"NodeMeta": {},
"ServiceID": "config-server-8888",
"ServiceName": "configservice",
"ServiceTags": [],
"ServiceAddress": "10.0.0.158",
"ServicePort": 8888,
"ServiceEnableTagOverride": false,
"CreateIndex": 15,
"ModifyIndex": 15
}
]
I can also access the config server using this uri-
http://localhost:8888/configclient/default
I have client application which uses properties from the config server.My understanding is that when I run this jar, it will contact consul and get the config server information from there and populate the properties by fetching it from the given git uri.
Following are the details about the client -
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
bootstrap.yml
spring:
application:
name: myservice
cloud:
config:
fail-fast: true
retry:
max-attempts: 20
initial-interval: 3000
enabled: true
discovery:
enabled: true
service-id: configserver
consul:
host: localhost
port: 8500
discovery:
enabled: true
register: true
service-name: myservice
application.yml
server:
port: 8081
MyServiceApplication.java
#EnableDiscoveryClient
#SpringBootApplication
public class MyServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ApplePaymentServiceApplication.class, args);
}
}
But during this startup of this jar, its not able to find the config server and instead falls back on the default uri .
This is the relevant startup log section
2017-03-28 00:40:20.407 WARN 99123 --- [ main] lientConfigServiceBootstrapConfiguration : No instances found of configserver (configserver)
2017-03-28 00:40:20.551 INFO 99123 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='consul', propertySources= . [ConsulPropertySource [name='config/myservice/'], ConsulPropertySource [name='config/application/']]]
2017-03-28 00:40:20.587 INFO 99123 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at: http://localhost:8888
2017-03-28 00:40:21.831 INFO 99123 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=myservice, profiles=[default], label=null, version=null, state=null
2017-03-28 00:40:21.832 INFO 99123 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='configService', propertySources=[MapPropertySource [name='https://github.com/xxxxx/spring-config-repo.git/myservice.yml'], MapPropertySource [name='https://github.com/xxxxx/spring-config-repo.git/application.yml']]]
2017-03-28 00:40:21.841 INFO 99123 --- [ main] c.h.MyServiceApplication : No active profile set, falling back to default profiles: default
I tinkered with a lot of different options from various examples such as
consul first bootstrap with spring cloud config
Can someone point to what I might be missing ?
Any pointers to a working example with access to proper application/bootstrap files for consul, configserver and client would be most helpful .
PS: I apologize for the information overload in the question. Some of it might not be relevant but I wanted to give as much information as possible so that someone can spot something that they have encountered before.
You're trying to look up configserver, but it is registered as configservice.