CircuitBreaker Fallback method not working - spring-boot

I have the below code in Billing service microservice:
#RestController
#RequestMapping("/billing")
public class WebController {
#Autowired
private BillingService service;
#GetMapping("/hi")
#CircuitBreaker(name="BillingServiceCapture", fallbackMethod = "hiFallback")
public String hi() {
return "Hello Khushboo!";
}
public String hiFallback() {
return "Hello Khushboo FallBack!";
}
Application.Properties file:
server.port=9191
spring.h2.console.enable=true
spring.application.name=billing-service
eureka.client.serviceurl.defaultzone=http://localhost:8761/eureka
eureka.instance.hostname=localhost
management.health.circuitbreakers.enabled=true
#actuator settings
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
resilience4j.circuitbreaker.instances.BillingServiceCapture.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.BillingServiceCapture.eventConsumerBufferSize=10
resilience4j.circuitbreaker.instances.BillingServiceCapture.failureRateThreshold=20
resilience4j.circuitbreaker.instances.BillingServiceCapture.minimumNumberOfCalls=5
resilience4j.circuitbreaker.instances.BillingServiceCapture.automaticTransitionFromOpenToHalfOpenEnabled=true
resilience4j.circuitbreaker.instances.BillingServiceCapture.waitDurationInOpenState=5s
resilience4j.circuitbreaker.instances.BillingServiceCapture.permittedNumberOfCallsInHalfOpenState=3
resilience4j.circuitbreaker.instances.BillingServiceCapture.slidingWindowSize=10
resilience4j.circuitbreaker.instances.BillingServiceCapture.slidingWindowType=COUNT_BASED
However, if I send a Get Request: localhost:8765/billing/hi
I get Hello Khushboo message.
But when I stop the BillingService microservice and again send the same request, the circuit breaker method doesn't get invoked.
Also, while accessing the Actuator Health status, I do not see circuit breaker information in the status logs which I should see.
I even added the CircuitBreaker code in OrderService which actually calls the BillingService:
#CircuitBreaker(name="BillingServiceCapture", fallbackMethod = "getAllBillingDetails")
public TransactionResponse saveOrder(TransactionRequest request) {
Order order=request.getOrder();
Billing billing=request.getBilling();
billing.setOrderId(order.getId());
billing.setAmount(order.getPrice());
Order ordermade=orderRepo.save(order);
Billing billingresponse=billingproxy.getBillingDone(billing);
TransactionResponse response=null;
String responseStr= billingresponse.getPaymentStatus().equals("success")?"Payment processing successful":"Payment failed";
response=new TransactionResponse(order, billingresponse.getTransactionId(),billingresponse.getAmount(),responseStr);
return response;
}
public Billing getAllBillingDetails(Billing bill,Exception e) {
return new Billing(1000,"pass",101,102,1000);
}
When I call http://localhost:8765/order/bookorder - this throws a 500 internal server exception but CircuitBreaker is not called. The error is:
[503] during [GET] to [http://billing-service/billing/preparebill] [BillingProxy#getBillingDone(Billing)]: [Load balancer does not contain an instance for the service billing-service]
Please note for testing purpose I'm not starting BillingService so the instance is not available for OrderService Feign to call.
Any insights will be appreciated.
Thanks.

The fallback method should pass the Exception parameter and return the same type as the original method:
public String hiFallback(Exception e) {
return "Hello Khushboo FallBack!";
}

Related

Feign ErrorDecoder is not invoked - how to configure feign to use it?

As i understand the decode() method of the feign ErrorDecoder will be called when a request responds with a status code != 2xx. Through debugging my tests i found out that the decode() method of my CustomErrorDecoder is not invoked on e.g. 504 or 404. I tried two ways to configure it:
Either include it as a Bean in the client configuration:
#Bean
public CustomErrorDecoder customErrorDecoder() {
return new CustomErrorDecoder();
}
or write it into the application configuration :
feign:
client:
config:
myCustomRestClientName:
retryer: com.a.b.some.package.CustomRetryer
errorDecoder: com.a.b.some.package.CustomErrorDecoder
Both ways don't invoke the ErrorDecoder. What am I doing wrong? The Bean is beeing instantiated and my CustomErrorDecoder looks like this:
#Component
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String s, Response response) {
Exception exception = defaultErrorDecoder.decode(s, response);
if (exception instanceof RetryableException) {
return exception;
}
if (response.status() == 504) {
// throwing new RetryableException to retry 504s
}
return exception;
}
}
Update:
I have created a minimal reproducible example in this git repo. Please look at the commit history to find 3 ways that I tried.
The problem is that your feign client uses feign.Response as the return type:
import feign.Param;
import feign.RequestLine;
import feign.Response;
public interface TestEngineRestClient {
#RequestLine(value = "GET /{uuid}")
Response getReport(#Param("uuid") String uuid);
}
In this case, Feign delegates its handling to the developer - e.g., you can retrieve HTTP status and a response body and do some stuff with it.
If interested, you can look at the source code of feign.SynchronousMethodHandler, executeAndDecode section.
To fix this, replace Response.class with the desired type in case of the correct response with status code = 2xx (probably some DTO class). I made a PR where I've changed it to String for simplicity.

SpringBoot: LoadBalancer [server]: Error choosing server for key default

I'm creating a load balance feature on my project in which I have three server that will simultaneously ping for 15 seconds. However, when I already run my client-side, it always goes to the fallback page and received an error of "LoadBalancer [server]: Error choosing server for key default" even if the servers are already running.
Here are the codes in my project:
app.properties
server.port=8788
server.ribbon.eureka.enabled=false
server.ribbon.listOfServers=localhost:8787,localhost:8789,localhost:8790
#every 15 seconds
server.ribbon.ServerListRefreshInterval=15000
client service (wherein it is my fallback method)
private LoadBalancerClient loadBalancer;
private RestTemplate restTemplate;
public ClientService(RestTemplate rest) {
this.restTemplate = rest;
}
#HystrixCommand(fallbackMethod = "reliable")
public String login() {
ServiceInstance instance = loadBalancer.choose("server");
URI uri = URI.create(String.format("http://%s:%s/admin/ping", instance.getHost(), instance.getPort()));
//URI uri = URI.create("http://localhost:8787/admin/ping");
return this.restTemplate.getForObject(uri, String.class);
}
MainController
public class MainController{
private final static Logger LOGGER = LoggerFactory.getLogger(MainController.class);
#Autowired
private ClientService clientService;
#LoadBalanced
#Bean
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
#Autowired
RestTemplate restTemplate;
...
Client client = new Client();
WebResource resource = client.resource("http://%s:%s/auth/loginvalidate");
ClientResponse response = resource.type(MediaType.APPLICATION_JSON)
.header("Authorization", "Basic " + encodePw)
.get(ClientResponse.class);
I got rid of that error by doing two things:
1) Add the following properties to the remote service:
management.endpoints.web.exposure.include: "*"
management.endpoint.health.enabled: "true"
management.endpoint.restart.enabled: "true"
management.endpoint.info.enabled: "true"
2) Make sure that there is a ping endpoint in the remote service:
public class MainController{
#RequestMapping("/")
public String ribbonPing() {
return this.hostName;
}
}
I added a few amendments to the example provided by Kubernetes Circuit Breaker & Load Balancer Example to test this scenario and put in here.
I suggest that you follow those links as a kind of "best practises" guide in order to build your Hystrix/Ribbon solution. Pay special attention to:
the starters/dependencies added to the pom files
the structure of the Java classes (how and where each bean is declared and injected)
how you configure your (micro-)services (in this case with K8s ConfigMaps)

