SpringBoot Disable rabbitTemplate retry policy for rabbit health check - spring-boot

My SpringBoot configuration contains very strong retry policy for rabbitTemplate retries
spring:
rabbitmq:
template:
retry:
enabled: true
initial-interval: 500
max-attempts: 10
multiplier: 5
max-interval: 60000
The problem with this configuration is when health endpoint is called and rabbitMQ is down, the connections hangs for really long time.
Adding properties like
spring.rabbitmq.connection-timeout=500 or
spring.rabbitmq.template.receive-timeout=500 or
spring.rabbitmq.template.reply-timeout=500 or
spring.rabbitmq.requested-heartbeat=1
does not help, since the retry.multiplier=5, so it will take a lot of time anyway.
If we disregard whether the retry policy is good or not, is there a way to disable rabbitTemplate retries for health check endpoint or at least give it some timeout?

You can override the default health indicator bean to use a template without retry enabled...
#Configuration
public class MyRabbitHealthIndicatorOverride
extends CompositeHealthIndicatorConfiguration<RabbitHealthIndicator, RabbitTemplate> {
#Bean
public HealthIndicator rabbitHealthIndicator(ConnectionFactory connectionFactory) {
return createHealthIndicator(new RabbitTemplate(connectionFactory));
}
}

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.

Can I apply graceful shutdown when using Spring Cloud Stream Kafka 3.0.3.RELEASE?

I developed the kafka consumer application based on Spring Cloud Stream 3.0.3.RELEASE.
(SpringBoot 2.3.4.RELEASE)
When I stop this application,
I want the consumer to gracefully shut down. Similar Questions
Stop polling new messages
Finish their work and Commit the offset to Kafka
Gracefully shut down application
Does the spring cloud stream default this work?
Then is there a related document?
For your information, I am using spring cloud stream kafka as below.
#Message handler
#Component
public class MessageHandler {
#Bean
public Consumer<MyEvent> handleMessage() {
return message -> {...}
}
...
}
#application.yml
spring:
cloud:
stream:
bindings:
handleMessage-in-0:
destination: myevent
group: test-group
consumer:
maxAttempts: 2
concurrency: 10
function:
definition: handleMessage
...
As mentioned in the answer you referenced, you can increase the shutdownTimeout container property (default 10 seconds) with a ListenerContainerCustomizer bean.
#Bean
public ListenerContainerCustomizer<AbstractMessageListenerContainer<?, ?>> customizer() {
return (container, dest, group) -> container.getContainerProperties()
.setShutdownTimeout(30_000L);
}

Spring Integration Connecting a Gateway to a Service Activator

I've created a Gateway and a polling notificationChannel which the Gateway uses to route messages. I want a service activator to poll from the channel and do its thing. But I can't seem to grasp a few things about Spring Integration.
In this case would we need an IntegrationFlow Bean? Wouldn't calling the gateway method just send the message trough the channel and the service activator can just poll automatically when there is a new message?
ConfigurationClass:
#EnableIntegration
#Configuration
#IntegrationComponentScan
class IntegrationConfiguration {
#Bean
fun notificationChannel(): MessageChannel {
return MessageChannels.queue().get()
}
#Bean
fun integrationFlow(): IntegrationFlow {
TODO()
}
}
Gateway:
#MessagingGateway(defaultRequestChannel = "notificationChannel")
#Component
interface NotificationGateway {
fun sendNotification(bytes: ByteArray)
}
Service:
#Service
class NotificationService {
#ServiceActivator(inputChannel = "notificationChannel")
fun sendNotification(bytes: ByteArray) {
TODO()
}
}
I am new to Spring Integration and having a rough time since I can't find understandable documentation for my level of knowledge especially on Spring Integration DSL.
My main problem might be that I do now understand the use of the IntegrationFlow Bean
For a simple use-case like yours you indeed don't need an IntegrationFlow. The simple #ServiceActivator as you have now is fully enough to process messages from the notificationChannel. Only what you need is a #Poller in that #ServiceActivator configuration since your notificationChannel is a PollableChannel one and it is not subscribable one.
See Reference Manual for more info: https://docs.spring.io/spring-integration/docs/current/reference/html/#configuration-using-poller-annotation
Also pay attention to the paragraph in the beginning of the doc: https://docs.spring.io/spring-integration/docs/current/reference/html/#programming-considerations

Session not replicated on session creation with Spring Boot, Session, and Redis

