This is why’s 64th original article


Drought cavity be out of tune

Hi, I’m Why, and welcome to the 64th installment of my weekly series of better original articles. Same old rule. We’ll talk about the rest.

The picture above is a jigsaw puzzle I put together earlier.

I do a lot of puzzles. I’ve probably done about 50 pieces of 1000 puzzle pieces, but the ones with letters or numbers on the back. The fastest time I’ve ever done a 1000 puzzle piece in a day.

But the one above, with only 800 pieces, is the hardest one I’ve ever made. Because there is no hint behind this, it can only be pieced together according to the color, decorative pattern and frame in front. It took me more than two weeks.

It’s all about torture.

But do you know what the pattern is called?

Altar city, where Buddha is said to live.

I first came to know this term in 2015 while watching the documentary The Third Pole in my dorm room.

There was a segment about monks painting altar city with specially collected colored sand for a festival. I was deeply touched by their concentration, piety and sincerity. When the magnificent altar city was finished, it was quietly waiting for the arrival of the festival.

It was expected that people would worship the altar city on the day of the festival, but in reality, at the beginning of the ceremony, people hold incense sticks and watch the monks quickly destroy the altar city with their broomsticks.

Haven’t had time to carefully appreciate the complex beautiful pattern, but with a broom sweep clean. The broom swept down that moment, my heart was a strong impact: can be hard to pick up, can easily put down.

The visual impact of that image is too big for me, the quality is still clean. So that I immediately firmly remember the word: altar city.

Later, I went to Beijing. Looking at the empty walls of my rented house in Beijing, I thought, “Why not build an altar city?

I watched the Third Pole again while I was working on it, and when I saw the destruction of the altar city, there was a barrage that said something like this:

All promising dharma, like a dream, like dew, like electricity, should be viewed as such.

This sentence comes from the vajra Prajna Brahmi Sutra, subtext 32, which should be changed into a non-true part. I have read the Diamond Sutra several times before when I read this sentence is very philosophical, but also vaguely understand. So I was very impressed.

When it again in such a form in front of my eyes, I immediately understand the philosophy of it, dare not say the full understanding, at least a little understanding.

Watching the destruction of altar city, the process of this colorful world changing and disappearing, I felt shocked, unfortunately, can not put down.

But the monk but wind light cloud light of say: all promising method, such as dream bubble, such as dew also such as electricity, should make such view.

The Documentary “The Third Pole”, 9.4 points on Douban, recommended.

All right, back to the article.

An interview question

Let’s cut to the chase: Dubbo has a server and a consumer. You know that?

You know there’s a thread pool on both the server side and the consumer side?

So here’s the interview question: generally, there are more service providers than service consumers. A service consumer may invoke multiple service providers concurrently, and each user thread will wait for a timeout after sending a request. Multiple service providers may do business at the same time and then return, and the thread pool of the service consumer receives multiple response objects. At this point, there is a question to consider: how can each response object in the thread pool be passed to the corresponding waiting user thread without error?

Answer first.

The question and the answer are actually on Dubbo’s website:

http://dubbo.apache.org/zh-cn/docs/source_code_guide/service-invoking-process.html

The following answers are from the official website:

The answer is to concatenate by calling numbers.

When DefaultFuture is created (and we’ll see what that is next), a Request object is passed in.

DefaultFuture can then get the call number from the Request object and store the < call number, DefaultFuture object > mapping into the static Map, FUTURES.

After receiving the Response object, the thread in the thread pool will fetch the corresponding DefaultFuture object from the FUTURES collection according to the call number in the Response object, and then set the Response object into the DefaultFuture object.

Finally, the user thread is woken up so that it can get the result of the call from the DefaultFuture object. The whole process is roughly as follows:


Above is the answer on the official website, write more clearly, but the official website is in the process of writing the service call incidentally explained the investigation point, the source code scattered everywhere, it looks more scattered, not too intuitive. Some readers reported that they were not particularly clear.

I know why you are not so clear about it. As I said in the previous article, you are just fucking on the official website and don’t do anything by yourself, which is exactly what you looked like when you read my article:


Well, anyway, I’m used to being whoremongered, so you can fuck with passion.


There are no secrets in the source code. Take you from the source to find the answer, so that you can put the official website answer and source corresponding, so that it is more convenient for you to do it yourself.

It should be noted that Dubbo source version of this article is 2.7.5. The official website documentation demonstrates the source version is 2.6.4. There are a few differences between the two versions, which I will highlight as I write.

The Demo presentation

Demo we can directly refer to the official quick start:

dubbo.apache.org/zh-cn/docs/user/quick-start.html

Here is a very simple server:


Clients consume in unit tests:


Yes, careful old friends must see, I have used this Demo many times. This Demo appears in almost every Dubbo article I write.

I suggest you spend 10 minutes building one yourself. It’s good for your study. Don’t be lazy, okay?

I’ll give you an address, and you can pull it down and run. I thought about that, too. Mainly to cure you of not wanting to do it yourself, and secondly, I’m too lazy to do it.


