RPC学习笔记
RPC学习笔记
概念详解
-
rpc 远程调用需要解决的问题:
1. 服务寻址
我们怎么告诉远程机器我们要调用哪个方法,而不是其他方法?
在本地调用中,函数体直接通过函数指针指定,我们调用queryById(int id)方法,编译器自动帮我们调用它对应的函数指针。
但是在远程调用中,函数指针不行的,因为两个进程地址空间完全不一样。
所以,在RPC中,所有的函数都必须有自己的一个id,这个id在所有进程中都是唯一确定的。
客户端在做远程调用时,必须附上该id。
我们还需要在A服务和B服务分别维护一个(函数和call id)的映射表,两者的表不一定要完全相同,但是相同的函数对应的call id必须相同。
当A服务即客户端需要远程调用时,它就查一下该表,找出对应的call id,然后把它传给B服务即服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行函数代码。
2. 序列化和反序列化(可逆的编解码方法)
A服务需要把本地参数传给B服务远程函数,本地调用的过程中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。
但是在远程调用过程中,A服务跟B服务是不同的进程,不是一个内存空间,不能通过内存来传递参数,因此需要A服务将参数转化为字节流传给B服务,然后B服务将字节流转化为自身能读取的格式,是一个序列化和反序列化的过程。
序列化可以简单理解为对象 –> 字节的过程,同理,反序列化则是相反的过程。这一过程的目的可以理解为转义,然后方便传输。
3. 网络传输(TCP、UDP、HTTP)
数据准备好以后,网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端。
因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。
-
一次完整的 rpc 调用流程(同步调用,异步另说)如下:
1. client调用以本地调用方式调用服务;
2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
3. client stub找到服务地址,并将消息发送到服务端;
4. server stub收到消息后进行解码;
5. server stub根据解码结果调用本地的服务;
6. 本地服务执行并将结果返回给server stub;
7. server stub将返回结果打包成消息并发送至客户端;
8. client stub接收到消息,并进行解码;
9. 客户端得到最终结果。
一、基于Gob协议的rpc调用(TCP)
server模块
package main
import (
"log"
"net"
"net/rpc"
)
func main() {
//1.实例化server
listener, err := net.Listen("tcp", ":8005") //监听套接字
if err != nil {
log.Fatal("listenTCP error :", err)
}
//2.注册微服务
rpc.RegisterName("HelloService", new(HelloService))
//3.启动服务
for {
conn, err := listener.Accept() //新的请求进来,接收socket套接字
if err != nil {
log.Fatal("Accept error :", err)
}
rpc.ServeConn(conn)
}
}
type HelloService struct{}
func (s *HelloService) Hello(request string, reply *string) error {
*reply = "hello" + request
return nil
}
client模块
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
//1.建立连接
client, err := rpc.Dial("tcp", "localhost:8005")
if err != nil {
log.Fatal("dialing:", err)
}
//2.调用
var reply string
err = client.Call("HelloService.Hello", "康少爷", &reply)
if err != nil {
log.Fatal("client.Call:", err)
}
fmt.Println(reply)
}
如何将Gob协议替换为常见序列化协议使得go语言rpc可以跨语言调用?
二、基于json协议的rpc调用(TCP)
server模块
package main
import (
"errors"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
func main() {
//1.实例化server
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal("listen error:", err)
}
//2.微服务注册
rpc.RegisterName("MathService", new(MathService))
//3.启动微服务(for循环保证长连接)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
type MathService struct {
}
type Args struct {
Arg1, Arg2 int //字段首字母必须大写
}
//“加”微服务
func (this *MathService) Add(args Args, reply *int) error {
*reply = args.Arg1 + args.Arg2
return nil
}
//“减”微服务
func (this *MathService) Sub(args Args, reply *int) error {
if args.Arg1 >= args.Arg2 {
*reply = args.Arg1 - args.Arg2
} else {
*reply = args.Arg2 - args.Arg1
}
return nil
}
//“乘”微服务
func (this *MathService) Mul(args Args, reply *int) error {
*reply = args.Arg1 * args.Arg2
return nil
}
//“除”微服务
func (this *MathService) Div(args Args, reply *int) error {
if args.Arg2 == 0 {
return errors.New("divide by zero")
}
*reply = args.Arg1 / args.Arg2
return nil
}
client模块
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
func main() {
conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal("can't not connect to 127.0.0.1:8080")
}
var reply int
var args = Args{15, 3}
err = conn.Call("MathService.Add", args, &reply)
if err != nil {
log.Fatal("call MathService.Add error:", err)
}
fmt.Printf("MathService.Add(%d,%d)=%d\n", args.Arg1, args.Arg2, reply)
err = conn.Call("MathService.Sub", args, &reply)
if err != nil {
log.Fatal("call MathService.Sub error:", err)
}
fmt.Printf("MathService.Sub(%d,%d)=%d\n", args.Arg1, args.Arg2, reply)
err = conn.Call("MathService.Mul", args, &reply)
if err != nil {
log.Fatal("call MathService.Mul error:", err)
}
fmt.Printf("MathService.Mul(%d,%d)=%d\n", args.Arg1, args.Arg2, reply)
err = conn.Call("MathService.Div", args, &reply)
if err != nil {
log.Fatal("call MathService.Div error:", err)
}
fmt.Printf("MathService.Div(%d,%d)=%d\n", args.Arg1, args.Arg2, reply)
}
type Args struct {
Arg1, Arg2 int
}
三、基于HTTP协议的rpc调用(HTTP)
package main
import (
"io"
"net/http"
"net/rpc"
"net/rpc/jsonrpc"
)
func main() {
rpc.RegisterName("HelloService.Hello", new(HelloService))
http.HandleFunc("/plus", func(writer http.ResponseWriter, request *http.Request) {
var conn io.ReadWriteCloser = struct {
io.Writer
io.ReadCloser
}{
ReadCloser: request.Body,
Writer: writer,
}
rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
})
http.ListenAndServe(":8080", nil)
}
type HelloService struct{}
func (s *HelloService) Hello(request string, reply *string) error {
*reply = "hello" + request
return nil
}
四、改造基于Gob协议的rpc调用(TCP)代码 —— 模拟生成grpc代码
① Client端接口封装:
- 新建handler文件用于封装HelloService:
package handler
//可以解决微服务名称冲突问题
const HelloServiceName = "handler/HelloService"
- 新建client_proxy文件封装rpc.Dial接口:
package client_proxy
import (
"handler"
"net/rpc"
)
type HelloServiceStub struct {
*rpc.Client
}
// Hello 封装client.Call方法
func (c *HelloServiceStub) Hello(request string, reply *string) error {
err := c.Call(handler.HelloServiceName+".Hello", request, reply)
if err != nil {
return err
}
return nil
}
// NewHelloServiceStub 封装rpc.Dial(network, address string) (*Client, error) {...}方法
func NewHelloServiceStub(network, address string) HelloServiceStub {
conn, err := rpc.Dial(network, address)
if err != nil {
panic("rpc.Dial error!")
}
return HelloServiceStub{conn}
}
修改后代码展示
client模块
package main
import (
"client_proxy"
"fmt"
"log"
)
func main() {
//1.建立连接
client := client_proxy.NewHelloServiceStub("tcp", "localhost:8005")
//2.调用
var reply string
err := client.Hello("康少爷", &reply)
if err != nil {
log.Fatal("client.Call:", err)
}
fmt.Println(reply)
}
② Server端接口封装:
- 将微服务HelloService放入handler文件区分业务逻辑
package handler
//可以解决微服务名称冲突问题
const HelloServiceName = "handler/HelloService"
type HelloService struct{}
func (s *HelloService) Hello(request string, reply *string) error {
*reply = "hello" + request
return nil
}
- 新建server_proxy文件封装rpc.RegisteName方法
package server_proxy
import (
"handler"
"net/rpc"
)
type HelloServicer interface {
Hello(request string, reply *string) error
}
func RegisterHelloService(name HelloServicer) error {
return rpc.RegisterName(handler.HelloServiceName, name)
}
修改后代码展示
server模块
package main
import (
"handler"
"log"
"net"
"net/rpc"
"server_proxy"
)
func main() {
//1.实例化server
listener, err := net.Listen("tcp", ":8005") //监听套接字
if err != nil {
log.Fatal("listenTCP error :", err)
}
//2.注册微服务
server_proxy.RegisterHelloService(&handler.HelloService{})
//3.启动服务
for {
conn, err := listener.Accept() //新的请求进来,接收socket套接字
if err != nil {
log.Fatal("Accept error :", err)
}
rpc.ServeConn(conn)
}
}
五、总结
-
client代理和server代理代码 写起来比较麻烦,能否自动生成?
答:可以,使用 protobuf + grpc 框架即可做到
-
有没有对应的框架?
答:rpc框架:grpc、dubbo