With the below Circuit breaker configuration, when I have throw simple exception like below service class,I have noticed the increment in the failedCalls and the transition happens from Closed-> OPEN -> half open->.... . But its not working with reactive call.
example:
resilience4j.circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
recordExceptions:
- com.bk.exceptions.ApiException
- java.util.concurrent.TimeoutException
- java.io.IOException
shared:
slidingWindowSize: 100
permittedNumberOfCallsInHalfOpenState: 30
waitDurationInOpenState: 1s
failureRateThreshold: 50
eventConsumerBufferSize: 10
resilience4jInstance:
baseConfig: default
Controller:
#GetMapping("fallback")
public String failureWithFallback(){
return resilience4jService.failureWithFallback();
}
Service:
#CircuitBreaker(name = "resilience4jInstance", fallbackMethod = "fallback")
public String failureWithFallback() {
return failure();
}
#CircuitBreaker(name = "resilience4jInstance")
public String failure() {
throw new ApiException();
}
but When I have Reactive web flux call, the failedCalls count is not incrementing and state also not getting change. I have a mock server which returns 500 error for the endpoint.
#CircuitBreaker(name = "resilience4jInstance", fallbackMethod = "fallbackReview")
public Mono<ReviewResponse> reviewUser(ReviewRequest reviewRequest, String id) {
return client.post("/users/review", reviewRequest)
.retrieve()
.bodyToMono(Review.class)
.retryWhen(reviewRetryScheme)
.doOnError(e -> { throw new ApiException(e);})
.doOnNext(reviewResponse -> log.debug("reviewResponse: {}", reviewResponse))
.map(ReviewMapper.INSTANCE::mapReviewResponse);
}
private Mono<ReviewResponse> fallbackReview( ReviewRequest request, String id, Throwable e) {
log.error("Circuit breaker for make review service call fallback");
return Mono.error(new ApiException(e));
}
**
"resilience4jInstance" : {
"status" : "UP",
"details" : {
"failureRate" : "-1.0%",
"failureRateThreshold" : "50.0%",
"slowCallRate" : "-1.0%",
"slowCallRateThreshold" : "100.0%",
"bufferedCalls" : 1,
"slowCalls" : 0,
"slowFailedCalls" : 0,
"failedCalls" : 0,
"notPermittedCalls" : 0,
"state" : "CLOSED"
}
}
}**
Related
What decorator can I put on the class field so that values other than true and false are not accepted in the request.
Now, any values can go into the request, including boolean.
I can send false, 0 and an empty string – it will be interpreted as false.
I can send true, all positive and negative numbers and it will be interpreted as true.
What can I do to accept only true and false?
open class KdInfo : Info {
#Min(0)
var brutto: Long = 0
#NotNull
var isRefund: Boolean = false // this field can get not only boolean values from request
}
I found solution. Make custom JsonDeserializer and add it as decorator to field.:
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
class JsonBooleanDeserializer : JsonDeserializer<Boolean>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Boolean {
return when (parser.text.toLowerCase()) {
"true" -> true
"false" -> false
else -> throw Exception()
}
}
}
open class KdInfo : Info {
#Min(0)
var brutto: Long = 0
#NotNull
#JsonProperty("refund")
#JsonDeserialize(using = JsonBooleanDeserializer::class)
var isRefund: Boolean = false // this field can get not only boolean values from request
}
I'm trying to add a conditional Retry for WebClient with Kotlin Coroutines + WebFlux.fn + reactor-addons:
suspend fun ClientResponse.asResponse(): ServerResponse =
status(statusCode())
.headers { headerConsumer -> headerConsumer.addAll(headers().asHttpHeaders()) }
.body(bodyToMono(DataBuffer::class.java), DataBuffer::class.java)
.retryWhen {
Retry.onlyIf { ctx: RetryContext<Throwable> -> (ctx.exception() as? WebClientResponseException)?.statusCode in retryableErrorCodes }
.exponentialBackoff(ofSeconds(1), ofSeconds(5))
.retryMax(3)
.doOnRetry { log.error("Retry for {}", it.exception()) }
)
.awaitSingle()
also adding a condition before the retry
if (statusCode().isError) {
body(
BodyInserters.fromPublisher(
Mono.error(StatusCodeError(status = statusCode())),
StatusCodeException::class.java
)
)
} else {
body(bodyToMono(DataBuffer::class.java), DataBuffer::class.java)
}
Call looks like:
suspend fun request(): ServerResponse =
webClient/*...*/
.awaitExchange()
.asResponse()
This spring webclient: retry with backoff on specific error gave me the hint to answer the question:
.awaitExchange() returns the ClientResponse and not Mono<ClientReponse>
This means my retry was acting on bodyToMono instead of the operation of exchange().
The solution now looks like
suspend fun Mono<ClientResponse>.asResponse(): ServerResponse =
flatMap {
if (it.statusCode().isError) {
Mono.error(StatusCodeException(status = it.statusCode()))
} else {
it.asResponse()
}
}.retryWhen(
Retry.onlyIf { ctx: RetryContext<Throwable> ->
(ctx.exception() as? StatusCodeException)?.shouldRetry() ?: false
}
.exponentialBackoff(ofSeconds(1), ofSeconds(5))
.retryMax(3)
.doOnRetry { log.error { it.exception() } }
).awaitSingle()
private fun ClientResponse.asResponse(): Mono<ServerResponse> =
status(statusCode())
.headers { headerConsumer -> headerConsumer.addAll(headers().asHttpHeaders()) }
.body(bodyToMono(DataBuffer::class.java), DataBuffer::class.java)
I'm implement websocket in spring-boot 1.5.8, on my local it working fine, but when I deploy to server (run in embedded server) the message notify from server is not stable, sometime client can receive message sometime not.
I check on the log and I got a message
2019-03-20 08:04:08.723 INFO 15506 --- [MessageBroker-1] o.s.w.s.c.WebSocketMessageBrokerStats : WebSocketSession[2 current WS(2)-HttpStream(0)-HttpPoll(0), 167 total, 0 closed abnormally (0 connect failure, 0 send limit, 27 transport error)], stompSubProtocol[processed CONNECT(107)-CONNECTED(107)-DISCONNECT(2)], stompBrokerRelay[null], inboundChannel[pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1149], outboundChannelpool size = 0, active threads = 0, queued tasks = 0, completed tasks = 306], sockJsScheduler[pool size = 1, active threads = 1, queued tasks = 4, completed tasks = 4468]
(0 connect failure, 0 send limit, 27 transport error)
The log message show that 27 message error, I don't know how to resolve that.
Can we handle on error message event to resolve this problem?
UPDATE
My websocket server
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/queue");
registry.enableSimpleBroker("/notify", "/queue", "/user");
registry.setUserDestinationPrefix("/user");
}
On client side
function connect() {
var socket = new SockJS('http://localhost:8080/ws/');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
stompClient.subscribe('/user/queue/notify', function(notification) {
notify(notification.body)
});
});
return;
}
/**
* Display the notification message.
*/
function notify(message) {
$("#notifications-area").append(message + "\n");
return;
}
I have a Jersey server running locally, it exposes a SSE resource just like the examples here: https://jersey.github.io/documentation/latest/sse.html. I have a local webpack Angular app, that binds to the exposed GET endpoint and listens for data.
On the GET, I start up a thread to send notifications at regular intervals over 6-8 seconds. I don't see anything on the client UNTIL the EventOutput object is closed.
What am I doing wrong, and how can I fix this?
The server code WORKS with just a simple curl, i.e.:
curl http://localhost:8002/api/v1/notify
But on both Chrome and Safari the following code exhibits the behavior
Client (TypeScript):
this.evSource = new EventSource('http://localhost:8002/api/v1/notify');
this.evSource.addEventListener(
'event',
(x => console.log('we have ', x))
);
this.evSource.onmessage = (data => console.log(data));
this.evSource.onopen = (data => console.log(data));
this.evSource.onerror = (data => {
console.log(data);
this.evSource.close();
});
Server (Java):
// cache callback
public void eventCallback(Iterable<CacheEntryEvent<? extends Integer, ? extends Integer>> events) {
for (CacheEntryEvent<? extends Integer, ? extends Integer> x : events) {
LOGGER.info("{} Sending the following value: " + x.getValue(), Thread.currentThread().getId());
final OutboundEvent sseEvent = new OutboundEvent.Builder().name("event")
.data(Integer.class, x.getValue()).build();
this.broadcaster.broadcast(sseEvent);
}
}
#GET
#Produces(SseFeature.SERVER_SENT_EVENTS)
#ApiOperation(value = "Setup SSE pipeline", notes = "Sets up the notification pipeline for clients to access")
#ApiResponses(value = {
#ApiResponse(code = HttpURLConnection.HTTP_UNAUTHORIZED,
message = "Missing, bad or untrusted cookie"),
#ApiResponse(code = HttpURLConnection.HTTP_OK,
message = "Events streamed successfully")
})
#Timed
#ResponseMetered
public EventOutput registerNotificationEvents(
#HeaderParam(SseFeature.LAST_EVENT_ID_HEADER) String lastEventId,
#QueryParam(SseFeature.LAST_EVENT_ID_HEADER) String lastEventIdQuery) {
if (!Strings.isNullOrEmpty(lastEventId) || !Strings.isNullOrEmpty(lastEventIdQuery)) {
LOGGER.info("Found Last-Event-ID header: {}", !Strings.isNullOrEmpty(lastEventId) ? lastEventId : lastEventIdQuery );
}
LOGGER.info("{} Received request", Thread.currentThread().getId());
this.continuation = true;
final EventOutput output = new EventOutput();
broadcaster.add(output);
Random rand = new Random();
IntStream rndStream = IntStream.generate(() -> rand.nextInt(90));
List<Integer> lottery = rndStream.limit(15).boxed().collect(Collectors.toList());
IgniteCache<Integer, Integer> cache = this.ignite.cache(topic_name);
executorService.execute(() -> {
try {
lottery.forEach(value -> {
try {
TimeUnit.MILLISECONDS.sleep(500);
LOGGER.info("{} Sending the following value to Ignite: " + value + " : " + count++, Thread.currentThread().getId());
if (!cache.isClosed()) {
cache.put(1, value);
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
TimeUnit.MILLISECONDS.sleep(500);
continuation = false;
TimeUnit.MILLISECONDS.sleep(500);
if (!output.isClosed()) {
// THIS is where the client sees ALL the data broadcast
// in one shot
output.close();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
});
LOGGER.info("{} Completing request", Thread.currentThread().getId());
return output;
}
}
Looks like http://github.com/dropwizard/dropwizard/issues/1673 captures the problem. GZip default won't flush even if upper levels ask for it. Solution is something like
((AbstractServerFactory)configuration.getServerFactory()).getGzipFilterFactory().setSyncFlush(true);
will enable flushing to synchronize with GZip if disabling GZip all up is not an option
I have a cluster of rabbitmqs. And configure the spring.rabbitmq.address=xxx,yyy,cccc
Then I start two consumer clients. The question is that the clients only connect one node, there,s not connection to the other nodes. I trace the codes, and found that :
public Connection newConnection(ExecutorService executor, AddressResolver addressResolver, String clientProvidedName) throws IOException, TimeoutException {
if (this.metricsCollector == null) {
this.metricsCollector = new NoOpMetricsCollector();
}
FrameHandlerFactory fhFactory = this.createFrameHandlerFactory();
ConnectionParams params = this.params(executor);
if (clientProvidedName != null) {
Map<String, Object> properties = new
HashMap(params.getClientProperties());
properties.put("connection_name", clientProvidedName);
params.setClientProperties(properties);
}
if (this.isAutomaticRecoveryEnabled()) {
AutorecoveringConnection conn = new AutorecoveringConnection(params,
fhFactory, addressResolver, this.metricsCollector);
conn.init();
return conn;
} else {
List<Address> addrs = addressResolver.getAddresses();
Exception lastException = null;
Iterator var8 = addrs.iterator();
while(var8.hasNext()) {
Address addr = (Address)var8.next();
try {
**FrameHandler handler = fhFactory.create(addr);
AMQConnection conn = this.createConnection(params, handler, this.metricsCollector);
conn.start();
this.metricsCollector.newConnection(conn);
return conn;**
} catch (IOException var12) {
lastException = var12;
} catch (TimeoutException var13) {
lastException = var13;
}
}
if (lastException != null) {
if (lastException instanceof IOException) {
throw (IOException)lastException;
}
if (lastException instanceof TimeoutException) {
throw (TimeoutException)lastException;
}
}
throw new IOException("failed to connect");
}
}
We can see that it creates one connection then returned. But if I wanna the other consumer client can connect to the left nodes instead the same node, although both of the consumer clients have the same configuration:
spring:
rabbitmq:
username: aaaa
password: aaaa
virtual-host: /
addresses: xxxx:5672,yyy:5672,zzzzz:5672
listener:
simple:
concurrency: 4
max-concurrency: 4
prefetch: 4
What should I do ? can someone give some suggestions?
The RabbitMQ team monitors this mailing list and only sometimes answers questions on StackOverflow.
Try rotating the list of addresses in each client's configuration:
First - xxxx:5672,yyy:5672,zzzzz:5672
Second - yyy:5672,zzzzz:5672,xxxx:5672
Third - zzzzz:5672,xxxx:5672,yyy:5672