golang rpc

news2024/11/15 19:53:26

RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务,对应rpc的是本地过程调用,函数调用是最常用的本地过程调用,将本地过程调用变成远程调用会面临着各种问题。

以两数相加为例

package main

import "fmt"

func add(a, b int) int {
	return a + b
}
func main() {
	fmt.Println(add(1, 2))
}

函数调用过程:
(1)将1和2压入函数的栈中
(2)进入add函数,从栈中取出1和2分别赋值给a和b
(3)执行a+b将结果压栈
(4)将栈中的结果取出打印

远程过程面临的问题
1.原本的本地函数放到另外一个服务器上去运行,但是引入了很多新问题
2.Call的id映射
我们怎么告诉远程机器我们要调用add,而不是sub或者Foo呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用add,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个(函数<–>Call ID}的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
3.序列化和反序列化
客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
4.网络传输
远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传给客户端。只要能完成这两者的,都可以作为传输层使用。因此,他所使用的协议其实是不限的,能完成传输就行,尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而RPC干脆就用了HTTP2。Java的Netty也属于这层的东西。

RPC第一个要点:数据编码协议

一般采用将数据传输到gin,gin传输到服务端,服务端负责解析数据
客户端流程

	1.建立连接tcp/http
	2.将employee对象序列化成json字符串-序列化
	3.发送json字符串-调用成功后实际上你接受到的是一个二进制的数据
	4.等待服务器发送结果
	5将服务返回的数据解析成PrintResult对象-反序列化

服务端流程

	1.监听网络接口80
	2.读取数据-二进制的json数据
	3.对数据进行反序列化Employee对象
	4.开始处理业务逻辑
	5.将处理的结果PrintReuslt发序列化成json二进制数据-序列化
	6.将数据返回

序列化和反序列化是可以选择的,不一定要采用json、xml、protobuf、msgpack

RPC第二个要点:传输协议

http协议:http1.x http2.0协议
http协议底层使用的也是tcp,http现在主流的是http1.x,这种协议有性能问题(一次性),一旦结果返回,连接就断开。我们可以直接基于tcp/udp协议去封装一层协议myhttp,没有通用型,http2.0既有http的特性也有长连接的特性(grpc就是基于http2.0的)
http协议是文本协议,http底层的传输协议是tcp。grpc基于http2.0,传输协议也是tcp
在这里插入图片描述
http协议具有一次性的问题:一旦对方返回了结果,连接断开,http2.0通过长连接解决了这个问题。

基于Http Server实现rpc请求

server端

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
)

func add(a, b int) int {
	return a + b
}

func main() {
	//get方法http://127.0.0.1:8000/add?a=1&b=2或http://127.0.0.1:8000?method=add&a=1&b=2
	//返回的格式化:json{"data":3}
	//1、callId的问题:r.URL.Path,2、数据的传输协议:url的参数传输协议,3、网络传输协议:http
	http.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm() //解析参数
		if err != nil {
			panic("error")
		}
		fmt.Println("path:", r.URL.Path)
		a, err := strconv.Atoi(r.Form["a"][0])
		if err != nil {
			panic("transform error")
		}
		b, err := strconv.Atoi(r.Form["b"][0])
		if err != nil {
			panic("transform error")
		}
		w.Header().Set("Content-Type", "application/json")
		jData, err := json.Marshal(map[string]int{
			"data": a + b,
		})
		w.Write(jData)
	})
	_ = http.ListenAndServe(":8000", nil)
}

在这里插入图片描述
缺点:http1.x,麻烦,性能不高
客户端:

package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/kirinlabs/HttpRequest"
)

type ResponseData struct {
	Data int `json:"data"`
}

func Add(a, b int) int {
	req := HttpRequest.NewRequest()
	res, _ := req.Get(fmt.Sprintf("http://127.0.0.1:8000/%s?a=%d&b=%d", "add", a, b))
	body, _ := res.Body()
	rspData := ResponseData{}
	_ = json.Unmarshal(body, &rspData)
	return rspData.Data
}
func main() {
	fmt.Println(Add(2, 2))
}

这里遇到了一个Get “http://127.0.0.1:8000/add?a=1&b=2”: context deadline exceeded (Client.Timeout exceeded while awaiting headers)的问题,主要是客户端默认网络请求时间太短
修改之后

