1:RPC是什么
1:RPC是啥?
远程过程调用(Remote Procedure Call,缩写为 RPC)。它可以让你如调用本地函数一样,去调用处在远处另一台计算机上面的函数。
有关RPC的想法至少可以追溯到1976年以“信使报”(Courier)的名义使用。RPC首次在UNIX平台上普及的执行工具程序是SUN公司的RPC(现在叫ONC RPC)
RPC 的消息传输可以通过 TCP、UDP 或者 HTTP等,所以有时候我们称之为 RPC over TCP、 RPC over HTTP。
RPC 通过 HTTP 传输消息的时候和 REST ful的架构是类似的,但是也有不同。
2:RESTful和RPC over TCP 区别
我们再来比较一下 RPC over TCP 和 RESTful。 如果我们直接使用socket实现 RPC,可以获得性能上的优势。
RPC over TCP可以通过长连接减少连接的建立所产生的花费,在调用次数非常巨大的时候(这是目前互联网公司经常遇到的情况,大并发的情况下),
这个花费影响是非常巨大的。 当然 RESTful 也可以通过 keep-alive 实现长连接,但是它最大的一个问题是它的request-response模型是阻塞的 (http1.0和 http1.1, http 2.0没这个问题), 发送一个请求后只有等到response返回才能发送第二个请求 (有些http server实现了pipeling的功能,但不是标配), RPC的实现没有这个限制。
在当今用户和资源都是大数据大并发的趋势下,一个大规模的公司不可能使用一个单体程序提供所有的功能,微服务的架构模式越来越多的被应用到产品的设计和开发中, 服务和服务之间的通讯也越发的重要, 所以 RPC 不失是一个解决服务之间通讯的好办法,
3:RPC实现
在前面我们理解socket链接方式,rpc也是s/c架构。它其实也socket的时候方式差不多,就是多了一个注册服务的概念。
server实现
package main import ( "fmt" "net" "net/rpc" ) //结构体必须实现Say格式的方法。两个参数 一个传入 一个指针传出而且返回值必须是error类型 type HelloWorld struct { } func (this *HelloWorld) Say(req string, rep *string) error { *rep = req + "hello" return nil } func main() { err := rpc.RegisterName("hello", new(HelloWorld)) if err != nil { fmt.Println("RegisterName Error") } listener, err := net.Listen("tcp", "127.0.0.1:9988") if err != nil { fmt.Println("listener Error") } defer func() { _ = listener.Close() }() for { conn, _ := listener.Accept() defer func() { _ = conn.Close() }() go rpc.ServeConn(conn) } }
client实现
package main import ( "fmt" "net/rpc" ) func main() { client, err := rpc.Dial("tcp", "127.0.0.1:9988") if err != nil { fmt.Println("Dial error") } var rep string //这里的hello.Say 表示调用远端的hello服务的Say方法。这种你不能写错,而且两个参数中第二个必须是指针地址 err = client.Call("hello.Say", "libai", &rep) if err != nil { fmt.Println("Call error") } fmt.Println("接受来着客户端的消息", rep) }
我们完成了上面的小案例!但是你有没有发现:
server端你必须要理解如何注册服务的格式(Say方法的格式)要不然肯定是失败的。
client端你必须要写对你的服务名和要调用的方法!而且第二个参数也是必须要指针的!
那么小白来了,我怎么提供给他们用!写接口!接口去约束方法格式。
并且上面的server和client如果我们写错了,他的错误是发生在运行时期的!我们说报错要宜早不宜迟,写了接口让我们在编译期就可以发现错误了。
4:接口封装rpc案例
server端
design.go
package main import "net/rpc" //----------server端-------- type RpcInterFace interface { Say(request string, response *string) error } //注册服务 func RegisterServer(name string, face RpcInterFace) error { return rpc.RegisterName(name, face) } //----------client端--------
server.go
package main import ( "fmt" "net" "net/rpc" ) type HelloWorld struct { } func (this *HelloWorld) Say(req string, rep *string) error { fmt.Println("接收来自客户端发来的消息", req) *rep = req + "hello" return nil } func main() { //使用注册方法 err := RegisterServer("hello", &HelloWorld{}) if err != nil { fmt.Println("RegisterServer error") } listener, err := net.Listen("tcp", "127.0.0.1:9988") if err != nil { fmt.Println("listener Error") } defer listener.Close() for { conn, _ := listener.Accept() defer conn.Close() go rpc.ServeConn(conn) } }
client端
design.go
package main import ( "fmt" "net/rpc" ) //----------client端-------- type Client struct { c *rpc.Client } func InitClient(addr string) *Client { conn,err:=rpc.Dial("tcp",addr) if err!= nil{ fmt.Println("error") } return &Client{conn} } func (this *Client)Say(request string,response *string) error { return this.c.Call("hello.Say",request,response) }
client.go
package main import "fmt" func main() { myclient := InitClient("127.0.0.1:9988") var rep string _ = myclient.Say("礼拜", &rep) fmt.Println("接受来自服务端的消息", rep) }
5:RPC使用什么序列化数据
要知道rpc我们一直使用的是go内置的rpc模块,他是用gob进行序列化数据的,这种gob使用在go语言之间使用。如果
想要支持跨语言,让别人也可以调用我们的服务,我们就需要用一种通用的序列化格式--JSON!
rpc怎么使用json呢?很简单!只要改一行
client: conn, err := jsonrpc.Dial("tcp", addr) server: go jsonrpc.ServeConn(conn)