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)
	}
}

五、总结

  1. client代理和server代理代码 写起来比较麻烦,能否自动生成?

    答:可以,使用 protobuf + grpc 框架即可做到

  2. 有没有对应的框架?

    答:rpc框架:grpc、dubbo

全部评论

相关推荐

Natrium_:这时间我以为飞机票
点赞 评论 收藏
分享
吃不饱的肱二头肌很想退休:tnnd 我以为选妹子呢,亏我兴高采烈的冲进来😠
投递快手等公司10个岗位
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务