package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/kirinlabs/HttpRequest"
)

type ResponseData struct {
	Data int `json:"data"`
}

func Add(a, b int) int {
	req := HttpRequest.NewRequest()
	req.SetTimeout(10 * time.Second)
	res, _ := req.Get(fmt.Sprintf("http://127.0.0.1:8000/%s?a=%d&b=%d", "add", a, b))
	body, _ := res.Body()
	rspData := ResponseData{}
	_ = json.Unmarshal(body, &rspData)
	return rspData.Data
}
func main() {
	fmt.Println(Add(2, 2))
}

rpc开发的要素分析

RPC技术在架构设计上有四部分组成,分别是:客户端、客户端存根、服务端、服务端存根。
客户端(Client):服务调用发起方,也称为服务消费者。
客户端存根(Client Stub):该程序运行在客户端所在的计算机机器上,主要用来存储要调用的服务器的地址,另外,该程序还负责将客户端请求远端服务器程序的数据信息打包成数据包,通过网络发送给服务端Stub程序;其次,还要接收服务端Stub程序发送的调用结果数据包,并解析返回给客户端。
服务端(Server):远端的计算机机器上运行的程序,其中有客户端要调用的方法。
服务端存根(Server Stub):接收客户Stub程序通过网络发送的请求消息数据包,并调用服务端中真正的程序功能方法,完成功能调用;其次,将服务端执行调用的结果进行数据处理打包发送给客户端Stub程序。

rpc需要使用到的术语

1、动态代理技术: 上文中我们提到的Client Stub和Sever Stub程序,在具体的编码和开发实践过程中,都是使用动态代理技术自动生成的一段程序。
序列化和反序列化: 在RPC调用的过程中,我们可以看到数据需要在一台机器上传输到另外一台机器上。在互联网上,所有的数据都是以字节的形式进行传输的。而我们在编程的过程中,往往都是使用数据对象,因此想要在网络上将数据对象和相关变量进行传输,就需要对数据对象做序列化和反序列化的操作。
序列化:把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。
我们常见的Json,XML等相关框架都可以对数据做序列化和反序列化编解码操作。后面我们要学习的Protobuf协议,这也是一种数据编解码的协议,在RPC框架中使用的更广泛。

简单的rpc实例

服务端

package main

import (
	"net"
	"net/rpc"
)

type HelloService struct {
}

func (s *HelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}
func main() {
	//rpc快速开发体验
	//1.实例化一个server
	listener, _ := net.Listen("tcp", ":1234")
	//2.注册处理逻辑
	_ = rpc.RegisterName("HelloService", &HelloService{})
	//3.启动服务
	conn, _ := listener.Accept() //当一个新的连接进来的时候
	rpc.ServeConn(conn)
}

客户端

package main

import (
	"encoding/json"
	"fmt"
	"net/rpc"
	"time"
)

func main() {

	//1.建立连接
	client, err := rpc.Dial("tcp", "localhost:1234")
	if err != nil {
		panic("连接失败")
	}
	var reply *string = new(string)
	err = client.Call("HelloService.Hello", "bobby", reply)
	if err != nil {
		panic("调用失败")
	}
	fmt.Println(*reply)
}

替换rpc的序列化协议为json

序列化协议为json,各种语言都可以调用服务端的内容
服务端

package main

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

type HelloService struct {
}

func (s *HelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}
func main() {
	//替换rpc的序列化协议为json
	//1.实例化一个server
	listener, _ := net.Listen("tcp", ":1234")
	//2.注册处理逻辑
	_ = rpc.RegisterName("HelloService", &HelloService{})
	//3.启动服务
	conn, _ := listener.Accept() //当一个新的连接进来的时候
	rpc.ServeCodec(jsonrpc.NewServerCodec(conn))

}

客户端

package main

import (
	"encoding/json"
	"fmt"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
	"time"

	"github.com/kirinlabs/HttpRequest"
)

func main() {
	//替换rpc的序列化协议为json
	//1.建立连接
	conn, err := net.Dial("tcp", "localhost:1234")
	if err != nil {
		panic("连接失败")
	}
	var reply *string = new(string)
	client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
	err = client.Call("HelloService.Hello", "bobby", reply)
	if err != nil {
		panic("调用失败")
	}
	fmt.Println(*reply)
}

