关于Go语言微服务的入门使用之RPC部分

该博客主要是关于微服务中RPC的使用。

关于RPC入门

1、前言

关于微服务在很多文章都有更加具体的介绍了,我在这篇文章就不进行详细介绍了。而在了解微服务前不得不先从RPC开始介绍。

RPC为Remote Procedure Call的简写,即远程过程调用。而RPC协议作为一种通信技术大部分用于微服务的架构之中。因此在正式学习微服务之前先进行RPC的入门学习。

2、什么是RPC

与自己在电脑上编程不同,当我们在公司编程时很多函数都不像在自己电脑上那样可以直接本地调用,很多都是部署在不同的地方,这些服务大都需要进行远程调用,RPC就是为了更加方便解决这个调用服务的一种技术。

通过RPC协议可以通过传递函数名、函数参数的方式来从远程服务中得到返回的值。其中每个服务都被封装成进程,彼此独立。而只要遵循RPC协议,进程与进程间也可以使用不同的语言实现通信。

3、RPC使用步骤

RPC协议实现远程调用的过程与一般的socket通信很相似:

3.1 Socket 中:

Server端

  • 建立监听
//监听客户端发送的内容
listener, error := net.Listener("tcp", address)
  • 建立连接 Accept() (Conn, error)
conn, err := listener.Accpet()//得到conn的套接字
  • 开始读数据 Read(b []byte) (n int, err error)
//将读的内容创建一个buf来进行接收
b := make([]byte, 1024)
//Read会将内容写入到b中,return的值为read到的内容的长度
n, err := conn.Read(b) 
  • 作出响应 Write(b []byte) (n int, err error)
//例如将收到的内容变成大写
data := strings.ToUpper(string(b[:n]))
n, err = conn.Write([]byte(data))
  • 调用Close进行关闭 conn.Close/listener.Close

Client端:

Client端操作和Server端是相对应的

  • 建立拨号 net.Dial()
  • 向服务端写内容 conn.Write()
  • 读取服务端响应的内容 conn.Read()
  • 调用Close进行关闭 defer conn.Close()

以上。

RPC的大体步骤与一般的socket通信差不多,但主要是完成远程函数调用,而不是数据传递,所以会存在一些差别。

3.2 RPC中:

RPC使用同样分为服务端和用户端

Server端:

  • 注册RPC服务对象,给对象绑定方法

    rpc.RegisterName("服务名", 回调对象)

    第一步就是需要给自己的服务注册一个名字方便被调用,同时需要传入一个回调对象,例如有一个People的结构体,给People的结构体绑定一个叫SayHello的方法,注册时在RegisterName中需要传入一个new(SayHello),并且给这个服务取名,如“people”,在Client端进行调用的时候需要使用 服务名.方法名 的方式来进行远程调用。具体的内容在后面还会再进行补充。

  • 创建监听器

    net.Listen()

  • 建立连接

    listener.Accept()

  • 将连接绑定RPC服务

    rpc.ServeConn(conn)

    将该建立的conn与RPC进行绑定

Client端:

  • 拨号,连接服务器

    rpc.Dial(network, address)

    使用rpc服务进行拨号

  • 调用远程函数

    client.Call(服务方法名, 传入参数, reply)

    远程调用Server端的服务

4、关于RPC的使用细节

4.1 注册rpc服务

相关函数:

func RegisterName(name string, rcvr interface{}) error

传入参数:

  1. 服务名;

  2. 该服务对应的rpc对象

    其中该对象绑定的方法需要满足几个条件:
    1、该方法必须是导出的,就是在包外可见
    2、方法必须有两个参数,这两个参数必须是导出类型(如struct、map、slice等)、内建类型(int、string等)
    3、方法的第二个参数(即传出参数)必须是指针类型
    4、方法主要一个 error 接口类型的返回值
    如:
    //rpc对象
    type People struct {
    }
    //该对象需要绑定的方法
    func (this *People) HelloPeople(args string, res *string) error {
    }
    //注册
    rpc.RegisterName(“服务名”, new(People))

4.2 绑定rpc服务

相关函数:

func ServeConn(conn io.ReadWriteCloser)

传入参数:

  1. 一个ReadWriteCloser

type ReadWriteCloser interface {
Reader
Writer
Closer
}
其中建立了socket连接的conn就可以作为一个ReadWriteCloser
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
……
}

4.3 调用远程服务

相关函数:

func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error

传入参数:

  1. 服务名.方法名 作为一个字符串传入
  2. 传入的数据
  3. 用以接受返回值的参数,一般用var &变量名 定义

5、相关代码