Spring Feign client call enters exception block when it should stay in try block

Need some small help about Spring Feign client. So here is the situation,
I have 2 Spring boot services. Let’s say Service A and Service B. I have configured my Service A with Feign client through which I call the Service B method.
So here is the code for my Service A,
My FeignCleint config interface,
#FeignClient(name = "FeignClient", url = "http://localhost:8081/ServiceB/hello")
public interface FeignApi {
#RequestMapping(method = RequestMethod.GET)
ResponseEntity<?> hello();
}
And my rest controller that uses above feign config to call the Service B method,
#RestController
public class ApiController {
#Autowired
private FeignApi feignApi;
#RequestMapping(value = "/callServiceB")
public ResponseEntity<?> companyInfo() {
ResponseEntity<?> response = new ResponseEntity("OK Response", HttpStatus.OK);
try {
response = feignApi.hello();
// Code for some other things related to application.
return response;
} catch (Exception ex) {
System.out.println("Service A Exception block reached.");
return new ResponseEntity(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
}
below is my controller for Service B,
#RestController
public class MyController {
#GetMapping("/hello")
public String hello() throws Exception {
if (true) {
throw new Exception("Service B Exception...");
}
return "Hello World";
}
}
And my Controller advice to handle the exception that I am manually throwing,
#ControllerAdvice
public class MyControllerAdvice {
#ExceptionHandler
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<?> handleException(Exception exception, Model model) {
return new ResponseEntity<>("Caused due to : " + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Now my flow is like below,
As you can see, I am calling service B from service A using feign client. My service B is throwing an exception manually which I am catching using the controller advice and sending the exception details as an ResponseEntity back to the calling service A. So that Service A can process the details and move forward based on that.
The problem is when I hit the call from Service A using
http://localhost:8080/feign/callServiceB
The service B fails as expected. Now what I expect is that the Service A should receive the response back in form of the ResponseEntity. But what really happens is that the flow enters the exception block instead of staying in the try block. I can see this line printed,
"Service A Exception block reached."
This is what I don't understand. If I have managed the service B exception using controller advice and sent back the response to service A in form of ResponseEntity, then how come the flow of service A enters catch block. I expect it to stay in try block only as I want to process further based on the data.
Any idea, how can I get around this thing? Or is this how it will behave even when I am using controller advice to manage exceptions? What should be the expected behavior in this case?
Please advice.
By default Feign throws FeignException for any error situation.
Make use of fallback mechanism to handle failures.
#FeignClient(name = "FeignClient", url = "http://localhost:8081/ServiceB/hello", fallback= FeignApiFallback.class)
public interface FeignApi {
#RequestMapping(method = RequestMethod.GET)
ResponseEntity<?> hello();
}
#Component
class FeignApiFallback implements FeignApi {
#Override
public ResponseEntity<?> hello() {
//do more logic here
return ResponseEntity.ok().build();
}
}
make sure you add below property to wrap methods in hystrix commands in recent releases
feign.hystrix.enabled=true
Any status other than 200, feign client will consider it as an exception and you are setting HttpStatus.INTERNAL_SERVER_ERROR in your controller advice.
You can use custom ErrorDecoder
refer https://github.com/OpenFeign/feign/wiki/Custom-error-handling

Zuul proxy server throwing Internal Server Error when request takes more time to process

Zuul Proxy Error
I am getting this error when the request takes more time to process in the service.But Zuul returns response of Internal Server Error
Using zuul 2.0.0.RC2 release
As far as I understand, in case of a service not responding, a missing route, etc. you can setup the /error endpoint to deliver a custom response to the user.
For example:
#Controller
public class CustomErrorController implements ErrorController {
#RequestMapping(value = "/error", produces = "application/json")
public #ResponseBody
ResponseEntity error(HttpServletRequest request) {
// consider putting these in a try catch
Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
Throwable exception = (Throwable)request.getAttribute("javax.servlet.error.exception");
// maybe add some error logging here, e.g. original status code, exception, traceid, etc.
// consider a better error to the user here
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{'message':'some error happened', 'trace_id':'some-trace-id-here'}");
}
#Override
public String getErrorPath() {
return "/error";
}
}

Server sends INTERNAL_SERVER_ERROR, but client receives OK

I've got a method on the server which returns HttpStatus.INTERNAL_SERVER_ERROR but the client application always gets OK http status.
This is servers method (It's implified):
#RequestMapping(value="/example", method = RequestMethod.POST)
HttpStatus createSomething(Principal principal, #RequestBody #Valid SomeObject so) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
I'm sure that the right request is being made by cient application. Any ideas what migh cause the problem?
It won't work like that you should either:
return new ResponseEntity<String>(HttpStatus. INTERNAL_SERVER_ERROR)
or throw some custom exception with http status:
ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerException extends RuntimeException {
public InternalServerException (String message) {
super(message);
}
}

Resources