Go RPC Inside (client)

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

The Go Language standard library can bring an RPC framework or is very force, this can greatly reduce the write back-end network communication service threshold, especially in large-scale distributed systems, RPC is basically a cross-machine communication standard. RPC minimizes network detail, allowing developers to focus on the development of service functions. The following describes the client internal implementation of the Go Language RPC framework.

The logic of the Go RPC client is very simple, in general, is to send a call request serialized after the atom sent to the server, and then there is a dedicated gorutine waiting for the server to answer, this goroutine will receive each reply to the corresponding request, this completes the RPC call.

Call Portal

func NewClient(conn io.ReadWriteCloser) *Clientfunc (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error

Using the RPC client, first newclient a client object and then executes the remote server method through the call method of the client object. It is important to note that the Newclient function not only creates a client object, but also creates an input goroutine. Input goroutine is blocking the connection and waiting for a response from the server. The main task of the call method here is to send an RPC request to the server, while putting in a wait queue, waiting for the response of the servers to arrive.

Send Request

After the request of RPC submitted by the client call method, the following send method is sent to the server, which focuses on the implementation of send.

The func (client *client) Send (call *call) {////entry has a lock, which is clearly intended to ensure that a request can be written to the socket by an atom. After all, one//RPC connection is written by multiple goroutine concurrently, So here we need to ensure the atomicity of the sending request. This pending queue needs to be write-protected concurrently. Register this call.client.mutex.Lock () if Client.shutdown | | client.closing {call. The Error = ErrShutdownclient.mutex.Unlock () call.done () return}////Pending queue is actually implemented using map, where you can see that each RPC request will survive a single incrementing seq, This//SEQ is used to mark the request, which is like the SEQ of the TCP packet. SEQ: = Client.seqclient.seq++client.pending[seq] = Callclient.mutex.Unlock ()////The following is all the data (request method name, SEQ, request parameters, etc.) that will be requested by an RPC The serialization is packaged and then sent//out. The main use of the Go standard library comes with the GOB algorithm for the serialization of requests. Encode and send the REQUEST.CLIENT.REQUEST.SEQ = Seqclient.request.ServiceMethod = call. Servicemethoderr: = Client.codec.WriteRequest (&client.request, call. Args) If err! = Nil {Client.mutex.Lock () call = Client.pending[seq]delete (client.pending, seq) client.mutex.Unlock () if Call! = Nil {call. Error = Errcall.done ()}}}

Through the analysis of the sending process, it can be seen that an RPC request issued to the main three processes, the first process is to put the request into the waiting queue, the second process is serialization, the last is to write to the socket.

The most important thing about the send process is the two locks + seq. The atomic write and concurrent requests for multiplexing connections are the basis for implementing the RPC framework.

Read answer

As mentioned earlier, when creating an RPC client object, an input goroutine is started to wait for the server to respond, and the following input method is all the work done by this goroutine.

Func (client *client) input () {var err errorvar response response////Here The For loop is permanently responsible for this connection's response read, and only exits if an error occurs on the connection. for err = = Nil {////First is the read response header, the response header generally has a very important information is the length of the body data, with this length information, only know//read how much text is a reply completed. Response = Response{}err = Client.codec.ReadResponseHeader (&response) if err! = Nil {break}////Here is a very important step, From the response head to the SEQ, this SEQ is the client-generated SEQ, in the above send//process sent to the server, the server answer, it must be the SEQ response to the client. Only in this way can the client//know that the response is the one that corresponds to the request in the pending queue. Here the pending queue yoke is protected, and the corresponding request call object is extracted through seq. SEQ: = Response. Seqclient.mutex.Lock () Call: = Client.pending[seq]delete (client.pending, seq) client.mutex.Unlock ()//// This switch is mostly about handling exceptions and reading the response body normally. Abnormal situation, the English explanation is very detailed. Switch {case call = = nil://We ' ve got no pending call. That usually means that//writerequest partially failed, and call was already//removed; Response is a server telling us about an//error reading request body. We should still attempt//to read the error body, but there ' s no one to give it to.err = Client.codec.ReadResponseBody (nil) if Err! = Nil {err = errors. New ("ReAding Error Body: "+ err. Error ())}case response. Error! = ""://We ' ve got an error response. Give the request;//any subsequent requests would get the readresponsebody//error if there is One.call.Error = Ser Vererror (response. Error) Err = client.codec.ReadResponseBody (nil) if err! = Nil {err = errors. New ("Reading error body:" + err.) Error ())}call.done () default:////begins reading the body of the response, and the body is placed in the reply of the call. Err = Client.codec.ReadResponseBody (call. Reply) If err! = Nil {call. Error = errors. New ("reading body" + err.) Error ())}call.done ()}}////The following section of the code is a cleanup of errors in handling connections, as well as shutting down connections on the server. This part is important,//may cause some calls to RPC goroutine permanent blocking wait, can not resume work. Terminate pending Calls.client.sending.Lock () Client.mutex.Lock () Client.shutdown = trueclosing: = Client.closingif Err = = Io. EOF {if closing {err = Errshutdown} else {err = io. Errunexpectedeof}}for _, Call: = Range Client.pending {call. Error = Errcall.done ()}client.mutex.unlock () Client.sending.Unlock () if err! = Io. EOF &&!closing {log. Println ("Rpc:client ProtocOL error: ", err)}} 

No matter how many goroutine in the concurrent write an RPC connection, waiting to read the answer on the connection goroutine only one, the goroutine is responsible for reading all the responses on the connection, and then distributing the response to the corresponding request, also completed an RPC request.

Custom Codecs

type ClientCodec interface {WriteRequest(*Request, interface{}) errorReadResponseHeader(*Response) errorReadResponseBody(interface{}) errorClose() error}

CLIENTCODEC defines the interface of the codec, which means that if you want to customize a codec, you just need to implement this interface.

Writerequest does is to serialize the RPC request's remote method name, parameters, and so on, and then write it into the socket. You can simply pack a request to send it according to your own ideas. The two read interface reads the response packet from the socket, and then uses the corresponding algorithm to resolve the packet in reverse sequence.

Go RPC If the codec cannot be customized, it can only be used in the context where both the client and the server are in go language development. This greatly reduces its flexibility. The ability to customize the codec can theoretically be used to implement other RPC protocols, such as: Thrift. GitHub's Go-thrift project is indeed the thrift protocol implemented via a custom codec on the go RPC basis.

Timeout

Call calls do not provide a timeout mechanism, what if the server responds too slowly? Death down? It's not reasonable. Some say you can implement the call method by itself without using the call provided by the RPC package by default. The code is as follows:

func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {select {case call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done:return call.Errorcase <-time.After(5*time.Second):}return errors.New("timeout")}

The above code does make the RPC call 5s not get a response, and then timeout to give up waiting for the answer.

However, there is another potential risk involved in this, namely memory leaks. Because, client. The Go method constructs a Call object, which is actually a request, and the request is sent out to a pending queue, which has already been analyzed. By analyzing the input goroutine process, you can see that if the server does not respond, the call object will remain in the pending queue and will never be deleted. Of course, this situation indicates a problem with our server design.

To implement an RPC client, the most important thing is to connect multiplexing, which means that one connection needs to run multiple requests concurrently. Go RPC uses two locks to complete this thing, and this place is likely to be a point of impact performance. After the server implementation analysis, let's try to test the performance of RPC to see exactly how.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.