I'm attempting to implement a microservices architecture using Spring Cloud's Zuul, Eureka, and my own services. I have multiple services that have UIs and services and each can authenticate users using x509 security. Now I'm trying to place Zuul in front of those services. Since Zuul can't forward client certs to the backend, I thought the next best thing would be to authenticate the user at the front door in Zuul, then use Spring Session to replicate their authenticated state across the backend services. I have followed the tutorial here from Dave Syer and it almost works, but not on the first request. Here is my basic setup:
Zuul Proxy in it's own application set to route to the backend services. Has Spring security enabled to do x509 auth. Successfully auths users. Also has Spring Session with #EnableRedisHttpSession
Backend service also has spring security enabled. I have tried both enabling/disabling x509 here but always requiring the user to be authenticated for specific endpoints. Also uses Spring Session and #EnableRedisHttpSession.
If you clear all the sessions and start fresh and try to hit the proxy, then it sends the request to the backend using the zuul server's certificate. The backend service then looks up the user based on that user cert and thinks the user is the server, not the user that was authenticated in the Zuul proxy. If you just refresh the page, then you suddenly become the correct user on the back end (the user authenticated in the Zuul proxy). The way I'm checking is to print out the Principal user in the backend controller. So on first request, I see the server user, and on second request, I see the real user. If I disable x509 on the back end, on the first request, I get a 403, then on refresh, it lets me in.
It seems like the session isn't replicated to the backend fast enough so when the user is authenticated in the frontend, it hasn't made it to the backend by the time Zuul forwards the request.
Is there a way to guarantee the session is replicated on the first request (i.e. session creation)? Or am I missing a step to ensure this works correctly?
Here are some of the important code snippets:
Zuul Proxy:
#SpringBootApplication
#Controller
#EnableAutoConfiguration
#EnableZuulProxy
#EnableRedisHttpSession
public class ZuulEdgeServer {
public static void main(String[] args) {
new SpringApplicationBuilder(ZuulEdgeServer.class).web(true).run(args);
}
}
Zuul Config:
info:
component: Zuul Server
endpoints:
restart:
enabled: true
shutdown:
enabled: true
health:
sensitive: false
zuul:
routes:
service1: /**
logging:
level:
ROOT: INFO
# org.springframework.web: DEBUG
net.acesinc: DEBUG
security.sessions: ALWAYS
server:
port: 8443
ssl:
key-store: classpath:dev/localhost.jks
key-store-password: thepassword
keyStoreType: JKS
keyAlias: localhost
clientAuth: want
trust-store: classpath:dev/localhost.jks
ribbon:
IsSecure: true
Backend Service:
#SpringBootApplication
#EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, ThymeleafAutoConfiguration.class, org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class })
#EnableEurekaClient
#EnableRedisHttpSession
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Backend Service Config:
spring.jmx.default-domain: ${spring.application.name}
server:
port: 8444
ssl:
key-store: classpath:dev/localhost.jks
key-store-password: thepassword
keyStoreType: JKS
keyAlias: localhost
clientAuth: want
trust-store: classpath:dev/localhost.jks
#Change the base url of all REST endpoints to be under /rest
spring.data.rest.base-uri: /rest
security.sessions: NEVER
logging:
level:
ROOT: INFO
# org.springframework.web: INFO
# org.springframework.security: DEBUG
net.acesinc: DEBUG
eureka:
instance:
nonSecurePortEnabled: false
securePortEnabled: true
securePort: ${server.port}
homePageUrl: https://${eureka.instance.hostname}:${server.port}/
secureVirtualHostName: ${spring.application.name}
One of the Backend Controllers:
#Controller
public class SecureContent1Controller {
private static final Logger log = LoggerFactory.getLogger(SecureContent1Controller.class);
#RequestMapping(value = {"/secure1"}, method = RequestMethod.GET)
#PreAuthorize("isAuthenticated()")
public #ResponseBody String getHomepage(ModelMap model, Principal p) {
log.debug("Secure Content for user [ " + p.getName() + " ]");
model.addAttribute("pageName", "secure1");
return "You are: [ " + p.getName() + " ] and here is your secure content: secure1";
}
}
Thanks to shobull for pointing me to Justin Taylor's answer to this problem. For completeness, I wanted to put the full answer here too. It's a two part solution:
Make Spring Session commit eagerly - since spring-session v1.0 there is annotation property #EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) which saves session data into Redis immediately. Documentation here.
Simple Zuul filter for adding session into current request's header:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.stereotype.Component;
#Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
#Autowired
private SessionRepository repository;
private static final Logger log = LoggerFactory.getLogger(SessionSavingZuulPreFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
Session session = repository.getSession(httpSession.getId());
context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());
log.trace("ZuulPreFilter session proxy: {}", session.getId());
return null;
}
}
Both of these should be within your Zuul Proxy.
Spring Session support currently writes to the data store when the request is committed. This is to try to reduce "chatty traffic" by writing all attributes at once.
It is recognized that this is not ideal for some scenarios (like the one you are facing). For these we have spring-session/issues/250. The workaround is to copy RedisOperationsSessionRepository and invoke saveDelta anytime property is changed.

Resources