Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to solve the problems caused by improper use of RestTemplate

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains "how to solve the problems caused by improper use of RestTemplate". The content of the explanation is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "how to solve the problems caused by improper use of RestTemplate".

Background

System: Web application developed by SpringBoot

ORM: JPA (Hibernate)

Interface function description: according to the entity class ID to query the entity information in the database, and then use RestTemplate to call the external system interface to obtain data.

Problem phenomenon

Browser pages sometimes report 504 GateWay Timeout errors, but after refreshing many times, it is always timeout

Database connection pool reports connection exhaustion exception

There is a Bad GateWay error when calling an external system.

Analysis process

For ease of description, this system is called An and the external system is called B.

These three problems are interlinked, and the trigger is the third problem, which leads to the second problem, and finally leads to the third problem.

Brief description of the reason: the third problem is that the system did not hang up system B under the Nginx load, resulting in an error in the request for external system Times 502, and A did not handle the exception correctly, resulting in the http request could not be closed normally, while springboot opened openSessionInView by default, and the database connection was closed only when the request for calling A was closed, but at this time the request for calling A was not closed, resulting in the database connection not closed.

This paper mainly analyzes the first question: why the request A connection appears 504 Timeout.

AbstractConnPool

Through the log, you can see that An is blocked when calling B until timeout, and print out the thread stack to view:

Thread blocking in the getPoolEntryBlocking method of the AbstractConnPool class

Private E getPoolEntryBlocking (final T route, final Object state, final long timeout, final TimeUnit timeUnit, final Future future) throws IOException, InterruptedException, TimeoutException {Date deadline = null; if (timeout > 0) {deadline = new Date (System.currentTimeMillis () + timeUnit.toMillis (timeout));} this.lock.lock () Try {/ / get the connection pool corresponding to route according to route final RouteSpecificPool pool = getPool (route); E entry; for (;;) {Asserts.check (! this.isShutDown, "Connection pool shut down"); for ( ) {/ / get available connections entry = pool.getFree (state); if (entry = = null) {break } / / determine whether the connection expires, close if it expires and remove if (entry.isExpired (System.currentTimeMillis () {entry.close () from the collection of available connections. } if (entry.isClosed ()) {this.available.remove (entry); pool.free (entry, false);} else {break }} / / if available connections are obtained from the connection pool, update the collection of available and unreleased connections if (entry! = null) {this.available.remove (entry); this.leased.add (entry); onReuse (entry) Return entry;} / / if no connection is available, create a new connection final int maxPerRoute = getMax (route) / / before creating a new connection, check whether the size of each route connection pool is exceeded, and if so, delete the appropriate number of connections in the available connection set (removed from the total set of available connections and the set of available connections for each route) final int excess = Math.max (0, pool.getAllocatedCount () + 1-maxPerRoute) If (excess > 0) {for (int I = 0; I)

< excess; i++) { final E lastUsed = pool.getLastUsed(); if (lastUsed == null) { break; } lastUsed.close(); this.available.remove(lastUsed); pool.remove(lastUsed); } } if (pool.getAllocatedCount() < maxPerRoute) { //比较总的可用连接数量与总的可用连接集合大小,释放多余的连接资源 final int totalUsed = this.leased.size(); final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0); if (freeCapacity >

0) {final int totalAvailable = this.available.size (); if (totalAvailable > freeCapacity-1) {if (! this.available.isEmpty ()) {final E lastUsed = this.available.removeLast (); lastUsed.close () Final RouteSpecificPool otherpool = getPool (lastUsed.getRoute ()); otherpool.remove (lastUsed);}} / / where the connection is actually created final C conn = this.connFactory.create (route) Entry = pool.add (conn); this.leased.add (entry); return entry }} / / if the connection pool size for each route has been exceeded, join the queue to be woken up when a connection is available or until a certain end time boolean success = false Try {if (future.isCancelled ()) {throw new InterruptedException ("Operation interrupted");} pool.queue (future); this.pending.add (future) If (deadline! = null) {success = this.condition.awaitUntil (deadline);} else {this.condition.await (); success = true } if (future.isCancelled ()) {throw new InterruptedException ("Operation interrupted");}} finally {/ / if it comes to the end time or is awakened, get out of the queue and join the next cycle pool.unqueue (future) This.pending.remove (future);} / / handles exception wake-up and timeout situations if (! success & & (deadline! = null & & deadline.getTime () 0) {if (delegate instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate) StreamingOutputMessage.setBody (new StreamingHttpOutputMessage.Body () {@ Override public void writeTo (final OutputStream outputStream) throws IOException {StreamUtils.copy (body, outputStream);}});} else {StreamUtils.copy (body, delegate.getBody ());}} return delegate.execute ();}}

The execute method of InterceptingClientHttpRequest, which first executes the interceptor and finally executes the real request object (what is the real request object? See the interceptor design section below).

Take a look at the configuration of RestTemplate:

RestTemplateBuilder builder = new RestTemplateBuilder (); return builder .setConnectTimeout (customConfig.getRest (). GetConnectTimeOut ()) .setReadTimeout (customConfig.getRest (). GetReadTimeout ()) .manufacturers (restTemplateLogInterceptor) .errorHandler (new ThrowErrorHandler ()) .build ();}

