This is the 27th day of my participation in Gwen Challenge

Microservices have been hot for a while, and I, as a Web developer, of course, can’t help but want to study microservices, but the whole knowledge system of microservices is too large, and there are too many concepts and technologies to master, which is a bit overwhelming at the moment. Individuals don’t have a lot of time to make a living. However, it is still difficult to give up microservices. Recently, I learned the GO language. I want to eat microservices piece by piece, starting with GO and containers.

We know that microservices need to call and communicate with each other, and RPC is generally used to realize the call and communication between different languages and non-services. So I’ll start with RPC to learn about microservices.

Features of RPC framework

The characteristic is that it can meet those requirements, which RPC frameworks need to meet in order to achieve the above purposes

  • Serialization (GOB) is a single language

  • Context Management (timeout control)

  • Interceptors (authentication, statistics, and traffic limiting)

  • cross-language

  • The service registry

  • The Call ID: Since the client and the server run in different processes, a mapping table between functions and Call ids needs to be maintained at both ends so that the client and the server know which function is called. The client obtains the Call ID of the function according to the table, initiates a request, and the server executes the corresponding function according to the Call ID and returns the value to the client.

  • Serialization and deserialization: locally called functions run with arguments from the stack. In remote calls, if you need to call functions between different languages, you need to serialize the parameters and pass them to the server as a byte stream, and the server deserializes the parameters

  • Network transport: Calls between clients and servers are usually made over the network. You can use TCP or UDP regardless of the protocol, as long as you can transfer data. HTTP2 used by gRcp.

What is the RPC

RPC refers to remote procedure call, that is, two servers A and B, one application deployed on server A, want to call the function/method provided by the application on server B, because they do not have the same memory space, they cannot call directly, so they need to express the call semantics and transfer the call data over the network.

Why do YOU need RPC

Because RPC is a popular communication method among different nodes in distributed systems, RPC, like IPC, has become an indispensable infrastructure in the Internet era. The standard library for the Go language also provides a simple RPC implementation.

RPC invokes processes between services

From the above picture, this process is relatively clear and not difficult to understand.

  1. Invokes the client handle to execute the transfer parameter
  2. Calls the local system kernel to send network messages
  3. The message is sent to the remote machine
  4. The server handle gets the message and gets the parameters
  5. Executing remote procedures
  6. The execution procedure returns the result to the server handle
  7. The server handle returns the result, calling the remote system kernel
  8. The message is sent back to the local host
  9. The client handle receives the message from the kernel
  10. The client receives the data returned by the handle

With the above theoretical basis, we implement it based on theory.

type RPCService struct{}
Copy the code

Create an RPCService service and then register it

func (s *RPCService) Hello(request string, reply *string) error{
	*reply = "Hello " + request
	return nil
}
Copy the code
  • The function must be externally accessible and the function name must begin with a capital letter
  • The function needs to take two arguments
    1. The first parameter is the received parameter
    2. The second argument is the one returned to the client and needs to be of a pointer type
  • The function also requires an error return value
rpc.RegisterName("RPCService",new(RPCService))
Copy the code

Registering the RPC Service

listener, err := net.Listen("tcp".": 1234")
Copy the code

Example Create a TCP service port 1234 for the RPC service.

    conn, err := listener.Accept()

    iferr ! =nil{
        log.Fatal("Accept error:", err)
    }

    rpc.ServeConn(conn)
Copy the code

Server complete code

package main

import(
	// "fmt"
	"log"
	"net"
	"net/rpc"
)

type RPCService struct{}

func (s *RPCService) Hello(request string, reply *string) error{
	*reply = "Hello " + request
	return nil
}

func main(a) {
	rpc.RegisterName("RPCService".new(RPCService))

	listener, err := net.Listen("tcp".": 1234")
	iferr ! =nil{
		log.Fatal("ListenTCP error:",err)
	}

	conn, err := listener.Accept()

	iferr ! =nil{
		log.Fatal("Accept error:", err)
	}

	rpc.ServeConn(conn)
}
Copy the code

Client code

client, err := rpc.Dial("tcp"."localhost:1234")
Copy the code

The client invokes the RPC service and then invokes the specific RPC method via client.call.

err = client.Call("RPCService.Hello"."World",&reply)
Copy the code

When calling client.Call, the first argument is the dot linked RPC service name and method name, and the second and third arguments are the two arguments we define for the RPC method.


package main

import(
	"fmt"
	"log"
	"net/rpc"
)

func main(a) {
	client, err := rpc.Dial("tcp"."localhost:1234")

	iferr ! =nil{
		log.Fatal("dialing:", err)
	}

	var reply string
	err = client.Call("RPCService.Hello"."World",&reply)

	iferr ! =nil{
		log.Fatal("call Hello method of RPCService:",err)
	}

	fmt.Println(reply)
}
Copy the code

We start the server and then the client to see the following effect

Hello World
Copy the code

Let’s get a little more practical and write an HTTP-based RPC service that provides two number four operations.

rpcService := new(RPCService)
    rpc.Register(rpcService)
    rpc.HandleHTTP()
Copy the code

