WebClient configuration with Spring Cloud LoadBalancer considering zone preference - spring

I am configuring a WebClient Bean using Spring Cloud Load Balancer with Multi-AZ configurations (e.g. A and B Zone), and I want to request A Zone first, and if I cannot connect to A Zone, I want to request it to B Zone.
What form should it be in at this time?
Configuration.java
#Bean
public WebClient webClient(ReactorLoadBalancerExchangeFilterFunction loadBalancerFilterFunction) {
...
return WebClient.builder()
.filter(loadBalancerFilterFunction)
.baseUrl("http://[a?b?]")
.build();
}
application.yml
spring:
cloud:
discovery:
client:
simple:
instances:
a:
// ServiceInstances
b:
// ServiceInstances

Related

Spring cloud load-balancer drops instances after cache refresh

I have a need to save Spring Cloud Gateway routes within a database and to do this we send a web request using the WebClient to another microservice.
I'm using Eureka for service discovery and want the WebClient to use discovery instance names instead of explicit URLs and I've therefore utilised the #LoadBalanced annotation on the bean method:
#Bean
public WebClient loadBalancedWebClientBuilder(WebClient.Builder builder) {
return builder
.exchangeStrategies(exchangeStrategies())
.build();
}
#Bean
#LoadBalanced
WebClient.Builder builder() {
return WebClient.builder();
}
private ExchangeStrategies exchangeStrategies() {
return ExchangeStrategies.builder()
.codecs(clientCodecConfigurer -> {
clientCodecConfigurer.defaultCodecs().jackson2JsonEncoder(getEncoder());
clientCodecConfigurer.defaultCodecs().jackson2JsonDecoder(getDecoder());
}).build();
}
This all works on start-up and for the default 35s cache time - i.e. the webClient discovers the required 'saveToDatabase' service instance and sends the request.
On each eventPublisher.publishEvent(new RefreshRoutesEvent(this)) a call is made to the same downstream microservice (via the WebClient) to retrieve all saved routes.
Again this works initially, but after the default 35seconds the load balancer cache seems to be cleared and the downstream service id can no longer be found:
WARN o.s.c.l.core.RoundRobinLoadBalancer - No servers available for service: QUERY-SERVICE
I have confirmed it is the cache refresh purging the cache and not re-acquiring the instances by setting
spring:
application:
name: my-gateway
cloud:
loadbalancer:
cache:
enabled: true
ttl: 240s
health-check:
refetch-instances: true
ribbon:
enabled: false
gateway:
...
I've struggled with this for days now and cannot find/ see where or why the cache is not being updated, only purged. Adding specific #LoadBalancerClient() configuration as below makes no difference.
#Bean
public ServiceInstanceListSupplier instanceSupplier(ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.withCaching()
.withRetryAwareness()
.build(context);
}
Clearly this must work for other people, so what am I missing?!
Thanks.

Http Pool Stats monitor using spring feign

I am using spring feign, with connection pooling
implementation 'io.github.openfeign:feign-httpclient:12.1'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.1.5'
application.yml
feign:
httpclient:
enabled: true
maxConnections: 55
maxConnectionsPerRoute: 25
I want to print pool metrics using like this at scheduled intervals
#Autowired
HttpClient httpClient;
#Scheduled(fixedDelay = 1000)
public void printStats(){
((PoolingHttpClientConnectionManager) ((CloseableHttpClient) httpClient).getConnectionManager()).getTotalStats();
}
But the method getConnectionManager() is deprecated.
Any alternatives or better way of doing it.

Spring Cache + Hibernate L2 cache via Hazelcast

