Dubbo service concurrent communication principle and source code analysis

Dubbo uses netty for TCP communication by default. TCP is a transport layer protocol. At the application layer, custom protocols are often expanded. First, it can handle the sticking and unpacking of TCP itself, and second, it can agree on other details of the communication process.
So dubbo adopts the custom dubbo protocol by default. Document description:
Dubbo's default protocol uses a single long connection and NIO asynchronous communication, which is suitable for small data volume and large concurrent service calls, and the situation where the number of service consumer machines is much greater than the number of service provider machines.

The default protocol is based on netty3.2.5+hessian3.2.1 interaction.
Number of connections: Single connection
Connection method: Long connection
Transmission protocol: TCP
Transmission method: NIO asynchronous transmission
Serialization: Hessian binary serialization
Scope of application: Incoming and outgoing parameter data packets are small (recommended less than 100K), consumers are more than provided The number of providers is large, and a single consumer cannot fill the provider. Try not to use the dubbo protocol to transfer large files or super-large strings.
Applicable scenario: regular remote service method call

The processing of the byte stream in the dubbo protocol is in the com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec class, which includes the encoding and decoding of the request request and the encoding and decoding of the response response.
The dubbo protocol uses a fixed-length message header (16 bytes) and a variable-length message body for data transmission. The message header defines some information that the communication framework netty needs when processing IO threads. The specific message format of the dubbo protocol is as follows:
Detailed explanation of the message header of the dubbo protocol:
magic: similar to the magic number in the java bytecode file, used to determine whether it is a data packet of the dubbo protocol. The magic number is a constant 0xdabb
flag: flag bit, a total of 8 address bits. The lower four bits are used to indicate the type of serialization tool used for the message body data (default hessian). Among the upper four bits, the first bit is 1, which means it is a request request, and the second bit is 1, which means two-way transmission (that is, there is a return response) , and the third bit is 1, indicating that it is a heartbeat ping event.
status: status bit, set request response status, dubbo defines some response types. For specific types, see com.alibaba.dubbo.remoting.exchange.Response
invoke id: message id, long type. The unique identification id of each request (due to the use of asynchronous communication, it is used to match the request request with the returned response)
body length: the length of the message body, int type, that is, how many bytes the Body Content has.

Because a single long connection is used, if the consumer requests from multiple threads and the server returns after processing the message, it will cause message confusion. The idea of ​​solving this problem is similar to solving the sticky packet problem in socket.
Solution to the socket sticky packet problem: the most used one is actually to define a fixed-length data packet header, which contains the length of the complete data packet, so as to complete the server-side unpacking work.
Similarly, the way dubbo solves the above problems is to add a globally unique identifier id to the header, and the server should also carry this id when responding to the request, so as to provide clues for the client to receive the corresponding response data in multiple threads.

Next, let's directly analyze the source code:
in the HeaderExchangeChannel class, let's take a look at the request method

public ResponseFuture request(Object request, int timeout) throws RemotingException {
   if (closed) {
       throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
   }
   // create request.
   Request req = new Request();
   req.setVersion("2.0.0");
   req.setTwoWay(true);
   req.setData(request);

//这个future就是前面我们提到的:客户端并发请求线程阻塞的对象
   DefaultFuture future = new DefaultFuture(channel, req, timeout);
   try{
       channel.send(req);  //非阻塞调用
   }catch (RemotingException e) {
       future.cancel();
       throw e;
   }
   return future;
}

Pay attention to the ResponseFuture object returned by this method. The thread currently processing the client request will get the ResponseFuture object after a series of calls, and finally the thread will be blocked on the get() method call of this object, as follows:

public Object get(int timeout) throws RemotingException {
    if (timeout <= 0) {
        timeout = Constants.DEFAULT_TIMEOUT;
    }
    if (! isDone()) {
        long start = System.currentTimeMillis();
        lock.lock();
        try {
            while (! isDone()) {    //无限连
                done.await(timeout, TimeUnit.MILLISECONDS);
                if (isDone() || System.currentTimeMillis() - start > timeout) {
                    break;
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
        if (! isDone()) {
            throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
        }
    }
    return returnFromResponse();
}

I have seen above that the request thread has been blocked, so how is it awakened? Now let's take a closer look at the definition of the HeaderExchangeHandler class, first look at the received method it defines, the following is a code snippet:

public void received(Channel channel, Object message) throws RemotingException {
    channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
    ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
    try {
        if (message instanceof Request) {
          .....
        } else if (message instanceof Response) {   
            //这里就是作为消费者的dubbo客户端在接收到响应后,触发通知对应等待线程的起点
            handleResponse(channel, (Response) message);
        } else if (message instanceof String) {
           .....
        } else {
            handler.received(exchangeChannel, message);
        }
    } finally {
        HeaderExchangeChannel.removeChannelIfDisconnected(channel);
    }
}

We mainly look at the conditional branch in the middle, which is used to process the response message, that is to say, when the dubbo client receives the response from the server, it will execute this branch, and it simply calls the handleResponse method:

static void handleResponse(Channel channel, Response response) throws RemotingException {
    if (response != null && !response.isHeartbeat()) {  //排除心跳类型的响应
        DefaultFuture.received(channel, response);
    }
}

The DefaultFuture here implements the ResponseFuture interface type we mentioned above.
Continue to look at the implementation details of the DefaultFuture.received method:

public static void received(Channel channel, Response response) {
    try {
        DefaultFuture future = FUTURES.remove(response.getId());
        if (future != null) {
            future.doReceived(response);
        } else {
            logger.warn("The timeout response finally returned at " 
                        + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())) 
                        + ", response " + response 
                        + (channel == null ? "" : ", channel: " + channel.getLocalAddress() 
                            + " -> " + channel.getRemoteAddress()));
        }
    } finally {
        CHANNELS.remove(response.getId());
    }
}

Through the flag id mentioned above, DefaultFuture.FUTURES can get the specific DefaultFuture object, which is the object we mentioned above that blocks the request thread. After finding the target, call its doReceived method to wake up the corresponding thread to execute:

private void doReceived(Response res) {
    lock.lock();
    try {
        response = res;
        if (done != null) {
            done.signal();
        }
    } finally {
        lock.unlock();
    }
    if (callback != null) {
        invokeCallback(callback);
    }
}

The above source code is summarized into the following steps:
1. A thread of the consumer calls the remote interface to generate a unique ID (such as a random string, UUID, etc.). Dubbo uses AtomicLong to accumulate numbers from 0.
2. Encapsulate the method call information (such as the called interface name, method name, parameter value list, etc.) and the callback object callback (that is, the ResponseFuture object) of the processing result to form an object object. Put(ID, object) into the global ConcurrentHashMap that stores call information specially.
3. Encapsulate the ID and packaged method call information into an object connRequest, and send it asynchronously through Netty.
4. The current thread uses the get() method of callback to try to obtain the result returned from the remote. Inside get(), use lock.lock() to obtain the lock of the callback object callback, and then first check whether the result has been obtained, if not , and then call the wait() method of the callback to release the lock on the callback, so that the current thread is in a waiting state.
5. After the server receives and processes the request, it sends the result (the result contains the previous ID, that is, the return) to the client. The thread on the client socket connection that listens to the message receives the message, analyzes the result, and takes Get the ID, and then get(ID) from the previous ConcurrentHashMap to find the callback, and set the method call result to the callback object.
6. The listening thread then uses lock.lock() to acquire the lock of the callback object callback, and then calls the signal() (similar to notifyAll()) method to wake up the previously waiting thread to continue execution. So far, the whole process is over.

 

The article is from my blog:  http://www.iotjike.com/article/68

 

 

 

 

 

 

 

Guess you like

Origin blog.csdn.net/dreamer23/article/details/109512670