写点什么

Go RPC 入门指南:RPC 的使用边界在哪里?如何实现跨语言调用?

作者:王中阳Go
  • 2022-10-16
    北京
  • 本文字数:3398 字

    阅读完需:约 1 分钟

Go RPC入门指南:RPC的使用边界在哪里?如何实现跨语言调用?

什么是 RPC

  1. RPC 的中文是“远程过程调用”,对应的英文全称是:Remote Procedure Call,可以简单理解为一个节点请求另一个节点提供的服务

  2. 理解“本地过程调用”可以更好的理解“远程过程调用”

  3. 知识点:RPC 主要依赖于客户端与服务端建立 socket 链接;而 HTTP RESTful 实现通讯的代价比较高,这是 RPC 的一个优势体现。

为什么用 RPC

就是因为无法在同一个进程内,或者无法在同一个服务器上通过本地调用的方式实现我们的需求。HTTP 能满足需求但是不够高效,所以我们需要使用 RPC。

RPC 的优势

  1. RPC 能够跨多种开发工具和平台

  2. RPC 能够跨语言调用

  3. RPC 能够提高系统的可扩展性,解耦,提高复用

  4. RPC 相较于 HTTP,传输效率更高,性能消耗更小,自带负载均衡策略,自动实现服务治理

RPC 和 HTTP 对比

  1. RPC 主要用于公司内部的服务调用,性能消耗低,传输效率高,服务治理方便。

  2. HTTP 主要用于对外的异构环境,浏览器接口调用,APP 接口调用,第三方接口调用等。

RPC 的使用边界

  1. 通过和 HTTP 的对比,我们倒推出 RPC 的边界:对外的异构环境,浏览器接口调用,APP 接口调用,第三方接口调用。

  2. 上述这些都不适合 RPC,不知道 RPC 不适合做什么,比知道 RPC 能做什么更重要。

RPC 入门 1:net/rpc

基本构成

  1. RPC 的基本构成:服务端,客户端

  2. 服务端基本构成:结构体,请求结构体,响应结构体

  3. 客户端基本构成:请求结构体,响应结构体

代码示例

rpc_service.go