python连接rpc,序列化协议为json

服务端

package main

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

type HelloService struct {
}

func (s *HelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}
func main() {
	//替换rpc的序列化协议为json
	//1.实例化一个server
	listener, _ := net.Listen("tcp", ":1234")
	//2.注册处理逻辑
	_ = rpc.RegisterName("HelloService", &HelloService{})
	//3.启动服务
	conn, _ := listener.Accept() //当一个新的连接进来的时候
	rpc.ServeCodec(jsonrpc.NewServerCodec(conn))

}

客户端

import json
import socket
request={
    "id":0,
    "params":["bobby"],
    "method":"HelloService.Hello"
}
client=socket.create_connection(("localhost",1234))
client.sendall(json.dumps(request).encode())
#获取服务器返回的数据
rsp=client.recv(4096)
rsp=json.loads(rsp.decode())
print(rsp)

替换rpc的传输协议为http

服务端

package main

import (
	"io"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
)
type HelloService struct {
}

func (s *HelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}
func main() {
	//替换rpc的序列化协议为http
	//2.注册处理逻辑
	_ = rpc.RegisterName("HelloService", &HelloService{})
	http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
		var conn io.ReadWriteCloser = struct {
			io.Writer
			io.ReadCloser
		}{
			ReadCloser: r.Body,
			Writer:     w,
		}
		rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
	})
	http.ListenAndServe(":1234", nil)
}

客户端

import requests
request={
    "id":0,
    "params":["bobby"],
    "method":"HelloService.Hello"
}
rsp=requests.post("http://localhost:1234/jsonrpc",json=request)
print(rsp.text)

客户端

package main

import (
	"encoding/json"
	"fmt"
	"net/rpc"
	"time"

	"github.com/kirinlabs/HttpRequest"
)

func main() {
	//1.实例化一个
	client, err := rpc.Dial("tcp", "127.0.0.1:1234")
	if err != nil {
		panic("连接失败")
	}
	var reply string
	err = client.Call("HelloService.Hello", "bobby", &reply)
	if err != nil {
		// panic("调用失败")
		fmt.Println(err)
	}
	fmt.Println(reply)
}

代理封装

代理类

package handler

import "net/rpc"

const HelloServiceName = "HelloServiceName"

type HelloServiceStub struct {
	*rpc.Client
}

func NewHelloServiceClient(protcol, address string) HelloServiceStub {
	conn, err := rpc.Dial(protcol, address)
	if err != nil {
		panic("connect error!")
	}
	return HelloServiceStub{conn}
}
func (c *HelloServiceStub) Hello(request string, reply *string) error {
	err := c.Call(HelloServiceName+".Hello", request, reply)
	if err != nil {
		return err
	}
	return nil
}

type HelloService struct {
}

func (s *HelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}

func RegisterHelloService() error {
	return rpc.RegisterName(HelloServiceName, &HelloService{})
}

服务端

package main

import (
	"TEMP/handler"
	"net"
	"net/rpc"
)

func main() {
	//进一步改造rpc调用的代码
	//1.实例化一个server
	listener, _ := net.Listen("tcp", ":1234")
	//2.注册处理逻辑handler
	_ = handler.RegisterHelloService()
	//3.启动服务
	conn, _ := listener.Accept() //当一个新的连接进来的时候
	rpc.ServeConn(conn)

}

客户端

package main

import (
	"TEMP/handler"
	"fmt"
)

func main() {
	//进一步改造rpc调用的代码
	//1.实例化一个
	client := handler.NewHelloServiceClient("tcp", "127.0.0.1:1234")
	var reply string
	err := client.Hello("bobby", &reply)
	if err != nil {
		// panic("调用失败")
		fmt.Println(err)
	}
	fmt.Println(reply)
}

解耦合

将服务端的Hello函数传递变换成接口类型
代理类

package handler

import "net/rpc"

const HelloServiceName = "HelloServiceName"

type HelloServiceStub struct {
	*rpc.Client
}

