IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> Go的RPC微服务开发初探 -> 正文阅读

[网络协议]Go的RPC微服务开发初探


观前提示:本文基于Go语言,基于语言的原生能力初步讨论了微服务的开发过程

RPC的概念

这里不想谈RPC微服务和单体服务的优缺点,网络上有很多很长的文章。这里假定读者同学对RPC有一个最基本的概念,只是分享一下我我对RPC的想法。
RPC是远程过程调用的简写(Remote Procedure Call),这个名词看起来有些抽象,我们一点点来分析。

过程调用

这里的RPC概念中的“过程调用”相信同学们并不陌生,在任何一个语言中def一个或者func一个 或者function一个函数之后,用函数名后面跟小括号,就完成了一次过程调用。而远程过程调用就是将函数定义过程和调用过程分开(远程的含义),利用网络协议进行传输

和REST的区别

以上就是RPC最朴素的解释,而和REST的区别,也是讨论RPC时最为津津乐道的话题,个人认为,REST作为一种为url制定的标准,现在得到较为主流的使用,更注重目的,通过对HTTP的请求方式映射成对资源的增删改查的操作,尝试从数据库的视角描述操作的目的,本身并不关心背后是怎么实现的。而RPC更注重过程,尝试基于给定的函数名和函数参数,完成函数的调用过程。当然这是它们从设计的角度来说最主要的一个区别,从这个区别的描述中,我们也可以看出以下两个区别

  • 网络协议
    • REST是url(http协议)的一种标准
    • RPC并不强调协议,事实上有很多种协议方式
  • 序列化与反序列化
    • REST是基于http协议,自然跟着http协议的序列化与反序列化的方式走
    • RPC也有很多协议方式,可以自定义,如Go原生的Gob、gRPC的Protobuf、包括之前的json和xml都可以实现

RPC的核心概念

三个核心概念:通过以上的讨论,我们可以发现RPC最重要的就是实现一个远程的【过程调用】,也就是调用方在不感知网络协议和序列化和反序列化方式的情况下,可以根据函数名和参数进行过程调用,就像在文件上面定义了这个函数那样进行调用。那么我们就很容易总结出RPC中比较关键的几个方面。

  • 传输中的网络协议
  • 传输中的序列化和反序列化的方式
  • 根据函数名在目标的地方找到对应的函数

我们也可以换一种方式理解这三个概念,想象一下【调用方】需要远方的一箱货,【被调用方】如何将这一箱货物给到【调用方】呢

  1. 首先需要一辆车能够按照指定路线到达(网络协议)
  2. 其次货物能够被装起来,也能够从箱子里面拿出来(序列化和反序列化)
  3. 最后也是最重要的就是【被调用方】得根据【调用方】的简单描述,知道是哪一箱货物(根据函数名在目标的地方找到对应的函数)

四个核心过程:对于客户端来说比较容易,以下我们只讨论对于服务端来说(也就是定义函数的地方)的过程,主要有四个:【定义函数】,【定义服务端】,【绑定函数】,【启动服务】。听起来有点抽象是不是,别着急,让我们用刚才【一箱货物】来举例:

  • 备货(定义函数)
  • 备车(定义服务端)
  • 装货(绑定函数)
  • 发车(启动服务)

其他讨论:在远程调用中由于网络的不确定性和传输较慢的影响,还需要考虑:失败时的重试,积压时的限流,高负载时的负载均衡等问题。本篇文章我们不做过多讨论。

极简RPC的实现

在具体代码实现这里,主要用了net/rpc包和net包,前者实现了【网络协议】,后者实现了【根据函数名在远程定位函数】,至于序列化方式,可以用rpc包实现,也可以通过其他方式实现

Server

import "net"       // 提供网络协议
import "net/rpc"   // 根据函数名在远程定位函数、序列化方法(Gob)

type Hello struct{}
func (ho *Hello) Call (req string, resp *string){  // 1. 定义函数(备货)
	*resp = "RPC Hello " + req
	return nil
}

func main(){
     listener, _ := net.Listen("tcp", ":8888")    // 2. 实例化server(备车)
     _ = rpc.RegisterName("Hello", &Hello{})      // 3. 绑定函数(装货)   Struct需要实现的方法  request string reply *string
     for {
     	conn, _ := listener.Accept()              // 4. 启动服务(发车)
     	rpc.ServeConn(conn)                       // Gob的反序列化
     }

}

