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

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

屏幕截图 2022-11-11 190547.png

RPC


什么是RPC


  1. RPC的中文是“远程过程调用”,对应的英文全称是:Remote Procedure Call,可以简单理解为一个节点请求另一个节点提供的服务
  2. 理解“本地过程调用”可以更好的理解“远程过程调用”
  3. 知识点:RPC主要依赖于客户端与服务端建立socket链接;而HTTP REST实现通讯的代价比较高,这是RPC的一个优势体现。
  4. RPC详解看这里


为什么用RPC


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


知乎大神的回答


RPC的优势


  1. RPC能够跨多种开发工具和平台
  2. RPC能够跨语言调用
  3. RPC能够提高系统的可扩展性,解耦,提高复用
  4. RPC相较于HTTP,传输效率更高,性能消耗更小,自带负载均衡策略,自动实现服务治理


RPC和HTTP对比


  1. RPC主要用于公司内部的服务调用,性能消耗低,传输效率高,服务治理方便。
  2. HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。
  3. RPC和HTTP的详细对别


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. 先启动服务端,再启动客户端连接服务端


//服务端console
start connection
//客户端console
10 * 20 = 200
10 / 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. 先启动服务端,再启动客户端连接服务端


//服务端console
start connection
//客户端console
9 * 2 = 18
9 / 2 = 4 余数是:1
//服务端console
new 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的框架来使用。


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

微信号:wangzhongyang1993

相关文章
|
4月前
|
Cloud Native 安全 Java
Go语言深度解析:从入门到精通的完整指南
🌟蒋星熠Jaxonic,Go语言探索者。深耕云计算、微服务与并发编程,以代码为笔,在二进制星河中书写极客诗篇。分享Go核心原理、性能优化与实战架构,助力开发者掌握云原生时代利器。#Go语言 #并发编程 #性能优化
510 43
Go语言深度解析:从入门到精通的完整指南
|
9月前
|
人工智能 安全 算法
Go入门实战:并发模式的使用
本文详细探讨了Go语言的并发模式,包括Goroutine、Channel、Mutex和WaitGroup等核心概念。通过具体代码实例与详细解释,介绍了这些模式的原理及应用。同时分析了未来发展趋势与挑战,如更高效的并发控制、更好的并发安全及性能优化。Go语言凭借其优秀的并发性能,在现代编程中备受青睐。
286 33
|
5月前
|
Cloud Native 安全 Java
Go语言深度解析:从入门到精通的完整指南
🌟 蒋星熠Jaxonic,执着的星际旅人,用Go语言编写代码诗篇。🚀 Go语言以简洁、高效、并发为核心,助力云计算与微服务革新。📚 本文详解Go语法、并发模型、性能优化与实战案例,助你掌握现代编程精髓。🌌 从goroutine到channel,从内存优化到高并发架构,全面解析Go的强大力量。🔧 实战构建高性能Web服务,展现Go在云原生时代的无限可能。✨ 附技术对比、最佳实践与生态全景,带你踏上Go语言的星辰征途。#Go语言 #并发编程 #云原生 #性能优化
|
5月前
|
编解码 网络协议 Java
RPC的三大问题:跨语言、跨平台通信的终极解决方案是如何炼成的?
本文深入解析现代RPC体系的核心挑战与解决方案,涵盖数据表示、传输机制与调用约定,探讨gRPC、HTTP/2、ProtoBuf等技术如何实现高效可靠的跨服务通信,并分析自研RPC协议的设计思路与未来发展路径。
207 8
|
9月前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
227 10
|
10月前
|
存储 算法 数据可视化
【二叉树遍历入门:从中序遍历到层序与右视图】【LeetCode 热题100】94:二叉树的中序遍历、102:二叉树的层序遍历、199:二叉树的右视图(详细解析)(Go语言版)
本文详细解析了二叉树的三种经典遍历方式:中序遍历(94题)、层序遍历(102题)和右视图(199题)。通过递归与迭代实现中序遍历,深入理解深度优先搜索(DFS);借助队列完成层序遍历和右视图,掌握广度优先搜索(BFS)。文章对比DFS与BFS的思维方式,总结不同遍历的应用场景,为后续构造树结构奠定基础。
508 10
|
12月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
319 3
Go 语言入门指南:切片
|
Go C语言
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
220 15
|
存储 设计模式 安全
Go语言中的并发编程:从入门到精通###
本文深入探讨了Go语言中并发编程的核心概念与实践技巧,旨在帮助读者从理论到实战全面掌握Go的并发机制。不同于传统的技术文章摘要,本部分将通过一系列生动的案例和代码示例,直观展示Go语言如何优雅地处理并发任务,提升程序性能与响应速度。无论你是Go语言初学者还是有一定经验的开发者,都能在本文中找到实用的知识与灵感。 ###
|
Serverless Go
Go语言中的并发编程:从入门到精通
本文将深入探讨Go语言中并发编程的核心概念和实践,包括goroutine、channel以及sync包等。通过实例演示如何利用这些工具实现高效的并发处理,同时避免常见的陷阱和错误。