Ok, let’s run the above Demo:


The output was no surprise. And of course, everybody knows that the output has to look like this.

Well, then you’re gonna try it all out.

So let’s go back and simplify what we started with.

The initial problem is one service consumer, multiple service providers, and then the service provider returns the response data at the same time. What does the consumer do?

In fact, the core problem is that the service consumer has received multiple response data at the same time, how should it find the request corresponding to the response data, only correctly find the request, can correctly return data.

So we focus on the client side.

In the example above, parameters WHy1 and why2 are sent to the server almost simultaneously, and the server responds to both requests almost simultaneously.

What are the two client requests doing when the server does not return? Is the receiving data waiting inside the user thread?

The question is: how does Dubbo pair these two response objects with two user threads waiting to receive data?

Next, we will take this problem, to find the answer to the source code.


A request is initiated and a response is awaited

First, in the previous two sections we talked about client user threads waiting, a request waiting for a response.

How does this wait manifest in the code?

The answer lies in this method:

org.apache.dubbo.rpc.protocol.AsyncToSyncInvoker#invoke


First of all, if you look at the class name, AsyncToSyncInvoker, AsyncToSyncInvoker, AsyncToSyncInvoker, AsyncToSyncInvoker, AsyncToSyncInvoker.

The place marked ① is the invoker call and the return from the call is an AsyncRpcResult.

In this method continue down Debug, a few steps will take you to this location:

org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int, java.util.concurrent.ExecutorService)


Line 135 is channel.send(req). Before sending out the request, build a DefaultFuture. Then line 140 returns the future after the request has been sent.

The key secret lies in this newFuture on line 133.

Take a look at the corresponding code:


This newFuture does two things:

  • Initialize DefaultFuture.

  • Check whether timeout occurs.

Let’s see what happens when we initialize DefaultFuture:


First we see the FUTURES object here, which is a ConcurrentHashMap. This FUTURES is a static Map.


The key in the Map is the call number, which is the id obtained from the request in line 82:


This id is incremented from 0 by AtomicLong.

The code also includes a comment like this:

getAndIncrement() When it grows to MAX_VALUE, it will grow to MIN_VALUE, and the negative can be used as ID

It says that this method will become MIN_VALUE when called again after being incremented to MAX_VALUE. But it doesn’t matter, negative numbers can also be used as ids.

The DefaultFuture object is returned after it has been built.

Where to return?

This is the code boxed below in DubboInvoker’s doInvoker method:


At line 103, the wrapped DefaultFuture is placed in the AsyncRpcResult object via the constructor:


The result returned by DubboInvoker’s doInvoker method, AsyncRpcResult, is the result returned by DubboInvoker’s doInvoker method.


Now let’s move on to the number 2.

The first is to determine whether the current invocation pattern is a synchronous invocation. We’re doing synchronous calls here, and we’re getting into the logic of if judgment. The get method is called with a timeout.

Let’s see how the get method works:


You can see that the get method is not a simple asynchronous programming completableFuture.get. It also contains the logic for the waitAndDrain method of ThreadlessExecutor.

This method comes in as queue.take, which blocks the wait.

What’s in the queue?

There is only one place to put things in the queue globally:


The queue contains a runable task.

What is the task?

So let’s just buy a little bit of a mystery here and put it into the next section.

All you need to know is that if there are no tasks in the queue, the user thread will keep blocking and waiting at take.


Some people may ask: how can there be a blocking infinite wait? Don’t interface calls have timeouts?

Notice, this is not an infinite wait. Dubbo ensures that when an interface times out or not, a Runable task is thrown to the queue. So take this is the maximum amount of time that you’re waiting in timeout.

So with that in mind, I’m going to show you the logic of timeout detection.

Now that we have a little bit of a connection to the answers on our website, let me give you a quick overview of what we have:

First: The user thread calls the following method in the AsyncToSyncInvoker class and is waiting for the result. The code corresponds to the description on the official website as follows:


The get method of different DefaultFuture objects will be called to wait for the DefaultFuture object. This should be done in 2.6.x.

In version 2.7.5, this is done in the GET method of the AsyncRpcResult object. In this case, the queue’s take method is called, blocking the wait.

Waiting on these two different objects are two completely different implementations. This was also done in version 2.7.5 to create a shared thread pool for clients. The implementation of a lot of elegant, we can take two versions of the code to compare, to understand his design ideas feel really wonderful ah.

However, regardless of the version, the request still needs to wait in the user thread after it is sent.

Second point: a DefaultFuture object is built before sending the Request object. A static MAP is maintained inside this object:


There is a mapping between the call number and the DefaultFuture object. So when we get the Response, we get the call number from the Response, and we know which DefaultFuture this call number corresponds to.

But wait. “Get this call number out of Response,” isn’t that an accident that we have to send the call number to the server? Where was it sent?

The answer is in the protocol. Remember when we talked about the protocol in the last article there was a call number in there as well?