I have Spring Boot application with Hibernate L2 cache enabled and I used a Hazelcast for this.
Also I want to add Spring cache, using #Cacheable annotation
I need to distribute this cache(spring cache and hibernate l2) between several pods in kubernetes, using embedded distributed cache pattern.
For now, I successfully distribute Hibernate l2 cache between pods using the following configuration
hazelcast.yaml
hazelcast:
instance-name: my-instance
network:
join:
multicast:
enabled: false
kubernetes:
enabled: true
namespace: dev
application.properties
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.url=jdbc:postgresql://host.docker.internal/postgres
spring.datasource.username=postgres
spring.datasource.password=pass
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=com.hazelcast.hibernate.HazelcastCacheRegionFactory
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.show_sql=true
hibernate.cache.hazelcast.instance_name=my-instance
But I also need to share spring cache, using hazelcast.
For example, I have the following code , and I want to distribute data, that cached in this service, between k8s pods
#Service
public class BookService {
#Autowired
private BookRepo bookRepo;
#Cacheable("books")
public Optional<Book> getBookById(int id) {
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
System.out.println("Book service triggered");
return bookRepo.findById(id);
}
}
And I have no idea How to correctly configure my application to share both spring and hibernate l2 cache between k8s pods ?

Spring Cloud Vault Configuration without YAML file

I have mentioned the Spring Cloud Vault Configuration in my bootstrap.ymlfile
spring:
cloud:
vault:
authentication: APPROLE
app-role:
role-id: *****
secret-id: ****
host: ****
port: 80
scheme: http
But i dont want to have these in my YML file, rather i would like to have these configured as a bean
#configuration / #bean
Please help. Thanks
I was able to do this successfully by configuring a Bean of type VaultProperties. Below is the code snippet which completely eliminated the need for maintaining the same in bootstrap.yml
#Configuration
public class VaultConfiguration {
#Bean
public VaultProperties vaultProperties() {
VaultProperties vaultProperties = new VaultProperties();
vaultProperties.setAuthentication(VaultProperties.AuthenticationMethod.APPROLE);
VaultProperties.AppRoleProperties appRoleProperties = new VaultProperties.AppRoleProperties();
appRoleProperties.setRoleId("****");
appRoleProperties.setSecretId("****");
vaultProperties.setAppRole(appRoleProperties);
vaultProperties.setHost("***");
vaultProperties.setPort(80);
vaultProperties.setScheme("http");
return vaultProperties;
}
}
Note : When you are having a configuration that should be treated as bootstrap-configuration, then you need to mention the class name under src/main/resources/META-INF/spring.factories
The content in spring.factories is
org.springframework.cloud.bootstrap.BootstrapConfiguration=com.arun.local.cloudconfig.VaultConfiguration

Issues with Spring cloud consul config while starting a Groovy app through Spring cloud cli

I am Exploring Consul for discovery and config server. I have added the required dependencies and yml file is set up. When i try to start the server using spring cloud cli (Spring run .) I am getting the below error which i am unable to resolve. Any help is appreciated.
Error :
"A component required a bean named 'configServerRetryInterceptor' that could >not be found."
I tried to define this bean but when i start the app through spring cloud cli it is not recognizing it.
Please see the code below
App.groovy
#Grab("spring-cloud-starter-consul-config")
#Grab("spring-cloud-starter-consul-discovery")
#EnableDiscoveryClient
#EnableCircuitBreaker
#RestController
#Log
class Application{
#Autowired
Greeter greeter
int counter = 0
#RequestMapping(value = "/counter", produces = "application/json")
String produce() {
counter++
log.info("Produced a value: ${counter}")
"{\"value\": ${counter}}"
}
#RequestMapping("/")
String home() {
"${greeter.greeting} World!"
}
#RequestMapping(value = '/questions/{questionId}')
#HystrixCommand(fallbackMethod = "defaultQuestion")
def question(#PathVariable String questionId) {
if(Math.random() < 0.5) {
throw new RuntimeException('random');
}
[questionId: questionId]
}
def defaultQuestion(String questionId) {
[questionId: 'defaultQuestion']
}
}
#Component
#RefreshScope
class Greeter {
#Value('${greeting}')
String greeting
}
bootstrap.yml
consul:
host: localhost
port: 8500
config:
enabled: true
prefix: config
defaultContext: master
profileSeparator: '::'
format: FILES
discovery:
instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}}
health-check-url: http://127.0.0.1:${server.port}/health
This issue was due to unwanted dependencies being pulled.
Explicitly disabling spring cloud config and spring cloud discovery fixed it.
spring:
cloud:
config:
enabled: false
discovery:
enabled: false
serviceId: CONFIG
eureka:
client:
register-with-eureka: false
fetch-registry: false

Resources