以上过程解释两个地方,一个是函数定义时接收的参数,必须是2个请求 string,响应 *string,比较好理解,因为在Go中接收响应的方法是通过传递指针,让函数内部改变外部变量指针指向的位置。这是Go中很典型的操作(json的反序列化或者接收命令行输入)个人理解,这种直接该地址的方式要比返回值的方式效率高。另一个是序列化和反序列化的方式,因为我们没有额外指定这个方式,所以用的是Go的默认Gob这个方式

Client

import "net/rpc"   // 映射方法、Gob的序列化
import "fmt"

func main(){
    client, _ := rpc.Dial("tcp", ":8888")     // rpc的拨号就是Gob的编码
    var resp string
    _ = client.Call("Hello.Call", "World", &resp)  // 通过传递地址进行修改
    fmt.Println(resp)
}

client端相对简单,就不多说了,此时输出,“RPC Hello World”

RPC改造

1. 换方式为json

Server端

刚才我们提到此时RPC利用的序列化和反序列化的方式是 Gob,一种Go特有的方式,但是这样,其他的语言就无法调用了,我们手动换一下方式,比如json

import "net"
import "net/rpc"

type Hello struct{}
func (ho *Hello) Call (request string, reply *string){
	*reply = "RPC Hello " + request
	return nil
}

func main(){
     listener, _ := net.Listen("tcp", ":8888") 
     _ = rpc.RegisterName("Hello", &Hello{}) 
     conn, _ := listener.Accept()
     rpc.ServeCodec(jsonrpc.NewServerCodec(conn))  // json的反序列化
}

以上,我们只改了最后一行,在启动服务之后追加了json的反序列化的方式

Client端

import "net/rpc"
import "fmt"

func main(){
    conn, _ := net.Dial("tcp", ":8888")     // rpc的拨号就是Gob的编码,此时改用普通的拨号
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) // json的序列化
    var resp string
    _ := client.Call("Hello.Call", "World", &resp)  // 通过传递地址进行修改
    fmt.Println(resp)
}

此时client端就以json的格式发送数据,那到底是什么样的数据呢,别的语言能否发送数据呢。答案当然是肯定的,格式如下

{"method": "Hello.Call", "params": ["hello"], "id": 0}

明显这种序列化方式效率不高,真正有用的信息,还不到一半,但胜在人类可读,至于想验证其他语言能否调用这个服务端的小伙伴可以自己试一下,注意:是TCP而不是HTTP

2. 换协议为http

刚才提到协议是TCP,我们可以使用HTTP吗

Server端

import "net/http"
import "net/rpc"

type Hello struct{}
func (ho *Hello) Call (request string, reply *string){
	*reply = "RPC Hello " + request
	return nil
}

func main(){
     //listener, _ := net.Listen("tcp", ":8888") 
     _ = rpc.RegisterName("Hello", &Hello{})             // 绑定handler
     http.HandleFunc("/httpjsonrpc", func(w http.ResponseWriter, r *http.Request){
     	var conn io.ReadWriteCloser = struct {           // 实例化server
     		io.Writer
     		io.ReadCloser
     	}{
			ReadCloser: r.Body, 
			Writer: w,
		}
		rpc.ServeRequest(jsonrpc.NewServerCodec(conn))   // 序列化的方式
     })
     //conn, _ := listener.Accept()
     http.ListenAndServe("8888", nil)                    // 启动服务
}

这次代码的改动有些大,这是因为http.HandleFunc需要将一个url绑定到一个处理函数上,这就要求这个处理函数中做更多的东西。不过还是可以看出,之前分析的那几个步骤还在。此时的请求就是一个标准的http的请求了,client端也就是标准的http的请求的写法,就不再赘述了

总结

本文由浅入深的介绍了RPC最重要的三部分组成,请求到函数的映射,网络协议和序列化/反序列化的方式,并且基于Go原生的能力实现了三版RPC的服务端和客户端,帮助读者同学理解RPC调用

深挖的话,还有很多不足,比如用字符串定位的方式,客户端如何知道服务端都有什么能力,以及之前提到的RPC由于网络传输带来的具有挑战的其他要求。实际开发过程中更多的是利用现在已有的开发框架gRPC,gRPC会帮我们处理好这些问题,也有着完善的生态,将在之后的文章中介绍

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-03 11:34:09  更:2021-08-03 11:35:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 13:58:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计