Search code examples
microservicesjhipstergatewayservice-discovery

JHipster microservice gateway temporarily throws HTTP 500 error when one service instance is terminating


We have a standard JHipster setup consisting of a gateway and one microservice application. JHipster-Registry is used for service discovery. We made no changes of any kind to the code or the config that was generated by the JHipster generator.

Everything works as expected. However, when shutting down one of the microservice application instances (SIGINT, ^C on CLI, Kubernetes pod termination), we temporarily receive HTTP 500 internal server errors for requests to microservice endpoints through the gateway (http://gateway/service/...).

Please find below a stacktrace from the HTTP 500 error in the gateway:

Caused by: org.apache.http.NoHttpResponseException: 172.16.94.61:8085 failed to respond
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:141)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
    at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
    at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
    at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:165)
    at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
    at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
    at org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient$1.doWithRetry(RetryableRibbonLoadBalancingHttpClient.java:93)
    at org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient$1.doWithRetry(RetryableRibbonLoadBalancingHttpClient.java:71)
    at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287)
    at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:164)
    at org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient.executeWithRetry(RetryableRibbonLoadBalancingHttpClient.java:113)
    at org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient.execute(RetryableRibbonLoadBalancingHttpClient.java:104)
    at org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient.execute(RetryableRibbonLoadBalancingHttpClient.java:50)
    at com.netflix.client.AbstractLoadBalancerAwareClient$1.call(AbstractLoadBalancerAwareClient.java:109)
    at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:303)
    at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:287)
    at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:231)
    at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:228)
    at rx.Observable.unsafeSubscribe(Observable.java:10211)
    at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.drain(OnSubscribeConcatMap.java:286)
    at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.onNext(OnSubscribeConcatMap.java:144)
    at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:185)
    at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180)
    at rx.Observable.unsafeSubscribe(Observable.java:10211)
    at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94)
    at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
    at rx.Observable.subscribe(Observable.java:10307)
    at rx.Observable.subscribe(Observable.java:10274)
    at rx.observables.BlockingObservable.blockForSingle(BlockingObservable.java:445)

It looks like the gateway's load balancer still uses the microservice instance which is terminating.

We also tried Consul instead of JHipster-Registry/Eureka and found the same behaviour.

Steps to reproduce:

  • Start one registry, one gateway and two microservice instances (using gradlew)
  • Wait and verify that everything is up and running (call http://gateway/service/...)
  • Terminate one microservice instance
  • Relaod http://gateway/service/... for a couple of times
    • After a few seconds HTTP 500 errors will occur
    • After a few more seconds errors will disappear and the remaining microservice receives all traffic

Since we are using an out-of-the-box JHipster setup, we were pretty confused by not finding any articles addressing this issue on the Internet.

Does anybody experience the same problem, have an idea what's wrong here or how to solve this problem? We tried pretty long to find the cause and a solution but we feel like we are stuck here.

If you need more information (logs, configs, traces, etc.) we will be happy to post it here.


Solution

  • You can enable spring-retry (included in pom.xml in a default JHipster gateway) by setting zuul.retryable: true in application.yml. Then any failed GET requests will be attempted again. If you also want other HTTP methods such as POST to be attempted more than once, set ribbon.OkToRetryOnAllOperations: true

    http://cloud.spring.io/spring-cloud-static/Dalston.SR1/#retrying-failed-requests