Did you get the response?

The header of each request and response contains a request number, which is specified by the protocol.

Encode before sending request:

org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#encodeRequest


And then Dubbo takes this very gently with the request for the requestId.

Guess what?


Just waiting for a response.

Receive the response and look for the request

Making a request is a simple matter.

But came back as a response and was stunned. A response comes back, can’t find who initiated it, do you think it’s uncomfortable? If you feel uncomfortable, you won’t be afraid that it will come back with a random request, leaving you stunned on the spot.

Where do you say response messages are processed?

As mentioned in the last article, those who don’t know are fake fans:

org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#decodeBody


You can see line 66 of the door code screenshot: get Request ID.

Where to get it?

From the header.

Where did the request number in the header come from?

When a request is made, it is fetched from the request object and written to the protocol.

Where does the request number come from in the Request object?

Incrementing from zero by AtomicLong.

Okay, so we know where this ID came from, and we got it. Where is it used?

org.apache.dubbo.remoting.exchange.support.DefaultFuture#received(org.apache.dubbo.remoting.Channel, org.apache.dubbo.remoting.exchange.Response, boolean)


The place marked ① is removed from the MAP of FUTURES based on the ID in response, i.e. the call number, and gets the corresponding request.

If the received request is NULL, a timeout has occurred.

If the request is not null, the timeout is determined. We’ll talk about the timeout logic at the end.

Labeled ② is the time to return the response to the corresponding user thread.

Reactive programming is used in doReceived:


This here is the current class, DefaultFuture.

So how did this doReceived method get here?

As mentioned in the previous article, Dubbo’s default dispatch policy is ALL, so ALL responses are dispatched to the client thread pool, which is this location:

When a response is received from the server, the response event is also thrown into the thread pool, which is a Runable task, as you can see from the code.

We then execute the execute method, which echoes the requests in the previous section.

Remember our request called queue.take and blocked?

And here we’re adding tasks to the queue.


There is a task in the queue! The user thread waiting in the block comes alive!

How will the user thread execute next?

Look at the code:


The run method of the task is executed after the task is fetched. Note that the run method does not start a new thread.

And what is that mission?


Is ChannelEventRunnable. Take a look at the run method overridden by this task:


Isn’t that a coincidence? Isn’t that a coincidence?


Last week’s article also covered this approach.

The handler.received method eventually calls the doReceived method we talked about earlier:


Closed loop complete.

So when the user thread has finished executing the Runable task, proceed:


The Result returned here is the final data returned by the server, or the returned exception.

Now if you go back and look at the picture on the website, it should make sense:


Timeout checks

When I said newFuture, one of the things it does is check if it timed out. The principle is simple:


First, there is a TimeoutCheckTask class, which is a task to be executed.


When triggered, it will fetch DefaultFuture from FUTURES based on the call number.

As I said earlier, if a future is completed properly, it will be removed from the FUTURES.

So if the request has not timed out, the Future has not been fetched according to the number or the Future is in the done state, then the request has not timed out.

If the Future is still in the Future, it means that it is time for you to be in it. Call notifyTimeout. The notifyTimeout parameter is given to true:


The received method has only two calls globally, one after the normal return and one after the timeout:


That is, the received method will eventually be called anyway, and that method will eventually be used to remove the DefaultFuture object with the corresponding call number from the FUTURE MAP.

How does the above mission trigger?

Dubbo has his own HashedWheelTimer. What is this thing?

Time round scheduling algorithm ah:


You make a request and do not return a result within a specified time, so cancel (future.cancel) the request.

Isn’t this a requirement similar to ordering something, not paying for it in 30 minutes, and then the platform automatically cancels the order for you?

The time wheel will solve your problem. As mentioned in this post: “Are you afraid of a time wheel hanging around the neck of a guard dog?”

Here is a 2.7.5 tip about checking Dubbo timeout.


Verify the number

I’ve been emphasizing that this call number is important.


So just to make it a little bit more intuitive, let me just draw a little graph to show you that this number really does run through the request and the response.

First, change our server:


If the name passed in is the specified parameter (why-debug), it is returned directly. Otherwise sleep for 10 seconds to keep the client user thread waiting for a response.

The client is modified as follows:


40 consecutive requests are sent to the server, which takes 10 seconds to complete.

Then a specific request occurs to the server, which can be returned even if it happens. And put a break on line 39.

First, take a look at the call number in DefaultFuture.

What is the call number of the current debug request?

Is it number 40 (numbered from 0)?

To verify this:


So in the header, where the request is sent, set the call number to 40:


Then see if the corresponding call number is 40 when the response comes back:


Thus, a call number concatenates the request and response. Let the request have a response, so that the response must find out which request originated.


If you find something wrong, you can put it up in the comments section and I will correct it.

Thank you for reading, I insist on original, very welcome and thank you for your attention.

I am Why, a literary creator delayed by code. I am not a big shot, but I like sharing. I am a nice man in Sichuan who is warm and interesting.