2.微服务--RPC
发表于:2024-11-24 作者:热门IT资讯网编辑
编辑最后更新 2024年11月24日,1.RPC简介1.远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。2.该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用
1.RPC简介
1.远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。2.该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。3.如果涉及的软件采用面向对象编程,那么远程过程调用也可称为远程调用或远程方法调用。
2.流行的RPC框架的对比
3.golang中如何实现RPC
Golang中实现RPC比较简单,官方提供了封装好的库,还有第三方库。官方库:net/rpc1.net/rpc库使用encoding/gob进行编码2.支持tcp和http数据传输方式3.由于其他语言不支持gob编解码方式,所以golang的RPC只支持golang开发的服务端与客户端的交互官方库:net/jsonrpc1.jsonrpc采用JSON进行数据编解码,因而支持跨语言调用。2.jsonrpc库是基于tcp协议实现的,暂不支持http传输方式。golang的RPC必须符合四个条件才可以:1.结构体字段首字母要大写,要跨域访问,所以大写。2.函数名必须首字母大写。3.函数第一个参数是接收参数,第二个参数是返回给客户端参数,必须是指针类型。4.函数必须有一个返回值err。
3.1示例1
golang实现RPC程序,实现求矩形面积和周长
//rpcClient/server.gopackage mainimport ( "log" "net/http" "net/rpc")//服务端,求举行面积和周长//声明矩形对象type Rect struct {}//声明参数结构体,字段首字母大写type Param struct { Width,Height int}//golang的RPC必须符合4个条件才可以//◼ 结构体字段首字母要大写,要跨域访问,所以大写//◼ 函数名必须首字母大写(可以序列号导出的)//◼ 函数第一个参数是接收参数,第二个参数是返回给客户端参数,必须是指针类型//◼ 函数必须有一个返回值error//定义求矩形面积的方法func (r Rect) Area(p Param,ret *int) error{ *ret = p.Width * p.Height return nil}//定义求矩形周长的方法func (r Rect) Perimeter(p Param,ret *int) error{ *ret = (p.Width + p.Height)*2 return nil}func main() { //1.注册服务 rect := new(Rect) rpc.Register(rect) //2.把服务处理绑定到http协议上 rpc.HandleHTTP() //3.监听服务,等待客户端调用请求面积和周长的方法 err := http.ListenAndServe(":8080",nil) if err != nil{ log.Fatal(err) }}
//rpcServer/client.gopackage mainimport ( "fmt" "log" "net/rpc")//声明参数结构体,字段首字母大写type Params struct { Width,Height int}//调用服务func main() { //1.链接远程RPC服务 rp,err := rpc.DialHTTP("tcp","127.0.0.1:8080") if err != nil{ log.Fatal(err) } //2.调用远程方法 //定义接收服务端传回来的计算结果的变量 ret := 0 //求矩形面积 err2 := rp.Call("Rect.Area",Params{50,10},&ret) if err2 != nil{ log.Fatal(err2) } fmt.Println("面积:",ret) //求矩形周长 err3 := rp.Call("Rect.Perimeter",Params{50,10},&ret) if err3 != nil{ log.Fatal(err3) } fmt.Println("周长:",ret)}
3.2示例2
模仿前面例题,自己实现 RPC 程序,服务端接收 2 个参数,可以做乘法运算,也可以做商和余数的运算,客户端进行传参和访问,得到结果如下: 9*2 = 18 9/2,商=4,余数=1 下面使用net/rpc/jsonrpc库通过json格式编码解码,支持跨语言调用
rpcServer/server.gopackage mainimport ( "errors" "fmt" "log" "net" "net/rpc" "net/rpc/jsonrpc")//结构体,用于注册的type Arith struct {}//声明参数结构体type ArithRequest struct { A,B int}//返回给客户端的结果type ArithResponse struct { //乘积 Pro int //商 Quo int //余数 Rem int}//乘法func (a *Arith) Multiply(req ArithRequest,res *ArithResponse) error { res.Pro = req.A * req.B return nil}//商和余数func (a *Arith) Divide(req ArithRequest,res *ArithResponse) error { if req.B == 0{ return errors.New("除数不能为0") } //商 res.Quo = req.A/req.B //余数 res.Rem = req.A%req.B return nil}func main() { rpc.Register(new(Arith)) lis,err := net.Listen("tcp",":8080") if err != nil{ log.Fatal(err) } //循环监听服务 for{ conn,err := lis.Accept() if err != nil{ continue } go func(conn net.Conn) { fmt.Println("new client") jsonrpc.ServeConn(conn) }(conn) }}
rpcClient/client.gopackage mainimport ( "fmt" "log" "net/rpc/jsonrpc")//声明参数结构体type ArithRequest struct { A,B int}//返回给客户端的结果type ArithResponse struct { //乘积 Pro int //商 Quo int //余数 Rem int}//调用服务func main() { conn,err := jsonrpc.Dial("tcp",":8080") if err != nil{ log.Fatal(err) } req := ArithRequest{9,2} var res ArithResponse err2 := conn.Call("Arith.Multiply", req, &res) if err2 != nil { log.Fatal(err2) } fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro) err3 := conn.Call("Arith.Divide", req, &res) if err3 != nil { log.Fatal(err3) } fmt.Printf("%d / %d 商 %d,余数 = %d\n", req.A, req.B, res.Quo, res.Rem)}
3.3RPC调用流程
1.微服务架构下数据交互一般是对内RPC,对外REST。2.将业务按功能模块拆分到各个微服务,具有提高项目协作效率、降低模块耦合度、提高系统可用性等优点,但是开发门槛比较高,比如RPC框架的时候,后期的服务监控等工作。3.一般情况下,我们会将功能代码在本地直接调用,微服务架构下,我们需要将这个函数作为单独的服务运行,客户端通过网络调用。
3.5自己实现RPC调用库(类似于net/rpc、net/rpc/jsonrpc)
3.5.1网络传输数据格式
成熟的RPC框架会有自定义传输协议,这里网络传输格式定义如下:前面是固定长度消息头,后面是变长消息体。
//会话中读写数据测试//rpc/session.gopackage rpcimport ( "encoding/binary" "io" "net")//测试网络中读写数据的情况//会话连接的结构体type Session struct { conn net.Conn}//构造方法func NewSession(conn net.Conn) *Session { return &Session{conn:conn}}//向连接中写数据func (s *Session) Write(data []byte) error { //定义写数据的格式 //4字节头部+可变体的长度 buf := make([]byte,4+len(data)) //写入头部,记录数据长度 binary.BigEndian.PutUint32(buf[:4],uint32(len(data))) //将整个数据,放在索引4的后面 copy(buf[4:],data) _,err := s.conn.Write(buf) if err != nil{ return err } return nil}//从连接中读数据func (s *Session) Read() ([]byte,error) { //读取头部记录的长度 header := make([]byte, 4) //按长度读取头部消息 _,err := io.ReadFull(s.conn, header) if err != nil{ return nil,err } //根据头部信息,读取数据长度 dataLen := binary.BigEndian.Uint32(header) data := make([]byte,dataLen) //读取到的数据存到data中 _,err = io.ReadFull(s.conn, data) if err != nil{ return nil,err } return data,nil}
//rpc/session_test.gopackage rpcimport ( "fmt" "net" "sync" "testing")func TestSession_ReadWriter(t *testing.T) { //定义地址 addr := "127.0.0.1:8000" my_data := "hello lili" wg := sync.WaitGroup{} wg.Add(2) //写数据的协成 go func() { defer wg.Done() lis,err := net.Listen("tcp", addr) if err != nil{ t.Fatal(err) } conn,_ := lis.Accept() s:= NewSession(conn) err = s.Write([]byte(my_data)) if err != nil{ t.Fatal(err) } }() //读数据的协成 go func() { defer wg.Done() conn,err := net.Dial("tcp", addr) if err != nil{ t.Fatal(err) } s:= NewSession(conn) data,err := s.Read() if err!= nil{} t.Fatal(err) //最后一层校验 if string(data) != my_data{ t.Fatal(err) } fmt.Println("读取到的数据为:",string(data)) }()}
测试结果: rpc go test -v === RUN TestSession_ReadWriter--- PASS: TestSession_ReadWriter (0.00s)PASSok _/Users/tongchao/Desktop/gopath/src/test/rpc 0.008s➜ rpc
3.5.2gob编码/解码
//codec.gopackage rpcimport ( "bytes" "encoding/gob")//定义RPC交互的数据结构type RPCData struct { //访问的函数 Name string //访问时的参数 Args []interface{}}//编码func encode(data RPCData) ([]byte, error) { //得到字节数组的编码器 var buf bytes.Buffer bufEnc := gob.NewEncoder(&buf) //编码器对数据编码 if err := bufEnc.Encode(data); err != nil{ return nil,err } return buf.Bytes(),nil}//解码func decode(b []byte) (RPCData,error) { buf := bytes.NewBuffer(b) //得到字节数组的解码器 bufDec := gob.NewDecoder(buf) //解码器对数据解码 var data RPCData if err:= bufDec.Decode(&data);err != nil{ return data,err } return data,nil}
//codec_test.gopackage rpcimport ( "fmt" "testing")func TestCode(t *testing.T) { args := make([]interface{},0) data := RPCData{ Name: "lili", Args: args, } encodeData,err := encode(data) if err != nil{ fmt.Println(err) } fmt.Printf("encodeData:%v\n",encodeData) decodeData,err := decode(encodeData) if err != nil{ fmt.Println(err) } fmt.Printf("decodeData:%v\n",decodeData)}结果:=== RUN TestCodeencodeData:[40 255 129 3 1 1 7 82 80 67 68 97 116 97 1 255 130 0 1 2 1 4 78 97 109 101 1 12 0 1 4 65 114 103 115 1 255 132 0 0 0 28 255 131 2 1 1 14 91 93 105 110 116 101 114 102 97 99 101 32 123 125 1 255 132 0 1 16 0 0 9 255 130 1 4 108 105 108 105 0]decodeData:{lili []}--- PASS: TestCode (0.00s)PASSProcess finished with exit code 0