func NewHelloServiceClient(protcol, address string) HelloServiceStub {
	conn, err := rpc.Dial(protcol, address)
	if err != nil {
		panic("connect error!")
	}
	return HelloServiceStub{conn}
}
func (c *HelloServiceStub) Hello(request string, reply *string) error {
	err := c.Call(HelloServiceName+".Hello", request, reply)
	if err != nil {
		return err
	}
	return nil
}

type NewHelloService struct {
}
type HelloServicer interface {
	Hello(request string, reply *string) error
}

func (s *NewHelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}

func RegisterHelloService() error {
	return rpc.RegisterName(HelloServiceName, &NewHelloService{})
}

服务端

package main

import (
	"TEMP/handler"
	"net"
	"net/rpc"
)

func main() {
	//进一步改造rpc调用的代码
	//1.实例化一个server
	listener, _ := net.Listen("tcp", ":1234")
	//2.注册处理逻辑handler
	_ = handler.RegisterHelloService()
	//3.启动服务
	conn, _ := listener.Accept() //当一个新的连接进来的时候
	rpc.ServeConn(conn)

}

客户端

package main

import (
	"TEMP/handler"
	"fmt"
)

func main() {
	//进一步改造rpc调用的代码
	//1.实例化一个
	client := handler.NewHelloServiceClient("tcp", "127.0.0.1:1234")
	var reply string
	err := client.Hello("bobby", &reply)
	if err != nil {
		// panic("调用失败")
		fmt.Println(err)
	}
	fmt.Println(reply)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2185238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第 21 章 一条记录的多幅面孔——事务的隔离级别与 MVCC

21.1 事前准备 CREATE TABLE hero ( number INT, NAME VARCHAR ( 100 ), country VARCHAR ( 100 ), PRIMARY KEY ( number ) ) ENGINE INNODB CHARSET utf8;INSERT INTO hero VALUES ( 1, 刘备, 蜀 );21.2 事务隔离级别 在保证事务隔离性的前提下,使用不同的隔…

【Burp入门第三十三篇】IP Rotate 插件实现IP轮换爆破

Burp Suite是一款功能强大的渗透测试工具,被广泛应用于Web应用程序的安全测试和漏洞挖掘中。 本专栏将结合实操及具体案例,带领读者入门、掌握这款漏洞挖掘利器 读者可订阅专栏:【Burp由入门到精通 |CSDN秋说】 文章目录 正文安装步骤使用步骤应用场景实战文章正文 在 Burp…

基于SpringBoot+Vue+MySQL的智能垃圾分类系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着城市化进程的加速,垃圾问题日益凸显,不仅对环境造成污染,也给城市管理带来了巨大挑战。传统的垃圾分类方式不仅费时费力,而且手工操作容易出现错误,导致垃圾分类效…

探索未来工业自动化的钥匙:OPC UA与AI的融合

文章目录 探索未来工业自动化的钥匙:OPC UA与AI的融合背景:为什么选择OPC UA?OPC UA库简介安装OPC UA库简单的库函数使用方法连接到服务器获取节点读取节点值设置节点值订阅数据变更 库的使用场景工业自动化监控能源管理系统预测性维护 常见问…

L8910 【哈工大_操作系统】CPU管理的直观想法多进程图像用户级线程

L2.1 CPU管理的直观想法 管理CPU -> 引出多进程视图 设置 PC 指针初值为程序在内存中开始的地址,自动取指执行多个程序同时放在内存中,让CPU交替执行(并发:程序在读I/O时太慢,CPU空闲,则会去执行其他程序…

Jupyterhub 多用户分析平台在线和离线部署(自定义用户认证)

Jupyterhub 文章目录 Jupyterhub1、简介2、安装配置(在线)2.1 安装准备2.2 安装jupyterhub2.2 自定义身份验证器2.3 自定义单用户jupyter服务生成器2.4 配置 jupyterhub_config.py2.4 启动服务2.5 登录测试2.5.1 用户登录 http://da.db.com2.5.2 管理界面…

synchronized底层是怎么通过monitor进行加锁的?

一、monitor是什么 monitor叫做对象监视器、也叫作监视器锁,JVM规定了每一个java对象都有一个monitor对象与之对应,这monitor是JVM帮我们创建的,在底层使用C实现的。 ObjectMonitor() {_header;_count ; // 非常重要,表示锁计数…

3 个简单的微分段项目

与许多大型网络安全项目一样,微分段似乎很复杂、耗时且成本高昂。 它涉及管理有关设备间服务连接的复杂细节。 一台 Web 服务器应连接到特定数据库,但不连接到其他数据库,或者负载平衡器应连接到某些 Web 服务器,同时限制与其他…

图解大模型计算加速系列:vLLM源码解析1,整体架构

整个vLLM代码读下来,给我最深的感觉就是:代码呈现上非常干净历练,但是逻辑比较复杂,环环嵌套,毕竟它是一个耦合了工程调度和模型架构改进的巨大工程。 所以在源码解读的第一篇,我想先写一下对整个代码架构…

Golang | Leetcode Golang题解之第449题序列化和反序列化二叉搜索树

题目: 题解: type Codec struct{}func Constructor() (_ Codec) { return }func (Codec) serialize(root *TreeNode) string {arr : []string{}var postOrder func(*TreeNode)postOrder func(node *TreeNode) {if node nil {return}postOrder(node.Le…

java基础 day1

学习视频链接 人机交互的小故事 微软和乔布斯借鉴了施乐实现了如今的图形化界面 图形化界面对于用户来说,操作更加容易上手,但是也存在一些问题。使用图形化界面需要加载许多图片,所以消耗内存;此外运行的速度没有命令行快 Wi…

针对考研的C语言学习(2019链表大题)

题目解析: 【考】双指针算法,逆置法,归并法。 解析:因为题目要求空间复杂度为O(1),即不能再开辟一条链表,因此我们只能用变量来整体挪动原链表。 第一步先找出中间节点 typedef NODE* Node; Node find_m…

latex有哪些颜色中文叫什么,Python绘制出来

latex有哪些颜色中文叫什么,Python绘制出来 为了展示xcolor包预定义的颜色及其对应的中文名称,并使用Python打印出来,我们可以先列出常见的预定义颜色名称,然后将它们翻译成中文,并最后用Python打印出来。 步骤 列出…

家庭记账本的设计与实现+ssm(lw+演示+源码+运行)

摘 要 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,家庭记账本小程序被用户普遍使用,为方便用户能…

MySQL高阶2066-账户余额

目录 题目 准备数据 分析数据 总结 题目 请写出能够返回用户每次交易完成后的账户余额. 我们约定所有用户在进行交易前的账户余额都为0, 并且保证所有交易行为后的余额不为负数。 返回的结果请依次按照 账户(account_id), 日期( day ) 进行升序排序…

leetcode_238:除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂…

Conditional Generative Adversarial Nets

条件生成对抗网络 1.生成对抗网络 生成对网络由两个“对抗性”模型组成:一个生成模型 G,用于捕获数据分布,另一个判别模型 D,用于估计样本来自训练数据而不是 G 的概率。G 和 D 都可以是非线性映射函数。 为了学习数据 x 上的生…

设计模式-生成器模式/建造者模式Builder

构建起模式:将一个复杂类的表示与其构造分离,使得相同的构建过程能够得出不同的表示。(建造者其实和工厂模式差不多) 详细的UML类图 图文说明:距离相同的构建过程 得出不同的展示。此时就用两个类(文本生成…

探索未来:hbmqtt,Python中的AI驱动MQTT

文章目录 **探索未来:hbmqtt,Python中的AI驱动MQTT**1. 背景介绍2. hbmqtt是什么?3. 安装hbmqtt4. 简单的库函数使用方法4.1 连接到MQTT服务器4.2 发布消息4.3 订阅主题4.4 接收消息4.5 断开连接 5. 应用场景示例5.1 智能家居控制5.2 环境监测…

WebGIS之Cesium三维软件开发

目录 第 1 章 三维 WebGIS 概述 1.1 Google Earth 1 1.2 SkylineGlobe 2 1.3 LocaSpace Viewe 2 1.4 Cesium 3 1.5 Cesium API 概要 4 第 2 章 Cesium 快速入门 2.1 Cesium 环境搭建 7 2.1.1 安装 Node.js 环境 7 2.1.2 配置 Cesium 依赖 8 2.2 搭建第一个 Cesi…