You can see that connection timeouts, read timeouts, interceptors, and error handlers are configured.

Look at the implementation of the interceptor:

Public ClientHttpResponse intercept (HttpRequest httpRequest, byte [] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {/ / print pre-access log ClientHttpResponse execute = clientHttpRequestExecution.execute (httpRequest, bytes); if (if the return code is not 200) {/ / throw a custom runtime exception} / / print post-access log return execute;}

You can see that when the return code is not 200, an exception is thrown. Remember the doExecute method in RestTemplate. If an exception is thrown here, the finally code in the doExecute method will be executed, but since the returned response is null (there is actually a response), response is not closed, so you cannot throw an exception here. If you really want to throw an exception, you can throw it in the error handler errorHandler to ensure that response can return and close normally.

The RestTemplate source code part parses how to decide which underlying http framework to use

Knowing why, let's take a look at when RestTemplate decides which http framework to use. It's actually decided when you instantiate a RestTemplate object through RestTemplateBuilder.

Take a look at RestTemplateBuilder's build method

Public RestTemplate build () {return build (RestTemplate.class);} public T build (Class restTemplateClass) {return configure (BeanUtils.instantiate (restTemplateClass));}

You can see that after instantiating the RestTemplate object, configure it. You can specify requestFactory or auto-probe

Public T configure (T restTemplate) {/ / configure requestFactory configureRequestFactory (restTemplate);. Omit other irrelevant codes} private void configureRequestFactory (RestTemplate restTemplate) {ClientHttpRequestFactory requestFactory = null; if (this.requestFactory! = null) {requestFactory = this.requestFactory;} else if (this.detectRequestFactory) {requestFactory = detectRequestFactory ();} if (requestFactory! = null) {ClientHttpRequestFactory unwrappedRequestFactory = unwrapRequestFactoryIfNecessary (requestFactory); for (RequestFactoryCustomizer customizer: this.requestFactoryCustomizers) {customizer.customize (unwrappedRequestFactory);} restTemplate.setRequestFactory (requestFactory);}}

Take a look at the detectRequestFactory method

Private ClientHttpRequestFactory detectRequestFactory () {for (Map.Entry candidate: REQUEST_FACTORY_CANDIDATES .entrySet ()) {ClassLoader classLoader = getClass (). GetClassLoader (); if (ClassUtils.isPresent (candidate.getKey (), classLoader)) {Class factoryClass = ClassUtils.resolveClassName (candidate.getValue (), classLoader); ClientHttpRequestFactory requestFactory = (ClientHttpRequestFactory) BeanUtils .instantiate (factoryClass); initializeIfNecessary (requestFactory); return requestFactory;}} return new SimpleClientHttpRequestFactory ();}

Loop the REQUEST_FACTORY_CANDIDATES collection to check if the corresponding jar package exists in the classpath classpath, and if so, create a wrapper class object for the corresponding framework. If none exists, the RequestFactory object implemented using JDK is returned.

Take a look at the REQUEST_FACTORY_CANDIDATES collection

Private static final Map REQUEST_FACTORY_CANDIDATES; static {Map candidates = new LinkedHashMap (); candidates.put ("org.apache.http.client.HttpClient", "org.springframework.http.client.HttpComponentsClientHttpRequestFactory"); candidates.put ("okhttp3.OkHttpClient", "org.springframework.http.client.OkHttp3ClientHttpRequestFactory"); candidates.put ("com.squareup.okhttp.OkHttpClient", "org.springframework.http.client.OkHttpClientHttpRequestFactory") Candidates.put ("io.netty.channel.EventLoopGroup", "org.springframework.http.client.Netty4ClientHttpRequestFactory"); REQUEST_FACTORY_CANDIDATES = Collections.unmodifiableMap (candidates);}

You can see that there are four ways to implement Http calls, which can be specified when configuring RestTemplate and provide the corresponding implementation jar package in the classpath.

Design of Request interceptor

Take another look at the execute method of the InterceptingRequestExecution class.

Public ClientHttpResponse execute (HttpRequest request, final byte [] body) throws IOException {/ / if there is an interceptor, execute the interceptor and return the result if (this.iterator.hasNext ()) {ClientHttpRequestInterceptor nextInterceptor = this.iterator.next (); return nextInterceptor.intercept (request, body, this) } else {/ / if there is no interceptor, create a request object through requestFactory and execute ClientHttpRequest delegate = requestFactory.createRequest (request.getURI (), request.getMethod ()); for (Map.Entry entry: request.getHeaders (). EntrySet ()) {List values = entry.getValue (); for (String value: values) {delegate.getHeaders (). Add (entry.getKey (), value) } if (body.length > 0) {if (delegate instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate; streamingOutputMessage.setBody (new StreamingHttpOutputMessage.Body () {@ Override public void writeTo (final OutputStream outputStream) throws IOException {StreamUtils.copy (body, outputStream);}});} else {StreamUtils.copy (body, delegate.getBody ());}} return delegate.execute () }}

You may wonder, the incoming object is already a request object, so why create a request object again when there is no interceptor?

In fact, the incoming request object is an InterceptingClientHttpRequest object when there is an interceptor, and when there is no interceptor, it is directly the Request that wraps each http call implementation box. Such as HttpComponentsClientHttpRequest, OkHttp3ClientHttpRequest and so on. When there is an interceptor, the interceptor is executed, there can be multiple interceptors, and here this.iterator.hasNext () is not a loop, why? The secret lies in the interceptor's intercept method.

ClientHttpResponse intercept (HttpRequest request, byte [] body, ClientHttpRequestExecution execution) throws IOException

This method contains request,body,execution. The exection type is ClientHttpRequestExecution interface, and the above InterceptingRequestExecution implements this interface, so that when calling the interceptor, pass in the exection object itself, and then call the execute method again to determine whether there is still an interceptor. If so, execute the next interceptor, regenerate all interceptors into real request objects, and execute the http call.

What if there's no interceptor?

It is already known that RestTemplate instantiates RequestFactory when instantiating. When initiating a http request, it executes the doExecute method of restTemplate, in which Request is created, and in the createRequest method, the RequestFactory is first obtained.

/ / org.springframework.http.client.support.HttpAccessorprotected ClientHttpRequest createRequest (URI url, HttpMethod method) throws IOException {ClientHttpRequest request = getRequestFactory (). CreateRequest (url, method); if (logger.isDebugEnabled ()) {logger.debug ("Created" + method.name () + "request for\" + url + "\");} return request;} / / org.springframework.http.client.support.InterceptingHttpAccessorpublic ClientHttpRequestFactory getRequestFactory () {ClientHttpRequestFactory delegate = super.getRequestFactory () If (! CollectionUtils.isEmpty (getInterceptors () {return new InterceptingClientHttpRequestFactory (delegate, getInterceptors ());} else {return delegate;}}

If you look at the relationship between RestTemplate and these two classes, you can see the invocation relationship.

After obtaining the RequestFactory, determine whether there is an interceptor, and if so, create an InterceptingClientHttpRequestFactory object. When the RequestFactory is createRequest, it will create an InterceptingClientHttpRequest object, so that you can execute the interceptor first, and finally create a real Request object to execute the http call.

Connection acquisition logic flow chart

Take HttpComponents as the underlying Http call to implement the logic flow chart.

The flowchart shows:

RestTemplate can instantiate the corresponding RequestFactory based on configuration, including apache httpComponents, OkHttp3, Netty, and so on.

The class that RestTemplate connects with HttpComponents is HttpClient, which is provided by apache httpComponents to the user and performs http calls. HttpClient is instantiated through HttpClientBuilder when the RequestFactory object is created, and HttpClientConnectionManager and multiple ClientExecChain,HttpRequestExecutor, HttpProcessor, and some policies are instantiated when the HttpClient object is instantiated.

When a request is initiated, the httpRequest is instantiated by the requestFactory, and then the ClientexecChain is executed in turn. There are four common types:

RedirectExec: request a jump; determine the address of the next jump based on the result of the last response and the jump policy. The maximum number of redirects is 50 times by default.

RetryExec: determines whether the request with an Igamo error will be executed again

ProtocolExec: populate the necessary http request header, process http response header, update session state

MainClientExec: the last node in the request execution chain; gets the connection from the connection pool CPool, executes the request call, and returns the request result

PoolingHttpClientConnectionManager is used to manage connection pooling, including connection pooling initialization, connection acquisition, connection acquisition, connection opening, connection release, connection pooling and other operations.

CPool stands for connection pooling, but connections are not stored in CPool; CPool maintains three sets of connection states: leased (leased, that is, to be released) / available (available) / pending (waiting), which records the status of all connections; and maintains the connection pool RouteSpecificPool for each Route

RouteSpecificPool is the real place where connections are stored, and three sets of connection states are also maintained internally, but only connections that belong to this route are recorded.

HttpComponents divides connections into connection pools according to route, which facilitates resource isolation so that each route request does not affect each other.

Thank you for your reading, the above is the content of "how to solve the problems caused by the improper use of RestTemplate". After the study of this article, I believe you have a deeper understanding of how to solve the problems caused by the improper use of RestTemplate. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report