Detailed explanation of spring cloud zuul retry mechanism
brief introduction
The version of spring cloud in this article is (Dalston. Sr4). The details are as follows:
Turn on zuul's function
First, how to use spring cloud zuul to complete the routing and forwarding function is very simple. You only need to make the following preparations:
We want zuul and back-end application services to register on Eureka server at the same time. When we visit an address of zuul, we actually visit an address of the back-end application, so as to return a piece of content from this address and display it on the browser.
Registry (Eureka server)
To create an Eureka server, you only need to add @ enableeurekaserver to the main function and simply configure it in the properties file. The details are as follows:
Zuul
Add the @ enablezuulproxy annotation to the main function (because Eureka is integrated, you need to add another @ enablediscoveryclient annotation). And configure the properties file as follows:
application service
All three projects are started. When we visit localhost: 8081 / api-a / Hello, you will see that the output of the browser is hello spring
Understand zuul's forwarding through the source code
Next, let's understand what has been done inside the next forwarding through the source code level.
First, let's look at zuul's configuration class zuulproxyautoconfiguration. One of the tasks of this class is to initialize zuul's default built-in filter. One of the filters is very important, which is ribbonroutingfilter. It mainly completes the routing and forwarding of requests. Next, let's look at his run method
You can see that the forwarding method is forward. Let's check this method further. The details are as follows:
Omit some codes
Ribboncommandfactory refers to httpclientribboncommandfactory. This class is initialized in ribboncommandfactoryconfiguration (the loading action of triggering ribboncommandfactoryconfiguration uses the @ import tag on zuulproxyautoconfiguration class). The specific code is as follows:
After knowing the specific implementation class of the ribboncommandfactory (httpclientribboncommandfactory), let's take a look at what its create method does
According to my understanding, this method mainly does the following things:
Here, we clearly know that in the forward method of the ribbonroutingfilter class, ribboncommand = this ribbonCommandFactory. create(context); What does this line of code do
The next call is command execute(); Method. Through the analysis just now, we know that command actually refers to httpclientriboncommand. At the same time, we also know that httpclientriboncommand inherits hystrixcommand. Therefore, when executing command execute(); It actually executes the run method of httpclientriboncommand. Looking at the source code, we did not find the run method, but we found that httpclientribboncommand directly inherited abstractribboncommand. Therefore, the run method of abstractribboncommand is actually executed. Next, let's see what is done in the run method:
You can see that the executewithloadbalancer method of the client will be called in the run method. Through the above introduction, we know that the client refers to ribbonloadbalanceinghttpclient, and there is no executewithloadbalancer method in ribbonloadbalanceinghttpclient. (the executewithloadbalancer method of its parent class abstractloadbalancearawareclient will eventually be called.)
The specific codes are as follows:
The following is a detailed description of each piece of content:
First, getrequestspecificretryhandler (request, requestconfig); This method actually calls the getrequestspecificretryhandler method of ribbonloadbalancing httpclient, which mainly returns a requestspecificretryhandler
Next, create loadbalancercommand and take the requestspecificretryhandler obtained in the previous step as the parameter content.
Finally, call the submit method of LoadBalancerCommand. The content of this method is too long, so the specific code details will not be posted here. According to my personal understanding, only the corresponding pseudo code will be posted:
operation. The call () method will eventually call the execute method of ribbonloadbalanceinghttpclient. The contents of this method are as follows:
You can see that the above method is mainly used to assemble the request parameters (including various timeout times), then initiate the forwarding request, and finally obtain the corresponding results.
At this point, the basic principle of zuul forwarding a request is finished. Let's review the whole process again.
Is that really the case? When I saw the number of retries set for observable in the source code, I thought this was zuul's retry logic. Unfortunately, my idea is wrong. Remember the getrequestspecificretryhandler (request, requestconfig) I mentioned above; This method? (students who don't remember can look back). This method returns the requestspecificretryhandler class, and the first two parameters of the constructor are false when the class is created. (this is very important). The two parameters are oktoretryonconnecterrors and oktoretryonallerrors.
My original idea was that the request was packaged as observable. If there were exceptions or other exceptions due to timeout, the observable retry mechanism (rxjava) would be triggered. However, this is not the case. Why? The reason is the above two parameters. When a timeout exception occurs, the isretriableexception() method of requestspecificretryhandler will be called before triggering the retry mechanism. This method is used to judge whether to execute the retry action. The specific code is as follows:
Here, we have a general understanding of the basic principle of zuul forwarding a request. At the same time, it also verifies the fact that the logic of realizing zuul retry is not the observable retry mechanism. So here comes the question...? What makes zuul retry?
How to enable zuul's retry mechanism
Enabling zuul retry requires the following additional settings based on the original configuration:
The specific properties file contents are as follows:
In order to simulate the zuul retry function, the back-end application service needs to be transformed. The transformed contents are as follows:
By using thread Sleep (100000) reaches zuul forwarding timeout (zuul default connection timeout is less than 2S and read timeout is 5S), thus triggering zuul's retry function. At this time, when you visit localhost: 8081 / api-a / hello and check the application service background, you will find that "request is coming..." is finally printed three times
See the essence through the phenomenon. Next, briefly introduce the principle of zuul retry. First of all, if spring retry exists in your project classpath, zuul will not create ribbonloadbalancing httpclient during initialization, but create retryableribbonloadbalancing httpclient. The specific source code is as follows:
Therefore, when the request needs to be forwarded when it arrives (the executewithloadbalancer method in the abstractloadbalancerawareclient class will call abstractloadbalancerawareclient. This. Execute()) actually calls the execute method of retryableribbonloadbalanceinghttpclient (instead of the execute method of ribbonloadbalanceinghttpclient when there is no retry), the source code content is as follows:
The executewithretry method is as follows:
According to my understanding, the main logic is as follows:
So far, we not only know the overall process of zuul routing a request, but also clarify the principle of zuul triggering retry due to backend timeout. However, it seems that there is still a problem, that is, the timeout problem. As mentioned earlier, zuul packages the routing request process as a hystrixcommnd, but the timeout of hystrix is not set in my properties file (the default time is 1s), while the timeout of read is 5S (described in the source code section earlier). Some people will ask here, because the outermost layer uses hystrix, and hystrix has timed out at this time, why is it allowed to continue to use spring retry internally? With this question, I checked the issues on the official GitHub and found that someone had raised questions about this issue. The reply given by the author is that when hystrix times out, it will not interrupt the internal retry operation.
In fact, to be honest, I don't quite understand this content (probably because I don't know much about the source code of hystrix). With this question, I sent an email to the author. The email conversation is as follows:
My (English is not good, please forgive me):
I want to confirm two issues with you,First of all zuul retry only spring-retry exists and zuul. retry this parameter is true to take effect? The second problem is that if my classpath spring-retry at the same time I let zuul. retry this parameter is true,which means that at this time zuul have a retry mechanism,then why when Hystrix time-out can not interrupt the spring- retry it. thank you very much
Author's reply (key):
Zuul will retry Failed requests IF Spring Retry is on the classpath and the property zuul. retryable is set to true. The retry is happening within the hystrix command,so if hystrix times out than a response is returned. Right Now there is no mechanism to stop further retries from happening if hystrix times out before all the retries are exhauted. On Thu, Nov 162017 at 8:40 am Li Gang spring_ holy@163.com wrote:
Although it has been confirmed by the author, this part of the content has not been fully understood. Later, we will look at the source code of hystrix.
Edgware. Optimization of RC1 version
At Edgware In RC1 version, the author modified the code, instead of using the default value of ribbon, but assigned connecttimeout and readtimeout to 1s), and adjusted the timeout of hystrix to (2S) The details are as follows: https://github.com/spring-cloud/spring-cloud-netflix/pull/2261