package main
import ( "errors" "fmt" "log" "net" "net/http" "net/rpc" "os")
type Arith struct {
}
//请求结构体type ArithRequest struct { A int B int}
//响应结构体type ArithResponse struct { Pro int //乘积 Quo int //商 Rem int //余数}
//乘积方法func (this *Arith) Multiply(req ArithRequest,res *ArithResponse) error{ res.Pro = req.A * req.B return nil}
//除法运算方法func (this *Arith) Divide(req ArithRequest,res *ArithResponse) error{ if req.B ==0 { return errors.New("divide by zero") } res.Quo = req.A / req.B res.Rem = req.A % req.B return nil}
func main() { //注册rpc服务 rpc.Register(new(Arith)) //采用http协议作为rpc载体 rpc.HandleHTTP()
lis,err := net.Listen("tcp","127.0.0.1:8095") if err!=nil { log.Fatalln("fatal error:",err) }
fmt.Fprintf(os.Stdout,"%s","start connection\n")
//常规启动http服务 http.Serve(lis,nil)}
复制代码


rpc_client.go


package main
import ( "fmt" "log" "net/rpc")
//算数运算请求结构体type ArithRequest struct { A int B int}
//响应结构体type ArithResponse struct { Pro int //乘 Quo int //商 Rem int //余数}
func main() { conn,err := rpc.DialHTTP("tcp","127.0.0.1:8095") if err!=nil { log.Fatalln("dialing error:",err) }
req := ArithRequest{10,20} var res ArithResponse
err = conn.Call("Arith.Multiply",req,&res) //乘法运算 if err!=nil { log.Fatalln("arith error:",err) } fmt.Printf("%d * %d = %d\n",req.A,req.B,res.Pro)
//除法运算 err = conn.Call("Arith.Divide",req,&res) if err!=nil { log.Fatalln("arith error:",err) } fmt.Printf("%d / %d = %d 余数是:%d",req.A,req.B,res.Quo,res.Rem)}
复制代码

运行结果

  1. 先启动服务端,再启动客户端连接服务端


//服务端consolestart connection
//客户端console10 * 20 = 20010 / 20 = 0 余数是:10
复制代码

RPC 入门 2:net/rpc/jsonrpc

实现跨语言调用

jsonrpc_server.go


package main
import ( "errors" "fmt" "log" "net" "net/rpc" "net/rpc/jsonrpc" "os")
type Arith struct {
}
//请求结构体type ArithRequest struct { A int B int}
//响应结构体type ArithResponse struct { Pro int //乘积 Quo int //商 Rem int //余数}
//乘积方法func (this *Arith) Multiply(req ArithRequest,res *ArithResponse) error{ res.Pro = req.A * req.B return nil}
//除法运算方法func (this *Arith) Divide(req ArithRequest,res *ArithResponse) error{ if req.B ==0 { return errors.New("divide by zero") } res.Quo = req.A / req.B res.Rem = req.A % req.B return nil}
func main() { //注册rpc服务 rpc.Register(new(Arith)) //采用http协议作为rpc载体 rpc.HandleHTTP()
lis,err := net.Listen("tcp","127.0.0.1:8096") if err!=nil { log.Fatalln("fatal error:",err) }
fmt.Fprintf(os.Stdout,"%s","start connection\n")
//接收客户端请求 并发处理 jsonrpc for { conn,err :=lis.Accept() //接收客户端连接请求 if err!=nil { continue }
//并发处理客户端请求 go func(conn net.Conn) { fmt.Fprintf(os.Stdout,"%s","new client in coming\n") jsonrpc.ServeConn(conn) }(conn) }
//常规启动http服务 //http.Serve(lis,nil)}
复制代码


jsonrpc_client.go


package main
import ( "fmt" "log" "net/rpc/jsonrpc")
//算数运算请求结构体type ArithRequest struct { A int B int}
//响应结构体type ArithResponse struct { Pro int //乘 Quo int //商 Rem int //余数}
func main() { // 只有这里不一样 conn,err := jsonrpc.Dial("tcp","127.0.0.1:8096") if err!=nil { log.Fatalln("dialing error:",err) }
req := ArithRequest{9,2} var res ArithResponse
err = conn.Call("Arith.Multiply",req,&res) //乘法运算 if err!=nil { log.Fatalln("arith error:",err) } fmt.Printf("%d * %d = %d\n",req.A,req.B,res.Pro)
//除法运算 err = conn.Call("Arith.Divide",req,&res) if err!=nil { log.Fatalln("arith error:",err) } fmt.Printf("%d / %d = %d 余数是:%d",req.A,req.B,res.Quo,res.Rem)}
复制代码

运行结果

  1. 先启动服务端,再启动客户端连接服务端


//服务端consolestart connection
//客户端console9 * 2 = 189 / 2 = 4 余数是:1
//服务端consolenew client in coming
复制代码

RPC 入门 3:go php 跨语言调用

Go 作为服务端,PHP 作为客户端

jsonrpc_server.go:和入门 2 服务端的代码一样


jsonrpc_client.php


<?php

class JsonRPC{
private $conn;
function __construct($host, $port) { $this->conn = fsockopen($host, $port, $errno, $errstr, 3); if (!$this->conn) { return false; } }
public function Call($method, $params) { if (!$this->conn) { return false; } $err = fwrite($this->conn, json_encode(array( 'method' => $method, 'params' => array($params), 'id' => 0, )) . "\n"); if ($err === false) { return false; } stream_set_timeout($this->conn, 0, 3000); $line = fgets($this->conn); if ($line === false) { return NULL; } return json_decode($line, true); }}
$client = new JsonRPC("127.0.0.1", 8096);$args = array('A' => 9, 'B' => 2);$r = $client->Call("Arith.Multiply", $args);printf("%d * %d = %d\n", $args['A'], $args['B'], $r['result']['Pro']);$r = $client->Call("Arith.Divide", array('A' => 9, 'B' => 2));printf("%d / %d, Quo is %d, Rem is %d\n", $args['A'], $args['B'], $r['result']['Quo'], $r['result']['Rem']);
复制代码


如何在本地启动PHP

运行结果

//本地启动PHP服务:http://127.0.0.1/jsonrpc_client.php,运行结果如下:9 * 2 = 18 9 / 2, Quo is 4, Rem is 1
复制代码

参考博客

名词解释

  • Thrift:是一种接口描述语言和二进制通讯协议,被当做 RPC 的框架来使用。

思考

如何优雅的使用 RPC 进行 web 开发

会在下一个博客中整理分享


一起学习,升级打怪

我们搞了一个对学 Go 真正有帮助的群,欢迎加入:

公众号:程序员升级打怪之旅

微信号:wangzhongyang1993

发布于: 刚刚阅读数: 4
用户头像

王中阳Go

关注

公众号:程序员升级打怪之旅 2022-10-09 加入

微信:wangzhongyang1993

评论 (1 条评论)

发布
用户头像
RPC主要依赖于客户端与服务端建立socket链接;而HTTP REST实现通讯的代价比较高,这是RPC的一个优势体现。 就是因为无法在同一个进程内,或者无法在同一个服务器上通过本地调用的方式
刚刚 · 北京
回复
没有更多了
Go RPC入门指南:RPC的使用边界在哪里?如何实现跨语言调用?_php_王中阳Go_InfoQ写作社区