一个golang的RPC框架设计中的坑点

golang学习笔记27——golang 实现 RPC 模块 首先,我们定义一个简单的服务,用于执行数学运算。import ("errors""net/http""net/rpc"// Args 用于传递请求参数A, B int// MathService 数学运算服务// Add 加法运算return nil// 注册服务// 开启 HTTP 服务if err!= nil {panic(err)在上述代码中,我们定义了一个MathService结构体,其中Add方法用于执行加法运算。 阅读详情

本文首发于我的个人博客
本文记录了作者在使用公司的RPC框架时,由于RPC框架本身的设计以及程序员的投机取巧而遇到的一个坑点。

简单来说,RPC框架没有能够做足够好的封装,程序员(不是作者,是前人)也没有按照RPC框架约定的方式进行API调用。

背景提要

我们知道,RPC调用是不同主机间的进程通信的方式,如果想要额外传递消息,我们往往需要修改RPC调用的接口,通过新增参数的方式来新增传递信息。

但是这样修改参数,修改接口的方式侵入性太强,需要进行上下游调用接口的适配,很麻烦。

当我们面对一些遍布在各个RPC服务的需求(也就是很多人喜欢提及的编程"切面"Aspect),比如这回我接到的日志系统的需求,将所有的接口都修改一遍,显然是不现实的。

在这里,golang的RPC框架可以通过传递context.Context来实现,也就是说,这些额外的,遍布各个RPC服务的消息,通过context.Context来传递。

// rpc 上游调用时,传递一个`context.Context`和本来必要的下游rpc接口参数
rpcClient.remoteMethod(ctx, request)

// rpc 下游接受请求时,接受一个`context.Context`和本来必要的接口参数
func methodHandler(ctx context.Context, request MethodRequest)

类比HTTP解释

上面提及的RPC调用传递context.Context和本来的接口参数,其实可以类比HTTP协议:

  • context.Context -> HTTP Request Headers
  • 接口参数 -> HTTP Request Body

二者都是传递信息的手段,但是接口参数和Request Body往往是明面上的写出来的主要业务逻辑需要的消息,context.Context和Request Headers往往是一些元数据(metadata)。

需求场景

这次的日志系统,需要我记录RPC运行时的动态调用链,也就是说,如果有一条RPC调用链路是

RPC1 -> RPC2 -> RPC3

那么实时的日志里,会有如下条目:

RPC1:
stack : []

RPC2:
stack : [RPC1]

RPC3:
stack : [RPC1, RPC2]

解决过程中遇到的问题

对于这个功能,我们发现RPC框架提供了三个接口:

// 向一个context.Context加入key-val键值对
func AddInfo(ctx context.Context, key string, val string) context.Context

// 获取上游通过AddInfo传来的key对应的val
func GetUpstreamInfo(ctx context.Context, key string) string

// 获取所有上游通过AddInfo传来的键值对,组织成一个map[string][string]
func GetAllUpstreamInfo(ctx context.Context) map[string]string

为此,我们的解决方案是,将stack做成[]RPC,其中struct RPC记录RPC的信息,通过JSON将[]RPC转化成string,而后用context.Context里的"stack" - JSON([]RPC)的键值对进行传递。

在我之前编码的程序员,没有遵守API调用规则,不使用AddInfo,而是使用的是如下方式进行stack的传递的

// 获取所有的键值对
m := util.GetAllUpstreamInfo(ctx)
// 取出stack并使用JSON解析
stack, err := json.Unmarshal(m["stack"])
if err != nil {
    logError(...)
    return
}

// 添加现有RPC调用
stack = append(stack, currentRPC)
// JOSN编码,更新ctx内的map
m["stack"], err = json.Marshal(stack)
if err != nil {
    logError(...)
    return
}

由于golang中的map是引用传值,所以看上去这个代码已经成功更新了ctx内部的map,使用Goland-Debug查看ctx也会发现map已经修改了

但事实上程序并没有按照预期的方式进行工作

排查原因

仔细观察AddInfo函数的签名

func AddInfo(ctx context.Context, key string, val string) context.Context

该函数返回了一个全新的Context,事实上AddInfo的逻辑是这样的:

newCtx := AddInfo(ctx, "key", "val")

+------------------------------+
|          newCtx              |
|   +------------------------+ |
|   |                        | |
|   |                        | |
|   |        ctx             | |
|   |                        | |
|   |                        | |
|   +------------------------+ |
|    K_KV ->                   |
| struct{key:"key",val:"val"}  |
+------------------------------+

也就是说,新的Context在原来的Context上多加了一层,这一层的结构是

K_KV(RPC框架定义的一个字符串) -> struct{key: "key", val: "val"}

我们通过newCtx.Value("key")是拿不到任何东西的,只能通过newCtx.Value(K_KV)才能拿到完整的键值对

这时,RPC框架有了一个骚操作
RPC1 ctx -------中间对ctx做了转化----------> RPC2

假设我们上游RPC1使用了如下AddInfo

newCtx := AddInfo(ctx, "key", "val")
newCtx2 := AddInfo(newCtx, "key2", "val2")

+------------------------------+
|          newCtx2             |
|   +------------------------+ |
|   |      newCtx            | |
|   |   +-------------+      | |
|   |   |    ctx      |      | |
|   |   +-------------+      | |
|   | K_KV -> struct{        | |
|   |          key:"key"     | |
|   |          val:"val"}    | |
|   +------------------------+ |
|  K_KV -> struct{key:"key2",  |
|                 val:"val2"}  |
+------------------------------+

那么newCtx2就该如图示的那样,但是下游RPC2拿到的是

+------------------------------+
|          newCtx              |
|   +------------------------+ |
|   |                        | |
|   |        ctx             | |
|   |                        | |
|   +------------------------+ |
|    K_UPSTREAM ->             |
|     map[string]string{       |
|           "key" : "val",     |
|           "key2" : "val2"    |
|     }                        |
+------------------------------+

所以,用于从Context取出键值对的索引从K_KV变为了K_UPSTREAM!!!

这意味着上游传来的消息最多只能保留一个RPC路径,所以我们必须使用AddInfo而不是直接写入map的方式来更新数据:

// 获取所有的键值对
m := util.GetAllUpstreamInfo(ctx)
// 取出stack并使用JSON解析
stack, err := json.Unmarshal(m["stack"])
if err != nil {
    logError(...)
    return
}

// 添加现有RPC调用
stack = append(stack, currentRPC)
// JOSN编码,更新ctx内的map
jsonStack, err := json.Marshal(stack)
if err != nil {
    logError(...)
    return
}
ctx = util.AddInfo(ctx, "stack", jsonStack)

总结

框架底层的逻辑并不是很易懂,解释比较麻烦,一篇博文难以说明清楚,但是需要记住的是:

  1. 使用框架,尽量使用框架的标准接口
  2. 框架封装的时候,如果有map这类可能会让人有hack欲望的,写明文档
Go语言中的RPC协议原理解析 本文介绍了Go语言中RPC协议的基本原理,并通过简单示例演示了其实现过程。通过net/rpc包,Go语言为开发者提供了一种简单且有效的方式来实现远程过程调用,从而大大简化了分布式系统中的通信。虽然net/rpc包默认仅支持Go语言,但在需要跨语言通信的场景中,可以考虑使用诸如gRPC等更为通用的解决方案。 阅读详情

相关推荐

go语言RPC实战

go语言RPC实战 本文将介绍rpc基本知识以及go中自带rpc应用实践 Markdown和扩展Markdown简洁的语法 RPC基本知识 RPC(Remote Procedure Call):远程过程调用。本质是一种协议,允许一台计算机的程序通过网络从远程计算机程序上请求服务,不需要使用者了解底层网络技术的协议。在OSI网络通信模型中,RPC跨越了传输层和应用层,使得开发包括网络...

xixisuli的博客 1957

Rpc调用RpcContext隐式传递参数

简介 获取上下文信息 RpcContext 可以使数据在Rpc调用间使消费者和提供者之间隐式的传递参数。 服务提供方使用RpcContext.getContext().getAttachments();获取参数。 服务器消费方使用RpcContext.getContext().setAttachment(); 传递参数。 RpcContext本质上是一个ThreadLocal,当接收到RPC请求或发起RPC请求时,RpcContext的状态会变化。比如A调用B,B再调用C,则B机器上,在B.

Gzq 3798

golang官方rpc包的使用

RPC(Remote Rrocedure Call,远程过程调用)是一个计算机通信协议。RPC协议假定某些传输协议的存在,如TCP和UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。 RPC采用C/S模式,请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠...

benben的博客 2394

Go Context的踩坑经历

0 引言 context是Go中广泛使用的程序包,由Google官方开发,在1.7版本引入。它用来简化在多个go routine传递上下文数据、(手动/超时)中止routine树等操作,比如,官方http包使用context传递请求的上下文数据,gRpc使用context来终止某个请求产生的routine树。由于它使用简单,现在基本成了编写go基础库的通用规范。笔者在使用context上有一些经验,遂分享下。 本文主要谈谈以下几个方面的内容: context的使用。 context实现原

聪明的竹子的博客 3152

7天用Go动手写/从零实现RPC框架GeeRPC

0 目录 第一天 - 服务端与消息编码 | Code 第二天 - 支持并发与异步的客户端 | Code 第三天 - 服务注册(service register) | Code 第四天 - 超时处理(timeout) | Code 第五天 - 支持HTTP协议 | Code 第六天 - 负载均衡(load balance) | Code 第七天 - 服务发现与注册中心(registry) | Code 1 谈谈 RPC 框架 RPC(Remote Procedure Call,远程过程调用)是一种计算机.

u014138650的专栏 1438

使用go reflect实现一套简易的rpc框架

go jsonrpc 在实际项目中,我们经常会碰到服务之间交互的情况,如何方便的与远端服务进行交互,就是一个需要我们考虑的问题。 通常,我们可以采用restful的编程方式,各个服务提供相应的web接口,相互之间通过http方式进行调用。或者采用rpc方式,约定json格式进行数据交互。 在我们的项目中,服务端对用户客户端提供的是restful的接口方式,而在服务器内部,我们则采

siddontang的专栏 4828

go-zero api服务调用rpc服务时遇到bug

error: desc = "error reading server preface: http2: frame too large

2301_80202115的博客 1118

grpc-go源码剖析五十四之取消功能的流程原理图介绍

本篇文章我们先整体了解一下取消cancellation功能的整体流程。 1、整体了解取消功能的流程 主要流程说明: 客户端一侧: 当满足业务条件时,客户端一侧主动调用上下文的取消函数cancel(), 上下文监听到后,就会触发客户端流关闭流程 从上下文里获取异常信息,获取到的是取消异常信息,如 context canceled 将异常信息转换成grpc框架对应的异常信息,如 rpc error: code = Canceled desc = context canceled 获取http2对应的取消异常

码二哥的技术池 1650

Go系列 --Context 解析

序言: 关于 go的并发编程 正文 前言需知, channel 的使用https://blog.youkuaiyun.com/dongjijiaoxiangqu/article/details/109642733 1.context 的使用场景 context 的含义,context 意为上下文, go中的context 的主要使用场景为在多个goroutine 中进行 通信,避免浪费无用的计算资源。 打个比方: 比如说一个 网络请求中, 系统起了一个 go routine A来服务此请...

一叶知秋 1250

【Go 语言编程实战】用 Golang 实现一个 Go Thrift RPC 接口的泛化调用代码实例

本文详细讲解一下 go rpc 泛化调用的原理和过程和如何使用 golang 实现的 go thrift RPC 接口的泛化调用代码。

AI天才研究院 1万+

医学图像分割实战:基于TransUNet训练自己的眼底硬渗出物数据集(附完整代码)

本文详细介绍了基于TransUNet的眼底硬渗出物分割实战指南,涵盖数据预处理、模型调优、训练策略及性能优化等关键环节。通过代码复现和实验对比,展示了TransUNet在医学图像分割中的优势,为糖尿病视网膜病变的早期诊断提供技术支持。

weixin_42524708的博客 363

从零实现RPC框架:Go语言版

良好的接口设计是可扩展性的基础。// Codec 编解码接口// Transport 传输层接口// Registry 服务注册接口// LoadBalancer 负载均衡接口通过从零实现一个RPC框架,我们深入理解了分布式系统通信的核心原理和实现细节。这个过程不仅仅是技术学习,更是对系统设计思维的锻炼。

sinat_27016095的博客 1848

PostgreSQL数据库全攻略:从入门到精通

本文是一份PostgreSQL数据库综合指南,涵盖安装配置、基础操作和高级管理。主要内容包括:1) PostgreSQL简介与各平台安装方法;2) 数据库基本操作,如创建数据库/表、CRUD操作、用户权限管理;3) 高级功能包括索引优化、事务控制、视图、存储过程、触发器和扩展模块;4) 数据库管理维护,涉及配置调优、备份恢复、性能监控和日常维护;5) 安全设置与高可用方案。指南提供了从入门到进阶的实用知识,强调结合官方文档进行深入学习,帮助用户高效使用这一功能强大的开源关系型数据库系统。

