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.
- Invokes the client handle to execute the transfer parameter
- Calls the local system kernel to send network messages
- The message is sent to the remote machine
- The server handle gets the message and gets the parameters
- Executing remote procedures
- The execution procedure returns the result to the server handle
- The server handle returns the result, calling the remote system kernel
- The message is sent back to the local host
- The client handle receives the message from the kernel
- 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
- The first parameter is the received parameter
- 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, ")
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, ")
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