为了自己以后方便回过头来看自己以前的内容,把代码和注释一起放在这里好了,这里是根据步骤进行代码的编写,在后面会继续进行改进

Server端:

package main

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

//定义一个rpc对象
type People struct{

}

//给People对象添加方法
//其中args和res都为内置类型,res为指针
func (this *People) SayHello(args string, res *string) error{
	*res = args + "\t say hello"
	return nil
}

func main() {
	//注册RPC服务,绑定对象方法
	err := rpc.RegisterName("sayhello", new(People))
	if err != nil{
		fmt.Println("hello 注册失败, err = ", err)
		return
	}

	//设置监听,假设端口号是8800
	listener, err := net.Listen("tcp", "127.0.0.1:8800")
	if err != nil{
		fmt.Println("net.listen error, err = ", err)
		return
	}
	defer listener.Close()
	//建立连接
	conn, err := listener.Accept()
	if err != nil{
		fmt.Println("listener.Accept error, err = ", err)
		return
	}
	defer conn.Close()
	fmt.Println("建立连接")
	//绑定服务
	rpc.ServeConn(conn)
}

Client端:

package main

import (
	"fmt"
	"net/rpc"
)

func main() {
	//用rpc连接服务器
	cli, err := rpc.Dial("tcp", "127.0.0.1:8800")
	if err != nil{
		fmt.Println("Dial error, err = ", err)
		return
	}

	defer cli.Close()

	var res string
	//调用远程函数
	if err = cli.Call("sayhello.SayHello", "xyz", &res); err!= nil{
		fmt.Println("Conn.Call error, err = ", err)
		return
	}

	fmt.Println(res)
}

6、使用jsonrpc进行改进

由于直接使用rpc的时候没有序列化,在进行数据传输的时候可能会出现乱码,因此使用jsonrpc对源代码进行改进:

在绑定服务时使用jsonrpc代替rpc:

jsonrpc.ServeConn(conn)

在客户端拨号时使用jsonrpc代替

cli, err :=jsonrpc.Dial("tcp", "127.0.0.1:8800")

7、封装

如注册函数时rpc绑定的方法需要遵守一定的规则,倘若绑定的方法形式与给定的规则不同则会在运行时报错。为了方便对传入的这些方法或者其他数据进行验证,可以将这些对象进行封装。

封装可以用接口来实现,可以创建一个MyInterface的接口类型,在该接口中定义方法的原型。

type MyInterface interface{
	//定义SayHello的方法原型,该方法需要满足
	//传入参数为两个
	//第二个参数为指针
	//返回值只有一个error
	
	SayHello(string, *string) error
	
}

封装注册服务方法

func RegisterMethod(i MyInterface){
	rpc.Register("hello", i)
}

原本调用远程函数时在客户端使用Call来进行,也对其进行封装。

type MyClient struct{
	//rpc.Client提供了send、call等方法
	r *rpc.Client
}

func (client *MyClient) SayHello(args string, res *string) error {
	return this.r.Call("hello.SayHello", args, res)
}

初始化MyClient

func InitClient(addr string) MyClient{
	conn, _ := jsonrpc.Dial("tcp", addr)
	return MyClient{r:conn}
}

之后对原来的Server端代码进行修改,将原来的注册函数使用RegisterMethod来代替,Client端也直接使用MyClient绑定的方法进行Call

修改后:

Server端:

package main

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

type People struct{

}
}

func (this *Peopel) SayHello(args string, res *string) error{
	*res = args + "\t say hello"
	return nil
}

func main() {
	//注册RPC服务,绑定对象方法
	RegisterService(new(People))
	//设置监听
	listener, err := net.Listen("tcp", "127.0.0.1:8800")
	if err != nil{
		fmt.Println("net.listen error, err = ", err)
		return
	}
	defer listener.Close()
	//建立连接
	conn, err := listener.Accept()
	if err != nil{
		fmt.Println("listener.Accept error, err = ", err)
		return
	}
	defer conn.Close()
	fmt.Println("建立连接")
	//绑定服务
	jsonrpc.ServeConn(conn)
}


Client端:

package main

import "fmt"

func main(){
	myClient := InitClient("127.0.0.1:8800")

	var res string
	if err := myClient.SayHello("xyz", &res); err!= nil{
		fmt.Println("err = ", err)
		return
	}
}

8、总结

以上就是对RPC的一个简单的入门使用,大概的步骤就是服务端注册服务、监听端口、建立连接、绑定服务,客户端拨号、调用服务,再对其进行封装

上一篇:IDEA SpringBoot-Mybatis实现增删改查(CRUD)


下一篇:Java之list去除浅析