索大神的博客 1970

Golang之rpc框架rpcx

前言 远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。通过该协议程序员可以实现像调取本地函数一样,调取远程服务的函数。这里介绍一个高效的rpc库(rpcx)。 rpcx 是一个分布式的Go语言的 RPC 框架,支持Zookepper、etcd、consul多种服务发现方式,多种服务路由方式, 是目前性能最好的 RPC 框架之一。 官方资料:https://books.studygolang.com/go-rpc-programming-guide/ 项目:ht

无风的雨 5715

基于溶解气体分析(DGA)的变压器DGA数据故障诊断含代码含数据

在拥有可靠的DGA数据的前提下,结合机器学习分类算法,可以实现变压器的故障诊断。在此数据集的基础上,用随机森林跑出的结果如图。

Jason_Orton的博客 2704

matebookego win11 arm 系统安装安卓子系统

一般的x86电脑如果安装android子系统,运行安卓子系统,由于要将android arm代码转译为x86代码,所以效率不一定高,但是如果电脑是arm架构的,通过安卓子系统运行android的程序执行效率就会 高不少,本文参考,都是在win11 home版本下的。两者跑分在cpu上差不太多了,大约是枭龙855的水平了,这个水平的子系统就有一定的实用性了,玩个一般游戏,刷个抖音都很流畅的。启动子系统后,点击左上角的设置,启用 “开发人员模式”。然后就可以再通运运行华为应用商店来进行安装其它应用了。