There’s a slightly different way to register, but it’s pretty much the same. Server complete code

package main

import(
	"errors"
	"fmt"
	"net/http"
	"net/rpc"
)

type Args struct{
	A, B int
}

type Quotient struct{
	Quo, Rem int
}

type RPCService int

func (t *RPCService) Add(args *Args, reply *int) error{
	*reply = args.A - args.B
	return nil
}

func (t *RPCService) Multiply(args *Args, reply *int) error{
	*reply = args.A * args.B
	return nil
}

func (t *RPCService) Divide(args *Args, quo *Quotient) error{
	if args.B == 0{
		return errors.New("divide by zero")
	}
	quo.Quo = args.A / args.B
	quo.Rem = args.A % args.B
	return nil
}


func main(a) {
	rpcService := new(RPCService)
	rpc.Register(rpcService)
	rpc.HandleHTTP()
	
	err := http.ListenAndServe(": 1234".nil)
	iferr ! =nil{
		fmt.Println(err.Error())
	}
}
Copy the code
package main

import(
	"fmt"
	"log"
	"net/rpc"
	"os"
)

type Args struct{
	A, B int
}

type Quotient struct{
	Quo, Rem int
}

func main(a)  {
	if len(os.Args) ! =2{
		fmt.Println("Usage: ", os.Args[0]."server")
		os.Exit(1)
	}
	serverAddress := os.Args[1]

	client, err := rpc.DialHTTP("tcp",serverAddress + ": 1234")
	iferr ! =nil {
		log.Fatal("dialing: ", err)
	}

	args := Args{17.8}
	var reply int
	err = client.Call("RPCService.Add",args, &reply)
	iferr ! =nil{
		log.Fatal("RPCService error: ", err)
	}
	fmt.Printf("RPCService: %d + %d = %d\n", args.A, args.B, &reply)

	var quot Quotient
	err = client.Call("RPCService.Divide",args, &quot)
	iferr ! =nil{
		log.Fatal("RPCService error: ",err)
	}

	fmt.Printf("RPCService: %d/%d=%d remainder %d\n",args.A, args.B, quot.Quo,quot.Rem)
}
Copy the code
RPCService: 17 + 8 = 824634312296
RPCService: 17/8=2 remainder 1
Copy the code

In fact, we need to modify it in the actual development, for example, RPC requests can get a context object, which contains user information, etc., and then the RPC can be timeout processing.

JSONRPC

The RPC framework built into the Go language already supports RPC services over Http. However, THE Http service uses the GOB protocol built in. Encoding is not JSON encoding and is not convenient for other languages to call. However, go provides RPC service support for JsonRPC, so let’s see how to do this in code.

Server code

package main

import(
	"errors"
	"fmt"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
	// "os"
)

type Args struct{
	A, B int
}

type Quotient struct{
	Quo, Rem int
}

type RPCService int
func (t *RPCService) Multiply(args *Args, reply *int) error{
	*reply = args.A * args.B
	return nil
}

func (t *RPCService) Divide(args *Args, quo *Quotient) error{
	if args.B == 0{
		return errors.New("divide by zero")
	}
	quo.Quo = args.A / args.B
	quo.Rem = args.A % args.B
	return nil
}

func main(a) {
	rpcService := new(RPCService)
	rpc.Register(rpcService)

	tcpAddr, err := net.ResolveTCPAddr("tcp".": 1234")
	checkError(err)
	

	listener, err := net.ListenTCP("tcp",tcpAddr)
	checkError(err)

	for{
		conn, err := listener.Accept()
		iferr ! =nil{
			continue
		}
		jsonrpc.ServeConn(conn)
	}


}

func checkError(err error){
	iferr ! =nil{
		fmt.Println("Fatal error ", err.Error())
	}
}



Copy the code

Client code

package main

import(
	"fmt"
	"log"
	"net/rpc/jsonrpc"
	"os"
)

type Args struct{
	A, B int
}

type Quotient struct{
	Quo, Rem int
}

func main(a)  {
	if len(os.Args) ! =2{
		fmt.Println("Usage: ", os.Args[0]."server")
		log.Fatal(1)
	}
	serverAddress := os.Args[1]

	client, err := jsonrpc.Dial("tcp",serverAddress + ": 1234")
	iferr ! =nil {
		log.Fatal("dialing: ", err)
	}

	args := Args{17.8}

	var quot Quotient
	err = client.Call("RPCService.Divide",args, &quot)
	iferr ! =nil{
		log.Fatal("RPCService error: ",err)
	}

	fmt.Printf("RPCService: %d/%d=%d remainder %d\n",args.A, args.B, quot.Quo,quot.Rem)
}
Copy the code

Context management

func (t *RPCService) Add(c contex.Context,args *Args, reply *int) error{
   *reply = args.A - args.B
   return nil
}
Copy the code

Timeout control

func (t *RPCService) Timeout(c contex.Context, args *RPCTimeout, reply *struct{}) error{
	log.Printf("Timeout: timeout=%s seq=%d\n",args.T, c.Seq())
	time.Sleep(args.T)
	return nil
}
Copy the code