babytiger的专栏 1万+

Go最全架构设计:如何实现一个高性能分布式 RPC 框架_rpc oneway,作为一名Golang面试者你应该知道的

RPC 框架包含三个最重要的组件,分别是客户端、服务端和注册中心。虽然 RPC 调用流程很容易理解,但是实现一个完整的 RPC 框架设计到很多内容,例如服务注册与发现、通信协议与序列化、负载均衡、动态代理等,下面我们一一进行初步地讲解。

2401_84904706的博客 1010

汇率波动率建模实战:从GARCH到EGARCH的金融时间序列分析

在金融时间序列分析中,波动率建模是量化市场风险的核心技术。其基本原理在于捕捉金融资产收益率序列中存在的波动聚集、尖峰厚尾等典型特征。通过建立条件异方差模型,如GARCH及其扩展模型,可以动态估计和预测未来波动率,这对于资产定价、风险管理和投资决策具有重要价值。在金融工程领域,该技术广泛应用于外汇、股票和衍生品市场。本文以汇率波动率建模为具体场景,详细阐述了如何使用Python中的arch库,从数据预处理、模型选型(包括GARCH、EGARCH等)、参数估计到样本外预测评估的全流程实践,并深入探讨了模型在应对

weixin_32629853的博客 372

七天实现一个go rpc框架

RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许调用不同进程空间的程序。RPC 的客户端和服务器可以在一台机器上,也可以在不同的机器上。程序员使用时,就像调用本地程序一样,无需关注内部的实现细节。不同的应用程序之间的通信方式有很多,比如浏览器和服务器之间广泛使用的基于 HTTP 协议的 Restful API。与 RPC 相比,Restful API 有相对统一的标准,因而更通用,兼容性更好,支持不同的语言。HTTP 协议是基于文本的,一般具备更好的可读性。但是缺

General_zy的博客 1443

如何用Python+Librosa实现轴承异响检测?5步搞定工业设备故障预警

本文详细介绍了如何利用Python和Librosa库,通过5个步骤构建工业轴承异响检测与故障预警系统。从音频采集、预处理、频谱特征提取,到构建机器学习模型并进行实时监测,提供了完整的代码级解决方案。该方法能有效识别轴承早期故障特征,实现低成本、高效的设备预测性维护。

rnn9storyteller的博客 633

从入门到精通|Yalmip+Cplex在电力系统中的应用(Matlab代码实现)

​ 火电成本包括运行成本、碳捕集成本,其中火电运行成本由运行维护成 本和发电煤耗成本构成,发电煤耗与其出力关系: ,F 为机组煤耗量(kg/h),P 为机组出力/MW;运行维护成本按照 0.5 倍煤耗成本考虑,碳捕集成本取决于碳排放量及碳捕集单价,火电机组相关参数如附表 1 所示,电煤价格为 700 元/t ​

weixin_46039719的博客 1553

轻量级golang rpc框架

最近有点闲余时间,于是开始思考以及编写一些东西。其实关于RPC,记得之前刚接触的时候,感觉很神秘,很牛x的一项技术。RPC随便百度一下,都看的好像很高深,很难懂的介绍。然后就算用了,也不懂啥米原理,引用beego的谢大给的图,看着也好像不是很懂的样子。 其实基于golang的RPC有好几个了,除去原生的一个外,有gRPC,也有一个自称很牛逼的RPC框架(xrpc:http://rpcx.site...

gtd138的博客 6335

CST与MATLAB连接设置

打开MATLAB,点击"Set Path"按钮,然后点击"Add Folder"。在CST中,点击"Options"菜单,然后选择"Advanced"选项。在弹出的对话框中,点击"COMSOL"选项卡,并确保"Enable COMSOL"复选框被选中。通过上述步骤和示例代码,你可以开始使用MATLAB与CST进行集成,并根据你的具体需求进行进一步的开发和应用。通过这个简单的示例,你可以开始在MATLAB中与CST进行交互,并根据你的需求进行更复杂的操作,如参数化仿真、优化和数据处理等。

TechPulseZ的博客 1966

从零开始实现一个RPC框架(零)

第一篇文章就到此为止了,主要先做一下准备,整理一下思路,如果有不正确或者不合理的部分还请大家多多指教。

ldxxxxll的博客 1003

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Golang | Go RPC & TLS 鉴权

RPC () 是一种远程通信协议,用于让不同服务之间进行通信。相比 HTTP,RPC 在传输信息时会减少一些额外的信息。HTTP 是实现 RPC 的一种手段,RPC 常见的实现有很多,比如 Dubbo、gRPC、Hessian 等。远程过程调用,实际上最终是一个调用。相比于本地调用,RPC 还需要知道对向的地址信息。本地调用就如同两个人面对面交流,RPC 则像是两个人打电话,需要知道对方的手机号码,但是并不关心语音怎么编码、传输、解码的过程。提供服务的一端,我们称为服务端,就好比是接听电话的一端。

雨下一整晚real的博客 1463

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

【GeeRPC】项目总结:使用 Golang 实现 RPC 框架

GeeRPC 项目总结。

YGGP的博客 1366

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

上一篇: Vue中为对象添加字段
下一篇: 编写自定义启动脚本时的注意项
FitzLeopard
博客等级 码龄9年 6粉丝 44原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值